@wolfstar/plugin-cache 0.1.0-next-20260926152534 → 0.2.0-next-20260926194301
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +15 -1
- package/dist/esm/index.d.ts +67 -2
- package/dist/esm/index.d.ts.map +1 -1
- package/dist/esm/index.js +153 -35
- package/dist/esm/index.js.map +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -103,6 +103,7 @@ const cache = createRedisCache({
|
|
|
103
103
|
| `compression` | `"none"` | `"gzip"`, `"brotli"`, or `"none"`. |
|
|
104
104
|
| `compressionThreshold` | `1024` | Minimum serialized size, in bytes, for a value to be compressed. |
|
|
105
105
|
| `ttl` | `{}` | Time-to-live in seconds, per entity cache. Omitted ones never expire. |
|
|
106
|
+
| `indexGuilds` | `true` | Index guild-scoped entity caches by guild, see below. |
|
|
106
107
|
|
|
107
108
|
Compressed values are tagged, so turning compression on or off never breaks reading the values
|
|
108
109
|
already stored. Each entity cache keeps a sorted set index (`<prefix>:<entity>:@index`) used by
|
|
@@ -111,6 +112,15 @@ one `MULTI` transaction, and with a `ttl` every write also prunes the expired in
|
|
|
111
112
|
index stays bounded even when nothing enumerates it. The client must therefore support `multi()`,
|
|
112
113
|
which `ioredis` does.
|
|
113
114
|
|
|
115
|
+
With `indexGuilds`, the entity caches holding guild data (channels, messages, members, roles, ...)
|
|
116
|
+
also keep one sorted set per guild (`<prefix>:<entity>:@guild:<guildId>`). A `GUILD_DELETE` then
|
|
117
|
+
reads the guild's keys from it instead of scanning every entry of every entity cache, which on a
|
|
118
|
+
large cache means reading and decompressing every stored message. The price is one more index write
|
|
119
|
+
per write, and a read before each delete. Entries written while the option was off are not indexed,
|
|
120
|
+
so turning it on for a populated cache leaves them behind on `GUILD_DELETE` until they expire or the
|
|
121
|
+
cache is cleared. When the transaction exposes `pexpire` (`ioredis` does), the guild indexes of an
|
|
122
|
+
entity cache with a `ttl` expire with their entries.
|
|
123
|
+
|
|
114
124
|
#### Errors
|
|
115
125
|
|
|
116
126
|
A missing value is not an error: `get` resolves to `undefined`. A value that cannot be read back
|
|
@@ -131,11 +141,15 @@ interface EntityCache<Raw> {
|
|
|
131
141
|
keys(): Awaitable<string[]>;
|
|
132
142
|
values(): Awaitable<Raw[]>;
|
|
133
143
|
entries(): Awaitable<[key: string, value: Raw][]>;
|
|
144
|
+
// Optional: `null` when the store does not index its entries by guild.
|
|
145
|
+
deleteGuild?(guildId: string): Awaitable<number | null>;
|
|
134
146
|
}
|
|
135
147
|
```
|
|
136
148
|
|
|
137
149
|
`keys`, `values`, and `entries` return snapshots rather than live iterators, which keeps the
|
|
138
|
-
semantics identical between synchronous and asynchronous stores.
|
|
150
|
+
semantics identical between synchronous and asynchronous stores. A store implementing `deleteGuild`
|
|
151
|
+
lets `applyGatewayDispatch` skip the scans of a `GUILD_DELETE`; without it, or when it resolves to
|
|
152
|
+
`null`, the scans run as before.
|
|
139
153
|
|
|
140
154
|
### Keys
|
|
141
155
|
|
package/dist/esm/index.d.ts
CHANGED
|
@@ -146,6 +146,14 @@ interface EntityCache<Raw> {
|
|
|
146
146
|
keys(): Awaitable<string[]>;
|
|
147
147
|
values(): Awaitable<Raw[]>;
|
|
148
148
|
entries(): Awaitable<[key: string, value: Raw][]>;
|
|
149
|
+
/**
|
|
150
|
+
* Deletes every entry belonging to a guild through an index, rather than a scan of the whole cache. Optional:
|
|
151
|
+
* {@link applyGatewayDispatch} falls back to a scan for caches without it.
|
|
152
|
+
*
|
|
153
|
+
* @param guildId The ID of the guild.
|
|
154
|
+
* @returns The amount of deleted entries, or `null` when this cache does not index its entries by guild.
|
|
155
|
+
*/
|
|
156
|
+
deleteGuild?(guildId: string): Awaitable<number | null>;
|
|
149
157
|
}
|
|
150
158
|
/**
|
|
151
159
|
* One {@link EntityCache} per entity kind.
|
|
@@ -190,10 +198,14 @@ type CacheOperation = {
|
|
|
190
198
|
type: "deletePrefix";
|
|
191
199
|
store: CacheEntityName;
|
|
192
200
|
prefix: string;
|
|
201
|
+
/** The guild the deleted entries are exactly the entries of, see {@link EntityCache.deleteGuild}. */
|
|
202
|
+
guildId?: Snowflake;
|
|
193
203
|
} | {
|
|
194
204
|
type: "deleteWhere";
|
|
195
205
|
store: CacheEntityName;
|
|
196
206
|
predicate: (value: unknown) => boolean;
|
|
207
|
+
/** The guild the deleted entries are exactly the entries of, see {@link EntityCache.deleteGuild}. */
|
|
208
|
+
guildId?: Snowflake;
|
|
197
209
|
};
|
|
198
210
|
/**
|
|
199
211
|
* What {@link createCacheOperations} needs to know besides the dispatch.
|
|
@@ -332,6 +344,11 @@ interface RedisTransactionLike {
|
|
|
332
344
|
zadd(key: string, ...scoreMembers: (string | number)[]): RedisTransactionLike;
|
|
333
345
|
zrem(key: string, ...members: string[]): RedisTransactionLike;
|
|
334
346
|
zremrangebyscore(key: string, min: number | string, max: number | string): RedisTransactionLike;
|
|
347
|
+
/**
|
|
348
|
+
* Optional: when available (`ioredis` has it), the guild indexes of a cache with a `ttl` expire once their guild
|
|
349
|
+
* stops being written to. Without it, they are only pruned by the next write to their guild.
|
|
350
|
+
*/
|
|
351
|
+
pexpire?(key: string, milliseconds: number): RedisTransactionLike;
|
|
335
352
|
exec(): Promise<[error: Error | null, result: unknown][] | null>;
|
|
336
353
|
}
|
|
337
354
|
/**
|
|
@@ -352,7 +369,7 @@ export declare class CacheValueError extends Error {
|
|
|
352
369
|
* The algorithm used to compress values before writing them to Redis.
|
|
353
370
|
*/
|
|
354
371
|
type RedisCacheCompression = "gzip" | "brotli" | "none";
|
|
355
|
-
interface RedisEntityCacheOptions {
|
|
372
|
+
interface RedisEntityCacheOptions<Raw = unknown> {
|
|
356
373
|
/**
|
|
357
374
|
* The prefix of every Redis key owned by this entity cache.
|
|
358
375
|
*/
|
|
@@ -373,6 +390,15 @@ interface RedisEntityCacheOptions {
|
|
|
373
390
|
* @default 1024
|
|
374
391
|
*/
|
|
375
392
|
compressionThreshold?: number;
|
|
393
|
+
/**
|
|
394
|
+
* Resolves the guild an entry belongs to, indexing the entries by guild so {@link RedisEntityCache.deleteGuild}
|
|
395
|
+
* does not have to scan the whole cache. Entries it resolves no guild for are not indexed. Without it, nothing is
|
|
396
|
+
* indexed and `deleteGuild` resolves to `null`.
|
|
397
|
+
*
|
|
398
|
+
* On delete, it is first called without the value: when the key alone gives the guild away, the value is not
|
|
399
|
+
* read back.
|
|
400
|
+
*/
|
|
401
|
+
guildOf?: (key: string, value?: Raw) => string | undefined;
|
|
376
402
|
}
|
|
377
403
|
/**
|
|
378
404
|
* An {@link EntityCache} backed by Redis.
|
|
@@ -380,6 +406,9 @@ interface RedisEntityCacheOptions {
|
|
|
380
406
|
* @remarks
|
|
381
407
|
* Every entry is stored as its own string key (`<prefix>:<key>`), and a sorted set (`<prefix>:@index`) tracks the
|
|
382
408
|
* stored keys with their expiration time as score, which is what `keys`, `entries`, `getSize`, and `clear` read from.
|
|
409
|
+
*
|
|
410
|
+
* With {@link RedisEntityCacheOptions.guildOf}, one more sorted set per guild (`<prefix>:@guild:<guildId>`) tracks the
|
|
411
|
+
* keys of that guild's entries the same way, and `<prefix>:@guilds` lists the guilds having one, for `clear`.
|
|
383
412
|
*/
|
|
384
413
|
export declare class RedisEntityCache<Raw> implements EntityCache<Raw> {
|
|
385
414
|
#private;
|
|
@@ -387,12 +416,23 @@ export declare class RedisEntityCache<Raw> implements EntityCache<Raw> {
|
|
|
387
416
|
readonly ttl: number | undefined;
|
|
388
417
|
readonly compression: RedisCacheCompression;
|
|
389
418
|
readonly compressionThreshold: number;
|
|
390
|
-
constructor(redis: RedisClientLike, options: RedisEntityCacheOptions);
|
|
419
|
+
constructor(redis: RedisClientLike, options: RedisEntityCacheOptions<Raw>);
|
|
391
420
|
get(key: string): Promise<Raw | undefined>;
|
|
392
421
|
set(key: string, value: Raw): Promise<void>;
|
|
393
422
|
has(key: string): Promise<boolean>;
|
|
394
423
|
delete(key: string): Promise<boolean>;
|
|
395
424
|
clear(): Promise<void>;
|
|
425
|
+
/**
|
|
426
|
+
* Deletes every entry of a guild, reading their keys from the guild's index rather than scanning the cache.
|
|
427
|
+
*
|
|
428
|
+
* @remarks
|
|
429
|
+
* Only indexed entries are deleted: when `guildOf` is set on a cache already holding entries, the ones written
|
|
430
|
+
* before are left behind until they expire or the cache is cleared.
|
|
431
|
+
*
|
|
432
|
+
* @param guildId The ID of the guild.
|
|
433
|
+
* @returns The amount of deleted entries, or `null` without {@link RedisEntityCacheOptions.guildOf}.
|
|
434
|
+
*/
|
|
435
|
+
deleteGuild(guildId: string): Promise<number | null>;
|
|
396
436
|
getSize(): Promise<number>;
|
|
397
437
|
keys(): Promise<string[]>;
|
|
398
438
|
values(): Promise<Raw[]>;
|
|
@@ -406,6 +446,20 @@ export declare class RedisEntityCache<Raw> implements EntityCache<Raw> {
|
|
|
406
446
|
* The Redis key of the sorted set indexing the stored keys.
|
|
407
447
|
*/
|
|
408
448
|
get indexKey(): string;
|
|
449
|
+
/**
|
|
450
|
+
* Gets the Redis key of the sorted set indexing a guild's entries.
|
|
451
|
+
* @param guildId The ID of the guild.
|
|
452
|
+
*/
|
|
453
|
+
guildIndexKey(guildId: string): string;
|
|
454
|
+
/**
|
|
455
|
+
* The Redis key of the sorted set listing the guilds having an index.
|
|
456
|
+
*/
|
|
457
|
+
get guildsKey(): string;
|
|
458
|
+
/**
|
|
459
|
+
* Reads the guild of a stored entry, to remove the entry from its guild index.
|
|
460
|
+
* @param key The entity cache key.
|
|
461
|
+
*/
|
|
462
|
+
private readGuild;
|
|
409
463
|
private prune;
|
|
410
464
|
private serialize;
|
|
411
465
|
private deserialize;
|
|
@@ -441,6 +495,17 @@ interface RedisCacheOptions {
|
|
|
441
495
|
* The time-to-live per entity cache, in seconds. Entity caches left out never expire.
|
|
442
496
|
*/
|
|
443
497
|
ttl?: Partial<Record<CacheEntityName, number>>;
|
|
498
|
+
/**
|
|
499
|
+
* Whether to index the guild-scoped entity caches by guild, so a `GUILD_DELETE` drops a guild's entries without
|
|
500
|
+
* scanning every entity cache. It costs one more sorted-set write per write, and a read before every delete.
|
|
501
|
+
*
|
|
502
|
+
* @remarks
|
|
503
|
+
* Entries written while it was off are not indexed: turning it on for a populated cache leaves them behind on
|
|
504
|
+
* `GUILD_DELETE`, until they expire or the cache is cleared.
|
|
505
|
+
*
|
|
506
|
+
* @default true
|
|
507
|
+
*/
|
|
508
|
+
indexGuilds?: boolean;
|
|
444
509
|
}
|
|
445
510
|
/**
|
|
446
511
|
* The default prefix of every Redis key owned by a cache created with {@link createRedisCache}.
|
package/dist/esm/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","names":[],"sources":["../../src/lib/keys.ts","../../src/lib/types.ts","../../src/lib/operations.ts","../../src/lib/gateway.ts","../../src/lib/memory.ts","../../src/lib/redis.ts"],"mappings":";;;;;wBAKgB,eAAe,SAAS,WAAW,IAAI;;;;wBAOvC,WAAW,WAAW,WAAW,WAAW;;;;wBAO5C,UAAU,SAAS,WAAW,QAAQ;;;;wBAOtC,YAAY,SAAS,WAAW,QAAQ;;;;wBAOxC,cAAc,SAAS,WAAW,QAAQ;;;;wBAO1C,QAAQ,SAAS,WAAW,QAAQ;;;;wBAOpC,SAAS,SAAS,WAAW,SAAS;;;;wBAOtC,WAAW,SAAS,WAAW,WAAW;;;;wBAO1C,kBAAkB,SAAS,WAAW,kBAAkB;;;;wBAOxD,iBAAiB,SAAS,WAAW,WAAW;;;;wBAOhD,mBAAmB,SAAS,WAAW,SAAS;;;;wBAOhD,sBAAsB,SAAS,WAAW,QAAQ;;;;wBAOlD,OAAO,SAAS,WAAW,QAAQ;;;;wBAOnC,eAAe,SAAS,WAAW,eAAe;;;;wBAOlD,UAAU,SAAS,8BAA8B;;;;wBAOjD,gBAAgB,UAAU,WAAW,QAAQ;;;;wBAO7C,iCACd,eAAe,WACf,SAAS,WACT,WAAW;;;;;;KCvFD,UAAU,KAAK,IAAI,QAAQ;;;;;;;;UAStB;EACf,+BAA+B;EAC/B,iBAAiB;IAAqB,UAAU;;EAChD,qBAAqB;EACrB,MAAM;EACN,UAAU;IAAe,WAAW;;EACpC,QAAQ;IAAa,UAAU;;EAC/B,cAAc;;;;;EAKd,QAAQ,KAAK,6CACX,QAAQ,KAAK,sCAAsC;EACrD,cAAc,uCAAuC;EACrD,SAAS;EACT,SAAS;IAAmB,UAAU;;EACtC,UAAU,mCAAmC;EAC7C,WAAW;EACX,OAAO;IAAY,UAAU;;EAC7B,iBAAiB;EACjB,kBAAkB;EAClB,gBAAgB;EAChB,UAAU;IAAe,UAAU;;EACnC,eAAe;EACf,eAAe;IAAoB,WAAW;;EAC9C,SAAS;EACT,OAAO;EACP,aAAa;;;;;KAMH,wBAAwB;;;;;;;;;UAUnB,YAAY;EAC3B,IAAI,cAAc,UAAU;EAC5B,IAAI,aAAa,OAAO,MAAM;EAC9B,IAAI,cAAc;EAClB,OAAO,cAAc;EACrB,SAAS;EACT,WAAW;EACX,QAAQ;EACR,UAAU,UAAU;EACpB,WAAW,WAAW,aAAa,OAAO;;;;;
|
|
1
|
+
{"version":3,"file":"index.d.ts","names":[],"sources":["../../src/lib/keys.ts","../../src/lib/types.ts","../../src/lib/operations.ts","../../src/lib/gateway.ts","../../src/lib/memory.ts","../../src/lib/redis.ts"],"mappings":"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"}
|
package/dist/esm/index.js
CHANGED
|
@@ -269,6 +269,39 @@ const cacheEntityNameRecord = {
|
|
|
269
269
|
*/
|
|
270
270
|
const CacheEntityNames = Object.keys(cacheEntityNameRecord);
|
|
271
271
|
/**
|
|
272
|
+
* The entity caches whose keys start with the ID of the guild the entity belongs to (`${guildId}:...`).
|
|
273
|
+
*
|
|
274
|
+
* @internal
|
|
275
|
+
*/
|
|
276
|
+
const GuildKeyedCacheEntityNames = [
|
|
277
|
+
"auditLogEntries",
|
|
278
|
+
"autoModerationRules",
|
|
279
|
+
"bans",
|
|
280
|
+
"emojis",
|
|
281
|
+
"integrations",
|
|
282
|
+
"members",
|
|
283
|
+
"presences",
|
|
284
|
+
"roles",
|
|
285
|
+
"scheduledEvents",
|
|
286
|
+
"soundboardSounds",
|
|
287
|
+
"stageInstances",
|
|
288
|
+
"stickers",
|
|
289
|
+
"voiceStates"
|
|
290
|
+
];
|
|
291
|
+
/**
|
|
292
|
+
* The entity caches keyed by the entity's own ID, which only know their guild through the stored `guild_id`.
|
|
293
|
+
*
|
|
294
|
+
* @internal
|
|
295
|
+
*/
|
|
296
|
+
const GuildFieldCacheEntityNames = [
|
|
297
|
+
"applicationCommandPermissions",
|
|
298
|
+
"channels",
|
|
299
|
+
"threads",
|
|
300
|
+
"threadMembers",
|
|
301
|
+
"messages",
|
|
302
|
+
"invites"
|
|
303
|
+
];
|
|
304
|
+
/**
|
|
272
305
|
* Translates a gateway dispatch into the list of {@link CacheOperation}s it implies, including the cascades (e.g.
|
|
273
306
|
* `CHANNEL_DELETE` also drops that channel's messages).
|
|
274
307
|
*
|
|
@@ -440,7 +473,8 @@ function createCacheOperations(payload, context = {}) {
|
|
|
440
473
|
operations.push({
|
|
441
474
|
type: "deletePrefix",
|
|
442
475
|
store: "emojis",
|
|
443
|
-
prefix: `${data.guild_id}
|
|
476
|
+
prefix: `${data.guild_id}:`,
|
|
477
|
+
guildId: data.guild_id
|
|
444
478
|
});
|
|
445
479
|
for (const emoji of data.emojis) {
|
|
446
480
|
if (!emoji.id) continue;
|
|
@@ -458,7 +492,8 @@ function createCacheOperations(payload, context = {}) {
|
|
|
458
492
|
operations.push({
|
|
459
493
|
type: "deletePrefix",
|
|
460
494
|
store: "stickers",
|
|
461
|
-
prefix: `${data.guild_id}
|
|
495
|
+
prefix: `${data.guild_id}:`,
|
|
496
|
+
guildId: data.guild_id
|
|
462
497
|
});
|
|
463
498
|
for (const sticker of data.stickers) operations.push({
|
|
464
499
|
type: "upsert",
|
|
@@ -915,13 +950,25 @@ async function applyCacheOperations(cache, operations) {
|
|
|
915
950
|
await store.delete(operation.key);
|
|
916
951
|
break;
|
|
917
952
|
case "deletePrefix":
|
|
953
|
+
if (await deleteGuildThroughIndex(store, operation.guildId)) break;
|
|
918
954
|
for (const key of await store.keys()) if (key.startsWith(operation.prefix)) await store.delete(key);
|
|
919
955
|
break;
|
|
920
|
-
case "deleteWhere":
|
|
956
|
+
case "deleteWhere":
|
|
957
|
+
if (await deleteGuildThroughIndex(store, operation.guildId)) break;
|
|
958
|
+
for (const [key, value] of await store.entries()) if (operation.predicate(value)) await store.delete(key);
|
|
921
959
|
}
|
|
922
960
|
}
|
|
923
961
|
}
|
|
924
962
|
/**
|
|
963
|
+
* Deletes a guild's entries through the store's guild index, when it has one.
|
|
964
|
+
*
|
|
965
|
+
* @returns Whether the entries were deleted, `false` when the caller has to scan the store instead.
|
|
966
|
+
*/
|
|
967
|
+
async function deleteGuildThroughIndex(store, guildId) {
|
|
968
|
+
if (guildId === void 0 || store.deleteGuild === void 0) return false;
|
|
969
|
+
return await store.deleteGuild(guildId) !== null;
|
|
970
|
+
}
|
|
971
|
+
/**
|
|
925
972
|
* Writes a gateway dispatch into every relevant entity cache of a {@link Cache}.
|
|
926
973
|
*
|
|
927
974
|
* @param cache The cache to mutate.
|
|
@@ -1025,7 +1072,8 @@ function hydrateGuildSoundboardSoundsUpdate(operations, data) {
|
|
|
1025
1072
|
operations.push({
|
|
1026
1073
|
type: "deletePrefix",
|
|
1027
1074
|
store: "soundboardSounds",
|
|
1028
|
-
prefix: `${data.guild_id}
|
|
1075
|
+
prefix: `${data.guild_id}:`,
|
|
1076
|
+
guildId: data.guild_id
|
|
1029
1077
|
});
|
|
1030
1078
|
for (const sound of data.soundboard_sounds) hydrateSoundboardSound(operations, data.guild_id, sound);
|
|
1031
1079
|
}
|
|
@@ -1118,36 +1166,17 @@ function hydrateSoundboardSound(operations, guildId, sound) {
|
|
|
1118
1166
|
});
|
|
1119
1167
|
}
|
|
1120
1168
|
function deleteGuildScopedResources(operations, guildId) {
|
|
1121
|
-
for (const store of
|
|
1122
|
-
"auditLogEntries",
|
|
1123
|
-
"autoModerationRules",
|
|
1124
|
-
"bans",
|
|
1125
|
-
"emojis",
|
|
1126
|
-
"integrations",
|
|
1127
|
-
"members",
|
|
1128
|
-
"presences",
|
|
1129
|
-
"roles",
|
|
1130
|
-
"scheduledEvents",
|
|
1131
|
-
"soundboardSounds",
|
|
1132
|
-
"stageInstances",
|
|
1133
|
-
"stickers",
|
|
1134
|
-
"voiceStates"
|
|
1135
|
-
]) operations.push({
|
|
1169
|
+
for (const store of GuildKeyedCacheEntityNames) operations.push({
|
|
1136
1170
|
type: "deletePrefix",
|
|
1137
1171
|
store,
|
|
1138
|
-
prefix: `${guildId}
|
|
1172
|
+
prefix: `${guildId}:`,
|
|
1173
|
+
guildId
|
|
1139
1174
|
});
|
|
1140
|
-
for (const store of
|
|
1141
|
-
"applicationCommandPermissions",
|
|
1142
|
-
"channels",
|
|
1143
|
-
"threads",
|
|
1144
|
-
"threadMembers",
|
|
1145
|
-
"messages",
|
|
1146
|
-
"invites"
|
|
1147
|
-
]) operations.push({
|
|
1175
|
+
for (const store of GuildFieldCacheEntityNames) operations.push({
|
|
1148
1176
|
type: "deleteWhere",
|
|
1149
1177
|
store,
|
|
1150
|
-
predicate: (value) => isObject(value) && value.guild_id === guildId
|
|
1178
|
+
predicate: (value) => isObject(value) && value.guild_id === guildId,
|
|
1179
|
+
guildId
|
|
1151
1180
|
});
|
|
1152
1181
|
}
|
|
1153
1182
|
function updateMessage(channelId, messageId, update) {
|
|
@@ -1279,6 +1308,7 @@ var CacheValueError = class extends Error {
|
|
|
1279
1308
|
this.key = key;
|
|
1280
1309
|
}
|
|
1281
1310
|
};
|
|
1311
|
+
const DeleteGuildChunkSize = 500;
|
|
1282
1312
|
const CompressionMarkers = {
|
|
1283
1313
|
gzip: "gz:",
|
|
1284
1314
|
brotli: "br:"
|
|
@@ -1289,6 +1319,9 @@ const CompressionMarkers = {
|
|
|
1289
1319
|
* @remarks
|
|
1290
1320
|
* Every entry is stored as its own string key (`<prefix>:<key>`), and a sorted set (`<prefix>:@index`) tracks the
|
|
1291
1321
|
* stored keys with their expiration time as score, which is what `keys`, `entries`, `getSize`, and `clear` read from.
|
|
1322
|
+
*
|
|
1323
|
+
* With {@link RedisEntityCacheOptions.guildOf}, one more sorted set per guild (`<prefix>:@guild:<guildId>`) tracks the
|
|
1324
|
+
* keys of that guild's entries the same way, and `<prefix>:@guilds` lists the guilds having one, for `clear`.
|
|
1292
1325
|
*/
|
|
1293
1326
|
var RedisEntityCache = class {
|
|
1294
1327
|
prefix;
|
|
@@ -1296,6 +1329,7 @@ var RedisEntityCache = class {
|
|
|
1296
1329
|
compression;
|
|
1297
1330
|
compressionThreshold;
|
|
1298
1331
|
#redis;
|
|
1332
|
+
#guildOf;
|
|
1299
1333
|
constructor(redis, options) {
|
|
1300
1334
|
if (options.ttl !== void 0 && !(options.ttl > 0)) throw new RangeError(`ttl must be a positive amount of seconds, received ${options.ttl}`);
|
|
1301
1335
|
this.#redis = redis;
|
|
@@ -1303,6 +1337,7 @@ var RedisEntityCache = class {
|
|
|
1303
1337
|
this.ttl = options.ttl;
|
|
1304
1338
|
this.compression = options.compression ?? "none";
|
|
1305
1339
|
this.compressionThreshold = options.compressionThreshold ?? 1024;
|
|
1340
|
+
this.#guildOf = options.guildOf;
|
|
1306
1341
|
}
|
|
1307
1342
|
async get(key) {
|
|
1308
1343
|
const valueKey = this.valueKey(key);
|
|
@@ -1311,12 +1346,21 @@ var RedisEntityCache = class {
|
|
|
1311
1346
|
}
|
|
1312
1347
|
async set(key, value) {
|
|
1313
1348
|
const serialized = await this.serialize(value);
|
|
1349
|
+
const guildId = this.#guildOf?.(key, value);
|
|
1314
1350
|
const transaction = this.#redis.multi();
|
|
1315
|
-
if (this.ttl === void 0)
|
|
1316
|
-
|
|
1351
|
+
if (this.ttl === void 0) {
|
|
1352
|
+
transaction.set(this.valueKey(key), serialized).zadd(this.indexKey, "+inf", key);
|
|
1353
|
+
if (guildId !== void 0) transaction.zadd(this.guildIndexKey(guildId), "+inf", key).zadd(this.guildsKey, "+inf", guildId);
|
|
1354
|
+
} else {
|
|
1317
1355
|
const now = Date.now();
|
|
1318
1356
|
const milliseconds = Math.round(this.ttl * 1e3);
|
|
1319
1357
|
transaction.set(this.valueKey(key), serialized, "PX", milliseconds).zadd(this.indexKey, now + milliseconds, key).zremrangebyscore(this.indexKey, "-inf", now);
|
|
1358
|
+
if (guildId !== void 0) {
|
|
1359
|
+
const guildIndexKey = this.guildIndexKey(guildId);
|
|
1360
|
+
transaction.zadd(guildIndexKey, now + milliseconds, key).zremrangebyscore(guildIndexKey, "-inf", now).zadd(this.guildsKey, now + milliseconds, guildId).zremrangebyscore(this.guildsKey, "-inf", now);
|
|
1361
|
+
transaction.pexpire?.(guildIndexKey, milliseconds);
|
|
1362
|
+
transaction.pexpire?.(this.guildsKey, milliseconds);
|
|
1363
|
+
}
|
|
1320
1364
|
}
|
|
1321
1365
|
await execute(transaction);
|
|
1322
1366
|
}
|
|
@@ -1324,13 +1368,40 @@ var RedisEntityCache = class {
|
|
|
1324
1368
|
return await this.#redis.exists(this.valueKey(key)) > 0;
|
|
1325
1369
|
}
|
|
1326
1370
|
async delete(key) {
|
|
1327
|
-
const
|
|
1371
|
+
const guildId = await this.readGuild(key);
|
|
1372
|
+
const transaction = this.#redis.multi().del(this.valueKey(key)).zrem(this.indexKey, key);
|
|
1373
|
+
if (guildId !== void 0) transaction.zrem(this.guildIndexKey(guildId), key);
|
|
1374
|
+
const [deleted] = await execute(transaction);
|
|
1328
1375
|
return deleted > 0;
|
|
1329
1376
|
}
|
|
1330
1377
|
async clear() {
|
|
1331
1378
|
const keys = await this.#redis.zrange(this.indexKey, "0", "-1");
|
|
1332
1379
|
if (keys.length > 0) await this.#redis.del(...keys.map((key) => this.valueKey(key)));
|
|
1333
|
-
await this.#redis.
|
|
1380
|
+
const guilds = this.#guildOf ? await this.#redis.zrange(this.guildsKey, "0", "-1") : [];
|
|
1381
|
+
await this.#redis.del(this.indexKey, this.guildsKey, ...guilds.map((guildId) => this.guildIndexKey(guildId)));
|
|
1382
|
+
}
|
|
1383
|
+
/**
|
|
1384
|
+
* Deletes every entry of a guild, reading their keys from the guild's index rather than scanning the cache.
|
|
1385
|
+
*
|
|
1386
|
+
* @remarks
|
|
1387
|
+
* Only indexed entries are deleted: when `guildOf` is set on a cache already holding entries, the ones written
|
|
1388
|
+
* before are left behind until they expire or the cache is cleared.
|
|
1389
|
+
*
|
|
1390
|
+
* @param guildId The ID of the guild.
|
|
1391
|
+
* @returns The amount of deleted entries, or `null` without {@link RedisEntityCacheOptions.guildOf}.
|
|
1392
|
+
*/
|
|
1393
|
+
async deleteGuild(guildId) {
|
|
1394
|
+
if (this.#guildOf === void 0) return null;
|
|
1395
|
+
const guildIndexKey = this.guildIndexKey(guildId);
|
|
1396
|
+
const keys = await this.#redis.zrange(guildIndexKey, "0", "-1");
|
|
1397
|
+
let deleted = 0;
|
|
1398
|
+
for (let index = 0; index < keys.length; index += DeleteGuildChunkSize) {
|
|
1399
|
+
const chunk = keys.slice(index, index + DeleteGuildChunkSize);
|
|
1400
|
+
const [count] = await execute(this.#redis.multi().del(...chunk.map((key) => this.valueKey(key))).zrem(this.indexKey, ...chunk).zrem(guildIndexKey, ...chunk));
|
|
1401
|
+
deleted += count;
|
|
1402
|
+
}
|
|
1403
|
+
await this.#redis.zrem(this.guildsKey, guildId);
|
|
1404
|
+
return deleted;
|
|
1334
1405
|
}
|
|
1335
1406
|
async getSize() {
|
|
1336
1407
|
await this.prune();
|
|
@@ -1367,6 +1438,36 @@ var RedisEntityCache = class {
|
|
|
1367
1438
|
get indexKey() {
|
|
1368
1439
|
return `${this.prefix}:@index`;
|
|
1369
1440
|
}
|
|
1441
|
+
/**
|
|
1442
|
+
* Gets the Redis key of the sorted set indexing a guild's entries.
|
|
1443
|
+
* @param guildId The ID of the guild.
|
|
1444
|
+
*/
|
|
1445
|
+
guildIndexKey(guildId) {
|
|
1446
|
+
return `${this.prefix}:@guild:${guildId}`;
|
|
1447
|
+
}
|
|
1448
|
+
/**
|
|
1449
|
+
* The Redis key of the sorted set listing the guilds having an index.
|
|
1450
|
+
*/
|
|
1451
|
+
get guildsKey() {
|
|
1452
|
+
return `${this.prefix}:@guilds`;
|
|
1453
|
+
}
|
|
1454
|
+
/**
|
|
1455
|
+
* Reads the guild of a stored entry, to remove the entry from its guild index.
|
|
1456
|
+
* @param key The entity cache key.
|
|
1457
|
+
*/
|
|
1458
|
+
async readGuild(key) {
|
|
1459
|
+
if (this.#guildOf === void 0) return void 0;
|
|
1460
|
+
const fromKey = this.#guildOf(key);
|
|
1461
|
+
if (fromKey !== void 0) return fromKey;
|
|
1462
|
+
let value;
|
|
1463
|
+
try {
|
|
1464
|
+
value = await this.get(key);
|
|
1465
|
+
} catch (error) {
|
|
1466
|
+
if (error instanceof CacheValueError) return void 0;
|
|
1467
|
+
throw error;
|
|
1468
|
+
}
|
|
1469
|
+
return value === void 0 ? void 0 : this.#guildOf(key, value);
|
|
1470
|
+
}
|
|
1370
1471
|
async prune() {
|
|
1371
1472
|
if (this.ttl !== void 0) await this.#redis.zremrangebyscore(this.indexKey, "-inf", Date.now());
|
|
1372
1473
|
}
|
|
@@ -1401,6 +1502,22 @@ function decode(value) {
|
|
|
1401
1502
|
return Buffer.from(value.slice(3), "base64");
|
|
1402
1503
|
}
|
|
1403
1504
|
/**
|
|
1505
|
+
* The guild of an entry keyed by its guild (`${guildId}:...`), matching what the `deletePrefix` scan drops.
|
|
1506
|
+
*/
|
|
1507
|
+
function guildOfKey(key, value) {
|
|
1508
|
+
const separator = key.indexOf(":");
|
|
1509
|
+
return separator > 0 ? key.slice(0, separator) : guildOfField(key, value);
|
|
1510
|
+
}
|
|
1511
|
+
/**
|
|
1512
|
+
* The guild of an entry keyed by its own ID, matching what the `deleteWhere` scan over `guild_id` drops.
|
|
1513
|
+
*/
|
|
1514
|
+
function guildOfField(_key, value) {
|
|
1515
|
+
if (typeof value !== "object" || value === null) return void 0;
|
|
1516
|
+
const guildId = value.guild_id;
|
|
1517
|
+
return typeof guildId === "string" ? guildId : void 0;
|
|
1518
|
+
}
|
|
1519
|
+
const GuildResolvers = Object.fromEntries([...GuildKeyedCacheEntityNames.map((name) => [name, guildOfKey]), ...GuildFieldCacheEntityNames.map((name) => [name, guildOfField])]);
|
|
1520
|
+
/**
|
|
1404
1521
|
* The default prefix of every Redis key owned by a cache created with {@link createRedisCache}.
|
|
1405
1522
|
*/
|
|
1406
1523
|
const DefaultRedisCachePrefix = "wolfstar:cache";
|
|
@@ -1422,12 +1539,13 @@ const DefaultRedisCachePrefix = "wolfstar:cache";
|
|
|
1422
1539
|
* @param options The options for the cache.
|
|
1423
1540
|
*/
|
|
1424
1541
|
function createRedisCache(options) {
|
|
1425
|
-
const { redis, prefix = DefaultRedisCachePrefix, compression, compressionThreshold, ttl } = options;
|
|
1542
|
+
const { redis, prefix = DefaultRedisCachePrefix, compression, compressionThreshold, ttl, indexGuilds = true } = options;
|
|
1426
1543
|
return Object.freeze(Object.fromEntries(CacheEntityNames.map((name) => [name, new RedisEntityCache(redis, {
|
|
1427
1544
|
prefix: `${prefix}:${name}`,
|
|
1428
1545
|
ttl: ttl?.[name],
|
|
1429
1546
|
compression,
|
|
1430
|
-
compressionThreshold
|
|
1547
|
+
compressionThreshold,
|
|
1548
|
+
guildOf: indexGuilds ? GuildResolvers[name] : void 0
|
|
1431
1549
|
})])));
|
|
1432
1550
|
}
|
|
1433
1551
|
|
package/dist/esm/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","names":[],"sources":["../../src/lib/keys.ts","../../src/lib/reactions.ts","../../src/lib/operations.ts","../../src/lib/gateway.ts","../../src/lib/memory.ts","../../src/lib/redis.ts"],"sourcesContent":["import type { Snowflake } from \"discord-api-types/v10\";\n\n/**\n * Creates a stable key for a guild-scoped entity.\n */\nexport function guildScopedKey(guildId: Snowflake, id: Snowflake): string {\n return `${guildId}:${id}`;\n}\n\n/**\n * Creates a stable key for a channel message.\n */\nexport function messageKey(channelId: Snowflake, messageId: Snowflake): string {\n return `${channelId}:${messageId}`;\n}\n\n/**\n * Creates a stable key for a guild member.\n */\nexport function memberKey(guildId: Snowflake, userId: Snowflake): string {\n return guildScopedKey(guildId, userId);\n}\n\n/**\n * Creates a stable key for a guild presence.\n */\nexport function presenceKey(guildId: Snowflake, userId: Snowflake): string {\n return guildScopedKey(guildId, userId);\n}\n\n/**\n * Creates a stable key for a guild voice state.\n */\nexport function voiceStateKey(guildId: Snowflake, userId: Snowflake): string {\n return guildScopedKey(guildId, userId);\n}\n\n/**\n * Creates a stable key for a guild role.\n */\nexport function roleKey(guildId: Snowflake, roleId: Snowflake): string {\n return guildScopedKey(guildId, roleId);\n}\n\n/**\n * Creates a stable key for a guild emoji.\n */\nexport function emojiKey(guildId: Snowflake, emojiId: Snowflake): string {\n return guildScopedKey(guildId, emojiId);\n}\n\n/**\n * Creates a stable key for a guild sticker.\n */\nexport function stickerKey(guildId: Snowflake, stickerId: Snowflake): string {\n return guildScopedKey(guildId, stickerId);\n}\n\n/**\n * Creates a stable key for a guild scheduled event.\n */\nexport function scheduledEventKey(guildId: Snowflake, scheduledEventId: Snowflake): string {\n return guildScopedKey(guildId, scheduledEventId);\n}\n\n/**\n * Creates a stable key for a stage instance.\n */\nexport function stageInstanceKey(guildId: Snowflake, channelId: Snowflake): string {\n return guildScopedKey(guildId, channelId);\n}\n\n/**\n * Creates a stable key for a guild soundboard sound.\n */\nexport function soundboardSoundKey(guildId: Snowflake, soundId: Snowflake): string {\n return guildScopedKey(guildId, soundId);\n}\n\n/**\n * Creates a stable key for a guild auto moderation rule.\n */\nexport function autoModerationRuleKey(guildId: Snowflake, ruleId: Snowflake): string {\n return guildScopedKey(guildId, ruleId);\n}\n\n/**\n * Creates a stable key for a guild ban.\n */\nexport function banKey(guildId: Snowflake, userId: Snowflake): string {\n return guildScopedKey(guildId, userId);\n}\n\n/**\n * Creates a stable key for a guild integration.\n */\nexport function integrationKey(guildId: Snowflake, integrationId: Snowflake): string {\n return guildScopedKey(guildId, integrationId);\n}\n\n/**\n * Creates a stable key for an invite, using `@global` for invites that do not belong to a guild.\n */\nexport function inviteKey(guildId: Snowflake | null | undefined, code: string): string {\n return `${guildId ?? \"@global\"}:${code}`;\n}\n\n/**\n * Creates a stable key for a thread member.\n */\nexport function threadMemberKey(threadId: Snowflake, userId: Snowflake): string {\n return `${threadId}:${userId}`;\n}\n\n/**\n * Creates a stable key for application command permissions.\n */\nexport function applicationCommandPermissionsKey(\n applicationId: Snowflake,\n guildId: Snowflake,\n commandId: Snowflake,\n): string {\n return `${applicationId}:${guildId}:${commandId}`;\n}\n","import type {\n APIMessage,\n APIPartialEmoji,\n APIReaction,\n GatewayMessagePollVoteDispatchData,\n GatewayMessageReactionAddDispatchData,\n GatewayMessageReactionRemoveDispatchData,\n} from \"discord-api-types/v10\";\n\n// Reactions and poll votes only carry the voter's ID, so whether the bot is the voter needs the bot's own ID.\n\n/**\n * Whether two reaction emojis are the same: custom emojis match by ID, Unicode emojis by name.\n */\nfunction isSameEmoji(a: APIPartialEmoji, b: APIPartialEmoji): boolean {\n return a.id ? a.id === b.id : !b.id && a.name === b.name;\n}\n\n/**\n * Adds a `MESSAGE_REACTION_ADD` to a cached message's reactions.\n *\n * @param message The cached message.\n * @param data The dispatch data.\n * @param clientUserId The bot's user ID, to set `me` when the bot reacted.\n */\nexport function addReaction(\n message: APIMessage,\n data: GatewayMessageReactionAddDispatchData,\n clientUserId?: string,\n): APIMessage {\n const me = data.user_id === clientUserId;\n const kind = data.burst ? \"burst\" : \"normal\";\n const reactions = message.reactions ?? [];\n const existing = reactions.find((reaction) => isSameEmoji(reaction.emoji, data.emoji));\n\n if (!existing) {\n const reaction: APIReaction = {\n emoji: data.emoji,\n count: 1,\n count_details: { normal: data.burst ? 0 : 1, burst: data.burst ? 1 : 0 },\n me: me && !data.burst,\n me_burst: me && data.burst,\n burst_colors: data.burst_colors ?? [],\n };\n return { ...message, reactions: [...reactions, reaction] };\n }\n\n return {\n ...message,\n reactions: reactions.map((reaction) =>\n reaction === existing\n ? {\n ...reaction,\n count: reaction.count + 1,\n count_details: {\n ...reaction.count_details,\n [kind]: reaction.count_details[kind] + 1,\n },\n me: reaction.me || (me && !data.burst),\n me_burst: reaction.me_burst || (me && data.burst),\n burst_colors: data.burst\n ? (data.burst_colors ?? reaction.burst_colors)\n : reaction.burst_colors,\n }\n : reaction,\n ),\n };\n}\n\n/**\n * Removes a `MESSAGE_REACTION_REMOVE` from a cached message's reactions, dropping the reaction when its count reaches\n * zero.\n *\n * @param message The cached message.\n * @param data The dispatch data.\n * @param clientUserId The bot's user ID, to clear `me` when the bot's reaction was removed.\n */\nexport function removeReaction(\n message: APIMessage,\n data: GatewayMessageReactionRemoveDispatchData,\n clientUserId?: string,\n): APIMessage {\n const me = data.user_id === clientUserId;\n const kind = data.burst ? \"burst\" : \"normal\";\n const reactions = (message.reactions ?? []).flatMap((reaction) => {\n if (!isSameEmoji(reaction.emoji, data.emoji)) return [reaction];\n if (reaction.count <= 1) return [];\n return [\n {\n ...reaction,\n count: reaction.count - 1,\n count_details: {\n ...reaction.count_details,\n [kind]: Math.max(0, reaction.count_details[kind] - 1),\n },\n me: reaction.me && !(me && !data.burst),\n me_burst: reaction.me_burst && !(me && data.burst),\n },\n ];\n });\n return { ...message, reactions };\n}\n\n/**\n * Removes every reaction with one emoji from a cached message.\n *\n * @param message The cached message.\n * @param emoji The emoji.\n */\nexport function removeReactionEmoji(message: APIMessage, emoji: APIPartialEmoji): APIMessage {\n return {\n ...message,\n reactions: (message.reactions ?? []).filter((reaction) => !isSameEmoji(reaction.emoji, emoji)),\n };\n}\n\n/**\n * Counts a `MESSAGE_POLL_VOTE_ADD` or `MESSAGE_POLL_VOTE_REMOVE` in a cached message's poll results.\n *\n * @param message The cached message.\n * @param data The dispatch data.\n * @param delta `1` for a vote, `-1` for a removed vote.\n * @param clientUserId The bot's user ID, to set `me_voted` when the bot voted.\n */\nexport function countPollVote(\n message: APIMessage,\n data: GatewayMessagePollVoteDispatchData,\n delta: 1 | -1,\n clientUserId?: string,\n): APIMessage {\n const { poll } = message;\n if (!poll) return message;\n\n const me = data.user_id === clientUserId;\n const results = poll.results ?? { is_finalized: false, answer_counts: [] };\n const counts = results.answer_counts.some((count) => count.id === data.answer_id)\n ? results.answer_counts\n : [...results.answer_counts, { id: data.answer_id, count: 0, me_voted: false }];\n\n return {\n ...message,\n poll: {\n ...poll,\n results: {\n ...results,\n answer_counts: counts.map((count) =>\n count.id === data.answer_id\n ? {\n ...count,\n count: Math.max(0, count.count + delta),\n me_voted: me ? delta === 1 : count.me_voted,\n }\n : count,\n ),\n },\n },\n };\n}\n","import { GatewayDispatchEvents } from \"discord-api-types/v10\";\nimport type {\n APIEmoji,\n APIMessage,\n APIGuildMember,\n APIRole,\n APISoundboardSound,\n APISticker,\n GatewayDispatchPayload,\n GatewayGuildCreateDispatchData,\n GatewayGuildMembersChunkDispatchData,\n GatewayGuildSoundboardSoundsUpdateDispatchData,\n GatewaySoundboardSoundsDispatchData,\n GatewayThreadListSync,\n GatewayThreadMembersUpdateDispatchData,\n Snowflake,\n} from \"discord-api-types/v10\";\nimport {\n applicationCommandPermissionsKey,\n autoModerationRuleKey,\n banKey,\n emojiKey,\n guildScopedKey,\n integrationKey,\n inviteKey,\n memberKey,\n messageKey,\n presenceKey,\n roleKey,\n scheduledEventKey,\n soundboardSoundKey,\n stageInstanceKey,\n stickerKey,\n threadMemberKey,\n voiceStateKey,\n} from \"./keys.js\";\nimport { addReaction, countPollVote, removeReaction, removeReactionEmoji } from \"./reactions.js\";\nimport type { Cache, CacheEntityName, EntityCache } from \"./types.js\";\n\n// A record rather than an array so the compiler enforces that every entity cache is listed.\nconst cacheEntityNameRecord: Record<CacheEntityName, true> = {\n applicationCommandPermissions: true,\n auditLogEntries: true,\n autoModerationRules: true,\n bans: true,\n channels: true,\n emojis: true,\n entitlements: true,\n guilds: true,\n integrations: true,\n invites: true,\n members: true,\n messages: true,\n presences: true,\n roles: true,\n scheduledEvents: true,\n soundboardSounds: true,\n stageInstances: true,\n stickers: true,\n subscriptions: true,\n threadMembers: true,\n threads: true,\n users: true,\n voiceStates: true,\n};\n\n/**\n * The name of every entity cache held by a {@link Cache}.\n */\nexport const CacheEntityNames = Object.keys(cacheEntityNameRecord) as readonly CacheEntityName[];\n\n/**\n * A single mutation a gateway dispatch produces on a {@link Cache}.\n */\nexport type CacheOperation =\n | {\n type: \"upsert\";\n store: CacheEntityName;\n key: string;\n raw: unknown;\n /** Whether to shallow-merge `raw` onto the existing value, used for partial updates. */\n merge?: boolean;\n }\n | {\n type: \"update\";\n store: CacheEntityName;\n key: string;\n /** Computes the new value from the cached one. Nothing is written when the key is not cached. */\n update: (value: unknown) => unknown;\n }\n | { type: \"delete\"; store: CacheEntityName; key: string }\n | { type: \"deletePrefix\"; store: CacheEntityName; prefix: string }\n | { type: \"deleteWhere\"; store: CacheEntityName; predicate: (value: unknown) => boolean };\n\n/**\n * What {@link createCacheOperations} needs to know besides the dispatch.\n */\nexport interface CacheOperationContext {\n /**\n * The bot's user ID. Reactions and poll votes only carry the voter's ID, so without it the cached `me` and\n * `me_voted` flags are never set.\n */\n clientUserId?: string;\n}\n\n/**\n * Translates a gateway dispatch into the list of {@link CacheOperation}s it implies, including the cascades (e.g.\n * `CHANNEL_DELETE` also drops that channel's messages).\n *\n * @remarks\n * This is a pure function, it does not touch any cache. Use {@link applyGatewayDispatch} to apply them.\n * `INTERACTION_CREATE` is intentionally ignored: interactions are short-lived and never cached.\n *\n * @param payload The gateway dispatch payload.\n * @param context The bot's user ID, for the `me` flags of reactions and poll votes.\n */\nexport function createCacheOperations(\n payload: GatewayDispatchPayload,\n context: CacheOperationContext = {},\n): CacheOperation[] {\n const { clientUserId } = context;\n const operations: CacheOperation[] = [];\n\n switch (payload.t) {\n case GatewayDispatchEvents.ApplicationCommandPermissionsUpdate: {\n const data = payload.d;\n operations.push({\n type: \"upsert\",\n store: \"applicationCommandPermissions\",\n key: applicationCommandPermissionsKey(data.application_id, data.guild_id, data.id),\n raw: data,\n });\n break;\n }\n\n case GatewayDispatchEvents.AutoModerationRuleCreate:\n case GatewayDispatchEvents.AutoModerationRuleUpdate: {\n const data = payload.d;\n operations.push({\n type: \"upsert\",\n store: \"autoModerationRules\",\n key: autoModerationRuleKey(data.guild_id, data.id),\n raw: data,\n });\n break;\n }\n\n case GatewayDispatchEvents.AutoModerationRuleDelete: {\n const data = payload.d;\n operations.push({\n type: \"delete\",\n store: \"autoModerationRules\",\n key: autoModerationRuleKey(data.guild_id, data.id),\n });\n break;\n }\n\n case GatewayDispatchEvents.ChannelCreate:\n case GatewayDispatchEvents.ChannelUpdate: {\n const data = payload.d;\n operations.push({\n type: \"upsert\",\n store: \"channels\",\n key: data.id,\n raw: data,\n merge: payload.t === GatewayDispatchEvents.ChannelUpdate,\n });\n break;\n }\n\n case GatewayDispatchEvents.ChannelDelete: {\n const data = payload.d;\n operations.push({ type: \"delete\", store: \"channels\", key: data.id });\n operations.push({ type: \"deletePrefix\", store: \"messages\", prefix: `${data.id}:` });\n break;\n }\n\n case GatewayDispatchEvents.EntitlementCreate:\n case GatewayDispatchEvents.EntitlementUpdate: {\n const data = payload.d;\n operations.push({ type: \"upsert\", store: \"entitlements\", key: data.id, raw: data });\n break;\n }\n\n case GatewayDispatchEvents.EntitlementDelete: {\n operations.push({ type: \"delete\", store: \"entitlements\", key: payload.d.id });\n break;\n }\n\n case GatewayDispatchEvents.GuildAuditLogEntryCreate: {\n const data = payload.d;\n operations.push({\n type: \"upsert\",\n store: \"auditLogEntries\",\n key: guildScopedKey(data.guild_id, data.id),\n raw: data,\n });\n break;\n }\n\n case GatewayDispatchEvents.GuildBanAdd: {\n const data = payload.d;\n operations.push({\n type: \"upsert\",\n store: \"bans\",\n key: banKey(data.guild_id, data.user.id),\n raw: data,\n });\n operations.push({ type: \"upsert\", store: \"users\", key: data.user.id, raw: data.user });\n break;\n }\n\n case GatewayDispatchEvents.GuildBanRemove: {\n const data = payload.d;\n operations.push({ type: \"delete\", store: \"bans\", key: banKey(data.guild_id, data.user.id) });\n operations.push({ type: \"upsert\", store: \"users\", key: data.user.id, raw: data.user });\n break;\n }\n\n case GatewayDispatchEvents.GuildCreate: {\n hydrateGuildCreate(operations, payload.d);\n break;\n }\n\n case GatewayDispatchEvents.GuildUpdate: {\n // Like `GUILD_CREATE`, the roles and emojis go to their own entity caches rather than the guild entry.\n const { roles, emojis, stickers, ...data } = payload.d;\n operations.push({ type: \"upsert\", store: \"guilds\", key: data.id, raw: data, merge: true });\n for (const role of roles ?? []) hydrateRole(operations, data.id, role);\n for (const emoji of emojis ?? []) hydrateEmoji(operations, data.id, emoji);\n for (const sticker of stickers ?? []) hydrateSticker(operations, data.id, sticker);\n break;\n }\n\n case GatewayDispatchEvents.GuildDelete: {\n const data = payload.d;\n // An `unavailable` guild is an outage, not a removal: keep its data around until it comes back.\n if (data.unavailable) {\n operations.push({ type: \"upsert\", store: \"guilds\", key: data.id, raw: data, merge: true });\n break;\n }\n\n operations.push({ type: \"delete\", store: \"guilds\", key: data.id });\n deleteGuildScopedResources(operations, data.id);\n break;\n }\n\n case GatewayDispatchEvents.GuildEmojisUpdate: {\n const data = payload.d;\n operations.push({ type: \"deletePrefix\", store: \"emojis\", prefix: `${data.guild_id}:` });\n for (const emoji of data.emojis) {\n if (!emoji.id) continue;\n operations.push({\n type: \"upsert\",\n store: \"emojis\",\n key: emojiKey(data.guild_id, emoji.id),\n raw: withGuildId(emoji, data.guild_id),\n });\n }\n break;\n }\n\n case GatewayDispatchEvents.GuildStickersUpdate: {\n const data = payload.d;\n operations.push({ type: \"deletePrefix\", store: \"stickers\", prefix: `${data.guild_id}:` });\n for (const sticker of data.stickers) {\n operations.push({\n type: \"upsert\",\n store: \"stickers\",\n key: stickerKey(data.guild_id, sticker.id),\n raw: withGuildId(sticker, data.guild_id),\n });\n }\n break;\n }\n\n case GatewayDispatchEvents.GuildMemberAdd:\n case GatewayDispatchEvents.GuildMemberUpdate: {\n const data = payload.d;\n operations.push({\n type: \"upsert\",\n store: \"members\",\n key: memberKey(data.guild_id, data.user.id),\n raw: data,\n merge: true,\n });\n operations.push({ type: \"upsert\", store: \"users\", key: data.user.id, raw: data.user });\n break;\n }\n\n case GatewayDispatchEvents.GuildMemberRemove: {\n const data = payload.d;\n operations.push({\n type: \"delete\",\n store: \"members\",\n key: memberKey(data.guild_id, data.user.id),\n });\n operations.push({ type: \"upsert\", store: \"users\", key: data.user.id, raw: data.user });\n break;\n }\n\n case GatewayDispatchEvents.GuildMembersChunk: {\n hydrateGuildMembersChunk(operations, payload.d);\n break;\n }\n\n case GatewayDispatchEvents.GuildRoleCreate:\n case GatewayDispatchEvents.GuildRoleUpdate: {\n const data = payload.d;\n operations.push({\n type: \"upsert\",\n store: \"roles\",\n key: roleKey(data.guild_id, data.role.id),\n raw: withGuildId(data.role, data.guild_id),\n merge: payload.t === GatewayDispatchEvents.GuildRoleUpdate,\n });\n break;\n }\n\n case GatewayDispatchEvents.GuildRoleDelete: {\n const data = payload.d;\n operations.push({\n type: \"delete\",\n store: \"roles\",\n key: roleKey(data.guild_id, data.role_id),\n });\n break;\n }\n\n case GatewayDispatchEvents.GuildScheduledEventCreate:\n case GatewayDispatchEvents.GuildScheduledEventUpdate: {\n const data = payload.d;\n operations.push({\n type: \"upsert\",\n store: \"scheduledEvents\",\n key: scheduledEventKey(data.guild_id, data.id),\n raw: data,\n merge: true,\n });\n if (data.creator) {\n operations.push({\n type: \"upsert\",\n store: \"users\",\n key: data.creator.id,\n raw: data.creator,\n });\n }\n break;\n }\n\n case GatewayDispatchEvents.GuildScheduledEventDelete: {\n const data = payload.d;\n operations.push({\n type: \"delete\",\n store: \"scheduledEvents\",\n key: scheduledEventKey(data.guild_id, data.id),\n });\n break;\n }\n\n case GatewayDispatchEvents.GuildSoundboardSoundCreate:\n case GatewayDispatchEvents.GuildSoundboardSoundUpdate: {\n const data = payload.d;\n if (!data.guild_id) break;\n operations.push({\n type: \"upsert\",\n store: \"soundboardSounds\",\n key: soundboardSoundKey(data.guild_id, data.sound_id),\n raw: data,\n merge: payload.t === GatewayDispatchEvents.GuildSoundboardSoundUpdate,\n });\n break;\n }\n\n case GatewayDispatchEvents.GuildSoundboardSoundDelete: {\n const data = payload.d;\n operations.push({\n type: \"delete\",\n store: \"soundboardSounds\",\n key: soundboardSoundKey(data.guild_id, data.sound_id),\n });\n break;\n }\n\n case GatewayDispatchEvents.GuildSoundboardSoundsUpdate: {\n hydrateGuildSoundboardSoundsUpdate(operations, payload.d);\n break;\n }\n\n case GatewayDispatchEvents.SoundboardSounds: {\n hydrateSoundboardSounds(operations, payload.d);\n break;\n }\n\n case GatewayDispatchEvents.IntegrationCreate:\n case GatewayDispatchEvents.IntegrationUpdate: {\n const data = payload.d;\n operations.push({\n type: \"upsert\",\n store: \"integrations\",\n key: integrationKey(data.guild_id, data.id),\n raw: data,\n merge: true,\n });\n break;\n }\n\n case GatewayDispatchEvents.IntegrationDelete: {\n const data = payload.d;\n operations.push({\n type: \"delete\",\n store: \"integrations\",\n key: integrationKey(data.guild_id, data.id),\n });\n break;\n }\n\n case GatewayDispatchEvents.InviteCreate: {\n const data = payload.d;\n operations.push({\n type: \"upsert\",\n store: \"invites\",\n key: inviteKey(data.guild_id, data.code),\n raw: data,\n });\n break;\n }\n\n case GatewayDispatchEvents.InviteDelete: {\n const data = payload.d;\n operations.push({\n type: \"delete\",\n store: \"invites\",\n key: inviteKey(data.guild_id, data.code),\n });\n break;\n }\n\n case GatewayDispatchEvents.MessageCreate:\n case GatewayDispatchEvents.MessageUpdate: {\n const data = payload.d;\n operations.push({\n type: \"upsert\",\n store: \"messages\",\n key: messageKey(data.channel_id, data.id),\n raw: data,\n merge: payload.t === GatewayDispatchEvents.MessageUpdate,\n });\n\n const author = \"author\" in data ? data.author : undefined;\n if (author) {\n operations.push({ type: \"upsert\", store: \"users\", key: author.id, raw: author });\n if (data.member && data.guild_id) {\n operations.push({\n type: \"upsert\",\n store: \"members\",\n key: memberKey(data.guild_id, author.id),\n raw: withGuildId(data.member, data.guild_id),\n merge: true,\n });\n }\n }\n break;\n }\n\n case GatewayDispatchEvents.MessageDelete: {\n const data = payload.d;\n operations.push({\n type: \"delete\",\n store: \"messages\",\n key: messageKey(data.channel_id, data.id),\n });\n break;\n }\n\n case GatewayDispatchEvents.MessageDeleteBulk: {\n const data = payload.d;\n for (const id of data.ids) {\n operations.push({\n type: \"delete\",\n store: \"messages\",\n key: messageKey(data.channel_id, id),\n });\n }\n break;\n }\n\n case GatewayDispatchEvents.MessageReactionAdd: {\n const data = payload.d;\n operations.push(\n updateMessage(data.channel_id, data.message_id, (message) =>\n addReaction(message, data, clientUserId),\n ),\n );\n // Guild reactions carry the reacting member, like messages carry their author.\n const user = data.member?.user;\n if (user && data.guild_id) {\n operations.push({ type: \"upsert\", store: \"users\", key: user.id, raw: user });\n operations.push({\n type: \"upsert\",\n store: \"members\",\n key: memberKey(data.guild_id, user.id),\n raw: withGuildId(data.member!, data.guild_id),\n merge: true,\n });\n }\n break;\n }\n\n case GatewayDispatchEvents.MessageReactionRemove: {\n const data = payload.d;\n operations.push(\n updateMessage(data.channel_id, data.message_id, (message) =>\n removeReaction(message, data, clientUserId),\n ),\n );\n break;\n }\n\n case GatewayDispatchEvents.MessageReactionRemoveAll: {\n const data = payload.d;\n operations.push(\n updateMessage(data.channel_id, data.message_id, (message) => ({\n ...message,\n reactions: [],\n })),\n );\n break;\n }\n\n case GatewayDispatchEvents.MessageReactionRemoveEmoji: {\n const data = payload.d;\n operations.push(\n updateMessage(data.channel_id, data.message_id, (message) =>\n removeReactionEmoji(message, data.emoji),\n ),\n );\n break;\n }\n\n case GatewayDispatchEvents.MessagePollVoteAdd:\n case GatewayDispatchEvents.MessagePollVoteRemove: {\n const data = payload.d;\n const delta = payload.t === GatewayDispatchEvents.MessagePollVoteAdd ? 1 : -1;\n operations.push(\n updateMessage(data.channel_id, data.message_id, (message) =>\n countPollVote(message, data, delta, clientUserId),\n ),\n );\n break;\n }\n\n case GatewayDispatchEvents.PresenceUpdate: {\n const data = payload.d;\n operations.push({\n type: \"upsert\",\n store: \"presences\",\n key: presenceKey(data.guild_id, data.user.id),\n raw: data,\n merge: true,\n });\n operations.push({\n type: \"upsert\",\n store: \"users\",\n key: data.user.id,\n raw: data.user,\n merge: true,\n });\n break;\n }\n\n case GatewayDispatchEvents.Ready: {\n const data = payload.d;\n operations.push({ type: \"upsert\", store: \"users\", key: data.user.id, raw: data.user });\n for (const guild of data.guilds) {\n operations.push({\n type: \"upsert\",\n store: \"guilds\",\n key: guild.id,\n raw: guild,\n merge: true,\n });\n }\n break;\n }\n\n case GatewayDispatchEvents.StageInstanceCreate:\n case GatewayDispatchEvents.StageInstanceUpdate: {\n const data = payload.d;\n operations.push({\n type: \"upsert\",\n store: \"stageInstances\",\n key: stageInstanceKey(data.guild_id, data.channel_id),\n raw: data,\n merge: true,\n });\n break;\n }\n\n case GatewayDispatchEvents.StageInstanceDelete: {\n const data = payload.d;\n operations.push({\n type: \"delete\",\n store: \"stageInstances\",\n key: stageInstanceKey(data.guild_id, data.channel_id),\n });\n break;\n }\n\n case GatewayDispatchEvents.SubscriptionCreate:\n case GatewayDispatchEvents.SubscriptionUpdate: {\n const data = payload.d;\n operations.push({ type: \"upsert\", store: \"subscriptions\", key: data.id, raw: data });\n break;\n }\n\n case GatewayDispatchEvents.SubscriptionDelete: {\n operations.push({ type: \"delete\", store: \"subscriptions\", key: payload.d.id });\n break;\n }\n\n case GatewayDispatchEvents.ThreadCreate:\n case GatewayDispatchEvents.ThreadUpdate: {\n const data = payload.d;\n operations.push({\n type: \"upsert\",\n store: \"threads\",\n key: data.id,\n raw: data,\n merge: payload.t === GatewayDispatchEvents.ThreadUpdate,\n });\n\n const member = \"member\" in data ? data.member : undefined;\n if (member?.user_id) {\n operations.push({\n type: \"upsert\",\n store: \"threadMembers\",\n key: threadMemberKey(data.id, member.user_id),\n // Thread members carry no guild ID of their own: without it, `GUILD_DELETE` could not sweep them.\n raw: data.guild_id ? withGuildId(member, data.guild_id) : member,\n merge: true,\n });\n }\n break;\n }\n\n case GatewayDispatchEvents.ThreadDelete: {\n const data = payload.d;\n operations.push({ type: \"delete\", store: \"threads\", key: data.id });\n operations.push({ type: \"deletePrefix\", store: \"threadMembers\", prefix: `${data.id}:` });\n operations.push({ type: \"deletePrefix\", store: \"messages\", prefix: `${data.id}:` });\n break;\n }\n\n case GatewayDispatchEvents.ThreadListSync: {\n hydrateThreadListSync(operations, payload.d);\n break;\n }\n\n case GatewayDispatchEvents.ThreadMemberUpdate: {\n const data = payload.d;\n if (!data.id || !data.user_id) break;\n operations.push({\n type: \"upsert\",\n store: \"threadMembers\",\n key: threadMemberKey(data.id, data.user_id),\n raw: data,\n merge: true,\n });\n break;\n }\n\n case GatewayDispatchEvents.ThreadMembersUpdate: {\n hydrateThreadMembersUpdate(operations, payload.d);\n break;\n }\n\n case GatewayDispatchEvents.UserUpdate: {\n const data = payload.d;\n operations.push({ type: \"upsert\", store: \"users\", key: data.id, raw: data, merge: true });\n break;\n }\n\n case GatewayDispatchEvents.VoiceStateUpdate: {\n const data = payload.d;\n if (!data.guild_id) break;\n\n const key = voiceStateKey(data.guild_id, data.user_id);\n if (data.channel_id) {\n operations.push({ type: \"upsert\", store: \"voiceStates\", key, raw: data, merge: true });\n } else {\n operations.push({ type: \"delete\", store: \"voiceStates\", key });\n }\n\n const user = data.member?.user;\n if (data.member && user) {\n operations.push({ type: \"upsert\", store: \"users\", key: user.id, raw: user });\n operations.push({\n type: \"upsert\",\n store: \"members\",\n key: memberKey(data.guild_id, user.id),\n raw: withGuildId(data.member, data.guild_id),\n merge: true,\n });\n }\n break;\n }\n\n default:\n break;\n }\n\n return operations;\n}\n\n/**\n * Applies a list of {@link CacheOperation}s to a {@link Cache}, sequentially and in order.\n *\n * @param cache The cache to mutate.\n * @param operations The operations to apply, usually created by {@link createCacheOperations}.\n */\nexport async function applyCacheOperations(\n cache: Cache,\n operations: readonly CacheOperation[],\n): Promise<void> {\n for (const operation of operations) {\n const store = cache[operation.store] as EntityCache<unknown>;\n\n switch (operation.type) {\n case \"upsert\": {\n const value = operation.merge\n ? mergeValues(await store.get(operation.key), operation.raw)\n : operation.raw;\n await store.set(operation.key, value);\n break;\n }\n case \"update\": {\n const existing = await store.get(operation.key);\n if (existing !== undefined) await store.set(operation.key, operation.update(existing));\n break;\n }\n case \"delete\":\n await store.delete(operation.key);\n break;\n case \"deletePrefix\":\n for (const key of await store.keys()) {\n if (key.startsWith(operation.prefix)) await store.delete(key);\n }\n break;\n case \"deleteWhere\":\n for (const [key, value] of await store.entries()) {\n if (operation.predicate(value)) await store.delete(key);\n }\n break;\n }\n }\n}\n\n/**\n * Writes a gateway dispatch into every relevant entity cache of a {@link Cache}.\n *\n * @param cache The cache to mutate.\n * @param payload The gateway dispatch payload.\n * @param context The bot's user ID, for the `me` flags of reactions and poll votes.\n */\nexport function applyGatewayDispatch(\n cache: Cache,\n payload: GatewayDispatchPayload,\n context?: CacheOperationContext,\n): Promise<void> {\n return applyCacheOperations(cache, createCacheOperations(payload, context));\n}\n\n/**\n * Shallow-merges `value` onto `existing` when both are plain objects, returning `value` otherwise.\n */\nexport function mergeValues<Value>(existing: Value | undefined, value: Value): Value {\n if (isObject(existing) && isObject(value)) return { ...existing, ...value };\n return value;\n}\n\nfunction hydrateGuildCreate(\n operations: CacheOperation[],\n guild: GatewayGuildCreateDispatchData,\n): void {\n // Unavailable guilds carry no data besides their ID, see `GuildDelete`.\n if (\"unavailable\" in guild && guild.unavailable) {\n operations.push({ type: \"upsert\", store: \"guilds\", key: guild.id, raw: guild, merge: true });\n return;\n }\n\n const {\n channels,\n threads,\n members,\n presences,\n voice_states: voiceStates,\n stage_instances: stageInstances,\n guild_scheduled_events: scheduledEvents,\n soundboard_sounds: soundboardSounds,\n roles,\n emojis,\n stickers,\n ...rest\n } = guild;\n // Collections are stored in their own entity caches, keeping the guild entry small.\n operations.push({ type: \"upsert\", store: \"guilds\", key: guild.id, raw: rest });\n\n for (const channel of channels ?? []) {\n operations.push({\n type: \"upsert\",\n store: \"channels\",\n key: channel.id,\n raw: withGuildId(channel, guild.id),\n });\n }\n\n for (const thread of threads ?? []) {\n operations.push({\n type: \"upsert\",\n store: \"threads\",\n key: thread.id,\n raw: withGuildId(thread, guild.id),\n });\n if (thread.member?.user_id) {\n operations.push({\n type: \"upsert\",\n store: \"threadMembers\",\n key: threadMemberKey(thread.id, thread.member.user_id),\n raw: withGuildId(thread.member, guild.id),\n });\n }\n }\n\n for (const member of members ?? []) hydrateMember(operations, guild.id, member);\n\n for (const presence of presences ?? []) {\n operations.push({\n type: \"upsert\",\n store: \"presences\",\n key: presenceKey(guild.id, presence.user.id),\n raw: withGuildId(presence, guild.id),\n });\n }\n\n for (const role of roles ?? []) hydrateRole(operations, guild.id, role);\n for (const emoji of emojis ?? []) hydrateEmoji(operations, guild.id, emoji);\n for (const sticker of stickers ?? []) hydrateSticker(operations, guild.id, sticker);\n\n for (const voiceState of voiceStates ?? []) {\n if (!voiceState.channel_id) continue;\n operations.push({\n type: \"upsert\",\n store: \"voiceStates\",\n key: voiceStateKey(guild.id, voiceState.user_id),\n raw: withGuildId(voiceState, guild.id),\n });\n }\n\n for (const stageInstance of stageInstances ?? []) {\n operations.push({\n type: \"upsert\",\n store: \"stageInstances\",\n key: stageInstanceKey(guild.id, stageInstance.channel_id),\n raw: stageInstance,\n });\n }\n\n for (const scheduledEvent of scheduledEvents ?? []) {\n operations.push({\n type: \"upsert\",\n store: \"scheduledEvents\",\n key: scheduledEventKey(guild.id, scheduledEvent.id),\n raw: scheduledEvent,\n });\n }\n\n for (const sound of soundboardSounds ?? []) hydrateSoundboardSound(operations, guild.id, sound);\n}\n\nfunction hydrateGuildMembersChunk(\n operations: CacheOperation[],\n data: GatewayGuildMembersChunkDispatchData,\n): void {\n for (const member of data.members) hydrateMember(operations, data.guild_id, member);\n for (const presence of data.presences ?? []) {\n operations.push({\n type: \"upsert\",\n store: \"presences\",\n key: presenceKey(data.guild_id, presence.user.id),\n raw: withGuildId(presence, data.guild_id),\n });\n }\n}\n\nfunction hydrateGuildSoundboardSoundsUpdate(\n operations: CacheOperation[],\n data: GatewayGuildSoundboardSoundsUpdateDispatchData,\n): void {\n operations.push({ type: \"deletePrefix\", store: \"soundboardSounds\", prefix: `${data.guild_id}:` });\n for (const sound of data.soundboard_sounds)\n hydrateSoundboardSound(operations, data.guild_id, sound);\n}\n\nfunction hydrateSoundboardSounds(\n operations: CacheOperation[],\n data: GatewaySoundboardSoundsDispatchData,\n): void {\n for (const sound of data.soundboard_sounds)\n hydrateSoundboardSound(operations, data.guild_id, sound);\n}\n\nfunction hydrateThreadListSync(operations: CacheOperation[], data: GatewayThreadListSync): void {\n for (const thread of data.threads) {\n operations.push({\n type: \"upsert\",\n store: \"threads\",\n key: thread.id,\n raw: withGuildId(thread, data.guild_id),\n merge: true,\n });\n }\n\n for (const member of data.members) {\n if (!member.id || !member.user_id) continue;\n operations.push({\n type: \"upsert\",\n store: \"threadMembers\",\n key: threadMemberKey(member.id, member.user_id),\n raw: withGuildId(member, data.guild_id),\n merge: true,\n });\n }\n}\n\nfunction hydrateThreadMembersUpdate(\n operations: CacheOperation[],\n data: GatewayThreadMembersUpdateDispatchData,\n): void {\n for (const member of data.added_members ?? []) {\n if (!member.user_id) continue;\n operations.push({\n type: \"upsert\",\n store: \"threadMembers\",\n key: threadMemberKey(data.id, member.user_id),\n raw: withGuildId(member, data.guild_id),\n merge: true,\n });\n }\n\n for (const userId of data.removed_member_ids ?? []) {\n operations.push({\n type: \"delete\",\n store: \"threadMembers\",\n key: threadMemberKey(data.id, userId),\n });\n }\n}\n\nfunction hydrateMember(\n operations: CacheOperation[],\n guildId: Snowflake,\n member: APIGuildMember,\n): void {\n if (!member.user) return;\n\n operations.push({\n type: \"upsert\",\n store: \"members\",\n key: memberKey(guildId, member.user.id),\n raw: withGuildId(member, guildId),\n merge: true,\n });\n operations.push({ type: \"upsert\", store: \"users\", key: member.user.id, raw: member.user });\n}\n\nfunction hydrateRole(operations: CacheOperation[], guildId: Snowflake, role: APIRole): void {\n operations.push({\n type: \"upsert\",\n store: \"roles\",\n key: roleKey(guildId, role.id),\n raw: withGuildId(role, guildId),\n });\n}\n\nfunction hydrateEmoji(operations: CacheOperation[], guildId: Snowflake, emoji: APIEmoji): void {\n if (!emoji.id) return;\n operations.push({\n type: \"upsert\",\n store: \"emojis\",\n key: emojiKey(guildId, emoji.id),\n raw: withGuildId(emoji, guildId),\n });\n}\n\nfunction hydrateSticker(\n operations: CacheOperation[],\n guildId: Snowflake,\n sticker: APISticker,\n): void {\n operations.push({\n type: \"upsert\",\n store: \"stickers\",\n key: stickerKey(guildId, sticker.id),\n raw: withGuildId(sticker, guildId),\n });\n}\n\nfunction hydrateSoundboardSound(\n operations: CacheOperation[],\n guildId: Snowflake,\n sound: APISoundboardSound,\n): void {\n operations.push({\n type: \"upsert\",\n store: \"soundboardSounds\",\n key: soundboardSoundKey(guildId, sound.sound_id),\n raw: withGuildId(sound, guildId),\n });\n}\n\nfunction deleteGuildScopedResources(operations: CacheOperation[], guildId: Snowflake): void {\n // Entities keyed by `${guildId}:...` can be dropped by prefix, which is cheap.\n const prefixed = [\n \"auditLogEntries\",\n \"autoModerationRules\",\n \"bans\",\n \"emojis\",\n \"integrations\",\n \"members\",\n \"presences\",\n \"roles\",\n \"scheduledEvents\",\n \"soundboardSounds\",\n \"stageInstances\",\n \"stickers\",\n \"voiceStates\",\n ] as const satisfies readonly CacheEntityName[];\n\n for (const store of prefixed) {\n operations.push({ type: \"deletePrefix\", store, prefix: `${guildId}:` });\n }\n\n // The rest are keyed by their own ID and need a scan over the stored `guild_id`.\n const scanned = [\n \"applicationCommandPermissions\",\n \"channels\",\n \"threads\",\n \"threadMembers\",\n \"messages\",\n \"invites\",\n ] as const satisfies readonly CacheEntityName[];\n\n for (const store of scanned) {\n operations.push({\n type: \"deleteWhere\",\n store,\n predicate: (value) => isObject(value) && value.guild_id === guildId,\n });\n }\n}\n\n// An `update` of a cached message; a message the cache does not hold stays uncached.\nfunction updateMessage(\n channelId: Snowflake,\n messageId: Snowflake,\n update: (message: APIMessage) => APIMessage,\n): CacheOperation {\n return {\n type: \"update\",\n store: \"messages\",\n key: messageKey(channelId, messageId),\n update: (value) => update(value as APIMessage),\n };\n}\n\nfunction withGuildId<Value extends object>(\n value: Value,\n guildId: Snowflake,\n): Value & { guild_id: Snowflake } {\n return { ...value, guild_id: guildId };\n}\n\nfunction isObject(value: unknown): value is Record<string, unknown> {\n return typeof value === \"object\" && value !== null && !Array.isArray(value);\n}\n","import type { GatewayDispatchPayload } from \"discord-api-types/v10\";\nimport { applyGatewayDispatch, type CacheOperationContext } from \"./operations.js\";\nimport type { Cache } from \"./types.js\";\n\n/** A gateway that emits the dispatch event used by @discordjs/ws and @discordjs/core. */\nexport interface GatewayDispatchSource {\n on(\n event: \"dispatch\",\n listener: (payload: GatewayDispatchPayload, shardId: number) => void,\n ): unknown;\n off?(\n event: \"dispatch\",\n listener: (payload: GatewayDispatchPayload, shardId: number) => void,\n ): unknown;\n}\n\nexport interface CacheGatewayOptions extends CacheOperationContext {\n /** Receives cache write failures from the asynchronous gateway listener. */\n onError?: (error: unknown, payload: GatewayDispatchPayload, shardId: number) => void;\n}\n\n/**\n * Writes gateway dispatches into a cache. Call the returned function to stop listening.\n * Use this when a gateway has no GatewayClient managing its dispatch queue.\n */\nexport function attachCacheToGateway(\n gateway: GatewayDispatchSource,\n cache: Cache,\n options: CacheGatewayOptions = {},\n): () => void {\n const listener = (payload: GatewayDispatchPayload, shardId: number): void => {\n void applyGatewayDispatch(cache, payload, { clientUserId: options.clientUserId }).catch(\n (error) => {\n if (options.onError) options.onError(error, payload, shardId);\n else console.error(\"Failed to cache gateway dispatch\", error);\n },\n );\n };\n\n gateway.on(\"dispatch\", listener);\n return () => gateway.off?.(\"dispatch\", listener);\n}\n","import { CacheEntityNames } from \"./operations.js\";\nimport type { Cache, CacheEntityName, CacheEntityTypes, EntityCache } from \"./types.js\";\n\n/**\n * An {@link EntityCache} backed by a `Map`, optionally bounded as a least-recently-used cache.\n */\nexport class MemoryEntityCache<Raw> implements EntityCache<Raw> {\n /**\n * The maximum amount of entries, `Infinity` for an unbounded cache.\n */\n public readonly maxSize: number;\n\n readonly #items = new Map<string, Raw>();\n\n public constructor(maxSize = Infinity) {\n if (maxSize !== Infinity && (!Number.isInteger(maxSize) || maxSize < 0)) {\n throw new RangeError(\n `maxSize must be a non-negative integer or Infinity, received ${maxSize}`,\n );\n }\n\n this.maxSize = maxSize;\n }\n\n public get(key: string): Raw | undefined {\n const value = this.#items.get(key);\n if (value !== undefined && this.maxSize !== Infinity) {\n // Re-insert to mark the entry as the most recently used one.\n this.#items.delete(key);\n this.#items.set(key, value);\n }\n\n return value;\n }\n\n public set(key: string, value: Raw): void {\n if (this.maxSize === 0) return;\n\n this.#items.delete(key);\n this.#items.set(key, value);\n\n if (this.#items.size > this.maxSize) {\n // Maps iterate in insertion order, so the first key is the least recently used one.\n this.#items.delete(this.#items.keys().next().value!);\n }\n }\n\n public has(key: string): boolean {\n return this.#items.has(key);\n }\n\n public delete(key: string): boolean {\n return this.#items.delete(key);\n }\n\n public clear(): void {\n this.#items.clear();\n }\n\n public getSize(): number {\n return this.#items.size;\n }\n\n public keys(): string[] {\n return [...this.#items.keys()];\n }\n\n public values(): Raw[] {\n return [...this.#items.values()];\n }\n\n public entries(): [key: string, value: Raw][] {\n return [...this.#items.entries()];\n }\n}\n\n/**\n * A {@link Cache} whose entity caches are all {@link MemoryEntityCache}s.\n */\nexport type InMemoryCache = {\n readonly [Name in CacheEntityName]: MemoryEntityCache<CacheEntityTypes[Name]>;\n};\n\nexport interface InMemoryCacheOptions {\n /**\n * The maximum amount of entries kept per entity cache before evicting the least recently used ones, either for\n * every entity cache or per entity cache. Entity caches left out are unbounded.\n *\n * @default Infinity\n */\n maxSize?: number | Partial<Record<CacheEntityName, number>>;\n}\n\n/**\n * Creates a {@link Cache} that keeps everything in the process' memory.\n *\n * @example\n * ```typescript\n * import { createInMemoryCache } from '@wolfstar/plugin-cache';\n *\n * // Keep at most 1000 messages around, everything else is unbounded.\n * const cache = createInMemoryCache({ maxSize: { messages: 1_000 } });\n * ```\n *\n * @param options The options for the cache.\n */\nexport function createInMemoryCache(options: InMemoryCacheOptions = {}): InMemoryCache & Cache {\n const { maxSize } = options;\n const resolve = (name: CacheEntityName) =>\n typeof maxSize === \"number\" ? maxSize : (maxSize?.[name] ?? Infinity);\n\n return Object.freeze(\n Object.fromEntries(\n CacheEntityNames.map((name) => [name, new MemoryEntityCache(resolve(name))]),\n ) as InMemoryCache,\n );\n}\n","/// <reference types=\"node\" />\nimport { promisify } from \"node:util\";\nimport { brotliCompress, brotliDecompress, gunzip, gzip } from \"node:zlib\";\nimport { CacheEntityNames } from \"./operations.js\";\nimport type { Cache, CacheEntityName, CacheEntityTypes, EntityCache } from \"./types.js\";\n\nconst gzipAsync = promisify(gzip);\nconst gunzipAsync = promisify(gunzip);\nconst brotliCompressAsync = promisify(brotliCompress);\nconst brotliDecompressAsync = promisify(brotliDecompress);\n\n/**\n * The subset of the [`ioredis`](https://github.com/redis/ioredis) client API the Redis cache relies on. An `ioredis`\n * `Redis` or `Cluster` instance satisfies it, as can any other client exposing the same commands.\n */\nexport interface RedisClientLike {\n get(key: string): Promise<string | null>;\n mget(...keys: string[]): Promise<(string | null)[]>;\n set(key: string, value: string): Promise<unknown>;\n set(key: string, value: string, mode: \"PX\", milliseconds: number): Promise<unknown>;\n del(...keys: string[]): Promise<number>;\n exists(...keys: string[]): Promise<number>;\n zadd(key: string, ...scoreMembers: (string | number)[]): Promise<unknown>;\n zrem(key: string, ...members: string[]): Promise<number>;\n zrange(key: string, start: string, stop: string): Promise<string[]>;\n zcard(key: string): Promise<number>;\n zremrangebyscore(key: string, min: number | string, max: number | string): Promise<number>;\n multi(): RedisTransactionLike;\n}\n\n/**\n * The subset of an [`ioredis`](https://github.com/redis/ioredis) `MULTI` transaction the Redis cache relies on: every\n * queued command returns the transaction, and `exec` runs them atomically.\n */\nexport interface RedisTransactionLike {\n set(key: string, value: string): RedisTransactionLike;\n set(key: string, value: string, mode: \"PX\", milliseconds: number): RedisTransactionLike;\n del(...keys: string[]): RedisTransactionLike;\n zadd(key: string, ...scoreMembers: (string | number)[]): RedisTransactionLike;\n zrem(key: string, ...members: string[]): RedisTransactionLike;\n zremrangebyscore(key: string, min: number | string, max: number | string): RedisTransactionLike;\n exec(): Promise<[error: Error | null, result: unknown][] | null>;\n}\n\n/**\n * Thrown when a value stored in Redis cannot be read back: invalid JSON, or compressed bytes that fail to decompress.\n *\n * @remarks\n * A missing value is not an error, `get` resolves to `undefined` for it. Redis connection errors are not wrapped\n * either, they propagate as the client throws them.\n */\nexport class CacheValueError extends Error {\n /**\n * The Redis key holding the unreadable value.\n */\n public readonly key: string;\n\n public constructor(key: string, cause: unknown) {\n super(`Cannot read the cached value at \"${key}\"`, { cause });\n this.name = \"CacheValueError\";\n this.key = key;\n }\n}\n\n/**\n * The algorithm used to compress values before writing them to Redis.\n */\nexport type RedisCacheCompression = \"gzip\" | \"brotli\" | \"none\";\n\nexport interface RedisEntityCacheOptions {\n /**\n * The prefix of every Redis key owned by this entity cache.\n */\n prefix: string;\n /**\n * The time-to-live of every entry, in seconds. Entries never expire when omitted.\n */\n ttl?: number;\n /**\n * The compression algorithm to use.\n *\n * @default \"none\"\n */\n compression?: RedisCacheCompression;\n /**\n * The minimum size, in bytes, a serialized value must reach to be compressed. Small payloads rarely benefit from it.\n *\n * @default 1024\n */\n compressionThreshold?: number;\n}\n\n// Compressed values are stored as `<marker><base64>`. JSON can never start with either marker, so values written with\n// a different `compression` setting (e.g. before it was turned on) are still read back correctly.\nconst CompressionMarkers = { gzip: \"gz:\", brotli: \"br:\" } as const;\n\n/**\n * An {@link EntityCache} backed by Redis.\n *\n * @remarks\n * Every entry is stored as its own string key (`<prefix>:<key>`), and a sorted set (`<prefix>:@index`) tracks the\n * stored keys with their expiration time as score, which is what `keys`, `entries`, `getSize`, and `clear` read from.\n */\nexport class RedisEntityCache<Raw> implements EntityCache<Raw> {\n public readonly prefix: string;\n public readonly ttl: number | undefined;\n public readonly compression: RedisCacheCompression;\n public readonly compressionThreshold: number;\n\n readonly #redis: RedisClientLike;\n\n public constructor(redis: RedisClientLike, options: RedisEntityCacheOptions) {\n if (options.ttl !== undefined && !(options.ttl > 0)) {\n throw new RangeError(`ttl must be a positive amount of seconds, received ${options.ttl}`);\n }\n\n this.#redis = redis;\n this.prefix = options.prefix;\n this.ttl = options.ttl;\n this.compression = options.compression ?? \"none\";\n this.compressionThreshold = options.compressionThreshold ?? 1024;\n }\n\n public async get(key: string): Promise<Raw | undefined> {\n const valueKey = this.valueKey(key);\n const value = await this.#redis.get(valueKey);\n return value === null ? undefined : this.deserialize(valueKey, value);\n }\n\n public async set(key: string, value: Raw): Promise<void> {\n const serialized = await this.serialize(value);\n // The value and its index entry are written in one transaction, so neither can exist without the other.\n const transaction = this.#redis.multi();\n if (this.ttl === undefined) {\n transaction.set(this.valueKey(key), serialized).zadd(this.indexKey, \"+inf\", key);\n } else {\n const now = Date.now();\n const milliseconds = Math.round(this.ttl * 1000);\n transaction\n .set(this.valueKey(key), serialized, \"PX\", milliseconds)\n .zadd(this.indexKey, now + milliseconds, key)\n // Pruning on every write keeps the index bounded even when nothing ever enumerates it.\n .zremrangebyscore(this.indexKey, \"-inf\", now);\n }\n\n await execute(transaction);\n }\n\n public async has(key: string): Promise<boolean> {\n return (await this.#redis.exists(this.valueKey(key))) > 0;\n }\n\n public async delete(key: string): Promise<boolean> {\n const [deleted] = await execute(\n this.#redis.multi().del(this.valueKey(key)).zrem(this.indexKey, key),\n );\n return (deleted as number) > 0;\n }\n\n public async clear(): Promise<void> {\n const keys = await this.#redis.zrange(this.indexKey, \"0\", \"-1\");\n if (keys.length > 0) await this.#redis.del(...keys.map((key) => this.valueKey(key)));\n await this.#redis.del(this.indexKey);\n }\n\n public async getSize(): Promise<number> {\n await this.prune();\n return this.#redis.zcard(this.indexKey);\n }\n\n public async keys(): Promise<string[]> {\n await this.prune();\n return this.#redis.zrange(this.indexKey, \"0\", \"-1\");\n }\n\n public async values(): Promise<Raw[]> {\n return (await this.entries()).map(([, value]) => value);\n }\n\n public async entries(): Promise<[key: string, value: Raw][]> {\n const keys = await this.keys();\n if (keys.length === 0) return [];\n\n const values = await this.#redis.mget(...keys.map((key) => this.valueKey(key)));\n const entries: [key: string, value: Raw][] = [];\n for (const [index, value] of values.entries()) {\n // The value may have been evicted by Redis between both reads.\n if (value !== null) {\n const key = keys[index]!;\n entries.push([key, await this.deserialize(this.valueKey(key), value)]);\n }\n }\n\n return entries;\n }\n\n /**\n * Gets the Redis key a value is stored at.\n * @param key The entity cache key.\n */\n public valueKey(key: string): string {\n return `${this.prefix}:${key}`;\n }\n\n /**\n * The Redis key of the sorted set indexing the stored keys.\n */\n public get indexKey(): string {\n return `${this.prefix}:@index`;\n }\n\n private async prune(): Promise<void> {\n if (this.ttl !== undefined)\n await this.#redis.zremrangebyscore(this.indexKey, \"-inf\", Date.now());\n }\n\n private async serialize(value: Raw): Promise<string> {\n const json = JSON.stringify(value);\n if (this.compression === \"none\" || Buffer.byteLength(json) < this.compressionThreshold) {\n return json;\n }\n\n const compressed =\n this.compression === \"gzip\" ? await gzipAsync(json) : await brotliCompressAsync(json);\n return `${CompressionMarkers[this.compression]}${compressed.toString(\"base64\")}`;\n }\n\n private async deserialize(valueKey: string, value: string): Promise<Raw> {\n try {\n if (value.startsWith(CompressionMarkers.gzip)) {\n return JSON.parse((await gunzipAsync(decode(value))).toString(\"utf8\")) as Raw;\n }\n\n if (value.startsWith(CompressionMarkers.brotli)) {\n return JSON.parse((await brotliDecompressAsync(decode(value))).toString(\"utf8\")) as Raw;\n }\n\n return JSON.parse(value) as Raw;\n } catch (error) {\n throw new CacheValueError(valueKey, error);\n }\n }\n}\n\n/**\n * Runs a transaction, throwing the first command error, and resolves to the command results.\n */\nasync function execute(transaction: RedisTransactionLike): Promise<unknown[]> {\n const results = await transaction.exec();\n if (results === null) throw new Error(\"The Redis transaction was aborted\");\n\n return results.map(([error, result]) => {\n if (error) throw error;\n return result;\n });\n}\n\nfunction decode(value: string): Buffer {\n return Buffer.from(value.slice(3), \"base64\");\n}\n\n/**\n * A {@link Cache} whose entity caches are all {@link RedisEntityCache}s.\n */\nexport type RedisCache = {\n readonly [Name in CacheEntityName]: RedisEntityCache<CacheEntityTypes[Name]>;\n};\n\nexport interface RedisCacheOptions {\n /**\n * The Redis client to use, e.g. an [`ioredis`](https://github.com/redis/ioredis) instance.\n */\n redis: RedisClientLike;\n /**\n * The prefix of every Redis key owned by the cache, which allows several caches to share a database.\n *\n * @default \"wolfstar:cache\"\n */\n prefix?: string;\n /**\n * The compression algorithm to use for values of at least {@link RedisCacheOptions.compressionThreshold} bytes.\n *\n * @default \"none\"\n */\n compression?: RedisCacheCompression;\n /**\n * The minimum size, in bytes, a serialized value must reach to be compressed.\n *\n * @default 1024\n */\n compressionThreshold?: number;\n /**\n * The time-to-live per entity cache, in seconds. Entity caches left out never expire.\n */\n ttl?: Partial<Record<CacheEntityName, number>>;\n}\n\n/**\n * The default prefix of every Redis key owned by a cache created with {@link createRedisCache}.\n */\nexport const DefaultRedisCachePrefix = \"wolfstar:cache\";\n\n/**\n * Creates a {@link Cache} stored in Redis, optionally compressing its values.\n *\n * @example\n * ```typescript\n * import { createRedisCache } from '@wolfstar/plugin-cache';\n * import { Redis } from 'ioredis';\n *\n * const cache = createRedisCache({\n * redis: new Redis(process.env.REDIS_URL!),\n * compression: 'gzip',\n * ttl: { guilds: 60 * 60, users: 30 * 60 },\n * });\n * ```\n *\n * @param options The options for the cache.\n */\nexport function createRedisCache(options: RedisCacheOptions): RedisCache & Cache {\n const {\n redis,\n prefix = DefaultRedisCachePrefix,\n compression,\n compressionThreshold,\n ttl,\n } = options;\n\n return Object.freeze(\n Object.fromEntries(\n CacheEntityNames.map((name) => [\n name,\n new RedisEntityCache(redis, {\n prefix: `${prefix}:${name}`,\n ttl: ttl?.[name],\n compression,\n compressionThreshold,\n }),\n ]),\n ) as RedisCache,\n );\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"index.js","names":[],"sources":["../../src/lib/keys.ts","../../src/lib/reactions.ts","../../src/lib/operations.ts","../../src/lib/gateway.ts","../../src/lib/memory.ts","../../src/lib/redis.ts"],"sourcesContent":["import type { Snowflake } from \"discord-api-types/v10\";\n\n/**\n * Creates a stable key for a guild-scoped entity.\n */\nexport function guildScopedKey(guildId: Snowflake, id: Snowflake): string {\n return `${guildId}:${id}`;\n}\n\n/**\n * Creates a stable key for a channel message.\n */\nexport function messageKey(channelId: Snowflake, messageId: Snowflake): string {\n return `${channelId}:${messageId}`;\n}\n\n/**\n * Creates a stable key for a guild member.\n */\nexport function memberKey(guildId: Snowflake, userId: Snowflake): string {\n return guildScopedKey(guildId, userId);\n}\n\n/**\n * Creates a stable key for a guild presence.\n */\nexport function presenceKey(guildId: Snowflake, userId: Snowflake): string {\n return guildScopedKey(guildId, userId);\n}\n\n/**\n * Creates a stable key for a guild voice state.\n */\nexport function voiceStateKey(guildId: Snowflake, userId: Snowflake): string {\n return guildScopedKey(guildId, userId);\n}\n\n/**\n * Creates a stable key for a guild role.\n */\nexport function roleKey(guildId: Snowflake, roleId: Snowflake): string {\n return guildScopedKey(guildId, roleId);\n}\n\n/**\n * Creates a stable key for a guild emoji.\n */\nexport function emojiKey(guildId: Snowflake, emojiId: Snowflake): string {\n return guildScopedKey(guildId, emojiId);\n}\n\n/**\n * Creates a stable key for a guild sticker.\n */\nexport function stickerKey(guildId: Snowflake, stickerId: Snowflake): string {\n return guildScopedKey(guildId, stickerId);\n}\n\n/**\n * Creates a stable key for a guild scheduled event.\n */\nexport function scheduledEventKey(guildId: Snowflake, scheduledEventId: Snowflake): string {\n return guildScopedKey(guildId, scheduledEventId);\n}\n\n/**\n * Creates a stable key for a stage instance.\n */\nexport function stageInstanceKey(guildId: Snowflake, channelId: Snowflake): string {\n return guildScopedKey(guildId, channelId);\n}\n\n/**\n * Creates a stable key for a guild soundboard sound.\n */\nexport function soundboardSoundKey(guildId: Snowflake, soundId: Snowflake): string {\n return guildScopedKey(guildId, soundId);\n}\n\n/**\n * Creates a stable key for a guild auto moderation rule.\n */\nexport function autoModerationRuleKey(guildId: Snowflake, ruleId: Snowflake): string {\n return guildScopedKey(guildId, ruleId);\n}\n\n/**\n * Creates a stable key for a guild ban.\n */\nexport function banKey(guildId: Snowflake, userId: Snowflake): string {\n return guildScopedKey(guildId, userId);\n}\n\n/**\n * Creates a stable key for a guild integration.\n */\nexport function integrationKey(guildId: Snowflake, integrationId: Snowflake): string {\n return guildScopedKey(guildId, integrationId);\n}\n\n/**\n * Creates a stable key for an invite, using `@global` for invites that do not belong to a guild.\n */\nexport function inviteKey(guildId: Snowflake | null | undefined, code: string): string {\n return `${guildId ?? \"@global\"}:${code}`;\n}\n\n/**\n * Creates a stable key for a thread member.\n */\nexport function threadMemberKey(threadId: Snowflake, userId: Snowflake): string {\n return `${threadId}:${userId}`;\n}\n\n/**\n * Creates a stable key for application command permissions.\n */\nexport function applicationCommandPermissionsKey(\n applicationId: Snowflake,\n guildId: Snowflake,\n commandId: Snowflake,\n): string {\n return `${applicationId}:${guildId}:${commandId}`;\n}\n","import type {\n APIMessage,\n APIPartialEmoji,\n APIReaction,\n GatewayMessagePollVoteDispatchData,\n GatewayMessageReactionAddDispatchData,\n GatewayMessageReactionRemoveDispatchData,\n} from \"discord-api-types/v10\";\n\n// Reactions and poll votes only carry the voter's ID, so whether the bot is the voter needs the bot's own ID.\n\n/**\n * Whether two reaction emojis are the same: custom emojis match by ID, Unicode emojis by name.\n */\nfunction isSameEmoji(a: APIPartialEmoji, b: APIPartialEmoji): boolean {\n return a.id ? a.id === b.id : !b.id && a.name === b.name;\n}\n\n/**\n * Adds a `MESSAGE_REACTION_ADD` to a cached message's reactions.\n *\n * @param message The cached message.\n * @param data The dispatch data.\n * @param clientUserId The bot's user ID, to set `me` when the bot reacted.\n */\nexport function addReaction(\n message: APIMessage,\n data: GatewayMessageReactionAddDispatchData,\n clientUserId?: string,\n): APIMessage {\n const me = data.user_id === clientUserId;\n const kind = data.burst ? \"burst\" : \"normal\";\n const reactions = message.reactions ?? [];\n const existing = reactions.find((reaction) => isSameEmoji(reaction.emoji, data.emoji));\n\n if (!existing) {\n const reaction: APIReaction = {\n emoji: data.emoji,\n count: 1,\n count_details: { normal: data.burst ? 0 : 1, burst: data.burst ? 1 : 0 },\n me: me && !data.burst,\n me_burst: me && data.burst,\n burst_colors: data.burst_colors ?? [],\n };\n return { ...message, reactions: [...reactions, reaction] };\n }\n\n return {\n ...message,\n reactions: reactions.map((reaction) =>\n reaction === existing\n ? {\n ...reaction,\n count: reaction.count + 1,\n count_details: {\n ...reaction.count_details,\n [kind]: reaction.count_details[kind] + 1,\n },\n me: reaction.me || (me && !data.burst),\n me_burst: reaction.me_burst || (me && data.burst),\n burst_colors: data.burst\n ? (data.burst_colors ?? reaction.burst_colors)\n : reaction.burst_colors,\n }\n : reaction,\n ),\n };\n}\n\n/**\n * Removes a `MESSAGE_REACTION_REMOVE` from a cached message's reactions, dropping the reaction when its count reaches\n * zero.\n *\n * @param message The cached message.\n * @param data The dispatch data.\n * @param clientUserId The bot's user ID, to clear `me` when the bot's reaction was removed.\n */\nexport function removeReaction(\n message: APIMessage,\n data: GatewayMessageReactionRemoveDispatchData,\n clientUserId?: string,\n): APIMessage {\n const me = data.user_id === clientUserId;\n const kind = data.burst ? \"burst\" : \"normal\";\n const reactions = (message.reactions ?? []).flatMap((reaction) => {\n if (!isSameEmoji(reaction.emoji, data.emoji)) return [reaction];\n if (reaction.count <= 1) return [];\n return [\n {\n ...reaction,\n count: reaction.count - 1,\n count_details: {\n ...reaction.count_details,\n [kind]: Math.max(0, reaction.count_details[kind] - 1),\n },\n me: reaction.me && !(me && !data.burst),\n me_burst: reaction.me_burst && !(me && data.burst),\n },\n ];\n });\n return { ...message, reactions };\n}\n\n/**\n * Removes every reaction with one emoji from a cached message.\n *\n * @param message The cached message.\n * @param emoji The emoji.\n */\nexport function removeReactionEmoji(message: APIMessage, emoji: APIPartialEmoji): APIMessage {\n return {\n ...message,\n reactions: (message.reactions ?? []).filter((reaction) => !isSameEmoji(reaction.emoji, emoji)),\n };\n}\n\n/**\n * Counts a `MESSAGE_POLL_VOTE_ADD` or `MESSAGE_POLL_VOTE_REMOVE` in a cached message's poll results.\n *\n * @param message The cached message.\n * @param data The dispatch data.\n * @param delta `1` for a vote, `-1` for a removed vote.\n * @param clientUserId The bot's user ID, to set `me_voted` when the bot voted.\n */\nexport function countPollVote(\n message: APIMessage,\n data: GatewayMessagePollVoteDispatchData,\n delta: 1 | -1,\n clientUserId?: string,\n): APIMessage {\n const { poll } = message;\n if (!poll) return message;\n\n const me = data.user_id === clientUserId;\n const results = poll.results ?? { is_finalized: false, answer_counts: [] };\n const counts = results.answer_counts.some((count) => count.id === data.answer_id)\n ? results.answer_counts\n : [...results.answer_counts, { id: data.answer_id, count: 0, me_voted: false }];\n\n return {\n ...message,\n poll: {\n ...poll,\n results: {\n ...results,\n answer_counts: counts.map((count) =>\n count.id === data.answer_id\n ? {\n ...count,\n count: Math.max(0, count.count + delta),\n me_voted: me ? delta === 1 : count.me_voted,\n }\n : count,\n ),\n },\n },\n };\n}\n","import { GatewayDispatchEvents } from \"discord-api-types/v10\";\nimport type {\n APIEmoji,\n APIMessage,\n APIGuildMember,\n APIRole,\n APISoundboardSound,\n APISticker,\n GatewayDispatchPayload,\n GatewayGuildCreateDispatchData,\n GatewayGuildMembersChunkDispatchData,\n GatewayGuildSoundboardSoundsUpdateDispatchData,\n GatewaySoundboardSoundsDispatchData,\n GatewayThreadListSync,\n GatewayThreadMembersUpdateDispatchData,\n Snowflake,\n} from \"discord-api-types/v10\";\nimport {\n applicationCommandPermissionsKey,\n autoModerationRuleKey,\n banKey,\n emojiKey,\n guildScopedKey,\n integrationKey,\n inviteKey,\n memberKey,\n messageKey,\n presenceKey,\n roleKey,\n scheduledEventKey,\n soundboardSoundKey,\n stageInstanceKey,\n stickerKey,\n threadMemberKey,\n voiceStateKey,\n} from \"./keys.js\";\nimport { addReaction, countPollVote, removeReaction, removeReactionEmoji } from \"./reactions.js\";\nimport type { Cache, CacheEntityName, EntityCache } from \"./types.js\";\n\n// A record rather than an array so the compiler enforces that every entity cache is listed.\nconst cacheEntityNameRecord: Record<CacheEntityName, true> = {\n applicationCommandPermissions: true,\n auditLogEntries: true,\n autoModerationRules: true,\n bans: true,\n channels: true,\n emojis: true,\n entitlements: true,\n guilds: true,\n integrations: true,\n invites: true,\n members: true,\n messages: true,\n presences: true,\n roles: true,\n scheduledEvents: true,\n soundboardSounds: true,\n stageInstances: true,\n stickers: true,\n subscriptions: true,\n threadMembers: true,\n threads: true,\n users: true,\n voiceStates: true,\n};\n\n/**\n * The name of every entity cache held by a {@link Cache}.\n */\nexport const CacheEntityNames = Object.keys(cacheEntityNameRecord) as readonly CacheEntityName[];\n\n/**\n * The entity caches whose keys start with the ID of the guild the entity belongs to (`${guildId}:...`).\n *\n * @internal\n */\nexport const GuildKeyedCacheEntityNames = [\n \"auditLogEntries\",\n \"autoModerationRules\",\n \"bans\",\n \"emojis\",\n \"integrations\",\n \"members\",\n \"presences\",\n \"roles\",\n \"scheduledEvents\",\n \"soundboardSounds\",\n \"stageInstances\",\n \"stickers\",\n \"voiceStates\",\n] as const satisfies readonly CacheEntityName[];\n\n/**\n * The entity caches keyed by the entity's own ID, which only know their guild through the stored `guild_id`.\n *\n * @internal\n */\nexport const GuildFieldCacheEntityNames = [\n \"applicationCommandPermissions\",\n \"channels\",\n \"threads\",\n \"threadMembers\",\n \"messages\",\n \"invites\",\n] as const satisfies readonly CacheEntityName[];\n\n/**\n * A single mutation a gateway dispatch produces on a {@link Cache}.\n */\nexport type CacheOperation =\n | {\n type: \"upsert\";\n store: CacheEntityName;\n key: string;\n raw: unknown;\n /** Whether to shallow-merge `raw` onto the existing value, used for partial updates. */\n merge?: boolean;\n }\n | {\n type: \"update\";\n store: CacheEntityName;\n key: string;\n /** Computes the new value from the cached one. Nothing is written when the key is not cached. */\n update: (value: unknown) => unknown;\n }\n | { type: \"delete\"; store: CacheEntityName; key: string }\n | {\n type: \"deletePrefix\";\n store: CacheEntityName;\n prefix: string;\n /** The guild the deleted entries are exactly the entries of, see {@link EntityCache.deleteGuild}. */\n guildId?: Snowflake;\n }\n | {\n type: \"deleteWhere\";\n store: CacheEntityName;\n predicate: (value: unknown) => boolean;\n /** The guild the deleted entries are exactly the entries of, see {@link EntityCache.deleteGuild}. */\n guildId?: Snowflake;\n };\n\n/**\n * What {@link createCacheOperations} needs to know besides the dispatch.\n */\nexport interface CacheOperationContext {\n /**\n * The bot's user ID. Reactions and poll votes only carry the voter's ID, so without it the cached `me` and\n * `me_voted` flags are never set.\n */\n clientUserId?: string;\n}\n\n/**\n * Translates a gateway dispatch into the list of {@link CacheOperation}s it implies, including the cascades (e.g.\n * `CHANNEL_DELETE` also drops that channel's messages).\n *\n * @remarks\n * This is a pure function, it does not touch any cache. Use {@link applyGatewayDispatch} to apply them.\n * `INTERACTION_CREATE` is intentionally ignored: interactions are short-lived and never cached.\n *\n * @param payload The gateway dispatch payload.\n * @param context The bot's user ID, for the `me` flags of reactions and poll votes.\n */\nexport function createCacheOperations(\n payload: GatewayDispatchPayload,\n context: CacheOperationContext = {},\n): CacheOperation[] {\n const { clientUserId } = context;\n const operations: CacheOperation[] = [];\n\n switch (payload.t) {\n case GatewayDispatchEvents.ApplicationCommandPermissionsUpdate: {\n const data = payload.d;\n operations.push({\n type: \"upsert\",\n store: \"applicationCommandPermissions\",\n key: applicationCommandPermissionsKey(data.application_id, data.guild_id, data.id),\n raw: data,\n });\n break;\n }\n\n case GatewayDispatchEvents.AutoModerationRuleCreate:\n case GatewayDispatchEvents.AutoModerationRuleUpdate: {\n const data = payload.d;\n operations.push({\n type: \"upsert\",\n store: \"autoModerationRules\",\n key: autoModerationRuleKey(data.guild_id, data.id),\n raw: data,\n });\n break;\n }\n\n case GatewayDispatchEvents.AutoModerationRuleDelete: {\n const data = payload.d;\n operations.push({\n type: \"delete\",\n store: \"autoModerationRules\",\n key: autoModerationRuleKey(data.guild_id, data.id),\n });\n break;\n }\n\n case GatewayDispatchEvents.ChannelCreate:\n case GatewayDispatchEvents.ChannelUpdate: {\n const data = payload.d;\n operations.push({\n type: \"upsert\",\n store: \"channels\",\n key: data.id,\n raw: data,\n merge: payload.t === GatewayDispatchEvents.ChannelUpdate,\n });\n break;\n }\n\n case GatewayDispatchEvents.ChannelDelete: {\n const data = payload.d;\n operations.push({ type: \"delete\", store: \"channels\", key: data.id });\n operations.push({ type: \"deletePrefix\", store: \"messages\", prefix: `${data.id}:` });\n break;\n }\n\n case GatewayDispatchEvents.EntitlementCreate:\n case GatewayDispatchEvents.EntitlementUpdate: {\n const data = payload.d;\n operations.push({ type: \"upsert\", store: \"entitlements\", key: data.id, raw: data });\n break;\n }\n\n case GatewayDispatchEvents.EntitlementDelete: {\n operations.push({ type: \"delete\", store: \"entitlements\", key: payload.d.id });\n break;\n }\n\n case GatewayDispatchEvents.GuildAuditLogEntryCreate: {\n const data = payload.d;\n operations.push({\n type: \"upsert\",\n store: \"auditLogEntries\",\n key: guildScopedKey(data.guild_id, data.id),\n raw: data,\n });\n break;\n }\n\n case GatewayDispatchEvents.GuildBanAdd: {\n const data = payload.d;\n operations.push({\n type: \"upsert\",\n store: \"bans\",\n key: banKey(data.guild_id, data.user.id),\n raw: data,\n });\n operations.push({ type: \"upsert\", store: \"users\", key: data.user.id, raw: data.user });\n break;\n }\n\n case GatewayDispatchEvents.GuildBanRemove: {\n const data = payload.d;\n operations.push({ type: \"delete\", store: \"bans\", key: banKey(data.guild_id, data.user.id) });\n operations.push({ type: \"upsert\", store: \"users\", key: data.user.id, raw: data.user });\n break;\n }\n\n case GatewayDispatchEvents.GuildCreate: {\n hydrateGuildCreate(operations, payload.d);\n break;\n }\n\n case GatewayDispatchEvents.GuildUpdate: {\n // Like `GUILD_CREATE`, the roles and emojis go to their own entity caches rather than the guild entry.\n const { roles, emojis, stickers, ...data } = payload.d;\n operations.push({ type: \"upsert\", store: \"guilds\", key: data.id, raw: data, merge: true });\n for (const role of roles ?? []) hydrateRole(operations, data.id, role);\n for (const emoji of emojis ?? []) hydrateEmoji(operations, data.id, emoji);\n for (const sticker of stickers ?? []) hydrateSticker(operations, data.id, sticker);\n break;\n }\n\n case GatewayDispatchEvents.GuildDelete: {\n const data = payload.d;\n // An `unavailable` guild is an outage, not a removal: keep its data around until it comes back.\n if (data.unavailable) {\n operations.push({ type: \"upsert\", store: \"guilds\", key: data.id, raw: data, merge: true });\n break;\n }\n\n operations.push({ type: \"delete\", store: \"guilds\", key: data.id });\n deleteGuildScopedResources(operations, data.id);\n break;\n }\n\n case GatewayDispatchEvents.GuildEmojisUpdate: {\n const data = payload.d;\n operations.push({\n type: \"deletePrefix\",\n store: \"emojis\",\n prefix: `${data.guild_id}:`,\n guildId: data.guild_id,\n });\n for (const emoji of data.emojis) {\n if (!emoji.id) continue;\n operations.push({\n type: \"upsert\",\n store: \"emojis\",\n key: emojiKey(data.guild_id, emoji.id),\n raw: withGuildId(emoji, data.guild_id),\n });\n }\n break;\n }\n\n case GatewayDispatchEvents.GuildStickersUpdate: {\n const data = payload.d;\n operations.push({\n type: \"deletePrefix\",\n store: \"stickers\",\n prefix: `${data.guild_id}:`,\n guildId: data.guild_id,\n });\n for (const sticker of data.stickers) {\n operations.push({\n type: \"upsert\",\n store: \"stickers\",\n key: stickerKey(data.guild_id, sticker.id),\n raw: withGuildId(sticker, data.guild_id),\n });\n }\n break;\n }\n\n case GatewayDispatchEvents.GuildMemberAdd:\n case GatewayDispatchEvents.GuildMemberUpdate: {\n const data = payload.d;\n operations.push({\n type: \"upsert\",\n store: \"members\",\n key: memberKey(data.guild_id, data.user.id),\n raw: data,\n merge: true,\n });\n operations.push({ type: \"upsert\", store: \"users\", key: data.user.id, raw: data.user });\n break;\n }\n\n case GatewayDispatchEvents.GuildMemberRemove: {\n const data = payload.d;\n operations.push({\n type: \"delete\",\n store: \"members\",\n key: memberKey(data.guild_id, data.user.id),\n });\n operations.push({ type: \"upsert\", store: \"users\", key: data.user.id, raw: data.user });\n break;\n }\n\n case GatewayDispatchEvents.GuildMembersChunk: {\n hydrateGuildMembersChunk(operations, payload.d);\n break;\n }\n\n case GatewayDispatchEvents.GuildRoleCreate:\n case GatewayDispatchEvents.GuildRoleUpdate: {\n const data = payload.d;\n operations.push({\n type: \"upsert\",\n store: \"roles\",\n key: roleKey(data.guild_id, data.role.id),\n raw: withGuildId(data.role, data.guild_id),\n merge: payload.t === GatewayDispatchEvents.GuildRoleUpdate,\n });\n break;\n }\n\n case GatewayDispatchEvents.GuildRoleDelete: {\n const data = payload.d;\n operations.push({\n type: \"delete\",\n store: \"roles\",\n key: roleKey(data.guild_id, data.role_id),\n });\n break;\n }\n\n case GatewayDispatchEvents.GuildScheduledEventCreate:\n case GatewayDispatchEvents.GuildScheduledEventUpdate: {\n const data = payload.d;\n operations.push({\n type: \"upsert\",\n store: \"scheduledEvents\",\n key: scheduledEventKey(data.guild_id, data.id),\n raw: data,\n merge: true,\n });\n if (data.creator) {\n operations.push({\n type: \"upsert\",\n store: \"users\",\n key: data.creator.id,\n raw: data.creator,\n });\n }\n break;\n }\n\n case GatewayDispatchEvents.GuildScheduledEventDelete: {\n const data = payload.d;\n operations.push({\n type: \"delete\",\n store: \"scheduledEvents\",\n key: scheduledEventKey(data.guild_id, data.id),\n });\n break;\n }\n\n case GatewayDispatchEvents.GuildSoundboardSoundCreate:\n case GatewayDispatchEvents.GuildSoundboardSoundUpdate: {\n const data = payload.d;\n if (!data.guild_id) break;\n operations.push({\n type: \"upsert\",\n store: \"soundboardSounds\",\n key: soundboardSoundKey(data.guild_id, data.sound_id),\n raw: data,\n merge: payload.t === GatewayDispatchEvents.GuildSoundboardSoundUpdate,\n });\n break;\n }\n\n case GatewayDispatchEvents.GuildSoundboardSoundDelete: {\n const data = payload.d;\n operations.push({\n type: \"delete\",\n store: \"soundboardSounds\",\n key: soundboardSoundKey(data.guild_id, data.sound_id),\n });\n break;\n }\n\n case GatewayDispatchEvents.GuildSoundboardSoundsUpdate: {\n hydrateGuildSoundboardSoundsUpdate(operations, payload.d);\n break;\n }\n\n case GatewayDispatchEvents.SoundboardSounds: {\n hydrateSoundboardSounds(operations, payload.d);\n break;\n }\n\n case GatewayDispatchEvents.IntegrationCreate:\n case GatewayDispatchEvents.IntegrationUpdate: {\n const data = payload.d;\n operations.push({\n type: \"upsert\",\n store: \"integrations\",\n key: integrationKey(data.guild_id, data.id),\n raw: data,\n merge: true,\n });\n break;\n }\n\n case GatewayDispatchEvents.IntegrationDelete: {\n const data = payload.d;\n operations.push({\n type: \"delete\",\n store: \"integrations\",\n key: integrationKey(data.guild_id, data.id),\n });\n break;\n }\n\n case GatewayDispatchEvents.InviteCreate: {\n const data = payload.d;\n operations.push({\n type: \"upsert\",\n store: \"invites\",\n key: inviteKey(data.guild_id, data.code),\n raw: data,\n });\n break;\n }\n\n case GatewayDispatchEvents.InviteDelete: {\n const data = payload.d;\n operations.push({\n type: \"delete\",\n store: \"invites\",\n key: inviteKey(data.guild_id, data.code),\n });\n break;\n }\n\n case GatewayDispatchEvents.MessageCreate:\n case GatewayDispatchEvents.MessageUpdate: {\n const data = payload.d;\n operations.push({\n type: \"upsert\",\n store: \"messages\",\n key: messageKey(data.channel_id, data.id),\n raw: data,\n merge: payload.t === GatewayDispatchEvents.MessageUpdate,\n });\n\n const author = \"author\" in data ? data.author : undefined;\n if (author) {\n operations.push({ type: \"upsert\", store: \"users\", key: author.id, raw: author });\n if (data.member && data.guild_id) {\n operations.push({\n type: \"upsert\",\n store: \"members\",\n key: memberKey(data.guild_id, author.id),\n raw: withGuildId(data.member, data.guild_id),\n merge: true,\n });\n }\n }\n break;\n }\n\n case GatewayDispatchEvents.MessageDelete: {\n const data = payload.d;\n operations.push({\n type: \"delete\",\n store: \"messages\",\n key: messageKey(data.channel_id, data.id),\n });\n break;\n }\n\n case GatewayDispatchEvents.MessageDeleteBulk: {\n const data = payload.d;\n for (const id of data.ids) {\n operations.push({\n type: \"delete\",\n store: \"messages\",\n key: messageKey(data.channel_id, id),\n });\n }\n break;\n }\n\n case GatewayDispatchEvents.MessageReactionAdd: {\n const data = payload.d;\n operations.push(\n updateMessage(data.channel_id, data.message_id, (message) =>\n addReaction(message, data, clientUserId),\n ),\n );\n // Guild reactions carry the reacting member, like messages carry their author.\n const user = data.member?.user;\n if (user && data.guild_id) {\n operations.push({ type: \"upsert\", store: \"users\", key: user.id, raw: user });\n operations.push({\n type: \"upsert\",\n store: \"members\",\n key: memberKey(data.guild_id, user.id),\n raw: withGuildId(data.member!, data.guild_id),\n merge: true,\n });\n }\n break;\n }\n\n case GatewayDispatchEvents.MessageReactionRemove: {\n const data = payload.d;\n operations.push(\n updateMessage(data.channel_id, data.message_id, (message) =>\n removeReaction(message, data, clientUserId),\n ),\n );\n break;\n }\n\n case GatewayDispatchEvents.MessageReactionRemoveAll: {\n const data = payload.d;\n operations.push(\n updateMessage(data.channel_id, data.message_id, (message) => ({\n ...message,\n reactions: [],\n })),\n );\n break;\n }\n\n case GatewayDispatchEvents.MessageReactionRemoveEmoji: {\n const data = payload.d;\n operations.push(\n updateMessage(data.channel_id, data.message_id, (message) =>\n removeReactionEmoji(message, data.emoji),\n ),\n );\n break;\n }\n\n case GatewayDispatchEvents.MessagePollVoteAdd:\n case GatewayDispatchEvents.MessagePollVoteRemove: {\n const data = payload.d;\n const delta = payload.t === GatewayDispatchEvents.MessagePollVoteAdd ? 1 : -1;\n operations.push(\n updateMessage(data.channel_id, data.message_id, (message) =>\n countPollVote(message, data, delta, clientUserId),\n ),\n );\n break;\n }\n\n case GatewayDispatchEvents.PresenceUpdate: {\n const data = payload.d;\n operations.push({\n type: \"upsert\",\n store: \"presences\",\n key: presenceKey(data.guild_id, data.user.id),\n raw: data,\n merge: true,\n });\n operations.push({\n type: \"upsert\",\n store: \"users\",\n key: data.user.id,\n raw: data.user,\n merge: true,\n });\n break;\n }\n\n case GatewayDispatchEvents.Ready: {\n const data = payload.d;\n operations.push({ type: \"upsert\", store: \"users\", key: data.user.id, raw: data.user });\n for (const guild of data.guilds) {\n operations.push({\n type: \"upsert\",\n store: \"guilds\",\n key: guild.id,\n raw: guild,\n merge: true,\n });\n }\n break;\n }\n\n case GatewayDispatchEvents.StageInstanceCreate:\n case GatewayDispatchEvents.StageInstanceUpdate: {\n const data = payload.d;\n operations.push({\n type: \"upsert\",\n store: \"stageInstances\",\n key: stageInstanceKey(data.guild_id, data.channel_id),\n raw: data,\n merge: true,\n });\n break;\n }\n\n case GatewayDispatchEvents.StageInstanceDelete: {\n const data = payload.d;\n operations.push({\n type: \"delete\",\n store: \"stageInstances\",\n key: stageInstanceKey(data.guild_id, data.channel_id),\n });\n break;\n }\n\n case GatewayDispatchEvents.SubscriptionCreate:\n case GatewayDispatchEvents.SubscriptionUpdate: {\n const data = payload.d;\n operations.push({ type: \"upsert\", store: \"subscriptions\", key: data.id, raw: data });\n break;\n }\n\n case GatewayDispatchEvents.SubscriptionDelete: {\n operations.push({ type: \"delete\", store: \"subscriptions\", key: payload.d.id });\n break;\n }\n\n case GatewayDispatchEvents.ThreadCreate:\n case GatewayDispatchEvents.ThreadUpdate: {\n const data = payload.d;\n operations.push({\n type: \"upsert\",\n store: \"threads\",\n key: data.id,\n raw: data,\n merge: payload.t === GatewayDispatchEvents.ThreadUpdate,\n });\n\n const member = \"member\" in data ? data.member : undefined;\n if (member?.user_id) {\n operations.push({\n type: \"upsert\",\n store: \"threadMembers\",\n key: threadMemberKey(data.id, member.user_id),\n // Thread members carry no guild ID of their own: without it, `GUILD_DELETE` could not sweep them.\n raw: data.guild_id ? withGuildId(member, data.guild_id) : member,\n merge: true,\n });\n }\n break;\n }\n\n case GatewayDispatchEvents.ThreadDelete: {\n const data = payload.d;\n operations.push({ type: \"delete\", store: \"threads\", key: data.id });\n operations.push({ type: \"deletePrefix\", store: \"threadMembers\", prefix: `${data.id}:` });\n operations.push({ type: \"deletePrefix\", store: \"messages\", prefix: `${data.id}:` });\n break;\n }\n\n case GatewayDispatchEvents.ThreadListSync: {\n hydrateThreadListSync(operations, payload.d);\n break;\n }\n\n case GatewayDispatchEvents.ThreadMemberUpdate: {\n const data = payload.d;\n if (!data.id || !data.user_id) break;\n operations.push({\n type: \"upsert\",\n store: \"threadMembers\",\n key: threadMemberKey(data.id, data.user_id),\n raw: data,\n merge: true,\n });\n break;\n }\n\n case GatewayDispatchEvents.ThreadMembersUpdate: {\n hydrateThreadMembersUpdate(operations, payload.d);\n break;\n }\n\n case GatewayDispatchEvents.UserUpdate: {\n const data = payload.d;\n operations.push({ type: \"upsert\", store: \"users\", key: data.id, raw: data, merge: true });\n break;\n }\n\n case GatewayDispatchEvents.VoiceStateUpdate: {\n const data = payload.d;\n if (!data.guild_id) break;\n\n const key = voiceStateKey(data.guild_id, data.user_id);\n if (data.channel_id) {\n operations.push({ type: \"upsert\", store: \"voiceStates\", key, raw: data, merge: true });\n } else {\n operations.push({ type: \"delete\", store: \"voiceStates\", key });\n }\n\n const user = data.member?.user;\n if (data.member && user) {\n operations.push({ type: \"upsert\", store: \"users\", key: user.id, raw: user });\n operations.push({\n type: \"upsert\",\n store: \"members\",\n key: memberKey(data.guild_id, user.id),\n raw: withGuildId(data.member, data.guild_id),\n merge: true,\n });\n }\n break;\n }\n\n default:\n break;\n }\n\n return operations;\n}\n\n/**\n * Applies a list of {@link CacheOperation}s to a {@link Cache}, sequentially and in order.\n *\n * @param cache The cache to mutate.\n * @param operations The operations to apply, usually created by {@link createCacheOperations}.\n */\nexport async function applyCacheOperations(\n cache: Cache,\n operations: readonly CacheOperation[],\n): Promise<void> {\n for (const operation of operations) {\n const store = cache[operation.store] as EntityCache<unknown>;\n\n switch (operation.type) {\n case \"upsert\": {\n const value = operation.merge\n ? mergeValues(await store.get(operation.key), operation.raw)\n : operation.raw;\n await store.set(operation.key, value);\n break;\n }\n case \"update\": {\n const existing = await store.get(operation.key);\n if (existing !== undefined) await store.set(operation.key, operation.update(existing));\n break;\n }\n case \"delete\":\n await store.delete(operation.key);\n break;\n case \"deletePrefix\":\n if (await deleteGuildThroughIndex(store, operation.guildId)) break;\n for (const key of await store.keys()) {\n if (key.startsWith(operation.prefix)) await store.delete(key);\n }\n break;\n case \"deleteWhere\":\n if (await deleteGuildThroughIndex(store, operation.guildId)) break;\n for (const [key, value] of await store.entries()) {\n if (operation.predicate(value)) await store.delete(key);\n }\n break;\n }\n }\n}\n\n/**\n * Deletes a guild's entries through the store's guild index, when it has one.\n *\n * @returns Whether the entries were deleted, `false` when the caller has to scan the store instead.\n */\nasync function deleteGuildThroughIndex(\n store: EntityCache<unknown>,\n guildId: Snowflake | undefined,\n): Promise<boolean> {\n if (guildId === undefined || store.deleteGuild === undefined) return false;\n return (await store.deleteGuild(guildId)) !== null;\n}\n\n/**\n * Writes a gateway dispatch into every relevant entity cache of a {@link Cache}.\n *\n * @param cache The cache to mutate.\n * @param payload The gateway dispatch payload.\n * @param context The bot's user ID, for the `me` flags of reactions and poll votes.\n */\nexport function applyGatewayDispatch(\n cache: Cache,\n payload: GatewayDispatchPayload,\n context?: CacheOperationContext,\n): Promise<void> {\n return applyCacheOperations(cache, createCacheOperations(payload, context));\n}\n\n/**\n * Shallow-merges `value` onto `existing` when both are plain objects, returning `value` otherwise.\n */\nexport function mergeValues<Value>(existing: Value | undefined, value: Value): Value {\n if (isObject(existing) && isObject(value)) return { ...existing, ...value };\n return value;\n}\n\nfunction hydrateGuildCreate(\n operations: CacheOperation[],\n guild: GatewayGuildCreateDispatchData,\n): void {\n // Unavailable guilds carry no data besides their ID, see `GuildDelete`.\n if (\"unavailable\" in guild && guild.unavailable) {\n operations.push({ type: \"upsert\", store: \"guilds\", key: guild.id, raw: guild, merge: true });\n return;\n }\n\n const {\n channels,\n threads,\n members,\n presences,\n voice_states: voiceStates,\n stage_instances: stageInstances,\n guild_scheduled_events: scheduledEvents,\n soundboard_sounds: soundboardSounds,\n roles,\n emojis,\n stickers,\n ...rest\n } = guild;\n // Collections are stored in their own entity caches, keeping the guild entry small.\n operations.push({ type: \"upsert\", store: \"guilds\", key: guild.id, raw: rest });\n\n for (const channel of channels ?? []) {\n operations.push({\n type: \"upsert\",\n store: \"channels\",\n key: channel.id,\n raw: withGuildId(channel, guild.id),\n });\n }\n\n for (const thread of threads ?? []) {\n operations.push({\n type: \"upsert\",\n store: \"threads\",\n key: thread.id,\n raw: withGuildId(thread, guild.id),\n });\n if (thread.member?.user_id) {\n operations.push({\n type: \"upsert\",\n store: \"threadMembers\",\n key: threadMemberKey(thread.id, thread.member.user_id),\n raw: withGuildId(thread.member, guild.id),\n });\n }\n }\n\n for (const member of members ?? []) hydrateMember(operations, guild.id, member);\n\n for (const presence of presences ?? []) {\n operations.push({\n type: \"upsert\",\n store: \"presences\",\n key: presenceKey(guild.id, presence.user.id),\n raw: withGuildId(presence, guild.id),\n });\n }\n\n for (const role of roles ?? []) hydrateRole(operations, guild.id, role);\n for (const emoji of emojis ?? []) hydrateEmoji(operations, guild.id, emoji);\n for (const sticker of stickers ?? []) hydrateSticker(operations, guild.id, sticker);\n\n for (const voiceState of voiceStates ?? []) {\n if (!voiceState.channel_id) continue;\n operations.push({\n type: \"upsert\",\n store: \"voiceStates\",\n key: voiceStateKey(guild.id, voiceState.user_id),\n raw: withGuildId(voiceState, guild.id),\n });\n }\n\n for (const stageInstance of stageInstances ?? []) {\n operations.push({\n type: \"upsert\",\n store: \"stageInstances\",\n key: stageInstanceKey(guild.id, stageInstance.channel_id),\n raw: stageInstance,\n });\n }\n\n for (const scheduledEvent of scheduledEvents ?? []) {\n operations.push({\n type: \"upsert\",\n store: \"scheduledEvents\",\n key: scheduledEventKey(guild.id, scheduledEvent.id),\n raw: scheduledEvent,\n });\n }\n\n for (const sound of soundboardSounds ?? []) hydrateSoundboardSound(operations, guild.id, sound);\n}\n\nfunction hydrateGuildMembersChunk(\n operations: CacheOperation[],\n data: GatewayGuildMembersChunkDispatchData,\n): void {\n for (const member of data.members) hydrateMember(operations, data.guild_id, member);\n for (const presence of data.presences ?? []) {\n operations.push({\n type: \"upsert\",\n store: \"presences\",\n key: presenceKey(data.guild_id, presence.user.id),\n raw: withGuildId(presence, data.guild_id),\n });\n }\n}\n\nfunction hydrateGuildSoundboardSoundsUpdate(\n operations: CacheOperation[],\n data: GatewayGuildSoundboardSoundsUpdateDispatchData,\n): void {\n operations.push({\n type: \"deletePrefix\",\n store: \"soundboardSounds\",\n prefix: `${data.guild_id}:`,\n guildId: data.guild_id,\n });\n for (const sound of data.soundboard_sounds)\n hydrateSoundboardSound(operations, data.guild_id, sound);\n}\n\nfunction hydrateSoundboardSounds(\n operations: CacheOperation[],\n data: GatewaySoundboardSoundsDispatchData,\n): void {\n for (const sound of data.soundboard_sounds)\n hydrateSoundboardSound(operations, data.guild_id, sound);\n}\n\nfunction hydrateThreadListSync(operations: CacheOperation[], data: GatewayThreadListSync): void {\n for (const thread of data.threads) {\n operations.push({\n type: \"upsert\",\n store: \"threads\",\n key: thread.id,\n raw: withGuildId(thread, data.guild_id),\n merge: true,\n });\n }\n\n for (const member of data.members) {\n if (!member.id || !member.user_id) continue;\n operations.push({\n type: \"upsert\",\n store: \"threadMembers\",\n key: threadMemberKey(member.id, member.user_id),\n raw: withGuildId(member, data.guild_id),\n merge: true,\n });\n }\n}\n\nfunction hydrateThreadMembersUpdate(\n operations: CacheOperation[],\n data: GatewayThreadMembersUpdateDispatchData,\n): void {\n for (const member of data.added_members ?? []) {\n if (!member.user_id) continue;\n operations.push({\n type: \"upsert\",\n store: \"threadMembers\",\n key: threadMemberKey(data.id, member.user_id),\n raw: withGuildId(member, data.guild_id),\n merge: true,\n });\n }\n\n for (const userId of data.removed_member_ids ?? []) {\n operations.push({\n type: \"delete\",\n store: \"threadMembers\",\n key: threadMemberKey(data.id, userId),\n });\n }\n}\n\nfunction hydrateMember(\n operations: CacheOperation[],\n guildId: Snowflake,\n member: APIGuildMember,\n): void {\n if (!member.user) return;\n\n operations.push({\n type: \"upsert\",\n store: \"members\",\n key: memberKey(guildId, member.user.id),\n raw: withGuildId(member, guildId),\n merge: true,\n });\n operations.push({ type: \"upsert\", store: \"users\", key: member.user.id, raw: member.user });\n}\n\nfunction hydrateRole(operations: CacheOperation[], guildId: Snowflake, role: APIRole): void {\n operations.push({\n type: \"upsert\",\n store: \"roles\",\n key: roleKey(guildId, role.id),\n raw: withGuildId(role, guildId),\n });\n}\n\nfunction hydrateEmoji(operations: CacheOperation[], guildId: Snowflake, emoji: APIEmoji): void {\n if (!emoji.id) return;\n operations.push({\n type: \"upsert\",\n store: \"emojis\",\n key: emojiKey(guildId, emoji.id),\n raw: withGuildId(emoji, guildId),\n });\n}\n\nfunction hydrateSticker(\n operations: CacheOperation[],\n guildId: Snowflake,\n sticker: APISticker,\n): void {\n operations.push({\n type: \"upsert\",\n store: \"stickers\",\n key: stickerKey(guildId, sticker.id),\n raw: withGuildId(sticker, guildId),\n });\n}\n\nfunction hydrateSoundboardSound(\n operations: CacheOperation[],\n guildId: Snowflake,\n sound: APISoundboardSound,\n): void {\n operations.push({\n type: \"upsert\",\n store: \"soundboardSounds\",\n key: soundboardSoundKey(guildId, sound.sound_id),\n raw: withGuildId(sound, guildId),\n });\n}\n\nfunction deleteGuildScopedResources(operations: CacheOperation[], guildId: Snowflake): void {\n // Every operation carries the guild, so a store indexing its entries by guild skips the scans below.\n // Entities keyed by `${guildId}:...` can be dropped by prefix, which only reads the keys.\n for (const store of GuildKeyedCacheEntityNames) {\n operations.push({ type: \"deletePrefix\", store, prefix: `${guildId}:`, guildId });\n }\n\n // The rest are keyed by their own ID and need a scan over the stored `guild_id`.\n for (const store of GuildFieldCacheEntityNames) {\n operations.push({\n type: \"deleteWhere\",\n store,\n predicate: (value) => isObject(value) && value.guild_id === guildId,\n guildId,\n });\n }\n}\n\n// An `update` of a cached message; a message the cache does not hold stays uncached.\nfunction updateMessage(\n channelId: Snowflake,\n messageId: Snowflake,\n update: (message: APIMessage) => APIMessage,\n): CacheOperation {\n return {\n type: \"update\",\n store: \"messages\",\n key: messageKey(channelId, messageId),\n update: (value) => update(value as APIMessage),\n };\n}\n\nfunction withGuildId<Value extends object>(\n value: Value,\n guildId: Snowflake,\n): Value & { guild_id: Snowflake } {\n return { ...value, guild_id: guildId };\n}\n\nfunction isObject(value: unknown): value is Record<string, unknown> {\n return typeof value === \"object\" && value !== null && !Array.isArray(value);\n}\n","import type { GatewayDispatchPayload } from \"discord-api-types/v10\";\nimport { applyGatewayDispatch, type CacheOperationContext } from \"./operations.js\";\nimport type { Cache } from \"./types.js\";\n\n/** A gateway that emits the dispatch event used by @discordjs/ws and @discordjs/core. */\nexport interface GatewayDispatchSource {\n on(\n event: \"dispatch\",\n listener: (payload: GatewayDispatchPayload, shardId: number) => void,\n ): unknown;\n off?(\n event: \"dispatch\",\n listener: (payload: GatewayDispatchPayload, shardId: number) => void,\n ): unknown;\n}\n\nexport interface CacheGatewayOptions extends CacheOperationContext {\n /** Receives cache write failures from the asynchronous gateway listener. */\n onError?: (error: unknown, payload: GatewayDispatchPayload, shardId: number) => void;\n}\n\n/**\n * Writes gateway dispatches into a cache. Call the returned function to stop listening.\n * Use this when a gateway has no GatewayClient managing its dispatch queue.\n */\nexport function attachCacheToGateway(\n gateway: GatewayDispatchSource,\n cache: Cache,\n options: CacheGatewayOptions = {},\n): () => void {\n const listener = (payload: GatewayDispatchPayload, shardId: number): void => {\n void applyGatewayDispatch(cache, payload, { clientUserId: options.clientUserId }).catch(\n (error) => {\n if (options.onError) options.onError(error, payload, shardId);\n else console.error(\"Failed to cache gateway dispatch\", error);\n },\n );\n };\n\n gateway.on(\"dispatch\", listener);\n return () => gateway.off?.(\"dispatch\", listener);\n}\n","import { CacheEntityNames } from \"./operations.js\";\nimport type { Cache, CacheEntityName, CacheEntityTypes, EntityCache } from \"./types.js\";\n\n/**\n * An {@link EntityCache} backed by a `Map`, optionally bounded as a least-recently-used cache.\n */\nexport class MemoryEntityCache<Raw> implements EntityCache<Raw> {\n /**\n * The maximum amount of entries, `Infinity` for an unbounded cache.\n */\n public readonly maxSize: number;\n\n readonly #items = new Map<string, Raw>();\n\n public constructor(maxSize = Infinity) {\n if (maxSize !== Infinity && (!Number.isInteger(maxSize) || maxSize < 0)) {\n throw new RangeError(\n `maxSize must be a non-negative integer or Infinity, received ${maxSize}`,\n );\n }\n\n this.maxSize = maxSize;\n }\n\n public get(key: string): Raw | undefined {\n const value = this.#items.get(key);\n if (value !== undefined && this.maxSize !== Infinity) {\n // Re-insert to mark the entry as the most recently used one.\n this.#items.delete(key);\n this.#items.set(key, value);\n }\n\n return value;\n }\n\n public set(key: string, value: Raw): void {\n if (this.maxSize === 0) return;\n\n this.#items.delete(key);\n this.#items.set(key, value);\n\n if (this.#items.size > this.maxSize) {\n // Maps iterate in insertion order, so the first key is the least recently used one.\n this.#items.delete(this.#items.keys().next().value!);\n }\n }\n\n public has(key: string): boolean {\n return this.#items.has(key);\n }\n\n public delete(key: string): boolean {\n return this.#items.delete(key);\n }\n\n public clear(): void {\n this.#items.clear();\n }\n\n public getSize(): number {\n return this.#items.size;\n }\n\n public keys(): string[] {\n return [...this.#items.keys()];\n }\n\n public values(): Raw[] {\n return [...this.#items.values()];\n }\n\n public entries(): [key: string, value: Raw][] {\n return [...this.#items.entries()];\n }\n}\n\n/**\n * A {@link Cache} whose entity caches are all {@link MemoryEntityCache}s.\n */\nexport type InMemoryCache = {\n readonly [Name in CacheEntityName]: MemoryEntityCache<CacheEntityTypes[Name]>;\n};\n\nexport interface InMemoryCacheOptions {\n /**\n * The maximum amount of entries kept per entity cache before evicting the least recently used ones, either for\n * every entity cache or per entity cache. Entity caches left out are unbounded.\n *\n * @default Infinity\n */\n maxSize?: number | Partial<Record<CacheEntityName, number>>;\n}\n\n/**\n * Creates a {@link Cache} that keeps everything in the process' memory.\n *\n * @example\n * ```typescript\n * import { createInMemoryCache } from '@wolfstar/plugin-cache';\n *\n * // Keep at most 1000 messages around, everything else is unbounded.\n * const cache = createInMemoryCache({ maxSize: { messages: 1_000 } });\n * ```\n *\n * @param options The options for the cache.\n */\nexport function createInMemoryCache(options: InMemoryCacheOptions = {}): InMemoryCache & Cache {\n const { maxSize } = options;\n const resolve = (name: CacheEntityName) =>\n typeof maxSize === \"number\" ? maxSize : (maxSize?.[name] ?? Infinity);\n\n return Object.freeze(\n Object.fromEntries(\n CacheEntityNames.map((name) => [name, new MemoryEntityCache(resolve(name))]),\n ) as InMemoryCache,\n );\n}\n","/// <reference types=\"node\" />\nimport { promisify } from \"node:util\";\nimport { brotliCompress, brotliDecompress, gunzip, gzip } from \"node:zlib\";\nimport {\n CacheEntityNames,\n GuildFieldCacheEntityNames,\n GuildKeyedCacheEntityNames,\n} from \"./operations.js\";\nimport type { Cache, CacheEntityName, CacheEntityTypes, EntityCache } from \"./types.js\";\n\nconst gzipAsync = promisify(gzip);\nconst gunzipAsync = promisify(gunzip);\nconst brotliCompressAsync = promisify(brotliCompress);\nconst brotliDecompressAsync = promisify(brotliDecompress);\n\n/**\n * The subset of the [`ioredis`](https://github.com/redis/ioredis) client API the Redis cache relies on. An `ioredis`\n * `Redis` or `Cluster` instance satisfies it, as can any other client exposing the same commands.\n */\nexport interface RedisClientLike {\n get(key: string): Promise<string | null>;\n mget(...keys: string[]): Promise<(string | null)[]>;\n set(key: string, value: string): Promise<unknown>;\n set(key: string, value: string, mode: \"PX\", milliseconds: number): Promise<unknown>;\n del(...keys: string[]): Promise<number>;\n exists(...keys: string[]): Promise<number>;\n zadd(key: string, ...scoreMembers: (string | number)[]): Promise<unknown>;\n zrem(key: string, ...members: string[]): Promise<number>;\n zrange(key: string, start: string, stop: string): Promise<string[]>;\n zcard(key: string): Promise<number>;\n zremrangebyscore(key: string, min: number | string, max: number | string): Promise<number>;\n multi(): RedisTransactionLike;\n}\n\n/**\n * The subset of an [`ioredis`](https://github.com/redis/ioredis) `MULTI` transaction the Redis cache relies on: every\n * queued command returns the transaction, and `exec` runs them atomically.\n */\nexport interface RedisTransactionLike {\n set(key: string, value: string): RedisTransactionLike;\n set(key: string, value: string, mode: \"PX\", milliseconds: number): RedisTransactionLike;\n del(...keys: string[]): RedisTransactionLike;\n zadd(key: string, ...scoreMembers: (string | number)[]): RedisTransactionLike;\n zrem(key: string, ...members: string[]): RedisTransactionLike;\n zremrangebyscore(key: string, min: number | string, max: number | string): RedisTransactionLike;\n /**\n * Optional: when available (`ioredis` has it), the guild indexes of a cache with a `ttl` expire once their guild\n * stops being written to. Without it, they are only pruned by the next write to their guild.\n */\n pexpire?(key: string, milliseconds: number): RedisTransactionLike;\n exec(): Promise<[error: Error | null, result: unknown][] | null>;\n}\n\n/**\n * Thrown when a value stored in Redis cannot be read back: invalid JSON, or compressed bytes that fail to decompress.\n *\n * @remarks\n * A missing value is not an error, `get` resolves to `undefined` for it. Redis connection errors are not wrapped\n * either, they propagate as the client throws them.\n */\nexport class CacheValueError extends Error {\n /**\n * The Redis key holding the unreadable value.\n */\n public readonly key: string;\n\n public constructor(key: string, cause: unknown) {\n super(`Cannot read the cached value at \"${key}\"`, { cause });\n this.name = \"CacheValueError\";\n this.key = key;\n }\n}\n\n/**\n * The algorithm used to compress values before writing them to Redis.\n */\nexport type RedisCacheCompression = \"gzip\" | \"brotli\" | \"none\";\n\nexport interface RedisEntityCacheOptions<Raw = unknown> {\n /**\n * The prefix of every Redis key owned by this entity cache.\n */\n prefix: string;\n /**\n * The time-to-live of every entry, in seconds. Entries never expire when omitted.\n */\n ttl?: number;\n /**\n * The compression algorithm to use.\n *\n * @default \"none\"\n */\n compression?: RedisCacheCompression;\n /**\n * The minimum size, in bytes, a serialized value must reach to be compressed. Small payloads rarely benefit from it.\n *\n * @default 1024\n */\n compressionThreshold?: number;\n /**\n * Resolves the guild an entry belongs to, indexing the entries by guild so {@link RedisEntityCache.deleteGuild}\n * does not have to scan the whole cache. Entries it resolves no guild for are not indexed. Without it, nothing is\n * indexed and `deleteGuild` resolves to `null`.\n *\n * On delete, it is first called without the value: when the key alone gives the guild away, the value is not\n * read back.\n */\n guildOf?: (key: string, value?: Raw) => string | undefined;\n}\n\n// The most keys a single `DEL`/`ZREM` of `deleteGuild` sends, so a large guild does not make one huge command.\nconst DeleteGuildChunkSize = 500;\n\n// Compressed values are stored as `<marker><base64>`. JSON can never start with either marker, so values written with\n// a different `compression` setting (e.g. before it was turned on) are still read back correctly.\nconst CompressionMarkers = { gzip: \"gz:\", brotli: \"br:\" } as const;\n\n/**\n * An {@link EntityCache} backed by Redis.\n *\n * @remarks\n * Every entry is stored as its own string key (`<prefix>:<key>`), and a sorted set (`<prefix>:@index`) tracks the\n * stored keys with their expiration time as score, which is what `keys`, `entries`, `getSize`, and `clear` read from.\n *\n * With {@link RedisEntityCacheOptions.guildOf}, one more sorted set per guild (`<prefix>:@guild:<guildId>`) tracks the\n * keys of that guild's entries the same way, and `<prefix>:@guilds` lists the guilds having one, for `clear`.\n */\nexport class RedisEntityCache<Raw> implements EntityCache<Raw> {\n public readonly prefix: string;\n public readonly ttl: number | undefined;\n public readonly compression: RedisCacheCompression;\n public readonly compressionThreshold: number;\n\n readonly #redis: RedisClientLike;\n readonly #guildOf: ((key: string, value?: Raw) => string | undefined) | undefined;\n\n public constructor(redis: RedisClientLike, options: RedisEntityCacheOptions<Raw>) {\n if (options.ttl !== undefined && !(options.ttl > 0)) {\n throw new RangeError(`ttl must be a positive amount of seconds, received ${options.ttl}`);\n }\n\n this.#redis = redis;\n this.prefix = options.prefix;\n this.ttl = options.ttl;\n this.compression = options.compression ?? \"none\";\n this.compressionThreshold = options.compressionThreshold ?? 1024;\n this.#guildOf = options.guildOf;\n }\n\n public async get(key: string): Promise<Raw | undefined> {\n const valueKey = this.valueKey(key);\n const value = await this.#redis.get(valueKey);\n return value === null ? undefined : this.deserialize(valueKey, value);\n }\n\n public async set(key: string, value: Raw): Promise<void> {\n const serialized = await this.serialize(value);\n const guildId = this.#guildOf?.(key, value);\n // The value and its index entries are written in one transaction, so neither can exist without the other.\n const transaction = this.#redis.multi();\n if (this.ttl === undefined) {\n transaction.set(this.valueKey(key), serialized).zadd(this.indexKey, \"+inf\", key);\n if (guildId !== undefined) {\n transaction\n .zadd(this.guildIndexKey(guildId), \"+inf\", key)\n .zadd(this.guildsKey, \"+inf\", guildId);\n }\n } else {\n const now = Date.now();\n const milliseconds = Math.round(this.ttl * 1000);\n transaction\n .set(this.valueKey(key), serialized, \"PX\", milliseconds)\n .zadd(this.indexKey, now + milliseconds, key)\n // Pruning on every write keeps the index bounded even when nothing ever enumerates it.\n .zremrangebyscore(this.indexKey, \"-inf\", now);\n if (guildId !== undefined) {\n const guildIndexKey = this.guildIndexKey(guildId);\n transaction\n .zadd(guildIndexKey, now + milliseconds, key)\n .zremrangebyscore(guildIndexKey, \"-inf\", now)\n .zadd(this.guildsKey, now + milliseconds, guildId)\n .zremrangebyscore(this.guildsKey, \"-inf\", now);\n // Every write pushes the expiration back, so the indexes outlive every entry they list.\n transaction.pexpire?.(guildIndexKey, milliseconds);\n transaction.pexpire?.(this.guildsKey, milliseconds);\n }\n }\n\n await execute(transaction);\n }\n\n public async has(key: string): Promise<boolean> {\n return (await this.#redis.exists(this.valueKey(key))) > 0;\n }\n\n public async delete(key: string): Promise<boolean> {\n const guildId = await this.readGuild(key);\n const transaction = this.#redis.multi().del(this.valueKey(key)).zrem(this.indexKey, key);\n if (guildId !== undefined) transaction.zrem(this.guildIndexKey(guildId), key);\n\n const [deleted] = await execute(transaction);\n return (deleted as number) > 0;\n }\n\n public async clear(): Promise<void> {\n const keys = await this.#redis.zrange(this.indexKey, \"0\", \"-1\");\n if (keys.length > 0) await this.#redis.del(...keys.map((key) => this.valueKey(key)));\n\n const guilds = this.#guildOf ? await this.#redis.zrange(this.guildsKey, \"0\", \"-1\") : [];\n await this.#redis.del(\n this.indexKey,\n this.guildsKey,\n ...guilds.map((guildId) => this.guildIndexKey(guildId)),\n );\n }\n\n /**\n * Deletes every entry of a guild, reading their keys from the guild's index rather than scanning the cache.\n *\n * @remarks\n * Only indexed entries are deleted: when `guildOf` is set on a cache already holding entries, the ones written\n * before are left behind until they expire or the cache is cleared.\n *\n * @param guildId The ID of the guild.\n * @returns The amount of deleted entries, or `null` without {@link RedisEntityCacheOptions.guildOf}.\n */\n public async deleteGuild(guildId: string): Promise<number | null> {\n if (this.#guildOf === undefined) return null;\n\n const guildIndexKey = this.guildIndexKey(guildId);\n const keys = await this.#redis.zrange(guildIndexKey, \"0\", \"-1\");\n let deleted = 0;\n for (let index = 0; index < keys.length; index += DeleteGuildChunkSize) {\n const chunk = keys.slice(index, index + DeleteGuildChunkSize);\n // Only the listed keys leave the guild index: entries written meanwhile stay indexed.\n const [count] = await execute(\n this.#redis\n .multi()\n .del(...chunk.map((key) => this.valueKey(key)))\n .zrem(this.indexKey, ...chunk)\n .zrem(guildIndexKey, ...chunk),\n );\n deleted += count as number;\n }\n\n await this.#redis.zrem(this.guildsKey, guildId);\n return deleted;\n }\n\n public async getSize(): Promise<number> {\n await this.prune();\n return this.#redis.zcard(this.indexKey);\n }\n\n public async keys(): Promise<string[]> {\n await this.prune();\n return this.#redis.zrange(this.indexKey, \"0\", \"-1\");\n }\n\n public async values(): Promise<Raw[]> {\n return (await this.entries()).map(([, value]) => value);\n }\n\n public async entries(): Promise<[key: string, value: Raw][]> {\n const keys = await this.keys();\n if (keys.length === 0) return [];\n\n const values = await this.#redis.mget(...keys.map((key) => this.valueKey(key)));\n const entries: [key: string, value: Raw][] = [];\n for (const [index, value] of values.entries()) {\n // The value may have been evicted by Redis between both reads.\n if (value !== null) {\n const key = keys[index]!;\n entries.push([key, await this.deserialize(this.valueKey(key), value)]);\n }\n }\n\n return entries;\n }\n\n /**\n * Gets the Redis key a value is stored at.\n * @param key The entity cache key.\n */\n public valueKey(key: string): string {\n return `${this.prefix}:${key}`;\n }\n\n /**\n * The Redis key of the sorted set indexing the stored keys.\n */\n public get indexKey(): string {\n return `${this.prefix}:@index`;\n }\n\n /**\n * Gets the Redis key of the sorted set indexing a guild's entries.\n * @param guildId The ID of the guild.\n */\n public guildIndexKey(guildId: string): string {\n return `${this.prefix}:@guild:${guildId}`;\n }\n\n /**\n * The Redis key of the sorted set listing the guilds having an index.\n */\n public get guildsKey(): string {\n return `${this.prefix}:@guilds`;\n }\n\n /**\n * Reads the guild of a stored entry, to remove the entry from its guild index.\n * @param key The entity cache key.\n */\n private async readGuild(key: string): Promise<string | undefined> {\n if (this.#guildOf === undefined) return undefined;\n\n // Most guild-scoped keys start with the guild ID, sparing a read (and a decompression) on every delete.\n const fromKey = this.#guildOf(key);\n if (fromKey !== undefined) return fromKey;\n\n let value: Raw | undefined;\n try {\n value = await this.get(key);\n } catch (error) {\n // An unreadable value is deleted all the same, it only stays listed in its guild index.\n if (error instanceof CacheValueError) return undefined;\n throw error;\n }\n\n return value === undefined ? undefined : this.#guildOf(key, value);\n }\n\n private async prune(): Promise<void> {\n if (this.ttl !== undefined)\n await this.#redis.zremrangebyscore(this.indexKey, \"-inf\", Date.now());\n }\n\n private async serialize(value: Raw): Promise<string> {\n const json = JSON.stringify(value);\n if (this.compression === \"none\" || Buffer.byteLength(json) < this.compressionThreshold) {\n return json;\n }\n\n const compressed =\n this.compression === \"gzip\" ? await gzipAsync(json) : await brotliCompressAsync(json);\n return `${CompressionMarkers[this.compression]}${compressed.toString(\"base64\")}`;\n }\n\n private async deserialize(valueKey: string, value: string): Promise<Raw> {\n try {\n if (value.startsWith(CompressionMarkers.gzip)) {\n return JSON.parse((await gunzipAsync(decode(value))).toString(\"utf8\")) as Raw;\n }\n\n if (value.startsWith(CompressionMarkers.brotli)) {\n return JSON.parse((await brotliDecompressAsync(decode(value))).toString(\"utf8\")) as Raw;\n }\n\n return JSON.parse(value) as Raw;\n } catch (error) {\n throw new CacheValueError(valueKey, error);\n }\n }\n}\n\n/**\n * Runs a transaction, throwing the first command error, and resolves to the command results.\n */\nasync function execute(transaction: RedisTransactionLike): Promise<unknown[]> {\n const results = await transaction.exec();\n if (results === null) throw new Error(\"The Redis transaction was aborted\");\n\n return results.map(([error, result]) => {\n if (error) throw error;\n return result;\n });\n}\n\nfunction decode(value: string): Buffer {\n return Buffer.from(value.slice(3), \"base64\");\n}\n\n/**\n * A {@link Cache} whose entity caches are all {@link RedisEntityCache}s.\n */\nexport type RedisCache = {\n readonly [Name in CacheEntityName]: RedisEntityCache<CacheEntityTypes[Name]>;\n};\n\nexport interface RedisCacheOptions {\n /**\n * The Redis client to use, e.g. an [`ioredis`](https://github.com/redis/ioredis) instance.\n */\n redis: RedisClientLike;\n /**\n * The prefix of every Redis key owned by the cache, which allows several caches to share a database.\n *\n * @default \"wolfstar:cache\"\n */\n prefix?: string;\n /**\n * The compression algorithm to use for values of at least {@link RedisCacheOptions.compressionThreshold} bytes.\n *\n * @default \"none\"\n */\n compression?: RedisCacheCompression;\n /**\n * The minimum size, in bytes, a serialized value must reach to be compressed.\n *\n * @default 1024\n */\n compressionThreshold?: number;\n /**\n * The time-to-live per entity cache, in seconds. Entity caches left out never expire.\n */\n ttl?: Partial<Record<CacheEntityName, number>>;\n /**\n * Whether to index the guild-scoped entity caches by guild, so a `GUILD_DELETE` drops a guild's entries without\n * scanning every entity cache. It costs one more sorted-set write per write, and a read before every delete.\n *\n * @remarks\n * Entries written while it was off are not indexed: turning it on for a populated cache leaves them behind on\n * `GUILD_DELETE`, until they expire or the cache is cleared.\n *\n * @default true\n */\n indexGuilds?: boolean;\n}\n\n/**\n * The guild of an entry keyed by its guild (`${guildId}:...`), matching what the `deletePrefix` scan drops.\n */\nfunction guildOfKey(key: string, value?: unknown): string | undefined {\n const separator = key.indexOf(\":\");\n return separator > 0 ? key.slice(0, separator) : guildOfField(key, value);\n}\n\n/**\n * The guild of an entry keyed by its own ID, matching what the `deleteWhere` scan over `guild_id` drops.\n */\nfunction guildOfField(_key: string, value?: unknown): string | undefined {\n if (typeof value !== \"object\" || value === null) return undefined;\n const guildId = (value as { guild_id?: unknown }).guild_id;\n return typeof guildId === \"string\" ? guildId : undefined;\n}\n\nconst GuildResolvers: Partial<\n Record<CacheEntityName, (key: string, value?: unknown) => string | undefined>\n> = Object.fromEntries([\n ...GuildKeyedCacheEntityNames.map((name) => [name, guildOfKey] as const),\n ...GuildFieldCacheEntityNames.map((name) => [name, guildOfField] as const),\n]);\n\n/**\n * The default prefix of every Redis key owned by a cache created with {@link createRedisCache}.\n */\nexport const DefaultRedisCachePrefix = \"wolfstar:cache\";\n\n/**\n * Creates a {@link Cache} stored in Redis, optionally compressing its values.\n *\n * @example\n * ```typescript\n * import { createRedisCache } from '@wolfstar/plugin-cache';\n * import { Redis } from 'ioredis';\n *\n * const cache = createRedisCache({\n * redis: new Redis(process.env.REDIS_URL!),\n * compression: 'gzip',\n * ttl: { guilds: 60 * 60, users: 30 * 60 },\n * });\n * ```\n *\n * @param options The options for the cache.\n */\nexport function createRedisCache(options: RedisCacheOptions): RedisCache & Cache {\n const {\n redis,\n prefix = DefaultRedisCachePrefix,\n compression,\n compressionThreshold,\n ttl,\n indexGuilds = true,\n } = options;\n\n return Object.freeze(\n Object.fromEntries(\n CacheEntityNames.map((name) => [\n name,\n new RedisEntityCache(redis, {\n prefix: `${prefix}:${name}`,\n ttl: ttl?.[name],\n compression,\n compressionThreshold,\n guildOf: indexGuilds ? GuildResolvers[name] : undefined,\n }),\n ]),\n ) as RedisCache,\n );\n}\n"],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@wolfstar/plugin-cache",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.2.0-next-20260926194301",
|
|
4
4
|
"description": "Pluggable, storage-agnostic Discord entity cache (in-memory and Redis, with optional compression) for @wolfstar/plugin-gateway",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"cache",
|