@wolfstar/plugin-gateway 0.14.0 → 0.15.0-next-20261017094524

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/dist/esm/index.js CHANGED
@@ -16189,6 +16189,19 @@ var Typing = class extends Structure {
16189
16189
  //#endregion
16190
16190
  //#region src/util/events.ts
16191
16191
  /**
16192
+ * The {@link GatewayOnlyEventName} events, for telling them from {@link DispatchEventName} ones at runtime.
16193
+ */
16194
+ const gatewayOnlyEvents = /* @__PURE__ */ new Set([
16195
+ "dispatch",
16196
+ "shardReady",
16197
+ "clientReady",
16198
+ "shardResume",
16199
+ "shardClose",
16200
+ "shardError",
16201
+ "cacheError",
16202
+ "cacheSweep"
16203
+ ]);
16204
+ /**
16192
16205
  * The name of every event a {@link GatewayClient} emits, mirroring the keys of {@link GatewayEventMap}, like
16193
16206
  * `@wolfstar/http-framework`'s own `Events`.
16194
16207
  *
@@ -17975,6 +17988,7 @@ var GatewayClient = class extends Client {
17975
17988
  */
17976
17989
  clientReadyTimestamp = null;
17977
17990
  #queue = new DispatchQueue();
17991
+ #dispatchForwarders = 0;
17978
17992
  #caches = /* @__PURE__ */ new Map();
17979
17993
  #cacheConstructor;
17980
17994
  #cacheOptions;
@@ -17984,7 +17998,7 @@ var GatewayClient = class extends Client {
17984
17998
  #shardCount;
17985
17999
  #intents;
17986
18000
  #token;
17987
- #expectedGuilds = /* @__PURE__ */ new Set();
18001
+ #expectedGuilds = /* @__PURE__ */ new Map();
17988
18002
  #clientReadyTimer = null;
17989
18003
  #userRestore = null;
17990
18004
  #sessions = null;
@@ -18351,6 +18365,29 @@ var GatewayClient = class extends Client {
18351
18365
  return emitAndWait(this, event, args);
18352
18366
  }
18353
18367
  /**
18368
+ * Whether the dispatches of this client are forwarded to workers, which replay them on their own listeners, see
18369
+ * `forwardGatewayDispatches` from `@wolfstar/plugin-broker`. Listeners registered with
18370
+ * {@link RegisterAsDispatchListener} stay idle on a client that does.
18371
+ */
18372
+ get forwardsDispatches() {
18373
+ return this.#dispatchForwarders > 0;
18374
+ }
18375
+ /**
18376
+ * Marks this client as forwarding its dispatches, until the returned function is called. Called by
18377
+ * `forwardGatewayDispatches`, it counts, so the client keeps forwarding while any forwarder is left.
18378
+ *
18379
+ * @returns A function to undo the mark, which does nothing when called again.
18380
+ */
18381
+ markDispatchesForwarded() {
18382
+ this.#dispatchForwarders++;
18383
+ let marked = true;
18384
+ return () => {
18385
+ if (!marked) return;
18386
+ marked = false;
18387
+ this.#dispatchForwarders--;
18388
+ };
18389
+ }
18390
+ /**
18354
18391
  * The dispatch types {@link GatewayClient.replayDispatch} turns into events.
18355
18392
  */
18356
18393
  get replayDispatchTypes() {
@@ -18407,7 +18444,11 @@ var GatewayClient = class extends Client {
18407
18444
  const isReady = payload.t === GatewayDispatchEvents.Ready;
18408
18445
  if (isReady) {
18409
18446
  await this.reconcileGuilds(payload.d, shardId);
18410
- for (const guild of payload.d.guilds) this.#expectedGuilds.add(guild.id);
18447
+ if (this.clientReadyTimestamp === null) {
18448
+ const expected = new Set(payload.d.guilds.map((guild) => guild.id));
18449
+ if (expected.size > 0) this.#expectedGuilds.set(shardId, expected);
18450
+ else this.#expectedGuilds.delete(shardId);
18451
+ }
18411
18452
  }
18412
18453
  let state;
18413
18454
  try {
@@ -18422,9 +18463,15 @@ var GatewayClient = class extends Client {
18422
18463
  await action?.handle(payload.d, state, shardId);
18423
18464
  if (this.clientReadyTimestamp === null) {
18424
18465
  if (isReady) await this.#checkClientReady();
18425
- else if ((payload.t === GatewayDispatchEvents.GuildCreate || payload.t === GatewayDispatchEvents.GuildDelete) && this.#expectedGuilds.delete(payload.d.id)) await this.#checkClientReady();
18466
+ else if ((payload.t === GatewayDispatchEvents.GuildCreate || payload.t === GatewayDispatchEvents.GuildDelete) && this.#forgetExpectedGuild(shardId, payload.d.id)) await this.#checkClientReady();
18426
18467
  }
18427
18468
  }
18469
+ #forgetExpectedGuild(shardId, guildId) {
18470
+ const expected = this.#expectedGuilds.get(shardId);
18471
+ if (!expected?.delete(guildId)) return false;
18472
+ if (expected.size === 0) this.#expectedGuilds.delete(shardId);
18473
+ return true;
18474
+ }
18428
18475
  async #readyAfterResume(shardId) {
18429
18476
  await this.#restoreUser().catch((error) => this.reportError(error, "RESUMED", shardId));
18430
18477
  await this.#checkClientReady().catch((error) => this.reportError(error, "RESUMED", shardId));
@@ -18485,12 +18532,17 @@ var GatewayClient = class extends Client {
18485
18532
  this.emit("clientReady", this);
18486
18533
  }
18487
18534
  /**
18488
- * Drops the cached guilds of a shard its `READY` no longer lists, and emits `guildDelete` for each of them.
18535
+ * Drops the cached guilds of a shard its `READY` no longer lists, and emits `dispatch` and `guildDelete` for each of
18536
+ * them.
18489
18537
  *
18490
18538
  * @remarks
18491
18539
  * They are the guilds the bot left while it was disconnected, or while the process was down with a persistent
18492
18540
  * cache. Discord does not replay those removals on a new session, so the cache would otherwise keep them forever.
18493
18541
  *
18542
+ * Each drop is handled as a `GUILD_DELETE` (with `s: 0`) Discord would have sent, `raw` aside: `dispatch` fires once
18543
+ * the guild is gone from the cache, with the cached guild as its state, so forwarding `dispatch` to other processes
18544
+ * (`@wolfstar/plugin-broker`'s `forwardGatewayDispatches`) carries these deletions too.
18545
+ *
18494
18546
  * It needs a guilds cache able to enumerate its entries, and is skipped without one.
18495
18547
  *
18496
18548
  * It is best effort: any failure (cache unreachable, unknown shard count) is reported through `error` and stops the
@@ -18515,16 +18567,19 @@ var GatewayClient = class extends Client {
18515
18567
  const count = data.shard?.[1] ?? this.#shardCount ?? await this.gateway.getShardCount();
18516
18568
  const listed = new Set(data.guilds.map((guild) => guild.id));
18517
18569
  const shardCount = BigInt(count);
18570
+ const action = this.actions.get(GatewayDispatchEvents.GuildDelete);
18518
18571
  for (const id of cached) {
18519
18572
  if (listed.has(id) || Number((BigInt(id) >> 22n) % shardCount) !== shardId) continue;
18520
- const guild = await this.guilds.cache.get(id) ?? null;
18521
- await applyGatewayDispatch(cache, {
18573
+ const payload = {
18522
18574
  op: GatewayOpcodes.Dispatch,
18523
18575
  s: 0,
18524
18576
  t: GatewayDispatchEvents.GuildDelete,
18525
18577
  d: { id }
18526
- });
18527
- this.emit("guildDelete", guild, { id });
18578
+ };
18579
+ const state = await action?.before(payload.d);
18580
+ await applyGatewayDispatch(cache, payload);
18581
+ this.emit("dispatch", payload, shardId, state);
18582
+ await action?.handle(payload.d, state, shardId);
18528
18583
  }
18529
18584
  }
18530
18585
  async runDispatch(payload, shardId, partition) {
@@ -18790,6 +18845,55 @@ function RegisterAsGatewayListener(event, options = {}) {
18790
18845
  };
18791
18846
  };
18792
18847
  }
18848
+ /**
18849
+ * Like {@link RegisterAsGatewayListener}, for an event that comes from a dispatch, but the listener only runs on one
18850
+ * process: the workers replaying the dispatches while the gateway client forwards them (see `forwardGatewayDispatches`
18851
+ * from `@wolfstar/plugin-broker`), the gateway client itself otherwise. A bot loading the same pieces on both
18852
+ * processes therefore handles each dispatch once, without reading its environment to tell which process it is on.
18853
+ *
18854
+ * @remarks
18855
+ * The decision is made on every emit, so it does not matter whether the forwarding starts before or after the
18856
+ * listener is loaded, and stopping it brings the listener back. A `once` listener is not spent while it idles.
18857
+ *
18858
+ * The events only the gateway client emits (`clientReady`, `shardReady`, `shardResume`, `shardClose`, `shardError`,
18859
+ * `dispatch`, `cacheError`, `cacheSweep`) are never replayed on a worker: they are rejected by the types, and the
18860
+ * decorator throws for them. Use {@link RegisterAsGatewayListener} for those.
18861
+ *
18862
+ * Only the dispatches that are forwarded reach the workers: with `forwardGatewayDispatches`'s `events` set, a listener
18863
+ * of an event built from another dispatch runs nowhere. A `guildDelete` that `READY` emits for the guilds it no longer
18864
+ * lists is not forwarded either (wolfstar-project/plugins#243), so a listener on the workers misses it. Delivery to the
18865
+ * workers is at-least-once, so the listener should be idempotent.
18866
+ *
18867
+ * @example
18868
+ * ```typescript
18869
+ * import { EventGatewayListener, RegisterAsDispatchListener, type Message } from '@wolfstar/plugin-gateway';
18870
+ *
18871
+ * @RegisterAsDispatchListener('messageCreate')
18872
+ * export class LogMessagesListener extends EventGatewayListener<'messageCreate'> {
18873
+ * public override run(message: Message) {
18874
+ * console.log(`${message.author.username}: ${message.content}`);
18875
+ * }
18876
+ * }
18877
+ * ```
18878
+ *
18879
+ * @param event The dispatch event to listen to.
18880
+ * @param options The additional listener options.
18881
+ * @throws {TypeError} When the event is one only the gateway client emits.
18882
+ */
18883
+ function RegisterAsDispatchListener(event, options = {}) {
18884
+ if (gatewayOnlyEvents.has(event)) throw new TypeError(`The "${event}" event is only emitted by the gateway client, and never reaches a worker: use RegisterAsGatewayListener.`);
18885
+ const register = RegisterAsGatewayListener(event, options);
18886
+ return function decorate(target) {
18887
+ const base = register(target);
18888
+ return class extends base {
18889
+ constructor(...args) {
18890
+ super(...args);
18891
+ const listener = this._listener;
18892
+ this._listener = (...listenerArgs) => this.emitter.forwardsDispatches ? void 0 : listener(...listenerArgs);
18893
+ }
18894
+ };
18895
+ };
18896
+ }
18793
18897
 
18794
18898
  //#endregion
18795
18899
  //#region src/util/limits.ts
@@ -18835,5 +18939,5 @@ const DefaultSweeperSettings = Object.freeze({
18835
18939
  });
18836
18940
 
18837
18941
  //#endregion
18838
- export { Action, ActionRow, ActionsManager, Activity, ActivityFlagsBitField, AnnouncementChannel, AnnouncementThreadChannel, AnonymousGuild, Application, ApplicationFlagsBitField, AppliedTagsMixin, Attachment, AttachmentBuilder, AttachmentFlagsBitField, AutoModerationActionExecution, AutoModerationRule, AutoModerationRuleManager, BaseChannel, BaseChannelMixin, BaseInvite, BaseManager, BitField, CachedManager, CategoryChannel, CategoryChannelChildManager, Channel, ChannelFlagsBitField, ChannelManager, ChannelMessageManager, ChannelOwnerMixin, ChannelParentMixin, ChannelPermissionMixin, ChannelSelectMenuComponent, ChannelSlowmodeMixin, ChannelThreadManager, ChannelThreadsMixin, ChannelTopicMixin, ChannelWebhooksMixin, CheckboxComponent, CheckboxGroupComponent, ClientApplication, ClientUser, CollectionCache, Colors, Component, ComponentMixin, ContainerComponent, DMChannel, DMChannelMixin, DataManager, DefaultSweeperSettings, DispatchAction, DispatchHandlers, DispatchQueue, DispatchStateCodecs, DispatchTimeoutError, Embed, Emoji, EntityStoreCache, EventGatewayListener, FileComponent, FileUploadComponent, ForumChannel, GatewayClient, GatewayError, GatewayErrorCodes, GatewayErrorMessages, GatewayEvents, GatewayRangeError, GatewaySessionStoreError, GatewayTypeError, GroupDMChannel, GroupDMInvite, GroupDMMixin, Guild, GuildAuditLogsEntry, GuildBan, GuildBanManager, GuildChannelFields, GuildChannelInfoTimeoutError, GuildChannelManager, GuildChannelMixin, GuildEmoji, GuildEmojiManager, GuildEmojiRoleManager, GuildIntegrationManager, GuildInvite, GuildInviteManager, GuildManager, GuildMember, GuildMemberFlagsBitField, GuildMemberManager, GuildMemberRoleManager, GuildMembersRateLimitError, GuildMembersTimeoutError, GuildOnboarding, GuildOnboardingPrompt, GuildOnboardingPromptOption, GuildPreview, GuildScheduledEvent, GuildScheduledEventManager, GuildSoundboardSoundManager, GuildSoundboardSoundsTimeoutError, GuildStickerManager, GuildTemplate, GuildTemplateManager, GuildTemplatesPattern, Integration, IntentsBitField, InteractiveButtonComponent, InviteGuild, InvitesPattern, LabelComponent, LinkButtonComponent, MediaChannel, MediaGalleryComponent, MediaGalleryItem, MentionableSelectMenuComponent, Message, MessageFlagsBitField, MessageManager, MessageMentions, MessagePayload, MessageReaction, Mixin, MultiDispatchAction, MultiDispatchHandlers, NullCache, NumberFlagsBitField, Partials, PermissionOverwriteManager, PermissionOverwrites, PermissionsBitField, Poll, PollAnswer, PollAnswerVoterManager, PremiumButtonComponent, Presence, PresenceManager, PrivateThreadChannel, PublicThreadChannel, RadioGroupComponent, ReactionEmoji, ReactionManager, ReactionUserManager, RegisterAsGatewayListener, RichPresenceAssets, Role, RoleFlagsBitField, RoleManager, RoleSelectMenuComponent, SectionComponent, SeparatorComponent, SoundboardSound, StageChannel, StageInstance, StageInstanceManager, Sticker, StickerPack, StringSelectMenuComponent, Structure, StructureMixin, SweepableEntityNames, Sweepers, SystemChannelFlagsBitField, Team, TeamMember, TextChannel, TextChannelMixin, TextDisplayComponent, TextGuildChannelMixin, TextInputComponent, ThreadChannelMemberManager, ThreadChannelMixin, ThreadManager, ThreadMember, ThreadMemberManager, ThreadOnlyChannelMixin, ThumbnailComponent, Typing, UnfurledMediaItem, User, UserFlagsBitField, UserManager, UserSelectMenuComponent, VoiceChannel, VoiceChannelMixin, VoiceState, VoiceStateManager, Webhook, WebhookManager, WebhookMixin, WelcomeScreen, Widget, WidgetMember, basename, bindClient, cacheWithLimits, cleanCodeBlockContent, cleanContent, compareRolePositions, computeCachedPermissionsIn, computeCachedTargetPermissions, computeChannelPermissions, computeGuildPermissions, computePermissionsIn, computePositions, computeTargetPermissions, createChannel, createComponent, createInvite, discordSort, dispatchPartition, fillGuildId, findComponentByCustomId, findName, flatten, getGatewayClient, getSortableGroupTypes, initStructure, isDeepEqual, isThreadChannelType, kClient, kClone, kData, kPatch, kPatchRelations, kRelations, lazyRelation, makeError, makePlainError, moveElementInArray, parseEmoji, parseWebhookURL, requireGuild, requireMe, resolveBase64, resolveCode, resolveColor, resolveFile, resolveGuildEmoji, resolveGuildTemplateCode, resolveId, resolveImage, resolveImageOption, resolveInviteCode, resolveOverwriteOptions, resolvePartialEmoji, resolveSKUId, sameOverwrites, snakeCase, snowflakeTimestamp, toAPIOverwrite, toChannelBody, toSnakeCase, transformAPIAuditLogChange, transformAPIAutoModerationAction, transformAPIAutoModerationRuleTriggerMetadata, transformAPIAvatarDecorationData, transformAPIChannelMention, transformAPIEmbedAsset, transformAPIEmbedAuthor, transformAPIEmbedFooter, transformAPIGuildDefaultReaction, transformAPIGuildForumTag, transformAPIGuildScheduledEventEntityMetadata, transformAPIGuildScheduledEventRecurrenceRule, transformAPIIncidentsData, transformAPIMessageActivity, transformAPIMessageCall, transformAPIMessageInteractionMetadata, transformAPIMessageReference, transformAPIMessageSharedClientTheme, transformAPIRoleSubscriptionData, transformAPIRoleTags, transformAPIUserPrimaryGuild, transformAutoModerationAction, transformAutoModerationRuleTriggerMetadata, transformCollectibles, transformGuildDefaultReaction, transformGuildForumTag, transformGuildScheduledEventRecurrenceRule, transformResolved, verifyString, withGuildId };
18942
+ export { Action, ActionRow, ActionsManager, Activity, ActivityFlagsBitField, AnnouncementChannel, AnnouncementThreadChannel, AnonymousGuild, Application, ApplicationFlagsBitField, AppliedTagsMixin, Attachment, AttachmentBuilder, AttachmentFlagsBitField, AutoModerationActionExecution, AutoModerationRule, AutoModerationRuleManager, BaseChannel, BaseChannelMixin, BaseInvite, BaseManager, BitField, CachedManager, CategoryChannel, CategoryChannelChildManager, Channel, ChannelFlagsBitField, ChannelManager, ChannelMessageManager, ChannelOwnerMixin, ChannelParentMixin, ChannelPermissionMixin, ChannelSelectMenuComponent, ChannelSlowmodeMixin, ChannelThreadManager, ChannelThreadsMixin, ChannelTopicMixin, ChannelWebhooksMixin, CheckboxComponent, CheckboxGroupComponent, ClientApplication, ClientUser, CollectionCache, Colors, Component, ComponentMixin, ContainerComponent, DMChannel, DMChannelMixin, DataManager, DefaultSweeperSettings, DispatchAction, DispatchHandlers, DispatchQueue, DispatchStateCodecs, DispatchTimeoutError, Embed, Emoji, EntityStoreCache, EventGatewayListener, FileComponent, FileUploadComponent, ForumChannel, GatewayClient, GatewayError, GatewayErrorCodes, GatewayErrorMessages, GatewayEvents, GatewayRangeError, GatewaySessionStoreError, GatewayTypeError, GroupDMChannel, GroupDMInvite, GroupDMMixin, Guild, GuildAuditLogsEntry, GuildBan, GuildBanManager, GuildChannelFields, GuildChannelInfoTimeoutError, GuildChannelManager, GuildChannelMixin, GuildEmoji, GuildEmojiManager, GuildEmojiRoleManager, GuildIntegrationManager, GuildInvite, GuildInviteManager, GuildManager, GuildMember, GuildMemberFlagsBitField, GuildMemberManager, GuildMemberRoleManager, GuildMembersRateLimitError, GuildMembersTimeoutError, GuildOnboarding, GuildOnboardingPrompt, GuildOnboardingPromptOption, GuildPreview, GuildScheduledEvent, GuildScheduledEventManager, GuildSoundboardSoundManager, GuildSoundboardSoundsTimeoutError, GuildStickerManager, GuildTemplate, GuildTemplateManager, GuildTemplatesPattern, Integration, IntentsBitField, InteractiveButtonComponent, InviteGuild, InvitesPattern, LabelComponent, LinkButtonComponent, MediaChannel, MediaGalleryComponent, MediaGalleryItem, MentionableSelectMenuComponent, Message, MessageFlagsBitField, MessageManager, MessageMentions, MessagePayload, MessageReaction, Mixin, MultiDispatchAction, MultiDispatchHandlers, NullCache, NumberFlagsBitField, Partials, PermissionOverwriteManager, PermissionOverwrites, PermissionsBitField, Poll, PollAnswer, PollAnswerVoterManager, PremiumButtonComponent, Presence, PresenceManager, PrivateThreadChannel, PublicThreadChannel, RadioGroupComponent, ReactionEmoji, ReactionManager, ReactionUserManager, RegisterAsDispatchListener, RegisterAsGatewayListener, RichPresenceAssets, Role, RoleFlagsBitField, RoleManager, RoleSelectMenuComponent, SectionComponent, SeparatorComponent, SoundboardSound, StageChannel, StageInstance, StageInstanceManager, Sticker, StickerPack, StringSelectMenuComponent, Structure, StructureMixin, SweepableEntityNames, Sweepers, SystemChannelFlagsBitField, Team, TeamMember, TextChannel, TextChannelMixin, TextDisplayComponent, TextGuildChannelMixin, TextInputComponent, ThreadChannelMemberManager, ThreadChannelMixin, ThreadManager, ThreadMember, ThreadMemberManager, ThreadOnlyChannelMixin, ThumbnailComponent, Typing, UnfurledMediaItem, User, UserFlagsBitField, UserManager, UserSelectMenuComponent, VoiceChannel, VoiceChannelMixin, VoiceState, VoiceStateManager, Webhook, WebhookManager, WebhookMixin, WelcomeScreen, Widget, WidgetMember, basename, bindClient, cacheWithLimits, cleanCodeBlockContent, cleanContent, compareRolePositions, computeCachedPermissionsIn, computeCachedTargetPermissions, computeChannelPermissions, computeGuildPermissions, computePermissionsIn, computePositions, computeTargetPermissions, createChannel, createComponent, createInvite, discordSort, dispatchPartition, fillGuildId, findComponentByCustomId, findName, flatten, gatewayOnlyEvents, getGatewayClient, getSortableGroupTypes, initStructure, isDeepEqual, isThreadChannelType, kClient, kClone, kData, kPatch, kPatchRelations, kRelations, lazyRelation, makeError, makePlainError, moveElementInArray, parseEmoji, parseWebhookURL, requireGuild, requireMe, resolveBase64, resolveCode, resolveColor, resolveFile, resolveGuildEmoji, resolveGuildTemplateCode, resolveId, resolveImage, resolveImageOption, resolveInviteCode, resolveOverwriteOptions, resolvePartialEmoji, resolveSKUId, sameOverwrites, snakeCase, snowflakeTimestamp, toAPIOverwrite, toChannelBody, toSnakeCase, transformAPIAuditLogChange, transformAPIAutoModerationAction, transformAPIAutoModerationRuleTriggerMetadata, transformAPIAvatarDecorationData, transformAPIChannelMention, transformAPIEmbedAsset, transformAPIEmbedAuthor, transformAPIEmbedFooter, transformAPIGuildDefaultReaction, transformAPIGuildForumTag, transformAPIGuildScheduledEventEntityMetadata, transformAPIGuildScheduledEventRecurrenceRule, transformAPIIncidentsData, transformAPIMessageActivity, transformAPIMessageCall, transformAPIMessageInteractionMetadata, transformAPIMessageReference, transformAPIMessageSharedClientTheme, transformAPIRoleSubscriptionData, transformAPIRoleTags, transformAPIUserPrimaryGuild, transformAutoModerationAction, transformAutoModerationRuleTriggerMetadata, transformCollectibles, transformGuildDefaultReaction, transformGuildForumTag, transformGuildScheduledEventRecurrenceRule, transformResolved, verifyString, withGuildId };
18839
18943
  //# sourceMappingURL=index.js.map