@wolfstar/plugin-cache 0.3.0-next-20260926221949 → 0.3.0-next-20260927012037
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 +42 -0
- package/dist/esm/index.d.ts +105 -1
- package/dist/esm/index.d.ts.map +1 -1
- package/dist/esm/index.js +62 -1
- package/dist/esm/index.js.map +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -128,6 +128,48 @@ A missing value is not an error: `get` resolves to `undefined`. A value that can
|
|
|
128
128
|
carrying the Redis `key`, and the original error as `cause`. Connection errors are not wrapped, they
|
|
129
129
|
propagate as the client throws them. `@wolfstar/plugin-gateway` surfaces both as an `error` event.
|
|
130
130
|
|
|
131
|
+
### Gateway sessions
|
|
132
|
+
|
|
133
|
+
`createRedisSessionStore` stores the gateway shards' sessions for `@wolfstar/plugin-gateway`'s
|
|
134
|
+
`sessionStore` option, so a restarted process resumes them instead of identifying again (see
|
|
135
|
+
[Resuming sessions across restarts](../plugin-gateway#resuming-sessions-across-restarts)). It only
|
|
136
|
+
needs `get`, `set`, and `del`, so it can share the cache's client:
|
|
137
|
+
|
|
138
|
+
```ts
|
|
139
|
+
import { createRedisCache, createRedisSessionStore } from "@wolfstar/plugin-cache";
|
|
140
|
+
import { Redis } from "ioredis";
|
|
141
|
+
|
|
142
|
+
const redis = new Redis(process.env.REDIS_URL!);
|
|
143
|
+
const cache = createRedisCache({ redis });
|
|
144
|
+
const sessionStore = createRedisSessionStore({ redis, prefix: "my-bot:sessions" });
|
|
145
|
+
```
|
|
146
|
+
|
|
147
|
+
| Option | Default | Description |
|
|
148
|
+
| -------- | --------------------- | -------------------------------------------------------------------------------- |
|
|
149
|
+
| `redis` | — | The client to use. |
|
|
150
|
+
| `prefix` | `"wolfstar:sessions"` | Prefix of every key, sessions live at `<prefix>:<shardId>` as JSON. |
|
|
151
|
+
| `ttl` | `600` | Seconds a session is kept after its last write, `null` to keep it until dropped. |
|
|
152
|
+
|
|
153
|
+
Discord only lets a session be resumed for a while after its connection closes, and does not say
|
|
154
|
+
for how long: the `ttl` spares a restart the attempt to resume a session long gone (which costs a
|
|
155
|
+
connection before identifying anyway) and keeps Redis tidy. Each write pushes the expiration back,
|
|
156
|
+
and the gateway writes on every dispatch, so only a shard receiving no dispatch for longer than the
|
|
157
|
+
`ttl` identifies on the next restart. A value that is not valid JSON rejects with a
|
|
158
|
+
`CacheValueError`, which the gateway reports before identifying.
|
|
159
|
+
|
|
160
|
+
The store is a `GatewaySessionStore`, any object with the same two methods works:
|
|
161
|
+
|
|
162
|
+
```ts
|
|
163
|
+
interface GatewaySessionStore {
|
|
164
|
+
get(shardId: number): Awaitable<GatewaySessionInfo | null>;
|
|
165
|
+
// `null` once the session can no longer be resumed.
|
|
166
|
+
set(shardId: number, info: GatewaySessionInfo | null): Awaitable<void>;
|
|
167
|
+
}
|
|
168
|
+
```
|
|
169
|
+
|
|
170
|
+
`GatewaySessionInfo` has the same shape as `@discordjs/ws`'s `SessionInfo`, without the package
|
|
171
|
+
depending on it.
|
|
172
|
+
|
|
131
173
|
### Custom stores
|
|
132
174
|
|
|
133
175
|
```ts
|
package/dist/esm/index.d.ts
CHANGED
|
@@ -547,5 +547,109 @@ export declare const DefaultRedisCachePrefix = "wolfstar:cache";
|
|
|
547
547
|
*/
|
|
548
548
|
export declare function createRedisCache(options: RedisCacheOptions): RedisCache & Cache;
|
|
549
549
|
//#endregion
|
|
550
|
-
|
|
550
|
+
//#region src/lib/sessions.d.ts
|
|
551
|
+
/**
|
|
552
|
+
* What a gateway shard needs to resume its session, the same shape as `@discordjs/ws`'s `SessionInfo`.
|
|
553
|
+
*/
|
|
554
|
+
interface GatewaySessionInfo {
|
|
555
|
+
/**
|
|
556
|
+
* The URL to connect to when resuming.
|
|
557
|
+
*/
|
|
558
|
+
resumeURL: string;
|
|
559
|
+
/**
|
|
560
|
+
* The sequence number of the last dispatch the shard received.
|
|
561
|
+
*/
|
|
562
|
+
sequence: number;
|
|
563
|
+
/**
|
|
564
|
+
* The ID of the session.
|
|
565
|
+
*/
|
|
566
|
+
sessionId: string;
|
|
567
|
+
/**
|
|
568
|
+
* The total number of shards when the shard identified. A session is not resumed once it changes.
|
|
569
|
+
*/
|
|
570
|
+
shardCount: number;
|
|
571
|
+
/**
|
|
572
|
+
* The ID of the shard.
|
|
573
|
+
*/
|
|
574
|
+
shardId: number;
|
|
575
|
+
}
|
|
576
|
+
/**
|
|
577
|
+
* Stores the gateway sessions of the shards, so a restarted process resumes them instead of identifying again, see
|
|
578
|
+
* `@wolfstar/plugin-gateway`'s `GatewayClientOptions.sessionStore`.
|
|
579
|
+
*/
|
|
580
|
+
interface GatewaySessionStore {
|
|
581
|
+
/**
|
|
582
|
+
* Reads the session of a shard.
|
|
583
|
+
*
|
|
584
|
+
* @param shardId The ID of the shard.
|
|
585
|
+
* @returns The session, or `null` when there is none to resume.
|
|
586
|
+
*/
|
|
587
|
+
get(shardId: number): Awaitable<GatewaySessionInfo | null>;
|
|
588
|
+
/**
|
|
589
|
+
* Writes the session of a shard.
|
|
590
|
+
*
|
|
591
|
+
* @param shardId The ID of the shard.
|
|
592
|
+
* @param info The session, or `null` once it can no longer be resumed.
|
|
593
|
+
*/
|
|
594
|
+
set(shardId: number, info: GatewaySessionInfo | null): Awaitable<void>;
|
|
595
|
+
}
|
|
596
|
+
interface RedisSessionStoreOptions {
|
|
597
|
+
/**
|
|
598
|
+
* The Redis client to use, e.g. an [`ioredis`](https://github.com/redis/ioredis) instance.
|
|
599
|
+
*/
|
|
600
|
+
redis: Pick<RedisClientLike, "get" | "set" | "del">;
|
|
601
|
+
/**
|
|
602
|
+
* The prefix of every Redis key owned by the store, sessions live at `<prefix>:<shardId>`.
|
|
603
|
+
*
|
|
604
|
+
* @default "wolfstar:sessions"
|
|
605
|
+
*/
|
|
606
|
+
prefix?: string;
|
|
607
|
+
/**
|
|
608
|
+
* The time-to-live of a session since its last write, in seconds, `null` to keep sessions until they are dropped.
|
|
609
|
+
*
|
|
610
|
+
* @remarks
|
|
611
|
+
* Discord only lets a session be resumed for a while after its connection closes, and resuming an expired one costs
|
|
612
|
+
* a connection before identifying. Every dispatch pushes the expiration back, so a shard receiving none for longer
|
|
613
|
+
* than the ttl identifies on the next restart.
|
|
614
|
+
*
|
|
615
|
+
* @default 600
|
|
616
|
+
*/
|
|
617
|
+
ttl?: number | null;
|
|
618
|
+
}
|
|
619
|
+
/**
|
|
620
|
+
* The default prefix of every Redis key owned by a store created with {@link createRedisSessionStore}.
|
|
621
|
+
*/
|
|
622
|
+
export declare const DefaultRedisSessionStorePrefix = "wolfstar:sessions";
|
|
623
|
+
/**
|
|
624
|
+
* A {@link GatewaySessionStore} backed by Redis, storing every session as JSON at `<prefix>:<shardId>`.
|
|
625
|
+
*/
|
|
626
|
+
export declare class RedisSessionStore implements GatewaySessionStore {
|
|
627
|
+
#private;
|
|
628
|
+
readonly prefix: string;
|
|
629
|
+
readonly ttl: number | null;
|
|
630
|
+
constructor(options: RedisSessionStoreOptions);
|
|
631
|
+
get(shardId: number): Promise<GatewaySessionInfo | null>;
|
|
632
|
+
set(shardId: number, info: GatewaySessionInfo | null): Promise<void>;
|
|
633
|
+
/**
|
|
634
|
+
* Gets the Redis key the session of a shard is stored at.
|
|
635
|
+
* @param shardId The ID of the shard.
|
|
636
|
+
*/
|
|
637
|
+
key(shardId: number): string;
|
|
638
|
+
}
|
|
639
|
+
/**
|
|
640
|
+
* Creates a {@link GatewaySessionStore} stored in Redis, for `@wolfstar/plugin-gateway`'s `sessionStore` option.
|
|
641
|
+
*
|
|
642
|
+
* @example
|
|
643
|
+
* ```typescript
|
|
644
|
+
* import { createRedisSessionStore } from '@wolfstar/plugin-cache';
|
|
645
|
+
* import { Redis } from 'ioredis';
|
|
646
|
+
*
|
|
647
|
+
* const sessionStore = createRedisSessionStore({ redis: new Redis(process.env.REDIS_URL!) });
|
|
648
|
+
* ```
|
|
649
|
+
*
|
|
650
|
+
* @param options The options for the store.
|
|
651
|
+
*/
|
|
652
|
+
export declare function createRedisSessionStore(options: RedisSessionStoreOptions): RedisSessionStore;
|
|
653
|
+
//#endregion
|
|
654
|
+
export type { Awaitable, Cache, CacheEntities, CacheEntityName, CacheEntityTypes, CacheGatewayOptions, CacheOperation, CacheOperationContext, EntityCache, GatewayDispatchSource, GatewaySessionInfo, GatewaySessionStore, InMemoryCache, InMemoryCacheOptions, RedisCache, RedisCacheCompression, RedisCacheOptions, RedisClientLike, RedisEntityCacheOptions, RedisSessionStoreOptions, RedisTransactionLike };
|
|
551
655
|
//# sourceMappingURL=index.d.ts.map
|
package/dist/esm/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","names":[],"sources":["../../src/lib/keys.ts","../../src/lib/types.ts","../../src/lib/operations.ts","../../src/lib/gateway.ts","../../src/lib/memory.ts","../../src/lib/redis.ts"],"mappings":"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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","../../src/lib/sessions.ts"],"mappings":"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"}
|
package/dist/esm/index.js
CHANGED
|
@@ -1558,5 +1558,66 @@ function createRedisCache(options) {
|
|
|
1558
1558
|
}
|
|
1559
1559
|
|
|
1560
1560
|
//#endregion
|
|
1561
|
-
|
|
1561
|
+
//#region src/lib/sessions.ts
|
|
1562
|
+
/**
|
|
1563
|
+
* The default prefix of every Redis key owned by a store created with {@link createRedisSessionStore}.
|
|
1564
|
+
*/
|
|
1565
|
+
const DefaultRedisSessionStorePrefix = "wolfstar:sessions";
|
|
1566
|
+
/**
|
|
1567
|
+
* A {@link GatewaySessionStore} backed by Redis, storing every session as JSON at `<prefix>:<shardId>`.
|
|
1568
|
+
*/
|
|
1569
|
+
var RedisSessionStore = class {
|
|
1570
|
+
prefix;
|
|
1571
|
+
ttl;
|
|
1572
|
+
#redis;
|
|
1573
|
+
constructor(options) {
|
|
1574
|
+
const ttl = options.ttl === void 0 ? 600 : options.ttl;
|
|
1575
|
+
if (ttl !== null && !(ttl > 0)) throw new RangeError(`ttl must be a positive amount of seconds, received ${ttl}`);
|
|
1576
|
+
this.#redis = options.redis;
|
|
1577
|
+
this.prefix = options.prefix ?? "wolfstar:sessions";
|
|
1578
|
+
this.ttl = ttl;
|
|
1579
|
+
}
|
|
1580
|
+
async get(shardId) {
|
|
1581
|
+
const key = this.key(shardId);
|
|
1582
|
+
const value = await this.#redis.get(key);
|
|
1583
|
+
if (value === null) return null;
|
|
1584
|
+
try {
|
|
1585
|
+
return JSON.parse(value);
|
|
1586
|
+
} catch (error) {
|
|
1587
|
+
throw new CacheValueError(key, error);
|
|
1588
|
+
}
|
|
1589
|
+
}
|
|
1590
|
+
async set(shardId, info) {
|
|
1591
|
+
const key = this.key(shardId);
|
|
1592
|
+
if (info === null) await this.#redis.del(key);
|
|
1593
|
+
else if (this.ttl === null) await this.#redis.set(key, JSON.stringify(info));
|
|
1594
|
+
else await this.#redis.set(key, JSON.stringify(info), "PX", Math.round(this.ttl * 1e3));
|
|
1595
|
+
}
|
|
1596
|
+
/**
|
|
1597
|
+
* Gets the Redis key the session of a shard is stored at.
|
|
1598
|
+
* @param shardId The ID of the shard.
|
|
1599
|
+
*/
|
|
1600
|
+
key(shardId) {
|
|
1601
|
+
return `${this.prefix}:${shardId}`;
|
|
1602
|
+
}
|
|
1603
|
+
};
|
|
1604
|
+
/**
|
|
1605
|
+
* Creates a {@link GatewaySessionStore} stored in Redis, for `@wolfstar/plugin-gateway`'s `sessionStore` option.
|
|
1606
|
+
*
|
|
1607
|
+
* @example
|
|
1608
|
+
* ```typescript
|
|
1609
|
+
* import { createRedisSessionStore } from '@wolfstar/plugin-cache';
|
|
1610
|
+
* import { Redis } from 'ioredis';
|
|
1611
|
+
*
|
|
1612
|
+
* const sessionStore = createRedisSessionStore({ redis: new Redis(process.env.REDIS_URL!) });
|
|
1613
|
+
* ```
|
|
1614
|
+
*
|
|
1615
|
+
* @param options The options for the store.
|
|
1616
|
+
*/
|
|
1617
|
+
function createRedisSessionStore(options) {
|
|
1618
|
+
return new RedisSessionStore(options);
|
|
1619
|
+
}
|
|
1620
|
+
|
|
1621
|
+
//#endregion
|
|
1622
|
+
export { CacheEntityNames, CacheValueError, DefaultRedisCachePrefix, DefaultRedisSessionStorePrefix, MemoryEntityCache, RedisEntityCache, RedisSessionStore, applicationCommandPermissionsKey, applyCacheOperations, applyGatewayDispatch, attachCacheToGateway, autoModerationRuleKey, banKey, createCacheOperations, createInMemoryCache, createRedisCache, createRedisSessionStore, emojiKey, guildScopedKey, integrationKey, inviteKey, memberKey, mergeValues, messageKey, presenceKey, roleKey, scheduledEventKey, soundboardSoundKey, stageInstanceKey, stickerKey, threadMemberKey, voiceStateKey };
|
|
1562
1623
|
//# sourceMappingURL=index.js.map
|
package/dist/esm/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","names":[],"sources":["../../src/lib/keys.ts","../../src/lib/reactions.ts","../../src/lib/operations.ts","../../src/lib/gateway.ts","../../src/lib/memory.ts","../../src/lib/redis.ts"],"sourcesContent":["import type { Snowflake } from \"discord-api-types/v10\";\n\n/**\n * Creates a stable key for a guild-scoped entity.\n */\nexport function guildScopedKey(guildId: Snowflake, id: Snowflake): string {\n return `${guildId}:${id}`;\n}\n\n/**\n * Creates a stable key for a channel message.\n */\nexport function messageKey(channelId: Snowflake, messageId: Snowflake): string {\n return `${channelId}:${messageId}`;\n}\n\n/**\n * Creates a stable key for a guild member.\n */\nexport function memberKey(guildId: Snowflake, userId: Snowflake): string {\n return guildScopedKey(guildId, userId);\n}\n\n/**\n * Creates a stable key for a guild presence.\n */\nexport function presenceKey(guildId: Snowflake, userId: Snowflake): string {\n return guildScopedKey(guildId, userId);\n}\n\n/**\n * Creates a stable key for a guild voice state.\n */\nexport function voiceStateKey(guildId: Snowflake, userId: Snowflake): string {\n return guildScopedKey(guildId, userId);\n}\n\n/**\n * Creates a stable key for a guild role.\n */\nexport function roleKey(guildId: Snowflake, roleId: Snowflake): string {\n return guildScopedKey(guildId, roleId);\n}\n\n/**\n * Creates a stable key for a guild emoji.\n */\nexport function emojiKey(guildId: Snowflake, emojiId: Snowflake): string {\n return guildScopedKey(guildId, emojiId);\n}\n\n/**\n * Creates a stable key for a guild sticker.\n */\nexport function stickerKey(guildId: Snowflake, stickerId: Snowflake): string {\n return guildScopedKey(guildId, stickerId);\n}\n\n/**\n * Creates a stable key for a guild scheduled event.\n */\nexport function scheduledEventKey(guildId: Snowflake, scheduledEventId: Snowflake): string {\n return guildScopedKey(guildId, scheduledEventId);\n}\n\n/**\n * Creates a stable key for a stage instance.\n */\nexport function stageInstanceKey(guildId: Snowflake, channelId: Snowflake): string {\n return guildScopedKey(guildId, channelId);\n}\n\n/**\n * Creates a stable key for a guild soundboard sound.\n */\nexport function soundboardSoundKey(guildId: Snowflake, soundId: Snowflake): string {\n return guildScopedKey(guildId, soundId);\n}\n\n/**\n * Creates a stable key for a guild auto moderation rule.\n */\nexport function autoModerationRuleKey(guildId: Snowflake, ruleId: Snowflake): string {\n return guildScopedKey(guildId, ruleId);\n}\n\n/**\n * Creates a stable key for a guild ban.\n */\nexport function banKey(guildId: Snowflake, userId: Snowflake): string {\n return guildScopedKey(guildId, userId);\n}\n\n/**\n * Creates a stable key for a guild integration.\n */\nexport function integrationKey(guildId: Snowflake, integrationId: Snowflake): string {\n return guildScopedKey(guildId, integrationId);\n}\n\n/**\n * Creates a stable key for an invite, using `@global` for invites that do not belong to a guild.\n */\nexport function inviteKey(guildId: Snowflake | null | undefined, code: string): string {\n return `${guildId ?? \"@global\"}:${code}`;\n}\n\n/**\n * Creates a stable key for a thread member.\n */\nexport function threadMemberKey(threadId: Snowflake, userId: Snowflake): string {\n return `${threadId}:${userId}`;\n}\n\n/**\n * Creates a stable key for application command permissions.\n */\nexport function applicationCommandPermissionsKey(\n applicationId: Snowflake,\n guildId: Snowflake,\n commandId: Snowflake,\n): string {\n return `${applicationId}:${guildId}:${commandId}`;\n}\n","import type {\n APIMessage,\n APIPartialEmoji,\n APIReaction,\n GatewayMessagePollVoteDispatchData,\n GatewayMessageReactionAddDispatchData,\n GatewayMessageReactionRemoveDispatchData,\n} from \"discord-api-types/v10\";\n\n// Reactions and poll votes only carry the voter's ID, so whether the bot is the voter needs the bot's own ID.\n\n/**\n * Whether two reaction emojis are the same: custom emojis match by ID, Unicode emojis by name.\n */\nfunction isSameEmoji(a: APIPartialEmoji, b: APIPartialEmoji): boolean {\n return a.id ? a.id === b.id : !b.id && a.name === b.name;\n}\n\n/**\n * Adds a `MESSAGE_REACTION_ADD` to a cached message's reactions.\n *\n * @param message The cached message.\n * @param data The dispatch data.\n * @param clientUserId The bot's user ID, to set `me` when the bot reacted.\n */\nexport function addReaction(\n message: APIMessage,\n data: GatewayMessageReactionAddDispatchData,\n clientUserId?: string,\n): APIMessage {\n const me = data.user_id === clientUserId;\n const kind = data.burst ? \"burst\" : \"normal\";\n const reactions = message.reactions ?? [];\n const existing = reactions.find((reaction) => isSameEmoji(reaction.emoji, data.emoji));\n\n if (!existing) {\n const reaction: APIReaction = {\n emoji: data.emoji,\n count: 1,\n count_details: { normal: data.burst ? 0 : 1, burst: data.burst ? 1 : 0 },\n me: me && !data.burst,\n me_burst: me && data.burst,\n burst_colors: data.burst_colors ?? [],\n };\n return { ...message, reactions: [...reactions, reaction] };\n }\n\n return {\n ...message,\n reactions: reactions.map((reaction) =>\n reaction === existing\n ? {\n ...reaction,\n count: reaction.count + 1,\n count_details: {\n ...reaction.count_details,\n [kind]: reaction.count_details[kind] + 1,\n },\n me: reaction.me || (me && !data.burst),\n me_burst: reaction.me_burst || (me && data.burst),\n burst_colors: data.burst\n ? (data.burst_colors ?? reaction.burst_colors)\n : reaction.burst_colors,\n }\n : reaction,\n ),\n };\n}\n\n/**\n * Removes a `MESSAGE_REACTION_REMOVE` from a cached message's reactions, dropping the reaction when its count reaches\n * zero.\n *\n * @param message The cached message.\n * @param data The dispatch data.\n * @param clientUserId The bot's user ID, to clear `me` when the bot's reaction was removed.\n */\nexport function removeReaction(\n message: APIMessage,\n data: GatewayMessageReactionRemoveDispatchData,\n clientUserId?: string,\n): APIMessage {\n const me = data.user_id === clientUserId;\n const kind = data.burst ? \"burst\" : \"normal\";\n const reactions = (message.reactions ?? []).flatMap((reaction) => {\n if (!isSameEmoji(reaction.emoji, data.emoji)) return [reaction];\n if (reaction.count <= 1) return [];\n return [\n {\n ...reaction,\n count: reaction.count - 1,\n count_details: {\n ...reaction.count_details,\n [kind]: Math.max(0, reaction.count_details[kind] - 1),\n },\n me: reaction.me && !(me && !data.burst),\n me_burst: reaction.me_burst && !(me && data.burst),\n },\n ];\n });\n return { ...message, reactions };\n}\n\n/**\n * Removes every reaction with one emoji from a cached message.\n *\n * @param message The cached message.\n * @param emoji The emoji.\n */\nexport function removeReactionEmoji(message: APIMessage, emoji: APIPartialEmoji): APIMessage {\n return {\n ...message,\n reactions: (message.reactions ?? []).filter((reaction) => !isSameEmoji(reaction.emoji, emoji)),\n };\n}\n\n/**\n * Counts a `MESSAGE_POLL_VOTE_ADD` or `MESSAGE_POLL_VOTE_REMOVE` in a cached message's poll results.\n *\n * @param message The cached message.\n * @param data The dispatch data.\n * @param delta `1` for a vote, `-1` for a removed vote.\n * @param clientUserId The bot's user ID, to set `me_voted` when the bot voted.\n */\nexport function countPollVote(\n message: APIMessage,\n data: GatewayMessagePollVoteDispatchData,\n delta: 1 | -1,\n clientUserId?: string,\n): APIMessage {\n const { poll } = message;\n if (!poll) return message;\n\n const me = data.user_id === clientUserId;\n const results = poll.results ?? { is_finalized: false, answer_counts: [] };\n const counts = results.answer_counts.some((count) => count.id === data.answer_id)\n ? results.answer_counts\n : [...results.answer_counts, { id: data.answer_id, count: 0, me_voted: false }];\n\n return {\n ...message,\n poll: {\n ...poll,\n results: {\n ...results,\n answer_counts: counts.map((count) =>\n count.id === data.answer_id\n ? {\n ...count,\n count: Math.max(0, count.count + delta),\n me_voted: me ? delta === 1 : count.me_voted,\n }\n : count,\n ),\n },\n },\n };\n}\n","import { GatewayDispatchEvents } from \"discord-api-types/v10\";\nimport type {\n APIEmoji,\n APIMessage,\n APIGuildMember,\n APIRole,\n APISoundboardSound,\n APISticker,\n GatewayDispatchPayload,\n GatewayGuildCreateDispatchData,\n GatewayGuildMembersChunkDispatchData,\n GatewayGuildSoundboardSoundsUpdateDispatchData,\n GatewaySoundboardSoundsDispatchData,\n GatewayThreadListSync,\n GatewayThreadMembersUpdateDispatchData,\n Snowflake,\n} from \"discord-api-types/v10\";\nimport {\n applicationCommandPermissionsKey,\n autoModerationRuleKey,\n banKey,\n emojiKey,\n guildScopedKey,\n integrationKey,\n inviteKey,\n memberKey,\n messageKey,\n presenceKey,\n roleKey,\n scheduledEventKey,\n soundboardSoundKey,\n stageInstanceKey,\n stickerKey,\n threadMemberKey,\n voiceStateKey,\n} from \"./keys.js\";\nimport { addReaction, countPollVote, removeReaction, removeReactionEmoji } from \"./reactions.js\";\nimport type { Cache, CacheEntityName, EntityCache } from \"./types.js\";\n\n// A record rather than an array so the compiler enforces that every entity cache is listed.\nconst cacheEntityNameRecord: Record<CacheEntityName, true> = {\n applicationCommandPermissions: true,\n auditLogEntries: true,\n autoModerationRules: true,\n bans: true,\n channels: true,\n emojis: true,\n entitlements: true,\n guilds: true,\n integrations: true,\n invites: true,\n members: true,\n messages: true,\n presences: true,\n roles: true,\n scheduledEvents: true,\n soundboardSounds: true,\n stageInstances: true,\n stickers: true,\n subscriptions: true,\n threadMembers: true,\n threads: true,\n users: true,\n voiceStates: true,\n};\n\n/**\n * The name of every entity cache held by a {@link Cache}.\n */\nexport const CacheEntityNames = Object.keys(cacheEntityNameRecord) as readonly CacheEntityName[];\n\n/**\n * 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 * Always `true`: every method returns synchronously.\n */\n public readonly synchronous = true;\n\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 /**\n * Always `false`: every method returns a promise, compressed or not.\n */\n public readonly synchronous = false;\n\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":";;;;;;;;AAKA,SAAgB,eAAe,SAAoB,IAAuB;CACxE,OAAO,GAAG,QAAQ,GAAG;AACvB;;;;AAKA,SAAgB,WAAW,WAAsB,WAA8B;CAC7E,OAAO,GAAG,UAAU,GAAG;AACzB;;;;AAKA,SAAgB,UAAU,SAAoB,QAA2B;CACvE,OAAO,eAAe,SAAS,MAAM;AACvC;;;;AAKA,SAAgB,YAAY,SAAoB,QAA2B;CACzE,OAAO,eAAe,SAAS,MAAM;AACvC;;;;AAKA,SAAgB,cAAc,SAAoB,QAA2B;CAC3E,OAAO,eAAe,SAAS,MAAM;AACvC;;;;AAKA,SAAgB,QAAQ,SAAoB,QAA2B;CACrE,OAAO,eAAe,SAAS,MAAM;AACvC;;;;AAKA,SAAgB,SAAS,SAAoB,SAA4B;CACvE,OAAO,eAAe,SAAS,OAAO;AACxC;;;;AAKA,SAAgB,WAAW,SAAoB,WAA8B;CAC3E,OAAO,eAAe,SAAS,SAAS;AAC1C;;;;AAKA,SAAgB,kBAAkB,SAAoB,kBAAqC;CACzF,OAAO,eAAe,SAAS,gBAAgB;AACjD;;;;AAKA,SAAgB,iBAAiB,SAAoB,WAA8B;CACjF,OAAO,eAAe,SAAS,SAAS;AAC1C;;;;AAKA,SAAgB,mBAAmB,SAAoB,SAA4B;CACjF,OAAO,eAAe,SAAS,OAAO;AACxC;;;;AAKA,SAAgB,sBAAsB,SAAoB,QAA2B;CACnF,OAAO,eAAe,SAAS,MAAM;AACvC;;;;AAKA,SAAgB,OAAO,SAAoB,QAA2B;CACpE,OAAO,eAAe,SAAS,MAAM;AACvC;;;;AAKA,SAAgB,eAAe,SAAoB,eAAkC;CACnF,OAAO,eAAe,SAAS,aAAa;AAC9C;;;;AAKA,SAAgB,UAAU,SAAuC,MAAsB;CACrF,OAAO,GAAG,WAAW,UAAU,GAAG;AACpC;;;;AAKA,SAAgB,gBAAgB,UAAqB,QAA2B;CAC9E,OAAO,GAAG,SAAS,GAAG;AACxB;;;;AAKA,SAAgB,iCACd,eACA,SACA,WACQ;CACR,OAAO,GAAG,cAAc,GAAG,QAAQ,GAAG;AACxC;;;;;;;AC7GA,SAAS,YAAY,GAAoB,GAA6B;CACpE,OAAO,EAAE,KAAK,EAAE,OAAO,EAAE,KAAK,CAAC,EAAE,MAAM,EAAE,SAAS,EAAE;AACtD;;;;;;;;AASA,SAAgB,YACd,SACA,MACA,cACY;CACZ,MAAM,KAAK,KAAK,YAAY;CAC5B,MAAM,OAAO,KAAK,QAAQ,UAAU;CACpC,MAAM,YAAY,QAAQ,aAAa,CAAC;CACxC,MAAM,WAAW,UAAU,MAAM,aAAa,YAAY,SAAS,OAAO,KAAK,KAAK,CAAC;CAErF,IAAI,CAAC,UAAU;EACb,MAAM,WAAwB;GAC5B,OAAO,KAAK;GACZ,OAAO;GACP,eAAe;IAAE,QAAQ,KAAK,QAAQ,IAAI;IAAG,OAAO,KAAK,QAAQ,IAAI;GAAE;GACvE,IAAI,MAAM,CAAC,KAAK;GAChB,UAAU,MAAM,KAAK;GACrB,cAAc,KAAK,gBAAgB,CAAC;EACtC;EACA,OAAO;GAAE,GAAG;GAAS,WAAW,CAAC,GAAG,WAAW,QAAQ;EAAE;CAC3D;CAEA,OAAO;EACL,GAAG;EACH,WAAW,UAAU,KAAK,aACxB,aAAa,WACT;GACE,GAAG;GACH,OAAO,SAAS,QAAQ;GACxB,eAAe;IACb,GAAG,SAAS;KACX,OAAO,SAAS,cAAc,QAAQ;GACzC;GACA,IAAI,SAAS,MAAO,MAAM,CAAC,KAAK;GAChC,UAAU,SAAS,YAAa,MAAM,KAAK;GAC3C,cAAc,KAAK,QACd,KAAK,gBAAgB,SAAS,eAC/B,SAAS;EACf,IACA,QACN;CACF;AACF;;;;;;;;;AAUA,SAAgB,eACd,SACA,MACA,cACY;CACZ,MAAM,KAAK,KAAK,YAAY;CAC5B,MAAM,OAAO,KAAK,QAAQ,UAAU;CACpC,MAAM,aAAa,QAAQ,aAAa,CAAC,EAAC,CAAE,SAAS,aAAa;EAChE,IAAI,CAAC,YAAY,SAAS,OAAO,KAAK,KAAK,GAAG,OAAO,CAAC,QAAQ;EAC9D,IAAI,SAAS,SAAS,GAAG,OAAO,CAAC;EACjC,OAAO,CACL;GACE,GAAG;GACH,OAAO,SAAS,QAAQ;GACxB,eAAe;IACb,GAAG,SAAS;KACX,OAAO,KAAK,IAAI,GAAG,SAAS,cAAc,QAAQ,CAAC;GACtD;GACA,IAAI,SAAS,MAAM,EAAE,MAAM,CAAC,KAAK;GACjC,UAAU,SAAS,YAAY,EAAE,MAAM,KAAK;EAC9C,CACF;CACF,CAAC;CACD,OAAO;EAAE,GAAG;EAAS;CAAU;AACjC;;;;;;;AAQA,SAAgB,oBAAoB,SAAqB,OAAoC;CAC3F,OAAO;EACL,GAAG;EACH,YAAY,QAAQ,aAAa,CAAC,EAAC,CAAE,QAAQ,aAAa,CAAC,YAAY,SAAS,OAAO,KAAK,CAAC;CAC/F;AACF;;;;;;;;;AAUA,SAAgB,cACd,SACA,MACA,OACA,cACY;CACZ,MAAM,EAAE,SAAS;CACjB,IAAI,CAAC,MAAM,OAAO;CAElB,MAAM,KAAK,KAAK,YAAY;CAC5B,MAAM,UAAU,KAAK,WAAW;EAAE,cAAc;EAAO,eAAe,CAAC;CAAE;CACzE,MAAM,SAAS,QAAQ,cAAc,MAAM,UAAU,MAAM,OAAO,KAAK,SAAS,IAC5E,QAAQ,gBACR,CAAC,GAAG,QAAQ,eAAe;EAAE,IAAI,KAAK;EAAW,OAAO;EAAG,UAAU;CAAM,CAAC;CAEhF,OAAO;EACL,GAAG;EACH,MAAM;GACJ,GAAG;GACH,SAAS;IACP,GAAG;IACH,eAAe,OAAO,KAAK,UACzB,MAAM,OAAO,KAAK,YACd;KACE,GAAG;KACH,OAAO,KAAK,IAAI,GAAG,MAAM,QAAQ,KAAK;KACtC,UAAU,KAAK,UAAU,IAAI,MAAM;IACrC,IACA,KACN;GACF;EACF;CACF;AACF;;;;ACrHA,MAAM,wBAAuD;CAC3D,+BAA+B;CAC/B,iBAAiB;CACjB,qBAAqB;CACrB,MAAM;CACN,UAAU;CACV,QAAQ;CACR,cAAc;CACd,QAAQ;CACR,cAAc;CACd,SAAS;CACT,SAAS;CACT,UAAU;CACV,WAAW;CACX,OAAO;CACP,iBAAiB;CACjB,kBAAkB;CAClB,gBAAgB;CAChB,UAAU;CACV,eAAe;CACf,eAAe;CACf,SAAS;CACT,OAAO;CACP,aAAa;AACf;;;;AAKA,MAAa,mBAAmB,OAAO,KAAK,qBAAqB;;;;;;AAOjE,MAAa,6BAA6B;CACxC;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;AACF;;;;;;AAOA,MAAa,6BAA6B;CACxC;CACA;CACA;CACA;CACA;CACA;AACF;;;;;;;;;;;;AA2DA,SAAgB,sBACd,SACA,UAAiC,CAAC,GAChB;CAClB,MAAM,EAAE,iBAAiB;CACzB,MAAM,aAA+B,CAAC;CAEtC,QAAQ,QAAQ,GAAhB;EACE,KAAK,sBAAsB,qCAAqC;GAC9D,MAAM,OAAO,QAAQ;GACrB,WAAW,KAAK;IACd,MAAM;IACN,OAAO;IACP,KAAK,iCAAiC,KAAK,gBAAgB,KAAK,UAAU,KAAK,EAAE;IACjF,KAAK;GACP,CAAC;GACD;EACF;EAEA,KAAK,sBAAsB;EAC3B,KAAK,sBAAsB,0BAA0B;GACnD,MAAM,OAAO,QAAQ;GACrB,WAAW,KAAK;IACd,MAAM;IACN,OAAO;IACP,KAAK,sBAAsB,KAAK,UAAU,KAAK,EAAE;IACjD,KAAK;GACP,CAAC;GACD;EACF;EAEA,KAAK,sBAAsB,0BAA0B;GACnD,MAAM,OAAO,QAAQ;GACrB,WAAW,KAAK;IACd,MAAM;IACN,OAAO;IACP,KAAK,sBAAsB,KAAK,UAAU,KAAK,EAAE;GACnD,CAAC;GACD;EACF;EAEA,KAAK,sBAAsB;EAC3B,KAAK,sBAAsB,eAAe;GACxC,MAAM,OAAO,QAAQ;GACrB,WAAW,KAAK;IACd,MAAM;IACN,OAAO;IACP,KAAK,KAAK;IACV,KAAK;IACL,OAAO,QAAQ,MAAM,sBAAsB;GAC7C,CAAC;GACD;EACF;EAEA,KAAK,sBAAsB,eAAe;GACxC,MAAM,OAAO,QAAQ;GACrB,WAAW,KAAK;IAAE,MAAM;IAAU,OAAO;IAAY,KAAK,KAAK;GAAG,CAAC;GACnE,WAAW,KAAK;IAAE,MAAM;IAAgB,OAAO;IAAY,QAAQ,GAAG,KAAK,GAAG;GAAG,CAAC;GAClF;EACF;EAEA,KAAK,sBAAsB;EAC3B,KAAK,sBAAsB,mBAAmB;GAC5C,MAAM,OAAO,QAAQ;GACrB,WAAW,KAAK;IAAE,MAAM;IAAU,OAAO;IAAgB,KAAK,KAAK;IAAI,KAAK;GAAK,CAAC;GAClF;EACF;EAEA,KAAK,sBAAsB;GACzB,WAAW,KAAK;IAAE,MAAM;IAAU,OAAO;IAAgB,KAAK,QAAQ,EAAE;GAAG,CAAC;GAC5E;EAGF,KAAK,sBAAsB,0BAA0B;GACnD,MAAM,OAAO,QAAQ;GACrB,WAAW,KAAK;IACd,MAAM;IACN,OAAO;IACP,KAAK,eAAe,KAAK,UAAU,KAAK,EAAE;IAC1C,KAAK;GACP,CAAC;GACD;EACF;EAEA,KAAK,sBAAsB,aAAa;GACtC,MAAM,OAAO,QAAQ;GACrB,WAAW,KAAK;IACd,MAAM;IACN,OAAO;IACP,KAAK,OAAO,KAAK,UAAU,KAAK,KAAK,EAAE;IACvC,KAAK;GACP,CAAC;GACD,WAAW,KAAK;IAAE,MAAM;IAAU,OAAO;IAAS,KAAK,KAAK,KAAK;IAAI,KAAK,KAAK;GAAK,CAAC;GACrF;EACF;EAEA,KAAK,sBAAsB,gBAAgB;GACzC,MAAM,OAAO,QAAQ;GACrB,WAAW,KAAK;IAAE,MAAM;IAAU,OAAO;IAAQ,KAAK,OAAO,KAAK,UAAU,KAAK,KAAK,EAAE;GAAE,CAAC;GAC3F,WAAW,KAAK;IAAE,MAAM;IAAU,OAAO;IAAS,KAAK,KAAK,KAAK;IAAI,KAAK,KAAK;GAAK,CAAC;GACrF;EACF;EAEA,KAAK,sBAAsB;GACzB,mBAAmB,YAAY,QAAQ,CAAC;GACxC;EAGF,KAAK,sBAAsB,aAAa;GAEtC,MAAM,EAAE,OAAO,QAAQ,UAAU,GAAG,SAAS,QAAQ;GACrD,WAAW,KAAK;IAAE,MAAM;IAAU,OAAO;IAAU,KAAK,KAAK;IAAI,KAAK;IAAM,OAAO;GAAK,CAAC;GACzF,KAAK,MAAM,QAAQ,SAAS,CAAC,GAAG,YAAY,YAAY,KAAK,IAAI,IAAI;GACrE,KAAK,MAAM,SAAS,UAAU,CAAC,GAAG,aAAa,YAAY,KAAK,IAAI,KAAK;GACzE,KAAK,MAAM,WAAW,YAAY,CAAC,GAAG,eAAe,YAAY,KAAK,IAAI,OAAO;GACjF;EACF;EAEA,KAAK,sBAAsB,aAAa;GACtC,MAAM,OAAO,QAAQ;GAErB,IAAI,KAAK,aAAa;IACpB,WAAW,KAAK;KAAE,MAAM;KAAU,OAAO;KAAU,KAAK,KAAK;KAAI,KAAK;KAAM,OAAO;IAAK,CAAC;IACzF;GACF;GAEA,WAAW,KAAK;IAAE,MAAM;IAAU,OAAO;IAAU,KAAK,KAAK;GAAG,CAAC;GACjE,2BAA2B,YAAY,KAAK,EAAE;GAC9C;EACF;EAEA,KAAK,sBAAsB,mBAAmB;GAC5C,MAAM,OAAO,QAAQ;GACrB,WAAW,KAAK;IACd,MAAM;IACN,OAAO;IACP,QAAQ,GAAG,KAAK,SAAS;IACzB,SAAS,KAAK;GAChB,CAAC;GACD,KAAK,MAAM,SAAS,KAAK,QAAQ;IAC/B,IAAI,CAAC,MAAM,IAAI;IACf,WAAW,KAAK;KACd,MAAM;KACN,OAAO;KACP,KAAK,SAAS,KAAK,UAAU,MAAM,EAAE;KACrC,KAAK,YAAY,OAAO,KAAK,QAAQ;IACvC,CAAC;GACH;GACA;EACF;EAEA,KAAK,sBAAsB,qBAAqB;GAC9C,MAAM,OAAO,QAAQ;GACrB,WAAW,KAAK;IACd,MAAM;IACN,OAAO;IACP,QAAQ,GAAG,KAAK,SAAS;IACzB,SAAS,KAAK;GAChB,CAAC;GACD,KAAK,MAAM,WAAW,KAAK,UACzB,WAAW,KAAK;IACd,MAAM;IACN,OAAO;IACP,KAAK,WAAW,KAAK,UAAU,QAAQ,EAAE;IACzC,KAAK,YAAY,SAAS,KAAK,QAAQ;GACzC,CAAC;GAEH;EACF;EAEA,KAAK,sBAAsB;EAC3B,KAAK,sBAAsB,mBAAmB;GAC5C,MAAM,OAAO,QAAQ;GACrB,WAAW,KAAK;IACd,MAAM;IACN,OAAO;IACP,KAAK,UAAU,KAAK,UAAU,KAAK,KAAK,EAAE;IAC1C,KAAK;IACL,OAAO;GACT,CAAC;GACD,WAAW,KAAK;IAAE,MAAM;IAAU,OAAO;IAAS,KAAK,KAAK,KAAK;IAAI,KAAK,KAAK;GAAK,CAAC;GACrF;EACF;EAEA,KAAK,sBAAsB,mBAAmB;GAC5C,MAAM,OAAO,QAAQ;GACrB,WAAW,KAAK;IACd,MAAM;IACN,OAAO;IACP,KAAK,UAAU,KAAK,UAAU,KAAK,KAAK,EAAE;GAC5C,CAAC;GACD,WAAW,KAAK;IAAE,MAAM;IAAU,OAAO;IAAS,KAAK,KAAK,KAAK;IAAI,KAAK,KAAK;GAAK,CAAC;GACrF;EACF;EAEA,KAAK,sBAAsB;GACzB,yBAAyB,YAAY,QAAQ,CAAC;GAC9C;EAGF,KAAK,sBAAsB;EAC3B,KAAK,sBAAsB,iBAAiB;GAC1C,MAAM,OAAO,QAAQ;GACrB,WAAW,KAAK;IACd,MAAM;IACN,OAAO;IACP,KAAK,QAAQ,KAAK,UAAU,KAAK,KAAK,EAAE;IACxC,KAAK,YAAY,KAAK,MAAM,KAAK,QAAQ;IACzC,OAAO,QAAQ,MAAM,sBAAsB;GAC7C,CAAC;GACD;EACF;EAEA,KAAK,sBAAsB,iBAAiB;GAC1C,MAAM,OAAO,QAAQ;GACrB,WAAW,KAAK;IACd,MAAM;IACN,OAAO;IACP,KAAK,QAAQ,KAAK,UAAU,KAAK,OAAO;GAC1C,CAAC;GACD;EACF;EAEA,KAAK,sBAAsB;EAC3B,KAAK,sBAAsB,2BAA2B;GACpD,MAAM,OAAO,QAAQ;GACrB,WAAW,KAAK;IACd,MAAM;IACN,OAAO;IACP,KAAK,kBAAkB,KAAK,UAAU,KAAK,EAAE;IAC7C,KAAK;IACL,OAAO;GACT,CAAC;GACD,IAAI,KAAK,SACP,WAAW,KAAK;IACd,MAAM;IACN,OAAO;IACP,KAAK,KAAK,QAAQ;IAClB,KAAK,KAAK;GACZ,CAAC;GAEH;EACF;EAEA,KAAK,sBAAsB,2BAA2B;GACpD,MAAM,OAAO,QAAQ;GACrB,WAAW,KAAK;IACd,MAAM;IACN,OAAO;IACP,KAAK,kBAAkB,KAAK,UAAU,KAAK,EAAE;GAC/C,CAAC;GACD;EACF;EAEA,KAAK,sBAAsB;EAC3B,KAAK,sBAAsB,4BAA4B;GACrD,MAAM,OAAO,QAAQ;GACrB,IAAI,CAAC,KAAK,UAAU;GACpB,WAAW,KAAK;IACd,MAAM;IACN,OAAO;IACP,KAAK,mBAAmB,KAAK,UAAU,KAAK,QAAQ;IACpD,KAAK;IACL,OAAO,QAAQ,MAAM,sBAAsB;GAC7C,CAAC;GACD;EACF;EAEA,KAAK,sBAAsB,4BAA4B;GACrD,MAAM,OAAO,QAAQ;GACrB,WAAW,KAAK;IACd,MAAM;IACN,OAAO;IACP,KAAK,mBAAmB,KAAK,UAAU,KAAK,QAAQ;GACtD,CAAC;GACD;EACF;EAEA,KAAK,sBAAsB;GACzB,mCAAmC,YAAY,QAAQ,CAAC;GACxD;EAGF,KAAK,sBAAsB;GACzB,wBAAwB,YAAY,QAAQ,CAAC;GAC7C;EAGF,KAAK,sBAAsB;EAC3B,KAAK,sBAAsB,mBAAmB;GAC5C,MAAM,OAAO,QAAQ;GACrB,WAAW,KAAK;IACd,MAAM;IACN,OAAO;IACP,KAAK,eAAe,KAAK,UAAU,KAAK,EAAE;IAC1C,KAAK;IACL,OAAO;GACT,CAAC;GACD;EACF;EAEA,KAAK,sBAAsB,mBAAmB;GAC5C,MAAM,OAAO,QAAQ;GACrB,WAAW,KAAK;IACd,MAAM;IACN,OAAO;IACP,KAAK,eAAe,KAAK,UAAU,KAAK,EAAE;GAC5C,CAAC;GACD;EACF;EAEA,KAAK,sBAAsB,cAAc;GACvC,MAAM,OAAO,QAAQ;GACrB,WAAW,KAAK;IACd,MAAM;IACN,OAAO;IACP,KAAK,UAAU,KAAK,UAAU,KAAK,IAAI;IACvC,KAAK;GACP,CAAC;GACD;EACF;EAEA,KAAK,sBAAsB,cAAc;GACvC,MAAM,OAAO,QAAQ;GACrB,WAAW,KAAK;IACd,MAAM;IACN,OAAO;IACP,KAAK,UAAU,KAAK,UAAU,KAAK,IAAI;GACzC,CAAC;GACD;EACF;EAEA,KAAK,sBAAsB;EAC3B,KAAK,sBAAsB,eAAe;GACxC,MAAM,OAAO,QAAQ;GACrB,WAAW,KAAK;IACd,MAAM;IACN,OAAO;IACP,KAAK,WAAW,KAAK,YAAY,KAAK,EAAE;IACxC,KAAK;IACL,OAAO,QAAQ,MAAM,sBAAsB;GAC7C,CAAC;GAED,MAAM,SAAS,YAAY,OAAO,KAAK,SAAS;GAChD,IAAI,QAAQ;IACV,WAAW,KAAK;KAAE,MAAM;KAAU,OAAO;KAAS,KAAK,OAAO;KAAI,KAAK;IAAO,CAAC;IAC/E,IAAI,KAAK,UAAU,KAAK,UACtB,WAAW,KAAK;KACd,MAAM;KACN,OAAO;KACP,KAAK,UAAU,KAAK,UAAU,OAAO,EAAE;KACvC,KAAK,YAAY,KAAK,QAAQ,KAAK,QAAQ;KAC3C,OAAO;IACT,CAAC;GAEL;GACA;EACF;EAEA,KAAK,sBAAsB,eAAe;GACxC,MAAM,OAAO,QAAQ;GACrB,WAAW,KAAK;IACd,MAAM;IACN,OAAO;IACP,KAAK,WAAW,KAAK,YAAY,KAAK,EAAE;GAC1C,CAAC;GACD;EACF;EAEA,KAAK,sBAAsB,mBAAmB;GAC5C,MAAM,OAAO,QAAQ;GACrB,KAAK,MAAM,MAAM,KAAK,KACpB,WAAW,KAAK;IACd,MAAM;IACN,OAAO;IACP,KAAK,WAAW,KAAK,YAAY,EAAE;GACrC,CAAC;GAEH;EACF;EAEA,KAAK,sBAAsB,oBAAoB;GAC7C,MAAM,OAAO,QAAQ;GACrB,WAAW,KACT,cAAc,KAAK,YAAY,KAAK,aAAa,YAC/C,YAAY,SAAS,MAAM,YAAY,CACzC,CACF;GAEA,MAAM,OAAO,KAAK,QAAQ;GAC1B,IAAI,QAAQ,KAAK,UAAU;IACzB,WAAW,KAAK;KAAE,MAAM;KAAU,OAAO;KAAS,KAAK,KAAK;KAAI,KAAK;IAAK,CAAC;IAC3E,WAAW,KAAK;KACd,MAAM;KACN,OAAO;KACP,KAAK,UAAU,KAAK,UAAU,KAAK,EAAE;KACrC,KAAK,YAAY,KAAK,QAAS,KAAK,QAAQ;KAC5C,OAAO;IACT,CAAC;GACH;GACA;EACF;EAEA,KAAK,sBAAsB,uBAAuB;GAChD,MAAM,OAAO,QAAQ;GACrB,WAAW,KACT,cAAc,KAAK,YAAY,KAAK,aAAa,YAC/C,eAAe,SAAS,MAAM,YAAY,CAC5C,CACF;GACA;EACF;EAEA,KAAK,sBAAsB,0BAA0B;GACnD,MAAM,OAAO,QAAQ;GACrB,WAAW,KACT,cAAc,KAAK,YAAY,KAAK,aAAa,aAAa;IAC5D,GAAG;IACH,WAAW,CAAC;GACd,EAAE,CACJ;GACA;EACF;EAEA,KAAK,sBAAsB,4BAA4B;GACrD,MAAM,OAAO,QAAQ;GACrB,WAAW,KACT,cAAc,KAAK,YAAY,KAAK,aAAa,YAC/C,oBAAoB,SAAS,KAAK,KAAK,CACzC,CACF;GACA;EACF;EAEA,KAAK,sBAAsB;EAC3B,KAAK,sBAAsB,uBAAuB;GAChD,MAAM,OAAO,QAAQ;GACrB,MAAM,QAAQ,QAAQ,MAAM,sBAAsB,qBAAqB,IAAI;GAC3E,WAAW,KACT,cAAc,KAAK,YAAY,KAAK,aAAa,YAC/C,cAAc,SAAS,MAAM,OAAO,YAAY,CAClD,CACF;GACA;EACF;EAEA,KAAK,sBAAsB,gBAAgB;GACzC,MAAM,OAAO,QAAQ;GACrB,WAAW,KAAK;IACd,MAAM;IACN,OAAO;IACP,KAAK,YAAY,KAAK,UAAU,KAAK,KAAK,EAAE;IAC5C,KAAK;IACL,OAAO;GACT,CAAC;GACD,WAAW,KAAK;IACd,MAAM;IACN,OAAO;IACP,KAAK,KAAK,KAAK;IACf,KAAK,KAAK;IACV,OAAO;GACT,CAAC;GACD;EACF;EAEA,KAAK,sBAAsB,OAAO;GAChC,MAAM,OAAO,QAAQ;GACrB,WAAW,KAAK;IAAE,MAAM;IAAU,OAAO;IAAS,KAAK,KAAK,KAAK;IAAI,KAAK,KAAK;GAAK,CAAC;GACrF,KAAK,MAAM,SAAS,KAAK,QACvB,WAAW,KAAK;IACd,MAAM;IACN,OAAO;IACP,KAAK,MAAM;IACX,KAAK;IACL,OAAO;GACT,CAAC;GAEH;EACF;EAEA,KAAK,sBAAsB;EAC3B,KAAK,sBAAsB,qBAAqB;GAC9C,MAAM,OAAO,QAAQ;GACrB,WAAW,KAAK;IACd,MAAM;IACN,OAAO;IACP,KAAK,iBAAiB,KAAK,UAAU,KAAK,UAAU;IACpD,KAAK;IACL,OAAO;GACT,CAAC;GACD;EACF;EAEA,KAAK,sBAAsB,qBAAqB;GAC9C,MAAM,OAAO,QAAQ;GACrB,WAAW,KAAK;IACd,MAAM;IACN,OAAO;IACP,KAAK,iBAAiB,KAAK,UAAU,KAAK,UAAU;GACtD,CAAC;GACD;EACF;EAEA,KAAK,sBAAsB;EAC3B,KAAK,sBAAsB,oBAAoB;GAC7C,MAAM,OAAO,QAAQ;GACrB,WAAW,KAAK;IAAE,MAAM;IAAU,OAAO;IAAiB,KAAK,KAAK;IAAI,KAAK;GAAK,CAAC;GACnF;EACF;EAEA,KAAK,sBAAsB;GACzB,WAAW,KAAK;IAAE,MAAM;IAAU,OAAO;IAAiB,KAAK,QAAQ,EAAE;GAAG,CAAC;GAC7E;EAGF,KAAK,sBAAsB;EAC3B,KAAK,sBAAsB,cAAc;GACvC,MAAM,OAAO,QAAQ;GACrB,WAAW,KAAK;IACd,MAAM;IACN,OAAO;IACP,KAAK,KAAK;IACV,KAAK;IACL,OAAO,QAAQ,MAAM,sBAAsB;GAC7C,CAAC;GAED,MAAM,SAAS,YAAY,OAAO,KAAK,SAAS;GAChD,IAAI,QAAQ,SACV,WAAW,KAAK;IACd,MAAM;IACN,OAAO;IACP,KAAK,gBAAgB,KAAK,IAAI,OAAO,OAAO;IAE5C,KAAK,KAAK,WAAW,YAAY,QAAQ,KAAK,QAAQ,IAAI;IAC1D,OAAO;GACT,CAAC;GAEH;EACF;EAEA,KAAK,sBAAsB,cAAc;GACvC,MAAM,OAAO,QAAQ;GACrB,WAAW,KAAK;IAAE,MAAM;IAAU,OAAO;IAAW,KAAK,KAAK;GAAG,CAAC;GAClE,WAAW,KAAK;IAAE,MAAM;IAAgB,OAAO;IAAiB,QAAQ,GAAG,KAAK,GAAG;GAAG,CAAC;GACvF,WAAW,KAAK;IAAE,MAAM;IAAgB,OAAO;IAAY,QAAQ,GAAG,KAAK,GAAG;GAAG,CAAC;GAClF;EACF;EAEA,KAAK,sBAAsB;GACzB,sBAAsB,YAAY,QAAQ,CAAC;GAC3C;EAGF,KAAK,sBAAsB,oBAAoB;GAC7C,MAAM,OAAO,QAAQ;GACrB,IAAI,CAAC,KAAK,MAAM,CAAC,KAAK,SAAS;GAC/B,WAAW,KAAK;IACd,MAAM;IACN,OAAO;IACP,KAAK,gBAAgB,KAAK,IAAI,KAAK,OAAO;IAC1C,KAAK;IACL,OAAO;GACT,CAAC;GACD;EACF;EAEA,KAAK,sBAAsB;GACzB,2BAA2B,YAAY,QAAQ,CAAC;GAChD;EAGF,KAAK,sBAAsB,YAAY;GACrC,MAAM,OAAO,QAAQ;GACrB,WAAW,KAAK;IAAE,MAAM;IAAU,OAAO;IAAS,KAAK,KAAK;IAAI,KAAK;IAAM,OAAO;GAAK,CAAC;GACxF;EACF;EAEA,KAAK,sBAAsB,kBAAkB;GAC3C,MAAM,OAAO,QAAQ;GACrB,IAAI,CAAC,KAAK,UAAU;GAEpB,MAAM,MAAM,cAAc,KAAK,UAAU,KAAK,OAAO;GACrD,IAAI,KAAK,YACP,WAAW,KAAK;IAAE,MAAM;IAAU,OAAO;IAAe;IAAK,KAAK;IAAM,OAAO;GAAK,CAAC;QAErF,WAAW,KAAK;IAAE,MAAM;IAAU,OAAO;IAAe;GAAI,CAAC;GAG/D,MAAM,OAAO,KAAK,QAAQ;GAC1B,IAAI,KAAK,UAAU,MAAM;IACvB,WAAW,KAAK;KAAE,MAAM;KAAU,OAAO;KAAS,KAAK,KAAK;KAAI,KAAK;IAAK,CAAC;IAC3E,WAAW,KAAK;KACd,MAAM;KACN,OAAO;KACP,KAAK,UAAU,KAAK,UAAU,KAAK,EAAE;KACrC,KAAK,YAAY,KAAK,QAAQ,KAAK,QAAQ;KAC3C,OAAO;IACT,CAAC;GACH;GACA;EACF;CAIF;CAEA,OAAO;AACT;;;;;;;AAQA,eAAsB,qBACpB,OACA,YACe;CACf,KAAK,MAAM,aAAa,YAAY;EAClC,MAAM,QAAQ,MAAM,UAAU;EAE9B,QAAQ,UAAU,MAAlB;GACE,KAAK,UAAU;IACb,MAAM,QAAQ,UAAU,QACpB,YAAY,MAAM,MAAM,IAAI,UAAU,GAAG,GAAG,UAAU,GAAG,IACzD,UAAU;IACd,MAAM,MAAM,IAAI,UAAU,KAAK,KAAK;IACpC;GACF;GACA,KAAK,UAAU;IACb,MAAM,WAAW,MAAM,MAAM,IAAI,UAAU,GAAG;IAC9C,IAAI,aAAa,QAAW,MAAM,MAAM,IAAI,UAAU,KAAK,UAAU,OAAO,QAAQ,CAAC;IACrF;GACF;GACA,KAAK;IACH,MAAM,MAAM,OAAO,UAAU,GAAG;IAChC;GACF,KAAK;IACH,IAAI,MAAM,wBAAwB,OAAO,UAAU,OAAO,GAAG;IAC7D,KAAK,MAAM,OAAO,MAAM,MAAM,KAAK,GACjC,IAAI,IAAI,WAAW,UAAU,MAAM,GAAG,MAAM,MAAM,OAAO,GAAG;IAE9D;GACF,KAAK;IACH,IAAI,MAAM,wBAAwB,OAAO,UAAU,OAAO,GAAG;IAC7D,KAAK,MAAM,CAAC,KAAK,UAAU,MAAM,MAAM,QAAQ,GAC7C,IAAI,UAAU,UAAU,KAAK,GAAG,MAAM,MAAM,OAAO,GAAG;EAG5D;CACF;AACF;;;;;;AAOA,eAAe,wBACb,OACA,SACkB;CAClB,IAAI,YAAY,UAAa,MAAM,gBAAgB,QAAW,OAAO;CACrE,OAAQ,MAAM,MAAM,YAAY,OAAO,MAAO;AAChD;;;;;;;;AASA,SAAgB,qBACd,OACA,SACA,SACe;CACf,OAAO,qBAAqB,OAAO,sBAAsB,SAAS,OAAO,CAAC;AAC5E;;;;AAKA,SAAgB,YAAmB,UAA6B,OAAqB;CACnF,IAAI,SAAS,QAAQ,KAAK,SAAS,KAAK,GAAG,OAAO;EAAE,GAAG;EAAU,GAAG;CAAM;CAC1E,OAAO;AACT;AAEA,SAAS,mBACP,YACA,OACM;CAEN,IAAI,iBAAiB,SAAS,MAAM,aAAa;EAC/C,WAAW,KAAK;GAAE,MAAM;GAAU,OAAO;GAAU,KAAK,MAAM;GAAI,KAAK;GAAO,OAAO;EAAK,CAAC;EAC3F;CACF;CAEA,MAAM,EACJ,UACA,SACA,SACA,WACA,cAAc,aACd,iBAAiB,gBACjB,wBAAwB,iBACxB,mBAAmB,kBACnB,OACA,QACA,UACA,GAAG,SACD;CAEJ,WAAW,KAAK;EAAE,MAAM;EAAU,OAAO;EAAU,KAAK,MAAM;EAAI,KAAK;CAAK,CAAC;CAE7E,KAAK,MAAM,WAAW,YAAY,CAAC,GACjC,WAAW,KAAK;EACd,MAAM;EACN,OAAO;EACP,KAAK,QAAQ;EACb,KAAK,YAAY,SAAS,MAAM,EAAE;CACpC,CAAC;CAGH,KAAK,MAAM,UAAU,WAAW,CAAC,GAAG;EAClC,WAAW,KAAK;GACd,MAAM;GACN,OAAO;GACP,KAAK,OAAO;GACZ,KAAK,YAAY,QAAQ,MAAM,EAAE;EACnC,CAAC;EACD,IAAI,OAAO,QAAQ,SACjB,WAAW,KAAK;GACd,MAAM;GACN,OAAO;GACP,KAAK,gBAAgB,OAAO,IAAI,OAAO,OAAO,OAAO;GACrD,KAAK,YAAY,OAAO,QAAQ,MAAM,EAAE;EAC1C,CAAC;CAEL;CAEA,KAAK,MAAM,UAAU,WAAW,CAAC,GAAG,cAAc,YAAY,MAAM,IAAI,MAAM;CAE9E,KAAK,MAAM,YAAY,aAAa,CAAC,GACnC,WAAW,KAAK;EACd,MAAM;EACN,OAAO;EACP,KAAK,YAAY,MAAM,IAAI,SAAS,KAAK,EAAE;EAC3C,KAAK,YAAY,UAAU,MAAM,EAAE;CACrC,CAAC;CAGH,KAAK,MAAM,QAAQ,SAAS,CAAC,GAAG,YAAY,YAAY,MAAM,IAAI,IAAI;CACtE,KAAK,MAAM,SAAS,UAAU,CAAC,GAAG,aAAa,YAAY,MAAM,IAAI,KAAK;CAC1E,KAAK,MAAM,WAAW,YAAY,CAAC,GAAG,eAAe,YAAY,MAAM,IAAI,OAAO;CAElF,KAAK,MAAM,cAAc,eAAe,CAAC,GAAG;EAC1C,IAAI,CAAC,WAAW,YAAY;EAC5B,WAAW,KAAK;GACd,MAAM;GACN,OAAO;GACP,KAAK,cAAc,MAAM,IAAI,WAAW,OAAO;GAC/C,KAAK,YAAY,YAAY,MAAM,EAAE;EACvC,CAAC;CACH;CAEA,KAAK,MAAM,iBAAiB,kBAAkB,CAAC,GAC7C,WAAW,KAAK;EACd,MAAM;EACN,OAAO;EACP,KAAK,iBAAiB,MAAM,IAAI,cAAc,UAAU;EACxD,KAAK;CACP,CAAC;CAGH,KAAK,MAAM,kBAAkB,mBAAmB,CAAC,GAC/C,WAAW,KAAK;EACd,MAAM;EACN,OAAO;EACP,KAAK,kBAAkB,MAAM,IAAI,eAAe,EAAE;EAClD,KAAK;CACP,CAAC;CAGH,KAAK,MAAM,SAAS,oBAAoB,CAAC,GAAG,uBAAuB,YAAY,MAAM,IAAI,KAAK;AAChG;AAEA,SAAS,yBACP,YACA,MACM;CACN,KAAK,MAAM,UAAU,KAAK,SAAS,cAAc,YAAY,KAAK,UAAU,MAAM;CAClF,KAAK,MAAM,YAAY,KAAK,aAAa,CAAC,GACxC,WAAW,KAAK;EACd,MAAM;EACN,OAAO;EACP,KAAK,YAAY,KAAK,UAAU,SAAS,KAAK,EAAE;EAChD,KAAK,YAAY,UAAU,KAAK,QAAQ;CAC1C,CAAC;AAEL;AAEA,SAAS,mCACP,YACA,MACM;CACN,WAAW,KAAK;EACd,MAAM;EACN,OAAO;EACP,QAAQ,GAAG,KAAK,SAAS;EACzB,SAAS,KAAK;CAChB,CAAC;CACD,KAAK,MAAM,SAAS,KAAK,mBACvB,uBAAuB,YAAY,KAAK,UAAU,KAAK;AAC3D;AAEA,SAAS,wBACP,YACA,MACM;CACN,KAAK,MAAM,SAAS,KAAK,mBACvB,uBAAuB,YAAY,KAAK,UAAU,KAAK;AAC3D;AAEA,SAAS,sBAAsB,YAA8B,MAAmC;CAC9F,KAAK,MAAM,UAAU,KAAK,SACxB,WAAW,KAAK;EACd,MAAM;EACN,OAAO;EACP,KAAK,OAAO;EACZ,KAAK,YAAY,QAAQ,KAAK,QAAQ;EACtC,OAAO;CACT,CAAC;CAGH,KAAK,MAAM,UAAU,KAAK,SAAS;EACjC,IAAI,CAAC,OAAO,MAAM,CAAC,OAAO,SAAS;EACnC,WAAW,KAAK;GACd,MAAM;GACN,OAAO;GACP,KAAK,gBAAgB,OAAO,IAAI,OAAO,OAAO;GAC9C,KAAK,YAAY,QAAQ,KAAK,QAAQ;GACtC,OAAO;EACT,CAAC;CACH;AACF;AAEA,SAAS,2BACP,YACA,MACM;CACN,KAAK,MAAM,UAAU,KAAK,iBAAiB,CAAC,GAAG;EAC7C,IAAI,CAAC,OAAO,SAAS;EACrB,WAAW,KAAK;GACd,MAAM;GACN,OAAO;GACP,KAAK,gBAAgB,KAAK,IAAI,OAAO,OAAO;GAC5C,KAAK,YAAY,QAAQ,KAAK,QAAQ;GACtC,OAAO;EACT,CAAC;CACH;CAEA,KAAK,MAAM,UAAU,KAAK,sBAAsB,CAAC,GAC/C,WAAW,KAAK;EACd,MAAM;EACN,OAAO;EACP,KAAK,gBAAgB,KAAK,IAAI,MAAM;CACtC,CAAC;AAEL;AAEA,SAAS,cACP,YACA,SACA,QACM;CACN,IAAI,CAAC,OAAO,MAAM;CAElB,WAAW,KAAK;EACd,MAAM;EACN,OAAO;EACP,KAAK,UAAU,SAAS,OAAO,KAAK,EAAE;EACtC,KAAK,YAAY,QAAQ,OAAO;EAChC,OAAO;CACT,CAAC;CACD,WAAW,KAAK;EAAE,MAAM;EAAU,OAAO;EAAS,KAAK,OAAO,KAAK;EAAI,KAAK,OAAO;CAAK,CAAC;AAC3F;AAEA,SAAS,YAAY,YAA8B,SAAoB,MAAqB;CAC1F,WAAW,KAAK;EACd,MAAM;EACN,OAAO;EACP,KAAK,QAAQ,SAAS,KAAK,EAAE;EAC7B,KAAK,YAAY,MAAM,OAAO;CAChC,CAAC;AACH;AAEA,SAAS,aAAa,YAA8B,SAAoB,OAAuB;CAC7F,IAAI,CAAC,MAAM,IAAI;CACf,WAAW,KAAK;EACd,MAAM;EACN,OAAO;EACP,KAAK,SAAS,SAAS,MAAM,EAAE;EAC/B,KAAK,YAAY,OAAO,OAAO;CACjC,CAAC;AACH;AAEA,SAAS,eACP,YACA,SACA,SACM;CACN,WAAW,KAAK;EACd,MAAM;EACN,OAAO;EACP,KAAK,WAAW,SAAS,QAAQ,EAAE;EACnC,KAAK,YAAY,SAAS,OAAO;CACnC,CAAC;AACH;AAEA,SAAS,uBACP,YACA,SACA,OACM;CACN,WAAW,KAAK;EACd,MAAM;EACN,OAAO;EACP,KAAK,mBAAmB,SAAS,MAAM,QAAQ;EAC/C,KAAK,YAAY,OAAO,OAAO;CACjC,CAAC;AACH;AAEA,SAAS,2BAA2B,YAA8B,SAA0B;CAG1F,KAAK,MAAM,SAAS,4BAClB,WAAW,KAAK;EAAE,MAAM;EAAgB;EAAO,QAAQ,GAAG,QAAQ;EAAI;CAAQ,CAAC;CAIjF,KAAK,MAAM,SAAS,4BAClB,WAAW,KAAK;EACd,MAAM;EACN;EACA,YAAY,UAAU,SAAS,KAAK,KAAK,MAAM,aAAa;EAC5D;CACF,CAAC;AAEL;AAGA,SAAS,cACP,WACA,WACA,QACgB;CAChB,OAAO;EACL,MAAM;EACN,OAAO;EACP,KAAK,WAAW,WAAW,SAAS;EACpC,SAAS,UAAU,OAAO,KAAmB;CAC/C;AACF;AAEA,SAAS,YACP,OACA,SACiC;CACjC,OAAO;EAAE,GAAG;EAAO,UAAU;CAAQ;AACvC;AAEA,SAAS,SAAS,OAAkD;CAClE,OAAO,OAAO,UAAU,YAAY,UAAU,QAAQ,CAAC,MAAM,QAAQ,KAAK;AAC5E;;;;;;;;AC1lCA,SAAgB,qBACd,SACA,OACA,UAA+B,CAAC,GACpB;CACZ,MAAM,YAAY,SAAiC,YAA0B;EAC3E,AAAK,qBAAqB,OAAO,SAAS,EAAE,cAAc,QAAQ,aAAa,CAAC,CAAC,CAAC,OAC/E,UAAU;GACT,IAAI,QAAQ,SAAS,QAAQ,QAAQ,OAAO,SAAS,OAAO;QACvD,QAAQ,MAAM,oCAAoC,KAAK;EAC9D,CACF;CACF;CAEA,QAAQ,GAAG,YAAY,QAAQ;CAC/B,aAAa,QAAQ,MAAM,YAAY,QAAQ;AACjD;;;;;;;ACnCA,IAAa,oBAAb,MAAgE;;;;CAI9D,AAAgB,cAAc;;;;CAK9B,AAAgB;CAEhB,AAAS,yBAAS,IAAI,IAAiB;CAEvC,AAAO,YAAY,UAAU,UAAU;EACrC,IAAI,YAAY,aAAa,CAAC,OAAO,UAAU,OAAO,KAAK,UAAU,IACnE,MAAM,IAAI,WACR,gEAAgE,SAClE;EAGF,KAAK,UAAU;CACjB;CAEA,AAAO,IAAI,KAA8B;EACvC,MAAM,QAAQ,KAAK,OAAO,IAAI,GAAG;EACjC,IAAI,UAAU,UAAa,KAAK,YAAY,UAAU;GAEpD,KAAK,OAAO,OAAO,GAAG;GACtB,KAAK,OAAO,IAAI,KAAK,KAAK;EAC5B;EAEA,OAAO;CACT;CAEA,AAAO,IAAI,KAAa,OAAkB;EACxC,IAAI,KAAK,YAAY,GAAG;EAExB,KAAK,OAAO,OAAO,GAAG;EACtB,KAAK,OAAO,IAAI,KAAK,KAAK;EAE1B,IAAI,KAAK,OAAO,OAAO,KAAK,SAE1B,KAAK,OAAO,OAAO,KAAK,OAAO,KAAK,CAAC,CAAC,KAAK,CAAC,CAAC,KAAM;CAEvD;CAEA,AAAO,IAAI,KAAsB;EAC/B,OAAO,KAAK,OAAO,IAAI,GAAG;CAC5B;CAEA,AAAO,OAAO,KAAsB;EAClC,OAAO,KAAK,OAAO,OAAO,GAAG;CAC/B;CAEA,AAAO,QAAc;EACnB,KAAK,OAAO,MAAM;CACpB;CAEA,AAAO,UAAkB;EACvB,OAAO,KAAK,OAAO;CACrB;CAEA,AAAO,OAAiB;EACtB,OAAO,CAAC,GAAG,KAAK,OAAO,KAAK,CAAC;CAC/B;CAEA,AAAO,SAAgB;EACrB,OAAO,CAAC,GAAG,KAAK,OAAO,OAAO,CAAC;CACjC;CAEA,AAAO,UAAuC;EAC5C,OAAO,CAAC,GAAG,KAAK,OAAO,QAAQ,CAAC;CAClC;AACF;;;;;;;;;;;;;;AAgCA,SAAgB,oBAAoB,UAAgC,CAAC,GAA0B;CAC7F,MAAM,EAAE,YAAY;CACpB,MAAM,WAAW,SACf,OAAO,YAAY,WAAW,UAAW,UAAU,SAAS;CAE9D,OAAO,OAAO,OACZ,OAAO,YACL,iBAAiB,KAAK,SAAS,CAAC,MAAM,IAAI,kBAAkB,QAAQ,IAAI,CAAC,CAAC,CAAC,CAC7E,CACF;AACF;;;;AC/GA,MAAM,YAAY,UAAU,IAAI;AAChC,MAAM,cAAc,UAAU,MAAM;AACpC,MAAM,sBAAsB,UAAU,cAAc;AACpD,MAAM,wBAAwB,UAAU,gBAAgB;;;;;;;;AA+CxD,IAAa,kBAAb,cAAqC,MAAM;;;;CAIzC,AAAgB;CAEhB,AAAO,YAAY,KAAa,OAAgB;EAC9C,MAAM,oCAAoC,IAAI,IAAI,EAAE,MAAM,CAAC;EAC3D,KAAK,OAAO;EACZ,KAAK,MAAM;CACb;AACF;AAwCA,MAAM,uBAAuB;AAI7B,MAAM,qBAAqB;CAAE,MAAM;CAAO,QAAQ;AAAM;;;;;;;;;;;AAYxD,IAAa,mBAAb,MAA+D;;;;CAI7D,AAAgB,cAAc;CAE9B,AAAgB;CAChB,AAAgB;CAChB,AAAgB;CAChB,AAAgB;CAEhB,AAAS;CACT,AAAS;CAET,AAAO,YAAY,OAAwB,SAAuC;EAChF,IAAI,QAAQ,QAAQ,UAAa,EAAE,QAAQ,MAAM,IAC/C,MAAM,IAAI,WAAW,sDAAsD,QAAQ,KAAK;EAG1F,KAAK,SAAS;EACd,KAAK,SAAS,QAAQ;EACtB,KAAK,MAAM,QAAQ;EACnB,KAAK,cAAc,QAAQ,eAAe;EAC1C,KAAK,uBAAuB,QAAQ,wBAAwB;EAC5D,KAAK,WAAW,QAAQ;CAC1B;CAEA,MAAa,IAAI,KAAuC;EACtD,MAAM,WAAW,KAAK,SAAS,GAAG;EAClC,MAAM,QAAQ,MAAM,KAAK,OAAO,IAAI,QAAQ;EAC5C,OAAO,UAAU,OAAO,SAAY,KAAK,YAAY,UAAU,KAAK;CACtE;CAEA,MAAa,IAAI,KAAa,OAA2B;EACvD,MAAM,aAAa,MAAM,KAAK,UAAU,KAAK;EAC7C,MAAM,UAAU,KAAK,WAAW,KAAK,KAAK;EAE1C,MAAM,cAAc,KAAK,OAAO,MAAM;EACtC,IAAI,KAAK,QAAQ,QAAW;GAC1B,YAAY,IAAI,KAAK,SAAS,GAAG,GAAG,UAAU,CAAC,CAAC,KAAK,KAAK,UAAU,QAAQ,GAAG;GAC/E,IAAI,YAAY,QACd,YACG,KAAK,KAAK,cAAc,OAAO,GAAG,QAAQ,GAAG,CAAC,CAC9C,KAAK,KAAK,WAAW,QAAQ,OAAO;EAE3C,OAAO;GACL,MAAM,MAAM,KAAK,IAAI;GACrB,MAAM,eAAe,KAAK,MAAM,KAAK,MAAM,GAAI;GAC/C,YACG,IAAI,KAAK,SAAS,GAAG,GAAG,YAAY,MAAM,YAAY,CAAC,CACvD,KAAK,KAAK,UAAU,MAAM,cAAc,GAAG,CAAC,CAE5C,iBAAiB,KAAK,UAAU,QAAQ,GAAG;GAC9C,IAAI,YAAY,QAAW;IACzB,MAAM,gBAAgB,KAAK,cAAc,OAAO;IAChD,YACG,KAAK,eAAe,MAAM,cAAc,GAAG,CAAC,CAC5C,iBAAiB,eAAe,QAAQ,GAAG,CAAC,CAC5C,KAAK,KAAK,WAAW,MAAM,cAAc,OAAO,CAAC,CACjD,iBAAiB,KAAK,WAAW,QAAQ,GAAG;IAE/C,YAAY,UAAU,eAAe,YAAY;IACjD,YAAY,UAAU,KAAK,WAAW,YAAY;GACpD;EACF;EAEA,MAAM,QAAQ,WAAW;CAC3B;CAEA,MAAa,IAAI,KAA+B;EAC9C,OAAQ,MAAM,KAAK,OAAO,OAAO,KAAK,SAAS,GAAG,CAAC,IAAK;CAC1D;CAEA,MAAa,OAAO,KAA+B;EACjD,MAAM,UAAU,MAAM,KAAK,UAAU,GAAG;EACxC,MAAM,cAAc,KAAK,OAAO,MAAM,CAAC,CAAC,IAAI,KAAK,SAAS,GAAG,CAAC,CAAC,CAAC,KAAK,KAAK,UAAU,GAAG;EACvF,IAAI,YAAY,QAAW,YAAY,KAAK,KAAK,cAAc,OAAO,GAAG,GAAG;EAE5E,MAAM,CAAC,WAAW,MAAM,QAAQ,WAAW;EAC3C,OAAQ,UAAqB;CAC/B;CAEA,MAAa,QAAuB;EAClC,MAAM,OAAO,MAAM,KAAK,OAAO,OAAO,KAAK,UAAU,KAAK,IAAI;EAC9D,IAAI,KAAK,SAAS,GAAG,MAAM,KAAK,OAAO,IAAI,GAAG,KAAK,KAAK,QAAQ,KAAK,SAAS,GAAG,CAAC,CAAC;EAEnF,MAAM,SAAS,KAAK,WAAW,MAAM,KAAK,OAAO,OAAO,KAAK,WAAW,KAAK,IAAI,IAAI,CAAC;EACtF,MAAM,KAAK,OAAO,IAChB,KAAK,UACL,KAAK,WACL,GAAG,OAAO,KAAK,YAAY,KAAK,cAAc,OAAO,CAAC,CACxD;CACF;;;;;;;;;;;CAYA,MAAa,YAAY,SAAyC;EAChE,IAAI,KAAK,aAAa,QAAW,OAAO;EAExC,MAAM,gBAAgB,KAAK,cAAc,OAAO;EAChD,MAAM,OAAO,MAAM,KAAK,OAAO,OAAO,eAAe,KAAK,IAAI;EAC9D,IAAI,UAAU;EACd,KAAK,IAAI,QAAQ,GAAG,QAAQ,KAAK,QAAQ,SAAS,sBAAsB;GACtE,MAAM,QAAQ,KAAK,MAAM,OAAO,QAAQ,oBAAoB;GAE5D,MAAM,CAAC,SAAS,MAAM,QACpB,KAAK,OACF,MAAM,CAAC,CACP,IAAI,GAAG,MAAM,KAAK,QAAQ,KAAK,SAAS,GAAG,CAAC,CAAC,CAAC,CAC9C,KAAK,KAAK,UAAU,GAAG,KAAK,CAAC,CAC7B,KAAK,eAAe,GAAG,KAAK,CACjC;GACA,WAAW;EACb;EAEA,MAAM,KAAK,OAAO,KAAK,KAAK,WAAW,OAAO;EAC9C,OAAO;CACT;CAEA,MAAa,UAA2B;EACtC,MAAM,KAAK,MAAM;EACjB,OAAO,KAAK,OAAO,MAAM,KAAK,QAAQ;CACxC;CAEA,MAAa,OAA0B;EACrC,MAAM,KAAK,MAAM;EACjB,OAAO,KAAK,OAAO,OAAO,KAAK,UAAU,KAAK,IAAI;CACpD;CAEA,MAAa,SAAyB;EACpC,QAAQ,MAAM,KAAK,QAAQ,EAAC,CAAE,KAAK,GAAG,WAAW,KAAK;CACxD;CAEA,MAAa,UAAgD;EAC3D,MAAM,OAAO,MAAM,KAAK,KAAK;EAC7B,IAAI,KAAK,WAAW,GAAG,OAAO,CAAC;EAE/B,MAAM,SAAS,MAAM,KAAK,OAAO,KAAK,GAAG,KAAK,KAAK,QAAQ,KAAK,SAAS,GAAG,CAAC,CAAC;EAC9E,MAAM,UAAuC,CAAC;EAC9C,KAAK,MAAM,CAAC,OAAO,UAAU,OAAO,QAAQ,GAE1C,IAAI,UAAU,MAAM;GAClB,MAAM,MAAM,KAAK;GACjB,QAAQ,KAAK,CAAC,KAAK,MAAM,KAAK,YAAY,KAAK,SAAS,GAAG,GAAG,KAAK,CAAC,CAAC;EACvE;EAGF,OAAO;CACT;;;;;CAMA,AAAO,SAAS,KAAqB;EACnC,OAAO,GAAG,KAAK,OAAO,GAAG;CAC3B;;;;CAKA,IAAW,WAAmB;EAC5B,OAAO,GAAG,KAAK,OAAO;CACxB;;;;;CAMA,AAAO,cAAc,SAAyB;EAC5C,OAAO,GAAG,KAAK,OAAO,UAAU;CAClC;;;;CAKA,IAAW,YAAoB;EAC7B,OAAO,GAAG,KAAK,OAAO;CACxB;;;;;CAMA,MAAc,UAAU,KAA0C;EAChE,IAAI,KAAK,aAAa,QAAW,OAAO;EAGxC,MAAM,UAAU,KAAK,SAAS,GAAG;EACjC,IAAI,YAAY,QAAW,OAAO;EAElC,IAAI;EACJ,IAAI;GACF,QAAQ,MAAM,KAAK,IAAI,GAAG;EAC5B,SAAS,OAAO;GAEd,IAAI,iBAAiB,iBAAiB,OAAO;GAC7C,MAAM;EACR;EAEA,OAAO,UAAU,SAAY,SAAY,KAAK,SAAS,KAAK,KAAK;CACnE;CAEA,MAAc,QAAuB;EACnC,IAAI,KAAK,QAAQ,QACf,MAAM,KAAK,OAAO,iBAAiB,KAAK,UAAU,QAAQ,KAAK,IAAI,CAAC;CACxE;CAEA,MAAc,UAAU,OAA6B;EACnD,MAAM,OAAO,KAAK,UAAU,KAAK;EACjC,IAAI,KAAK,gBAAgB,UAAU,OAAO,WAAW,IAAI,IAAI,KAAK,sBAChE,OAAO;EAGT,MAAM,aACJ,KAAK,gBAAgB,SAAS,MAAM,UAAU,IAAI,IAAI,MAAM,oBAAoB,IAAI;EACtF,OAAO,GAAG,mBAAmB,KAAK,eAAe,WAAW,SAAS,QAAQ;CAC/E;CAEA,MAAc,YAAY,UAAkB,OAA6B;EACvE,IAAI;GACF,IAAI,MAAM,WAAW,mBAAmB,IAAI,GAC1C,OAAO,KAAK,OAAO,MAAM,YAAY,OAAO,KAAK,CAAC,EAAC,CAAE,SAAS,MAAM,CAAC;GAGvE,IAAI,MAAM,WAAW,mBAAmB,MAAM,GAC5C,OAAO,KAAK,OAAO,MAAM,sBAAsB,OAAO,KAAK,CAAC,EAAC,CAAE,SAAS,MAAM,CAAC;GAGjF,OAAO,KAAK,MAAM,KAAK;EACzB,SAAS,OAAO;GACd,MAAM,IAAI,gBAAgB,UAAU,KAAK;EAC3C;CACF;AACF;;;;AAKA,eAAe,QAAQ,aAAuD;CAC5E,MAAM,UAAU,MAAM,YAAY,KAAK;CACvC,IAAI,YAAY,MAAM,MAAM,IAAI,MAAM,mCAAmC;CAEzE,OAAO,QAAQ,KAAK,CAAC,OAAO,YAAY;EACtC,IAAI,OAAO,MAAM;EACjB,OAAO;CACT,CAAC;AACH;AAEA,SAAS,OAAO,OAAuB;CACrC,OAAO,OAAO,KAAK,MAAM,MAAM,CAAC,GAAG,QAAQ;AAC7C;;;;AAoDA,SAAS,WAAW,KAAa,OAAqC;CACpE,MAAM,YAAY,IAAI,QAAQ,GAAG;CACjC,OAAO,YAAY,IAAI,IAAI,MAAM,GAAG,SAAS,IAAI,aAAa,KAAK,KAAK;AAC1E;;;;AAKA,SAAS,aAAa,MAAc,OAAqC;CACvE,IAAI,OAAO,UAAU,YAAY,UAAU,MAAM,OAAO;CACxD,MAAM,UAAW,MAAiC;CAClD,OAAO,OAAO,YAAY,WAAW,UAAU;AACjD;AAEA,MAAM,iBAEF,OAAO,YAAY,CACrB,GAAG,2BAA2B,KAAK,SAAS,CAAC,MAAM,UAAU,CAAU,GACvE,GAAG,2BAA2B,KAAK,SAAS,CAAC,MAAM,YAAY,CAAU,CAC3E,CAAC;;;;AAKD,MAAa,0BAA0B;;;;;;;;;;;;;;;;;;AAmBvC,SAAgB,iBAAiB,SAAgD;CAC/E,MAAM,EACJ,OACA,SAAS,yBACT,aACA,sBACA,KACA,cAAc,SACZ;CAEJ,OAAO,OAAO,OACZ,OAAO,YACL,iBAAiB,KAAK,SAAS,CAC7B,MACA,IAAI,iBAAiB,OAAO;EAC1B,QAAQ,GAAG,OAAO,GAAG;EACrB,KAAK,MAAM;EACX;EACA;EACA,SAAS,cAAc,eAAe,QAAQ;CAChD,CAAC,CACH,CAAC,CACH,CACF;AACF"}
|
|
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","../../src/lib/sessions.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 * Always `true`: every method returns synchronously.\n */\n public readonly synchronous = true;\n\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 /**\n * Always `false`: every method returns a promise, compressed or not.\n */\n public readonly synchronous = false;\n\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","import { CacheValueError, type RedisClientLike } from \"./redis.js\";\nimport type { Awaitable } from \"./types.js\";\n\n/**\n * What a gateway shard needs to resume its session, the same shape as `@discordjs/ws`'s `SessionInfo`.\n */\nexport interface GatewaySessionInfo {\n /**\n * The URL to connect to when resuming.\n */\n resumeURL: string;\n /**\n * The sequence number of the last dispatch the shard received.\n */\n sequence: number;\n /**\n * The ID of the session.\n */\n sessionId: string;\n /**\n * The total number of shards when the shard identified. A session is not resumed once it changes.\n */\n shardCount: number;\n /**\n * The ID of the shard.\n */\n shardId: number;\n}\n\n/**\n * Stores the gateway sessions of the shards, so a restarted process resumes them instead of identifying again, see\n * `@wolfstar/plugin-gateway`'s `GatewayClientOptions.sessionStore`.\n */\nexport interface GatewaySessionStore {\n /**\n * Reads the session of a shard.\n *\n * @param shardId The ID of the shard.\n * @returns The session, or `null` when there is none to resume.\n */\n get(shardId: number): Awaitable<GatewaySessionInfo | null>;\n /**\n * Writes the session of a shard.\n *\n * @param shardId The ID of the shard.\n * @param info The session, or `null` once it can no longer be resumed.\n */\n set(shardId: number, info: GatewaySessionInfo | null): Awaitable<void>;\n}\n\nexport interface RedisSessionStoreOptions {\n /**\n * The Redis client to use, e.g. an [`ioredis`](https://github.com/redis/ioredis) instance.\n */\n redis: Pick<RedisClientLike, \"get\" | \"set\" | \"del\">;\n /**\n * The prefix of every Redis key owned by the store, sessions live at `<prefix>:<shardId>`.\n *\n * @default \"wolfstar:sessions\"\n */\n prefix?: string;\n /**\n * The time-to-live of a session since its last write, in seconds, `null` to keep sessions until they are dropped.\n *\n * @remarks\n * Discord only lets a session be resumed for a while after its connection closes, and resuming an expired one costs\n * a connection before identifying. Every dispatch pushes the expiration back, so a shard receiving none for longer\n * than the ttl identifies on the next restart.\n *\n * @default 600\n */\n ttl?: number | null;\n}\n\n/**\n * The default prefix of every Redis key owned by a store created with {@link createRedisSessionStore}.\n */\nexport const DefaultRedisSessionStorePrefix = \"wolfstar:sessions\";\n\n/**\n * A {@link GatewaySessionStore} backed by Redis, storing every session as JSON at `<prefix>:<shardId>`.\n */\nexport class RedisSessionStore implements GatewaySessionStore {\n public readonly prefix: string;\n public readonly ttl: number | null;\n\n readonly #redis: Pick<RedisClientLike, \"get\" | \"set\" | \"del\">;\n\n public constructor(options: RedisSessionStoreOptions) {\n const ttl = options.ttl === undefined ? 600 : options.ttl;\n if (ttl !== null && !(ttl > 0)) {\n throw new RangeError(`ttl must be a positive amount of seconds, received ${ttl}`);\n }\n\n this.#redis = options.redis;\n this.prefix = options.prefix ?? DefaultRedisSessionStorePrefix;\n this.ttl = ttl;\n }\n\n public async get(shardId: number): Promise<GatewaySessionInfo | null> {\n const key = this.key(shardId);\n const value = await this.#redis.get(key);\n if (value === null) return null;\n\n try {\n return JSON.parse(value) as GatewaySessionInfo;\n } catch (error) {\n throw new CacheValueError(key, error);\n }\n }\n\n public async set(shardId: number, info: GatewaySessionInfo | null): Promise<void> {\n const key = this.key(shardId);\n if (info === null) await this.#redis.del(key);\n else if (this.ttl === null) await this.#redis.set(key, JSON.stringify(info));\n else await this.#redis.set(key, JSON.stringify(info), \"PX\", Math.round(this.ttl * 1000));\n }\n\n /**\n * Gets the Redis key the session of a shard is stored at.\n * @param shardId The ID of the shard.\n */\n public key(shardId: number): string {\n return `${this.prefix}:${shardId}`;\n }\n}\n\n/**\n * Creates a {@link GatewaySessionStore} stored in Redis, for `@wolfstar/plugin-gateway`'s `sessionStore` option.\n *\n * @example\n * ```typescript\n * import { createRedisSessionStore } from '@wolfstar/plugin-cache';\n * import { Redis } from 'ioredis';\n *\n * const sessionStore = createRedisSessionStore({ redis: new Redis(process.env.REDIS_URL!) });\n * ```\n *\n * @param options The options for the store.\n */\nexport function createRedisSessionStore(options: RedisSessionStoreOptions): RedisSessionStore {\n return new RedisSessionStore(options);\n}\n"],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@wolfstar/plugin-cache",
|
|
3
|
-
"version": "0.3.0-next-
|
|
3
|
+
"version": "0.3.0-next-20260927012037",
|
|
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",
|