@wolfstar/plugin-gateway 0.2.0-next-20260926221949 → 0.2.0-next-20260926222321
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 +61 -9
- package/dist/esm/index.d.ts +65 -2
- package/dist/esm/index.d.ts.map +1 -1
- package/dist/esm/index.js +145 -4
- package/dist/esm/index.js.map +1 -1
- package/package.json +2 -2
package/dist/esm/index.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { Client, Listener, container } from "@wolfstar/http-framework";
|
|
2
|
-
import { WebSocketManager, WebSocketShardEvents } from "@discordjs/ws";
|
|
2
|
+
import { CloseCodes, DefaultWebSocketManagerOptions, WebSocketManager, WebSocketShardEvents } from "@discordjs/ws";
|
|
3
3
|
import { Client as Client$1 } from "@discordjs/core";
|
|
4
4
|
import { applyGatewayDispatch, autoModerationRuleKey, banKey, emojiKey, integrationKey, inviteKey, memberKey, messageKey, presenceKey, roleKey, scheduledEventKey, soundboardSoundKey, stageInstanceKey, stickerKey, threadMemberKey, voiceStateKey } from "@wolfstar/plugin-cache";
|
|
5
5
|
import { ActivityFlags, ActivityType, AttachmentFlags, AuditLogEvent, ChannelFlags, ChannelType, FormattingPatterns, GatewayDispatchEvents, GatewayIntentBits, GatewayOpcodes, GuildFeature, GuildMemberFlags, GuildPremiumTier, GuildScheduledEventStatus, GuildSystemChannelFlags, GuildWidgetStyle, InviteType, MessageFlags, MessageReferenceType, MessageType, OverwriteType, PermissionFlagsBits, PresenceUpdateStatus, RoleFlags, RouteBases, Routes, StickerFormatType, UserFlags, WebhookType } from "discord-api-types/v10";
|
|
@@ -6915,6 +6915,29 @@ var GuildMembersRateLimitError = class extends Error {
|
|
|
6915
6915
|
this.retryAfter = retryAfter;
|
|
6916
6916
|
}
|
|
6917
6917
|
};
|
|
6918
|
+
/**
|
|
6919
|
+
* Emitted as an `error` when `GatewayClientOptions.sessionStore` fails to read or write the session of a shard.
|
|
6920
|
+
*
|
|
6921
|
+
* @remarks
|
|
6922
|
+
* Neither failure stops the shard: a failed (or timed out) read identifies instead of resuming, and a failed write
|
|
6923
|
+
* only means the session may not be resumed after a restart. The store's error is the `cause`.
|
|
6924
|
+
*/
|
|
6925
|
+
var GatewaySessionStoreError = class extends Error {
|
|
6926
|
+
/**
|
|
6927
|
+
* Whether the store failed to read (`"get"`) or write (`"set"`) the session.
|
|
6928
|
+
*/
|
|
6929
|
+
operation;
|
|
6930
|
+
/**
|
|
6931
|
+
* The shard whose session was read or written.
|
|
6932
|
+
*/
|
|
6933
|
+
shardId;
|
|
6934
|
+
constructor(operation, shardId, cause) {
|
|
6935
|
+
super(`Cannot ${operation === "get" ? "read" : "write"} the session of shard ${shardId} in the session store`, { cause });
|
|
6936
|
+
this.name = "GatewaySessionStoreError";
|
|
6937
|
+
this.operation = operation;
|
|
6938
|
+
this.shardId = shardId;
|
|
6939
|
+
}
|
|
6940
|
+
};
|
|
6918
6941
|
|
|
6919
6942
|
//#endregion
|
|
6920
6943
|
//#region src/managers/GuildMemberManager.ts
|
|
@@ -11206,6 +11229,79 @@ var DispatchQueue = class {
|
|
|
11206
11229
|
}
|
|
11207
11230
|
};
|
|
11208
11231
|
|
|
11232
|
+
//#endregion
|
|
11233
|
+
//#region src/util/sessions.ts
|
|
11234
|
+
/**
|
|
11235
|
+
* Sits between `@discordjs/ws` and a {@link GatewaySessionStore}, which `@discordjs/ws` would otherwise read on every
|
|
11236
|
+
* dispatch and heartbeat, and write on every sequence change, while holding the dispatch back.
|
|
11237
|
+
*
|
|
11238
|
+
* @remarks
|
|
11239
|
+
* The session of a shard is read from the store once, when the shard first connects, then served from memory. Writes
|
|
11240
|
+
* never hold a dispatch back either: they run in the background, one at a time per shard, and the ones queued while a
|
|
11241
|
+
* write is in flight collapse into a single write of the latest session. A stored sequence is therefore slightly
|
|
11242
|
+
* behind after a crash, which Discord handles by replaying the dispatches since then.
|
|
11243
|
+
*
|
|
11244
|
+
* @internal
|
|
11245
|
+
*/
|
|
11246
|
+
var GatewaySessionMirror = class {
|
|
11247
|
+
#store;
|
|
11248
|
+
#timeout;
|
|
11249
|
+
#onError;
|
|
11250
|
+
#sessions = /* @__PURE__ */ new Map();
|
|
11251
|
+
#pending = /* @__PURE__ */ new Map();
|
|
11252
|
+
#writes = /* @__PURE__ */ new Map();
|
|
11253
|
+
constructor(store, timeout, onError) {
|
|
11254
|
+
this.#store = store;
|
|
11255
|
+
this.#timeout = timeout;
|
|
11256
|
+
this.#onError = onError;
|
|
11257
|
+
}
|
|
11258
|
+
async get(shardId) {
|
|
11259
|
+
if (this.#sessions.has(shardId)) return this.#sessions.get(shardId);
|
|
11260
|
+
const session = await this.read(shardId);
|
|
11261
|
+
if (!this.#sessions.has(shardId)) this.#sessions.set(shardId, session);
|
|
11262
|
+
return this.#sessions.get(shardId);
|
|
11263
|
+
}
|
|
11264
|
+
set(shardId, info) {
|
|
11265
|
+
this.#sessions.set(shardId, info);
|
|
11266
|
+
const queued = this.#pending.has(shardId);
|
|
11267
|
+
this.#pending.set(shardId, info);
|
|
11268
|
+
if (queued) return;
|
|
11269
|
+
const previous = this.#writes.get(shardId) ?? Promise.resolve();
|
|
11270
|
+
this.#writes.set(shardId, previous.then(() => this.write(shardId)));
|
|
11271
|
+
}
|
|
11272
|
+
/**
|
|
11273
|
+
* Resolves once every session written so far reached the store.
|
|
11274
|
+
*/
|
|
11275
|
+
async flush() {
|
|
11276
|
+
await Promise.all(this.#writes.values());
|
|
11277
|
+
}
|
|
11278
|
+
async read(shardId) {
|
|
11279
|
+
let timer;
|
|
11280
|
+
try {
|
|
11281
|
+
const read = Promise.resolve(this.#store.get(shardId));
|
|
11282
|
+
if (this.#timeout === null) return await read;
|
|
11283
|
+
const timeout = this.#timeout;
|
|
11284
|
+
return await Promise.race([read, new Promise((_, reject) => {
|
|
11285
|
+
timer = setTimeout(() => reject(/* @__PURE__ */ new Error(`Timed out after ${timeout}ms`)), timeout);
|
|
11286
|
+
})]);
|
|
11287
|
+
} catch (error) {
|
|
11288
|
+
this.#onError(new GatewaySessionStoreError("get", shardId, error));
|
|
11289
|
+
return null;
|
|
11290
|
+
} finally {
|
|
11291
|
+
if (timer) clearTimeout(timer);
|
|
11292
|
+
}
|
|
11293
|
+
}
|
|
11294
|
+
async write(shardId) {
|
|
11295
|
+
const info = this.#pending.get(shardId);
|
|
11296
|
+
this.#pending.delete(shardId);
|
|
11297
|
+
try {
|
|
11298
|
+
await this.#store.set(shardId, info);
|
|
11299
|
+
} catch (error) {
|
|
11300
|
+
this.#onError(new GatewaySessionStoreError("set", shardId, error));
|
|
11301
|
+
}
|
|
11302
|
+
}
|
|
11303
|
+
};
|
|
11304
|
+
|
|
11209
11305
|
//#endregion
|
|
11210
11306
|
//#region src/GatewayClient.ts
|
|
11211
11307
|
/**
|
|
@@ -11267,6 +11363,9 @@ var GatewayClient = class extends Client {
|
|
|
11267
11363
|
dispatchTimeout;
|
|
11268
11364
|
#queue = new DispatchQueue();
|
|
11269
11365
|
#shardCount;
|
|
11366
|
+
#sessions = null;
|
|
11367
|
+
#keepSessions = false;
|
|
11368
|
+
#destroying = null;
|
|
11270
11369
|
constructor(options) {
|
|
11271
11370
|
super(options);
|
|
11272
11371
|
container.gatewayClient = this;
|
|
@@ -11288,6 +11387,7 @@ var GatewayClient = class extends Client {
|
|
|
11288
11387
|
this.presences = new PresenceManager(this);
|
|
11289
11388
|
this.gateway = new WebSocketManager({
|
|
11290
11389
|
...options.gateway,
|
|
11390
|
+
...this.sessionCallbacks(options),
|
|
11291
11391
|
token: options.discordToken ?? process.env.DISCORD_TOKEN,
|
|
11292
11392
|
intents: options.intents,
|
|
11293
11393
|
rest: container.rest,
|
|
@@ -11322,9 +11422,33 @@ var GatewayClient = class extends Client {
|
|
|
11322
11422
|
}
|
|
11323
11423
|
/**
|
|
11324
11424
|
* Disconnects every shard from the gateway. The HTTP server, if listening, is left untouched.
|
|
11425
|
+
*
|
|
11426
|
+
* @remarks
|
|
11427
|
+
* By default, the shards close their sessions, which Discord invalidates. Pass `resumable: true` to keep them
|
|
11428
|
+
* resumable instead, e.g. on a graceful shutdown before a deploy. Either way, it waits for the sessions to be
|
|
11429
|
+
* written to {@link GatewayClientOptions.sessionStore}.
|
|
11430
|
+
*
|
|
11431
|
+
* A call made while another one is still disconnecting the shards joins it, keeping the first call's `resumable`.
|
|
11432
|
+
*
|
|
11433
|
+
* @param options Whether to keep the sessions resumable.
|
|
11325
11434
|
*/
|
|
11326
|
-
|
|
11327
|
-
|
|
11435
|
+
destroy(options = {}) {
|
|
11436
|
+
this.#destroying ??= this.disconnect(options.resumable ?? false).finally(() => {
|
|
11437
|
+
this.#destroying = null;
|
|
11438
|
+
});
|
|
11439
|
+
return this.#destroying;
|
|
11440
|
+
}
|
|
11441
|
+
async disconnect(resumable) {
|
|
11442
|
+
this.#keepSessions = resumable;
|
|
11443
|
+
try {
|
|
11444
|
+
await this.gateway.destroy(this.#keepSessions ? {
|
|
11445
|
+
code: CloseCodes.Resuming,
|
|
11446
|
+
reason: "Resumable shutdown"
|
|
11447
|
+
} : {});
|
|
11448
|
+
} finally {
|
|
11449
|
+
this.#keepSessions = false;
|
|
11450
|
+
}
|
|
11451
|
+
await this.#sessions?.flush();
|
|
11328
11452
|
await this.idle();
|
|
11329
11453
|
}
|
|
11330
11454
|
/**
|
|
@@ -11503,6 +11627,23 @@ var GatewayClient = class extends Client {
|
|
|
11503
11627
|
if (timer) clearTimeout(timer);
|
|
11504
11628
|
}
|
|
11505
11629
|
}
|
|
11630
|
+
sessionCallbacks(options) {
|
|
11631
|
+
const { sessionStore, gateway } = options;
|
|
11632
|
+
if (sessionStore && (gateway?.retrieveSessionInfo || gateway?.updateSessionInfo)) throw new TypeError("sessionStore replaces gateway.retrieveSessionInfo and gateway.updateSessionInfo, pass one or the other");
|
|
11633
|
+
let retrieve = gateway?.retrieveSessionInfo ?? DefaultWebSocketManagerOptions.retrieveSessionInfo;
|
|
11634
|
+
let update = gateway?.updateSessionInfo ?? DefaultWebSocketManagerOptions.updateSessionInfo;
|
|
11635
|
+
if (sessionStore) {
|
|
11636
|
+
const timeout = options.sessionStoreTimeout === void 0 ? 5e3 : options.sessionStoreTimeout;
|
|
11637
|
+
const sessions = new GatewaySessionMirror(sessionStore, timeout, (error) => this.reportError(error, `the session store ${error.operation}`, error.shardId));
|
|
11638
|
+
this.#sessions = sessions;
|
|
11639
|
+
retrieve = (shardId) => sessions.get(shardId);
|
|
11640
|
+
update = (shardId, info) => sessions.set(shardId, info);
|
|
11641
|
+
}
|
|
11642
|
+
return {
|
|
11643
|
+
retrieveSessionInfo: retrieve,
|
|
11644
|
+
updateSessionInfo: (shardId, info) => info === null && this.#keepSessions ? void 0 : update(shardId, info)
|
|
11645
|
+
};
|
|
11646
|
+
}
|
|
11506
11647
|
reportError(error, type, shardId) {
|
|
11507
11648
|
if (this.listenerCount("error") > 0) this.emit("error", error);
|
|
11508
11649
|
else this.logger.error(`[Gateway] [Shard ${shardId}] Failed to process ${type}:`, error);
|
|
@@ -11547,5 +11688,5 @@ function RegisterAsGatewayListener(event, options = {}) {
|
|
|
11547
11688
|
}
|
|
11548
11689
|
|
|
11549
11690
|
//#endregion
|
|
11550
|
-
export { Action, ActionsManager, Activity, ActivityFlagsBitField, AnnouncementChannel, AnnouncementThreadChannel, AnonymousGuild, AppliedTagsMixin, Attachment, AttachmentFlagsBitField, AutoModerationActionExecution, AutoModerationRule, AutoModerationRuleManager, BaseChannel, BaseChannelMixin, BaseInvite, BitField, CachedManager, CategoryChannel, Channel, ChannelFlagsBitField, ChannelManager, ChannelMessageManager, ChannelOwnerMixin, ChannelParentMixin, ChannelPermissionMixin, ChannelSlowmodeMixin, ChannelThreadManager, ChannelThreadsMixin, ChannelTopicMixin, ChannelWebhooksMixin, ClientUser, DMChannel, DMChannelMixin, DispatchAction, DispatchHandlers, DispatchQueue, DispatchTimeoutError, Embed, Emoji, EventGatewayListener, ForumChannel, GatewayClient, GroupDMChannel, GroupDMInvite, GroupDMMixin, Guild, GuildAuditLogsEntry, GuildBan, GuildBanManager, GuildChannelManager, GuildChannelMixin, GuildEmoji, GuildEmojiManager, GuildEmojiRoleManager, GuildIntegrationManager, GuildInvite, GuildInviteManager, GuildManager, GuildMember, GuildMemberFlagsBitField, GuildMemberManager, GuildMemberRoleManager, GuildMembersRateLimitError, GuildMembersTimeoutError, GuildOnboarding, GuildOnboardingPrompt, GuildOnboardingPromptOption, GuildPreview, GuildScheduledEvent, GuildScheduledEventManager, GuildSoundboardSoundManager, GuildStickerManager, GuildTemplate, GuildTemplateManager, Integration, IntentsBitField, InviteGuild, MediaChannel, Message, MessageFlagsBitField, MessageManager, MessageMentions, MessageReaction, Mixin, MultiDispatchAction, MultiDispatchHandlers, NumberFlagsBitField, PermissionOverwriteManager, PermissionOverwrites, PermissionsBitField, Poll, PollAnswer, Presence, PresenceManager, PrivateThreadChannel, PublicThreadChannel, ReactionEmoji, ReactionManager, ReactionUserManager, RegisterAsGatewayListener, RichPresenceAssets, Role, RoleFlagsBitField, RoleManager, SoundboardSound, StageChannel, StageInstance, StageInstanceManager, Sticker, StickerPack, Structure, SystemChannelFlagsBitField, TextChannel, TextChannelMixin, ThreadChannelMemberManager, ThreadChannelMixin, ThreadManager, ThreadMember, ThreadMemberManager, ThreadOnlyChannelMixin, Typing, User, UserFlagsBitField, UserManager, VoiceChannel, VoiceChannelMixin, VoiceState, VoiceStateManager, Webhook, WebhookManager, WelcomeScreen, Widget, WidgetMember, compareRolePositions, computeChannelPermissions, computeGuildPermissions, computePermissionsIn, computeTargetPermissions, createChannel, createInvite, dispatchPartition, getGatewayClient, isDeepEqual, isThreadChannelType, kClone, kData, kPatch, kRelations, resolveId, resolveMessageOptions, resolveOverwriteOptions, snowflakeTimestamp, toAPIOverwrite, toChannelBody };
|
|
11691
|
+
export { Action, ActionsManager, Activity, ActivityFlagsBitField, AnnouncementChannel, AnnouncementThreadChannel, AnonymousGuild, AppliedTagsMixin, Attachment, AttachmentFlagsBitField, AutoModerationActionExecution, AutoModerationRule, AutoModerationRuleManager, BaseChannel, BaseChannelMixin, BaseInvite, BitField, CachedManager, CategoryChannel, Channel, ChannelFlagsBitField, ChannelManager, ChannelMessageManager, ChannelOwnerMixin, ChannelParentMixin, ChannelPermissionMixin, ChannelSlowmodeMixin, ChannelThreadManager, ChannelThreadsMixin, ChannelTopicMixin, ChannelWebhooksMixin, ClientUser, DMChannel, DMChannelMixin, DispatchAction, DispatchHandlers, DispatchQueue, DispatchTimeoutError, Embed, Emoji, EventGatewayListener, ForumChannel, GatewayClient, GatewaySessionStoreError, GroupDMChannel, GroupDMInvite, GroupDMMixin, Guild, GuildAuditLogsEntry, GuildBan, GuildBanManager, GuildChannelManager, GuildChannelMixin, GuildEmoji, GuildEmojiManager, GuildEmojiRoleManager, GuildIntegrationManager, GuildInvite, GuildInviteManager, GuildManager, GuildMember, GuildMemberFlagsBitField, GuildMemberManager, GuildMemberRoleManager, GuildMembersRateLimitError, GuildMembersTimeoutError, GuildOnboarding, GuildOnboardingPrompt, GuildOnboardingPromptOption, GuildPreview, GuildScheduledEvent, GuildScheduledEventManager, GuildSoundboardSoundManager, GuildStickerManager, GuildTemplate, GuildTemplateManager, Integration, IntentsBitField, InviteGuild, MediaChannel, Message, MessageFlagsBitField, MessageManager, MessageMentions, MessageReaction, Mixin, MultiDispatchAction, MultiDispatchHandlers, NumberFlagsBitField, PermissionOverwriteManager, PermissionOverwrites, PermissionsBitField, Poll, PollAnswer, Presence, PresenceManager, PrivateThreadChannel, PublicThreadChannel, ReactionEmoji, ReactionManager, ReactionUserManager, RegisterAsGatewayListener, RichPresenceAssets, Role, RoleFlagsBitField, RoleManager, SoundboardSound, StageChannel, StageInstance, StageInstanceManager, Sticker, StickerPack, Structure, SystemChannelFlagsBitField, TextChannel, TextChannelMixin, ThreadChannelMemberManager, ThreadChannelMixin, ThreadManager, ThreadMember, ThreadMemberManager, ThreadOnlyChannelMixin, Typing, User, UserFlagsBitField, UserManager, VoiceChannel, VoiceChannelMixin, VoiceState, VoiceStateManager, Webhook, WebhookManager, WelcomeScreen, Widget, WidgetMember, compareRolePositions, computeChannelPermissions, computeGuildPermissions, computePermissionsIn, computeTargetPermissions, createChannel, createInvite, dispatchPartition, getGatewayClient, isDeepEqual, isThreadChannelType, kClone, kData, kPatch, kRelations, resolveId, resolveMessageOptions, resolveOverwriteOptions, snowflakeTimestamp, toAPIOverwrite, toChannelBody };
|
|
11551
11692
|
//# sourceMappingURL=index.js.map
|