@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 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
 
@@ -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}.
@@ -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":"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"}
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": for (const [key, value] of await store.entries()) if (operation.predicate(value)) await store.delete(key);
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) transaction.set(this.valueKey(key), serialized).zadd(this.indexKey, "+inf", key);
1316
- else {
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 [deleted] = await execute(this.#redis.multi().del(this.valueKey(key)).zrem(this.indexKey, key));
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.del(this.indexKey);
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
 
@@ -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.1.0-next-20260926152534",
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",