@wolfstar/plugin-gateway 0.8.0 → 0.9.0-next-20261001183204
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 +224 -90
- package/dist/esm/index.d.ts +573 -236
- package/dist/esm/index.d.ts.map +1 -1
- package/dist/esm/index.js +1002 -506
- package/dist/esm/index.js.map +1 -1
- package/package.json +2 -1
package/README.md
CHANGED
|
@@ -23,9 +23,9 @@ interactions continue to use the framework's existing client.
|
|
|
23
23
|
The gateway connection uses [`@discordjs/ws`](https://www.npmjs.com/package/@discordjs/ws) for
|
|
24
24
|
sharding, reconnects, and session resumes. Actions turn dispatches into events containing structures
|
|
25
25
|
such as `Message`, `User`, and `Guild`; `EventGatewayListener` lets pieces in the `listeners`
|
|
26
|
-
directory handle those events. Managers such as `client.users` and `client.guilds` read
|
|
27
|
-
|
|
28
|
-
`@discordjs/core`. The same core API is available as `client.api`.
|
|
26
|
+
directory handle those events. Managers such as `client.users` and `client.guilds` read through
|
|
27
|
+
`manager.cache`, in memory by default, optionally backed by an [`@wolfstar/plugin-cache`](../plugin-cache)
|
|
28
|
+
store, and fetch missing data through `@discordjs/core`. The same core API is available as `client.api`.
|
|
29
29
|
|
|
30
30
|
> [!NOTE]
|
|
31
31
|
> A gateway connection is long-lived: a `GatewayClient` needs a persistent process, unlike a bot
|
|
@@ -40,18 +40,16 @@ pnpm add @wolfstar/plugin-gateway @wolfstar/plugin-cache
|
|
|
40
40
|
## Usage
|
|
41
41
|
|
|
42
42
|
```ts
|
|
43
|
-
import { createInMemoryCache } from "@wolfstar/plugin-cache";
|
|
44
43
|
import { GatewayClient } from "@wolfstar/plugin-gateway";
|
|
45
44
|
import { GatewayIntentBits } from "discord-api-types/v10";
|
|
46
45
|
|
|
47
46
|
const client = new GatewayClient({
|
|
48
47
|
intents:
|
|
49
48
|
GatewayIntentBits.Guilds | GatewayIntentBits.GuildMessages | GatewayIntentBits.MessageContent,
|
|
50
|
-
cache: createInMemoryCache(),
|
|
51
49
|
});
|
|
52
50
|
|
|
53
|
-
client.on("messageCreate",
|
|
54
|
-
const guild = message.guildId ?
|
|
51
|
+
client.on("messageCreate", (message) => {
|
|
52
|
+
const guild = message.guildId ? client.guilds.cache.get(message.guildId) : undefined;
|
|
55
53
|
console.log(`${message.author.username} in ${guild?.name ?? "a DM"}: ${message.content}`);
|
|
56
54
|
});
|
|
57
55
|
|
|
@@ -62,19 +60,21 @@ await client.start({ listen: { port: 8080 } }); // loads pieces, starts HTTP, co
|
|
|
62
60
|
|
|
63
61
|
On top of the `Client` options:
|
|
64
62
|
|
|
65
|
-
| Option | Default
|
|
66
|
-
| --------------------- |
|
|
67
|
-
| `intents` | —
|
|
68
|
-
| `
|
|
69
|
-
| `
|
|
70
|
-
| `
|
|
71
|
-
| `
|
|
72
|
-
| `
|
|
73
|
-
| `
|
|
74
|
-
| `
|
|
75
|
-
| `
|
|
76
|
-
| `
|
|
77
|
-
| `
|
|
63
|
+
| Option | Default | Description |
|
|
64
|
+
| --------------------- | ----------------- | ---------------------------------------------------------------------------------------------------------------------------------- |
|
|
65
|
+
| `intents` | — | The gateway intents. |
|
|
66
|
+
| `cacheConstructor` | `CollectionCache` | Builds `manager.cache`, the cache of structure instances each manager reads through, see [Cache](#cache). |
|
|
67
|
+
| `cacheOptions` | `undefined` | Per-entity options of those caches, e.g. `{ messages: { maxSize: 1_000 } }`, see [Bounding memory](#bounding-memory). |
|
|
68
|
+
| `cache` | `undefined` | A `@wolfstar/plugin-cache` cache whose raw stores back the managers instead, see [Cache](#cache). |
|
|
69
|
+
| `shardCount` | `null` | Total shards across every process, `null` for Discord's recommendation. |
|
|
70
|
+
| `shardIds` | `null` | The shards this client runs, as an array or a `{ start, end }` range. `null` for all. |
|
|
71
|
+
| `gateway` | `{}` | Extra `@discordjs/ws` `WebSocketManager` options (`compression`, `initialPresence`, ...). |
|
|
72
|
+
| `cacheFailure` | `"skip"` | On a cache read/write failure, `"skip"` drops the event, `"emitUncached"` emits it from the payload. |
|
|
73
|
+
| `dispatchTimeout` | `30_000` | Milliseconds after which a dispatch still processing is reported as a `DispatchTimeoutError`. `null` disables it. |
|
|
74
|
+
| `sessionStore` | `undefined` | A `GatewaySessionStore` keeping the shards' sessions across restarts, see [Resuming sessions](#resuming-sessions-across-restarts). |
|
|
75
|
+
| `sessionStoreTimeout` | `5_000` | Milliseconds a shard waits for `sessionStore` to read its session before identifying. `null` waits forever. |
|
|
76
|
+
| `partials` | `[]` | The structures to build partially for uncached entities, see [Partials](#partials). |
|
|
77
|
+
| `waitGuildTimeout` | `15_000` | Milliseconds `clientReady` waits for initially unavailable guilds before emitting anyway, see [Events](#events). |
|
|
78
78
|
|
|
79
79
|
`client.gateway` exposes the underlying `WebSocketManager`, e.g. to send presence updates.
|
|
80
80
|
|
|
@@ -263,53 +263,185 @@ framework's `container.client`. The latter stays typed as the base `Client`: a m
|
|
|
263
263
|
cannot redeclare it with another type (TypeScript reports TS2717, or silently keeps `Client` under
|
|
264
264
|
`skipLibCheck`).
|
|
265
265
|
|
|
266
|
-
##
|
|
266
|
+
## Cache
|
|
267
|
+
|
|
268
|
+
Every manager exposes its cache as `manager.cache`, the `Cache` of the discord.js RFC:
|
|
269
|
+
|
|
270
|
+
```typescript
|
|
271
|
+
const user = client.users.cache.get(userId);
|
|
272
|
+
const member = client.members.cache.get(client.members.resolveKey(guildId, userId));
|
|
273
|
+
|
|
274
|
+
client.users.cache.has(userId);
|
|
275
|
+
client.users.cache.getSize();
|
|
276
|
+
```
|
|
267
277
|
|
|
268
|
-
|
|
278
|
+
By default, entities are kept in memory by `CollectionCache`, a `Collection` of structure instances: updates patch
|
|
279
|
+
the cached instance in place, as in discord.js, and every method is synchronous. Its relations (e.g. `member.voice`)
|
|
280
|
+
are re-resolved on every `cache.get`; instances reached by iterating the collection itself (`find`, `filter`, `map`,
|
|
281
|
+
...) carry the relations of their last `get`.
|
|
269
282
|
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
events.
|
|
283
|
+
```typescript
|
|
284
|
+
import { createRedisCache } from "@wolfstar/plugin-cache";
|
|
273
285
|
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
| `client.threads` | `threadId` |
|
|
280
|
-
| `client.messages` | `channelId`, `messageId` |
|
|
281
|
-
| `client.members` | `guildId`, `userId` |
|
|
282
|
-
| `client.roles` | `guildId`, `roleId` |
|
|
286
|
+
new GatewayClient({ intents }); // CollectionCache, in memory
|
|
287
|
+
new GatewayClient({ intents, cache: createRedisCache(redis) }); // raw data in Redis
|
|
288
|
+
new GatewayClient({ intents, cache: null }); // nothing is cached
|
|
289
|
+
new GatewayClient({ intents, cacheConstructor: MyCache }); // your own Cache, see "Custom caches"
|
|
290
|
+
```
|
|
283
291
|
|
|
284
|
-
|
|
292
|
+
> [!IMPORTANT]
|
|
293
|
+
> The default keeps **every entity the gateway sends in memory until a dispatch removes it** (`MESSAGE_DELETE`,
|
|
294
|
+
> `GUILD_DELETE`, ...): nothing expires, so messages, users, and presences grow for as long as the process runs.
|
|
295
|
+
> See [Bounding memory](#bounding-memory).
|
|
296
|
+
|
|
297
|
+
With a `@wolfstar/plugin-cache` store, the cache holds raw API data and builds a structure on every read: the
|
|
298
|
+
methods return promises when the store is remote (`await` works with every cache, `manager.cache.synchronous` tells
|
|
299
|
+
them apart, see [Synchronous reads](#synchronous-reads)), and two reads return two objects. `policies` apply to
|
|
300
|
+
every write, in every mode (dispatches and manager writes alike); their `ttl` only applies to plugin-cache stores,
|
|
301
|
+
see [Store-backed caches](#store-backed-caches).
|
|
302
|
+
|
|
303
|
+
`manager.fetch(...)` reads the cache first and falls back to the REST API. `manager.cache` is keyed by a single ID
|
|
304
|
+
for most managers, and by `manager.resolveKey(...)` for the ones taking more than one argument:
|
|
305
|
+
|
|
306
|
+
| Manager | `resolveKey` / `fetch` / `refresh` arguments |
|
|
307
|
+
| ----------------- | -------------------------------------------- |
|
|
308
|
+
| `client.users` | `userId` |
|
|
309
|
+
| `client.guilds` | `guildId` |
|
|
310
|
+
| `client.channels` | `channelId` (threads included) |
|
|
311
|
+
| `client.threads` | `threadId` |
|
|
312
|
+
| `client.messages` | `channelId`, `messageId` |
|
|
313
|
+
| `client.members` | `guildId`, `userId` |
|
|
314
|
+
| `client.roles` | `guildId`, `roleId` |
|
|
315
|
+
|
|
316
|
+
- `cache.get` only reads the cache, resolving to `undefined` on a miss;
|
|
285
317
|
- `fetch` reads the cache, falling back to the REST API (and caching the result). Pass
|
|
286
318
|
`{ force: true }` after the IDs to always hit the API, `{ cache: false }` not to store the result:
|
|
287
319
|
`client.messages.fetch(channelId, messageId, { force: true })`;
|
|
288
320
|
- `refresh` is `fetch` with `{ force: true }`;
|
|
289
|
-
- `resolve` takes a structure (returned as is) or a cache key, like discord.js's `resolve
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
(
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
the
|
|
321
|
+
- `resolve` takes a structure (returned as is) or a cache key, like discord.js's `resolve`.
|
|
322
|
+
|
|
323
|
+
Every API payload is written to the cache: a cached entry is patched with it (the fields a partial payload lacks
|
|
324
|
+
keep their cached value) and the patched instance, or the newly built structure, is returned. Relations are
|
|
325
|
+
resolved from the cache too: `message.author` is the entry of `client.users`, `message.member` the one of
|
|
326
|
+
`client.members`, and the same goes for `member.user`, `emoji.author`, `sticker.user`, and `invite.inviter`. Every
|
|
327
|
+
structure of a guild (channels, threads, members, roles, messages, emojis, stickers, invites) has `guild`, built
|
|
328
|
+
from the cached guild, and messages have `channel`. These are `null` when the entity is not cached; `fetchGuild()`
|
|
329
|
+
and `fetchChannel()` always get it. A structure built by hand, with `new Message(data)`, falls back to the copy
|
|
330
|
+
embedded in its payload.
|
|
331
|
+
|
|
332
|
+
Which object a relation is depends on the relation. With the default cache, `message.author` and `message.channel`
|
|
333
|
+
are the cached instances, the very objects `client.users.cache.get(id)` and `client.channels.cache.get(id)` return.
|
|
334
|
+
`guild` is not: it is a shallow copy of the cached guild, holding the same data at the time the structure was read,
|
|
335
|
+
but `message.guild !== client.guilds.cache.get(message.guildId)`. Compare guilds by `id`, and read
|
|
336
|
+
`client.guilds.cache.get(id)` when you need the instance that later updates patch.
|
|
337
|
+
|
|
338
|
+
The same goes for what the client hands out outside of `manager.cache`: the structures delivered by events (the
|
|
339
|
+
message of `messageCreate`, the `new` of update events such as `guildMemberUpdate`, ...) and the ones `listCached`
|
|
340
|
+
returns are freshly built from the cache, they are not the cached instances and later dispatches do not patch them.
|
|
341
|
+
The previous state of update and delete events is a copy of the cached instance taken before the write; with the
|
|
342
|
+
default cache it carries the relations of the entity's last read rather than re-resolving them.
|
|
343
|
+
Only `manager.cache.get` (and `fetch`, `resolve`, which read it) returns the cached instance.
|
|
344
|
+
|
|
345
|
+
### Bounding memory
|
|
346
|
+
|
|
347
|
+
With the default cache, nothing is evicted unless a dispatch removes it. There are three ways to bound it:
|
|
348
|
+
|
|
349
|
+
- **`cacheOptions`** sets a `maxSize` per entity: once reached, the oldest entry is evicted for each new one, and
|
|
350
|
+
`0` holds nothing. It is passed to the `cacheConstructor`, the default `CollectionCache` included.
|
|
351
|
+
The bound is per entity, not per channel: unlike discord.js, a busy channel can evict the messages of a quiet
|
|
352
|
+
one.
|
|
353
|
+
|
|
354
|
+
`client.channels.cache` spans the channel and the thread caches, so it is a `Cache` but not a `Collection`: it
|
|
355
|
+
has no `size`, `filter`, `find` or iteration, and `maxSize` applies to channels and threads separately.
|
|
356
|
+
|
|
357
|
+
```ts
|
|
358
|
+
// The 1000 most recent messages, and no presence.
|
|
359
|
+
new GatewayClient({
|
|
360
|
+
intents,
|
|
361
|
+
cacheOptions: { messages: { maxSize: 1_000 }, presences: { maxSize: 0 } },
|
|
362
|
+
});
|
|
363
|
+
```
|
|
364
|
+
|
|
365
|
+
- **`policies.filter`** decides entry by entry: a rejected entry is not cached, and the entry already cached under
|
|
366
|
+
its key is deleted.
|
|
367
|
+
|
|
368
|
+
```ts
|
|
369
|
+
// No bot user, and no message written by a bot.
|
|
370
|
+
new GatewayClient({
|
|
371
|
+
intents,
|
|
372
|
+
policies: {
|
|
373
|
+
users: { filter: (user) => !user.bot },
|
|
374
|
+
messages: { filter: (message) => !message.author.bot },
|
|
375
|
+
},
|
|
376
|
+
});
|
|
377
|
+
```
|
|
302
378
|
|
|
303
|
-
|
|
304
|
-
|
|
379
|
+
- **`cache: null`** caches nothing at all: `cache.get` resolves to `undefined` and `fetch` always hits the API.
|
|
380
|
+
|
|
381
|
+
```ts
|
|
382
|
+
new GatewayClient({ intents, cache: null });
|
|
383
|
+
```
|
|
384
|
+
|
|
385
|
+
`cacheOptions` only applies to caches built by a constructor: like `cacheConstructor`, combining it with `cache` or
|
|
386
|
+
`makeCache` throws (those stores have their own bounds), and it is ignored with `cache: null`.
|
|
387
|
+
|
|
388
|
+
### Custom caches
|
|
305
389
|
|
|
306
|
-
|
|
390
|
+
`cacheConstructor` takes a class implementing `Cache`, instantiated once per entity with
|
|
391
|
+
`(creator, name, options)`:
|
|
307
392
|
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
393
|
+
- `creator` builds a structure out of raw data: it is the cache's `construct`;
|
|
394
|
+
- `name` is the entity's name, e.g. `"users"`;
|
|
395
|
+
- `options` (`CacheConstructorOptions`) carries:
|
|
396
|
+
- `keyOf(data)`, the cache key of raw data. `add` **must** key its entries with it: most entities are not keyed
|
|
397
|
+
by `id` (a member is keyed by guild and user, a message by channel and ID, ...);
|
|
398
|
+
- `refresh(value)`, which resolves the relations of an instance again and returns it. Call it on what `get` and
|
|
399
|
+
`add` hand out, or long-lived instances keep the relations of the day they were built (`member.voice` would
|
|
400
|
+
not follow `VOICE_STATE_UPDATE`);
|
|
401
|
+
- the entity's `cacheOptions`, i.e. `maxSize`.
|
|
402
|
+
|
|
403
|
+
The recommended way is to extend `CollectionCache`, which does all of this:
|
|
404
|
+
|
|
405
|
+
```ts
|
|
406
|
+
import {
|
|
407
|
+
CollectionCache,
|
|
408
|
+
type CacheConstructorOptions,
|
|
409
|
+
type RawAPIType,
|
|
410
|
+
type StructureCreator,
|
|
411
|
+
type StructureMixin,
|
|
412
|
+
} from "@wolfstar/plugin-gateway";
|
|
413
|
+
import type { CacheEntityName } from "@wolfstar/plugin-cache";
|
|
414
|
+
|
|
415
|
+
class BoundedCache<
|
|
416
|
+
Value extends StructureMixin<object>,
|
|
417
|
+
Raw extends RawAPIType<Value> = RawAPIType<Value>,
|
|
418
|
+
> extends CollectionCache<Value, Raw> {
|
|
419
|
+
public constructor(
|
|
420
|
+
creator: StructureCreator<Value, Raw>,
|
|
421
|
+
name: CacheEntityName,
|
|
422
|
+
options: CacheConstructorOptions<Value, Raw>,
|
|
423
|
+
) {
|
|
424
|
+
// Forward `keyOf` and `refresh`, with a default bound for the entities `cacheOptions` does not set.
|
|
425
|
+
super(creator, name, { ...options, maxSize: options.maxSize ?? 10_000 });
|
|
426
|
+
}
|
|
427
|
+
}
|
|
428
|
+
|
|
429
|
+
new GatewayClient({ intents, cacheConstructor: BoundedCache });
|
|
430
|
+
```
|
|
431
|
+
|
|
432
|
+
- The cache must be synchronous: structures are built synchronously from it.
|
|
433
|
+
- A cache that is not a `Map` cannot be enumerated, so what needs to list its entries does not work with it: the
|
|
434
|
+
dispatch cascades (`GUILD_DELETE` and `CHANNEL_DELETE` leave the guild's or channel's entries behind), the
|
|
435
|
+
reconciliation of guilds left while offline on `READY`, the granular emoji and sticker diff events, and
|
|
436
|
+
`listCached`.
|
|
437
|
+
- `cacheConstructor` cannot be combined with `cache` or `makeCache` (it throws). With `cache: null`, `null` wins:
|
|
438
|
+
nothing is cached and the class is never instantiated.
|
|
439
|
+
|
|
440
|
+
### Store-backed caches
|
|
441
|
+
|
|
442
|
+
`cache` and `makeCache` (`@wolfstar/plugin-cache`) keep the managers backed by raw stores, in memory or Redis,
|
|
443
|
+
instead of `CollectionCache`: a structure is built on every read. `makeCache` takes precedence over `cache`, and is
|
|
444
|
+
called once per entity kind, `null` not to cache that kind. `cacheConstructor` cannot be combined with either.
|
|
313
445
|
|
|
314
446
|
```ts
|
|
315
447
|
import { MemoryEntityCache } from "@wolfstar/plugin-cache";
|
|
@@ -319,52 +451,54 @@ const client = new GatewayClient({
|
|
|
319
451
|
// Called once per entity kind: `null` not to cache it.
|
|
320
452
|
makeCache: (entity) =>
|
|
321
453
|
["guilds", "channels", "roles"].includes(entity) ? new MemoryEntityCache() : null,
|
|
322
|
-
// Entry by entry, for dispatches and managers alike.
|
|
454
|
+
// Entry by entry, for dispatches and managers alike; `ttl` only applies to these stores.
|
|
323
455
|
policies: { users: { filter: (user) => !user.bot }, messages: { ttl: () => 3_600_000 } },
|
|
324
456
|
// A failing store (e.g. Redis down) is a cache miss, reported through `cacheError`.
|
|
325
457
|
cacheErrors: "miss",
|
|
326
458
|
});
|
|
327
459
|
```
|
|
328
460
|
|
|
329
|
-
`
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
|
337
|
-
|
|
|
338
|
-
|
|
|
339
|
-
|
|
|
461
|
+
Swapping `createInMemoryCache()` for `createRedisCache({ redis })` changes nothing else, see
|
|
462
|
+
[`@wolfstar/plugin-cache`](../plugin-cache). Every feature still works without a store for an entity kind (or with
|
|
463
|
+
`cache: null`, for every entity), following the
|
|
464
|
+
[discord.js RFC #11426](https://github.com/discordjs/discord.js/issues/11426): every event is still emitted, and
|
|
465
|
+
the previous state of update and delete events is `null` (or a partial, see [Partials](#partials)). What else needs
|
|
466
|
+
a store:
|
|
467
|
+
|
|
468
|
+
| Without the store of… | What happens |
|
|
469
|
+
| ---------------------------- | -------------------------------------------------------------------------------- |
|
|
470
|
+
| any entity | `cache.get` resolves to `undefined`, `fetch` hits the API, previous state `null` |
|
|
471
|
+
| `emojis` / `stickers` | only `guildEmojisUpdate` / `guildStickersUpdate`, no granular diff events |
|
|
472
|
+
| `guilds` (able to enumerate) | guilds left while offline are not reconciled on `READY` |
|
|
473
|
+
| `presences` | `presences.fetch` rejects: presences only come from the gateway |
|
|
474
|
+
| `roles` | overwrite types and member roles are read from one `GET /guilds/:id/roles` |
|
|
475
|
+
| `threadMembers` | `thread.joined` is `null` unless the payload carries the bot's member |
|
|
476
|
+
| a relation's entity | the relation getter (`message.guild`, `member.voice`, ...) returns `null` |
|
|
340
477
|
|
|
341
478
|
`listCached` resolves to `[]` without a store, and throws a `TypeError` for a store that cannot
|
|
342
479
|
enumerate its entries. With `cacheErrors: "throw"`, a failing store rejects instead of missing.
|
|
343
480
|
|
|
344
481
|
### Synchronous reads
|
|
345
482
|
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
cache
|
|
349
|
-
structure, relations included, without a promise:
|
|
483
|
+
`manager.cache`'s methods are `Awaitable`: synchronous with the default `CollectionCache`, a promise with a
|
|
484
|
+
`@wolfstar/plugin-cache` store, unless every store the entity's relations are read from is synchronous too.
|
|
485
|
+
`manager.cache.synchronous` tells them apart, so a hot path such as a message filter can skip `await` when it can:
|
|
350
486
|
|
|
351
487
|
```ts
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
// ...
|
|
356
|
-
});
|
|
488
|
+
const user = client.users.cache.synchronous
|
|
489
|
+
? client.users.cache.get(userId)
|
|
490
|
+
: await client.users.cache.get(userId);
|
|
357
491
|
```
|
|
358
492
|
|
|
359
|
-
|
|
360
|
-
cannot tell a miss apart without awaiting it, so there `cached` throws a `TypeError` rather than
|
|
361
|
-
silently returning `undefined`; the same goes for a custom cache mixing both kinds, when a relation
|
|
362
|
-
lives in an asynchronous entity cache. `manager.cache?.synchronous` tells which path to take:
|
|
493
|
+
`await` works either way, since a non-promise value resolves to itself:
|
|
363
494
|
|
|
364
495
|
```ts
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
496
|
+
client.on("messageCreate", async (message) => {
|
|
497
|
+
const key = message.guildId && client.members.resolveKey(message.guildId, message.author.id);
|
|
498
|
+
const member = key ? await client.members.cache.get(key) : undefined;
|
|
499
|
+
if (member?.roleIds.includes(mutedRoleId)) return;
|
|
500
|
+
// ...
|
|
501
|
+
});
|
|
368
502
|
```
|
|
369
503
|
|
|
370
504
|
## Resuming sessions across restarts
|
|
@@ -394,8 +528,8 @@ process.once("SIGTERM", async () => {
|
|
|
394
528
|
```
|
|
395
529
|
|
|
396
530
|
- **Pair it with a persistent cache.** A resumed session only replays the missed events, not the
|
|
397
|
-
guilds: with
|
|
398
|
-
`GUILD_CREATE` never refills.
|
|
531
|
+
guilds: with an in-memory cache, the default `CollectionCache` or `createInMemoryCache()`, a
|
|
532
|
+
restarted process resumes with an empty cache that `GUILD_CREATE` never refills.
|
|
399
533
|
- **Shut down with `destroy({ resumable: true })`.** By default `destroy()` closes the connections
|
|
400
534
|
with code `1000`, which makes Discord invalidate the sessions, and `@discordjs/ws` drops them from
|
|
401
535
|
the store. With `resumable`, the shards close with code `4200` and their sessions stay stored.
|
|
@@ -512,9 +646,9 @@ The client also has discord.js's `fetchSticker`, `fetchStickerPacks`, and `fetch
|
|
|
512
646
|
|
|
513
647
|
### Users, members and roles
|
|
514
648
|
|
|
515
|
-
They follow discord.js's API
|
|
516
|
-
|
|
517
|
-
|
|
649
|
+
They follow discord.js's API. `client.users.cache`, `client.members.cache`, and `client.roles.cache`
|
|
650
|
+
read synchronously with the default `CollectionCache`, like discord.js; only a `@wolfstar/plugin-cache`
|
|
651
|
+
store that is not synchronous (e.g. Redis) makes them asynchronous, see
|
|
518
652
|
[Synchronous reads](#synchronous-reads).
|
|
519
653
|
|
|
520
654
|
```ts
|
|
@@ -544,7 +678,7 @@ apply through `member.fetchPermissionsIn(channel)`, see below.
|
|
|
544
678
|
`guild.members.fetch()`) asks the guild's shard for its members over the gateway instead of REST:
|
|
545
679
|
every member by default, those matching a `query` (with a `limit`), or up to 100 `userIds`, with
|
|
546
680
|
their `presences` if asked. It resolves with the `GuildMember`s once Discord's last
|
|
547
|
-
`GUILD_MEMBERS_CHUNK` for its `nonce` is cached, so `client.members.get` sees them, and rejects
|
|
681
|
+
`GUILD_MEMBERS_CHUNK` for its `nonce` is cached, so `client.members.cache.get` sees them, and rejects
|
|
548
682
|
with a `GuildMembersTimeoutError` when no chunk arrives for `time` milliseconds (120 seconds by
|
|
549
683
|
default), or a `GuildMembersRateLimitError` when Discord answers with `RATE_LIMITED`. Every chunk
|
|
550
684
|
is also emitted as `guildMembersChunk`, whose `data.not_found` lists the requested IDs that are
|