@seedcord/core 0.12.0-next.0 → 0.12.0-next.2

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.
Files changed (41) hide show
  1. package/README.md +3 -3
  2. package/dist/{Bus-W1K-uMeO.mjs → Bus-VJw-QpoY.mjs} +110 -54
  3. package/dist/Bus-VJw-QpoY.mjs.map +1 -0
  4. package/dist/{CoordinatedShutdown-BVtNNSsF.d.mts → CoordinatedShutdown-fPKLWjc5.d.mts} +2 -2
  5. package/dist/{CoordinatedStartup-DA3gW8op.mjs → CoordinatedStartup-Zw_Lk7Zk.mjs} +3 -3
  6. package/dist/{CoordinatedStartup-DA3gW8op.mjs.map → CoordinatedStartup-Zw_Lk7Zk.mjs.map} +1 -1
  7. package/dist/HmrModuleHandler-Btgu8r96.mjs +165 -0
  8. package/dist/HmrModuleHandler-Btgu8r96.mjs.map +1 -0
  9. package/dist/{PluginHost-DWBgBS6Q.d.mts → PluginHost-DFnkIldj.d.mts} +10 -9
  10. package/dist/{PluginHost-CYe-UVpp.mjs → PluginHost-DTtxWRiL.mjs} +4 -4
  11. package/dist/PluginHost-DTtxWRiL.mjs.map +1 -0
  12. package/dist/{PluginLifecycle-DdwRzcDj.mjs → PluginLifecycle-hx5zUc-P.mjs} +4 -4
  13. package/dist/PluginLifecycle-hx5zUc-P.mjs.map +1 -0
  14. package/dist/{ServerHost-whpI0h4d.d.mts → ServerHost-B5o3ScCF.d.mts} +3 -3
  15. package/dist/{ServerHost-DHg-Y2_q.mjs → ServerHost-CDaEDR4C.mjs} +4 -4
  16. package/dist/{ServerHost-DHg-Y2_q.mjs.map → ServerHost-CDaEDR4C.mjs.map} +1 -1
  17. package/dist/hmr.index.d.mts +20 -20
  18. package/dist/hmr.index.mjs +1 -1
  19. package/dist/{index-Boo33hh3.d.mts → index-DGHng1IN.d.mts} +108 -193
  20. package/dist/index.d.mts +216 -71
  21. package/dist/index.mjs +215 -100
  22. package/dist/index.mjs.map +1 -1
  23. package/dist/internal.index.d.mts +204 -82
  24. package/dist/internal.index.mjs +572 -73
  25. package/dist/internal.index.mjs.map +1 -1
  26. package/dist/node-internal.index.d.mts +4 -3
  27. package/dist/node-internal.index.mjs +3 -3
  28. package/dist/node.index.d.mts +2 -2
  29. package/dist/node.index.mjs +4 -4
  30. package/dist/{phases-Dojnr-ng.d.mts → phases-C2QCSqRu.d.mts} +60 -50
  31. package/dist/plugin.index.d.mts +1 -1
  32. package/dist/plugin.index.mjs +1 -1
  33. package/dist/{subscribers-D4_HZcgw.mjs → subscribers-CGuVjbrg.mjs} +139 -253
  34. package/dist/subscribers-CGuVjbrg.mjs.map +1 -0
  35. package/package.json +10 -9
  36. package/dist/Bus-W1K-uMeO.mjs.map +0 -1
  37. package/dist/HmrModuleHandler-BMD7dg8N.mjs +0 -162
  38. package/dist/HmrModuleHandler-BMD7dg8N.mjs.map +0 -1
  39. package/dist/PluginHost-CYe-UVpp.mjs.map +0 -1
  40. package/dist/PluginLifecycle-DdwRzcDj.mjs.map +0 -1
  41. package/dist/subscribers-D4_HZcgw.mjs.map +0 -1
@@ -1,17 +1,22 @@
1
1
  import { Logger } from "@seedcord/logger";
2
2
  import { HmrUpdateEvent } from "@seedcord/types";
3
3
  //#region src/hmr/HmrModuleHandler.d.ts
4
- interface HmrModuleHandlerOptions<THandler, TMiddleware = void, TArtifacts = unknown> {
5
- handlersDir: string;
6
- middlewaresDir?: string;
7
- isHandler: (val: unknown) => val is THandler;
8
- isMiddleware?: (val: unknown) => val is TMiddleware;
9
- registerHandler: (handler: THandler, file: string) => void;
10
- registerMiddleware?: (middleware: TMiddleware, file: string) => void;
11
- unregisterHandler: (handler: THandler, artifacts?: TArtifacts) => void;
12
- unregisterMiddleware?: (middleware: TMiddleware) => void;
13
- /** Snapshotted per handler so a rollback re-registers with the same artifacts after the module re-imports. */
4
+ interface HmrFolderOptions<TUnit> {
5
+ dir: string;
6
+ is: (val: unknown) => val is TUnit;
7
+ register: (unit: TUnit, file: string) => void;
8
+ }
9
+ interface HmrHandlerOptions<THandler, TArtifacts> extends HmrFolderOptions<THandler> {
10
+ unregister: (handler: THandler, artifacts?: TArtifacts) => void;
11
+ /** Called each time a handler is tracked. `unregister` receives the result as its second argument. */
14
12
  getArtifacts?: (handler: THandler) => TArtifacts;
13
+ }
14
+ interface HmrMiddlewareOptions<TMiddleware> extends HmrFolderOptions<TMiddleware> {
15
+ unregister: (middleware: TMiddleware) => void;
16
+ }
17
+ interface HmrModuleHandlerOptions<THandler, TMiddleware = void, TArtifacts = unknown> {
18
+ handlers: HmrHandlerOptions<THandler, TArtifacts>;
19
+ middlewares?: HmrMiddlewareOptions<TMiddleware>;
15
20
  logger: Logger;
16
21
  }
17
22
  /**
@@ -22,22 +27,17 @@ interface HmrModuleHandlerOptions<THandler, TMiddleware = void, TArtifacts = unk
22
27
  * This is only useful during development.
23
28
  */
24
29
  export declare class HmrModuleHandler<THandler, TMiddleware = void, TArtifacts = unknown> {
25
- private readonly store;
26
- private readonly options;
30
+ #private;
27
31
  constructor(options: HmrModuleHandlerOptions<THandler, TMiddleware, TArtifacts>);
28
- /** Unloads or reloads the modules affected by an {@link HmrUpdateEvent}, scoped to the handler and middleware directories. */
32
+ /**
33
+ * Unloads or reloads the modules affected by an {@link HmrUpdateEvent}, scoped to the handler and middleware directories.
34
+ * When that unloads something, it also retries every file that failed earlier on a duplicate name.
35
+ */
29
36
  handle(event: HmrUpdateEvent): Promise<void>;
30
- private inScope;
31
- private report;
32
- private reloadWithRollback;
33
37
  /** Links the handler to its source file so a later update can unload it. */
34
38
  trackHandler(file: string, handler: THandler): void;
35
39
  /** Links the middleware to its source file so a later update can unload it. */
36
40
  trackMiddleware(file: string, middleware: TMiddleware): void;
37
- private unload;
38
- private reloadFile;
39
- private snapshotUnits;
40
- private restoreUnits;
41
41
  }
42
42
  //#endregion
43
43
  //# sourceMappingURL=hmr.index.d.mts.map
@@ -1,3 +1,3 @@
1
- import { t as HmrModuleHandler } from "./HmrModuleHandler-BMD7dg8N.mjs";
1
+ import { t as HmrModuleHandler } from "./HmrModuleHandler-Btgu8r96.mjs";
2
2
 
3
3
  export { HmrModuleHandler };
@@ -1,10 +1,11 @@
1
- import { K as MiddlewareKind, L as DispatchContext, Y as Notice, b as SubscriptionKey, i as CoreBase, u as Subscriber } from "./phases-Dojnr-ng.mjs";
1
+ import { I as DispatchContext, K as MiddlewareKind, c as AllSubscriptions, i as CoreBase, y as SubscriptionKey } from "./phases-C2QCSqRu.mjs";
2
2
  import "reflect-metadata";
3
3
  import { CustomId, CustomIdField } from "@seedcord/custom-id";
4
4
  import { ActionRowBuilder, ButtonBuilder, ChannelSelectMenuBuilder, CheckboxBuilder, CheckboxGroupBuilder, CheckboxGroupOptionBuilder, ContainerBuilder, ContextMenuCommandBuilder, EmbedBuilder, FileBuilder, FileUploadBuilder, LabelBuilder, MediaGalleryBuilder, MentionableSelectMenuBuilder, ModalBuilder, RadioGroupBuilder, RadioGroupOptionBuilder, RoleSelectMenuBuilder, SectionBuilder, SeparatorBuilder, SlashCommandBuilder, SlashCommandSubcommandBuilder, SlashCommandSubcommandGroupBuilder, StringSelectMenuBuilder, StringSelectMenuOptionBuilder, TextDisplayBuilder, TextInputBuilder, UserSelectMenuBuilder } from "@discordjs/builders";
5
5
  import { APIApplicationCommand, APIMessageComponentEmoji, APIMessageTopLevelComponent, ApplicationCommandType, ChannelType, RESTPostAPIApplicationCommandsJSONBody, Snowflake } from "discord-api-types/v10";
6
+ import { Logger } from "@seedcord/logger";
6
7
  import { EventFrequency, ILogger, ReplyFile, ReplyResponse } from "@seedcord/types";
7
- import { Constructor, IsNever, LiteralUnion, NonEmptyTuple, Simplify } from "type-fest";
8
+ import { Constructor, IsNever, IsUnion, LiteralUnion, NonEmptyTuple, Promisable, Simplify } from "type-fest";
8
9
  //#region src/registries/ContextMenuRegistry.d.ts
9
10
  /** The two context-menu command kinds Discord defines. */
10
11
  type ContextMenuKind = ApplicationCommandType.User | ApplicationCommandType.Message;
@@ -471,7 +472,7 @@ declare function serializeReply<TNative>(response: ReplyResponse<TNative> | stri
471
472
  /**
472
473
  * The transport-agnostic gate context, the scalar identity of the acting user resolvable from any
473
474
  * transport's payload. Every field except `core` is nullable, since an event or DM may carry no
474
- * guild or member. Each transport extends it with its own arm. For example, the gateway adds the djs
475
+ * guild or member. Each transport extends it with its own context. For example, the gateway adds the djs
475
476
  * `user`/`guild`/`member` objects and the live interaction or event payload.
476
477
  *
477
478
  * @example
@@ -501,19 +502,9 @@ interface GateContextBase {
501
502
  * channel-scoped permissions the payload contains, and on a gateway event they're the member's guild-level ones.
502
503
  */
503
504
  readonly memberPermissions: bigint | null;
504
- /**
505
- * The bot's permissions in the invoked channel, the wire `app_permissions` value. Null on gateway events.
506
- */
507
- readonly appPermissions: bigint | null;
508
- /**
509
- * The handler's own route, as `kind:route` (`slash:daily`, `button:confirm`). A handler registered
510
- * on several reports the one this dispatch matched. Null when the handler declares none, which
511
- * covers a plain event handler, the unhandled default, and a gate run outside a handler.
512
- */
513
- readonly declaredRoute: string | null;
514
505
  /**
515
506
  * The bag for this dispatch, the same instance the handler and its middleware hold. Every middleware
516
- * runs before the first gate. A gate therefore reads what a middleware wrote.
507
+ * runs before the first gate. A gate can therefore read what a middleware wrote.
517
508
  */
518
509
  readonly dispatch: DispatchContext;
519
510
  }
@@ -535,10 +526,25 @@ interface GuildPermissionsContext extends GateContextBase {
535
526
  */
536
527
  readonly memberGuildPermissions: bigint | null;
537
528
  /**
538
- * The bot's base permission set from the transport's cache. Null when the cached member is unavailable.
529
+ * The bot's base permission set from the transport's cache. It's `0n` in a guild the bot isn't in. It's null
530
+ * when the guild is cached but the bot's member isn't.
539
531
  */
540
532
  readonly appGuildPermissions: bigint | null;
541
533
  }
534
+ /**
535
+ * The context an interaction provides on both transports, where Discord always sends the bot's channel
536
+ * permissions. A gate that requires it fits no event handler.
537
+ *
538
+ * @example
539
+ * ```ts
540
+ * // the default channel scope returns a Gate<AppPermissionsContext>
541
+ * \@Gated(RequireBotPermissions([PermissionFlagsBits.ManageMessages]))
542
+ * ```
543
+ */
544
+ interface AppPermissionsContext extends GateContextBase {
545
+ /** The bot's permissions in the invoked channel, the wire `app_permissions` value. */
546
+ readonly appPermissions: bigint;
547
+ }
542
548
  declare const GateBrand: unique symbol;
543
549
  /**
544
550
  * A precondition attached to a handler. It refuses by throwing a `Notice` (a reply) or a `Silence`
@@ -632,11 +638,11 @@ interface EffectGate<Ctx extends GateContextBase = GateContextBase, Name extends
632
638
  * });
633
639
  * ```
634
640
  */
635
- declare function defineGate<const Name extends string, Ctx extends GateContextBase = GateContextBase>(name: Name, fn: (ctx: Ctx) => void | Promise<void>): Gate<Ctx, Name>;
641
+ declare function defineGate<const Name extends string, Ctx extends GateContextBase = GateContextBase>(name: Name, fn: (ctx: Ctx) => Promisable<void>): Gate<Ctx, Name>;
636
642
  /**
637
643
  * Builds an {@link EffectGate} from a `check` and a `commit`. `check` peeks and refuses by throwing, and
638
644
  * `commit` applies the side effect, running only once the whole gate set passes so a later refusal never
639
- * commits. In an `or`, a refusing arm's queued commit is rolled back. This is how {@link Cooldown} peeks
645
+ * commits. In an `or`, seedcord rolls back the commit a refusing gate queued. This is how {@link Cooldown} peeks
640
646
  * in `check` and charges the slot in `commit`.
641
647
  *
642
648
  * @typeParam Name - The gate's literal name, used to name a mismatch error.
@@ -668,7 +674,7 @@ declare function defineGate<const Name extends string, Ctx extends GateContextBa
668
674
  * }
669
675
  * ```
670
676
  */
671
- declare function defineEffectGate<const Name extends string, Ctx extends GateContextBase = GateContextBase>(name: Name, check: (ctx: Ctx) => void | Promise<void>, commit: (ctx: Ctx) => void | Promise<void>): EffectGate<Ctx, Name>;
677
+ declare function defineEffectGate<const Name extends string, Ctx extends GateContextBase = GateContextBase>(name: Name, check: (ctx: Ctx) => Promisable<void>, commit: (ctx: Ctx) => Promisable<void>): EffectGate<Ctx, Name>;
672
678
  /**
673
679
  * The context a {@link Gate} requires.
674
680
  *
@@ -687,164 +693,9 @@ type TwoOrMore<Item> = readonly [Item, Item, ...Item[]];
687
693
  type GateName<TGate> = TGate extends Gate<GateContextBase, infer Name> ? Name : string;
688
694
  type Grouped<Name extends string> = Name extends `${string} & ${string}` | `${string} | ${string}` ? `(${Name})` : Name;
689
695
  type JoinNames<Gates extends readonly Gate<GateContextBase>[], Sep extends string> = Gates extends readonly [infer Only extends Gate<GateContextBase>] ? Grouped<GateName<Only>> : Gates extends readonly [infer First extends Gate<GateContextBase>, ...infer Rest extends readonly Gate<GateContextBase>[]] ? `${Grouped<GateName<First>>}${Sep}${JoinNames<Rest, Sep>}` : string;
690
- type GateMismatch<Name extends string, Want extends string, Got extends string> = `gate '${Name}' requires a ${Want} handler, and this handler is ${Got}`;
696
+ type GateMismatch<Name extends string, Want extends string, Got extends string> = `gate '${Name}' needs a handler of kind ${Want}, but this handler's kind is ${Got}`;
691
697
  type GateFitsWith<Provided, ProvidedLabel extends string, TGate, ReqLabel extends string> = [Provided] extends [RequiredOf<TGate>] ? TGate : Constructor<[GateMismatch<GateName<TGate>, ReqLabel, ProvidedLabel>]>;
692
698
  //#endregion
693
- //#region src/gates/catalog/options.d.ts
694
- /**
695
- * The override a catalog gate accepts. A one-line `message` reword or a full `notice` replacement. Accepted by
696
- * the agnostic catalog gates and, nested per refusal, by {@link RequireRole}. {@link RequirePermissions} and
697
- * {@link RequireBotPermissions} take `RequirePermissionsOptions` instead.
698
- *
699
- * @example
700
- * ```ts
701
- * // reword the default refusal
702
- * OwnerOnly({ message: 'Bot owners only.' });
703
- * ```
704
- *
705
- * @example
706
- * ```ts
707
- * // replace the refusal entirely, for example a translated Notice
708
- * GuildOnly({ notice: new MyTranslatedNotice() });
709
- * ```
710
- */
711
- interface GateNoticeOptions {
712
- /** Reword the default refusal, keeping its embed styling. */
713
- message?: string;
714
- /** Replace the default refusal Notice entirely, for full control or a translated copy. */
715
- notice?: Notice;
716
- }
717
- declare function pickNotice(options: GateNoticeOptions | undefined, makeDefault: (message?: string) => Notice): Notice;
718
- //#endregion
719
- //#region src/permissions/bits.d.ts
720
- /**
721
- * The permission flag bits a caller or the bot must hold. Pass the `PermissionFlagsBits` values from
722
- * `discord-api-types/v10` (or `discord.js`).
723
- */
724
- type PermissionScope = readonly bigint[];
725
- declare const PermissionNames: Map<bigint, string>;
726
- //#endregion
727
- //#region src/gates/catalog/permissions.d.ts
728
- /**
729
- * Options for {@link RequirePermissions} and {@link RequireBotPermissions}.
730
- */
731
- interface RequirePermissionsOptions {
732
- /**
733
- * The set the check reads. `'channel'` reads the effective channel set the payload carries, what
734
- * Discord enforces in the invoked channel. `'guild'` reads the base set from roles, available on the
735
- * gateway transport only.
736
- *
737
- * @defaultValue `'channel'`
738
- */
739
- in?: 'channel' | 'guild';
740
- /** Reword or replace the refusal shown when the set is unavailable (outside a guild, or unresolved inside one). */
741
- notInGuild?: GateNoticeOptions;
742
- /** Reword or replace the refusal shown when a permission is missing. */
743
- missing?: GateNoticeOptions;
744
- }
745
- /**
746
- * Options for {@link RequireRole}.
747
- */
748
- interface RequireRoleOptions {
749
- /** Reword or replace the refusal shown outside a guild. */
750
- notInGuild?: GateNoticeOptions;
751
- /** Reword or replace the refusal shown when the caller lacks the role. */
752
- missingRole?: GateNoticeOptions;
753
- }
754
- /**
755
- * Requires the caller to hold every permission in `scope`. Refuses outside a guild first, then when a
756
- * permission is missing. The Administrator bit passes any scope.
757
- *
758
- * The default `'channel'` scope reads the effective channel set the payload carries, what Discord enforces
759
- * in the invoked channel. The `'guild'` scope reads the base set from roles and requires a
760
- * {@link GuildPermissionsContext}, so it fits a gateway handler only.
761
- *
762
- * @param scope - The permission flag bits the caller must all hold.
763
- * @param options - Pick the scope with `in`, and override each refusal with `notInGuild` or `missing`.
764
- *
765
- * @see the `@Gated` decorator from your transport package
766
- *
767
- * @example
768
- * ```ts
769
- * import { PermissionFlagsBits } from 'discord-api-types/v10';
770
- *
771
- * \@Gated(RequirePermissions([PermissionFlagsBits.BanMembers]))
772
- * \@SlashRoute('ban')
773
- * class BanHandler extends SlashHandler<'ban'> {
774
- * async execute() {
775
- * // ...
776
- * }
777
- * }
778
- * ```
779
- *
780
- * @example
781
- * ```ts
782
- * // the base set from roles, on a gateway handler
783
- * RequirePermissions([PermissionFlagsBits.BanMembers], { in: 'guild' });
784
- * ```
785
- */
786
- declare function RequirePermissions(scope: PermissionScope, options: RequirePermissionsOptions & {
787
- in: 'guild';
788
- }): Gate<GuildPermissionsContext, 'RequirePermissions'>;
789
- declare function RequirePermissions(scope: PermissionScope, options?: RequirePermissionsOptions & {
790
- in?: 'channel';
791
- }): Gate<GateContextBase, 'RequirePermissions'>;
792
- /**
793
- * Requires the bot to hold every permission in `scope`. Refuses outside a guild first, then when a
794
- * permission is missing. The Administrator bit passes any scope.
795
- *
796
- * The default `'channel'` scope reads the bot's effective channel set (wire `app_permissions`), what
797
- * Discord enforces in the invoked channel. The `'guild'` scope reads the bot's base set from roles and
798
- * requires a {@link GuildPermissionsContext}, fitting a gateway handler only. `app_permissions` is
799
- * absent on gateway events, the channel scope refuses there as unresolved, use `in: 'guild'` on an
800
- * event handler.
801
- *
802
- * @param scope - The permission flag bits the bot must all hold.
803
- * @param options - Pick the scope with `in`, and override each refusal with `notInGuild` or `missing`.
804
- *
805
- * @see the `@Gated` decorator from your transport package
806
- *
807
- * @example
808
- * ```ts
809
- * import { PermissionFlagsBits } from 'discord-api-types/v10';
810
- *
811
- * \@Gated(RequireBotPermissions([PermissionFlagsBits.ManageMessages]))
812
- * \@SlashRoute('purge')
813
- * class PurgeHandler extends SlashHandler<'purge'> {
814
- * async execute() {
815
- * // ...
816
- * }
817
- * }
818
- * ```
819
- */
820
- declare function RequireBotPermissions(scope: PermissionScope, options: RequirePermissionsOptions & {
821
- in: 'guild';
822
- }): Gate<GuildPermissionsContext, 'RequireBotPermissions'>;
823
- declare function RequireBotPermissions(scope: PermissionScope, options?: RequirePermissionsOptions & {
824
- in?: 'channel';
825
- }): Gate<GateContextBase, 'RequireBotPermissions'>;
826
- /**
827
- * Requires the caller to hold `roleId`, read from the member's role ids. Refuses outside a guild first,
828
- * then when the role is missing. Roles are guild-global, so there is no scope dimension.
829
- *
830
- * @param roleId - The role snowflake the caller must hold.
831
- * @param options - Override each refusal, the outside-guild one with `notInGuild` and the missing-role one with `missingRole`.
832
- *
833
- * @see the `@Gated` decorator from your transport package
834
- *
835
- * @example
836
- * ```ts
837
- * \@Gated(RequireRole('123456789012345678'))
838
- * \@SlashRoute('vip')
839
- * class VipHandler extends SlashHandler<'vip'> {
840
- * async execute() {
841
- * // ...
842
- * }
843
- * }
844
- * ```
845
- */
846
- declare function RequireRole(roleId: string, options?: RequireRoleOptions): Gate<GateContextBase, 'RequireRole'>;
847
- //#endregion
848
699
  //#region src/miscellaneous/ResolvedEmoji.d.ts
849
700
  /**
850
701
  * A resolved custom emoji. Renders as `<:name:id>` in message content, or `<a:name:id>` when animated,
@@ -907,6 +758,66 @@ type PageCursor<Prefix extends string> = CustomId<Prefix, {
907
758
  /** Build the page cursor for a paginator. */
908
759
  declare function pageCursor<Prefix extends string>(prefix: Prefix): PageCursor<Prefix>;
909
760
  //#endregion
761
+ //#region src/handlers/runCase.d.ts
762
+ type ResolvedReturn<Case> = Case extends ((...args: never[]) => infer Result) ? Awaited<Result> : never;
763
+ type CaseReturn<Ret> = [Ret] extends [never] ? unknown : Promisable<Ret>;
764
+ type MatchResult<Ret, Cases> = [Ret] extends [never] ? ResolvedReturn<Cases[keyof Cases]> : Ret;
765
+ type ShapeKey<Shape> = keyof Shape | `${Extract<keyof Shape, string>}`;
766
+ type OnlyCases<Cases, Shape> = Cases & Record<Exclude<keyof Cases, ShapeKey<Shape>>, never>;
767
+ declare function runCase<Result>(handler: object, cases: object, kind: string, value: string, ...args: unknown[]): Promise<Result>;
768
+ //#endregion
769
+ //#region src/subscribers/Subscriber.d.ts
770
+ type SubscriberMatchCases<Keys extends SubscriptionKey, Ret> = { [Key in Keys]: (data: AllSubscriptions[Key]) => CaseReturn<Ret>; };
771
+ type SingleKeyData<Keys extends SubscriptionKey> = IsUnion<Keys> extends true ? never : AllSubscriptions[Keys];
772
+ /**
773
+ * Abstract base class for handling application subscribers.
774
+ *
775
+ * Pass the subscription key(s) as the generic, the same one(s) as `@Subscribe`. On a single-key subscriber, read
776
+ * `this.data` directly. On a subscriber for several keys, branch with `this.match`, keyed by subscription key.
777
+ *
778
+ * @typeParam Keys - One or more subscription keys, like `'userJoin'` or `'userJoin' | 'userLeave'`.
779
+ * @typeParam TCore - The transport's Core
780
+ */
781
+ declare abstract class Subscriber<in out Keys extends SubscriptionKey, TCore extends CoreBase> {
782
+ #private;
783
+ protected readonly core: TCore;
784
+ protected readonly key: Keys;
785
+ protected readonly logger: Logger;
786
+ constructor(payload: AllSubscriptions[Keys], core: TCore, key: Keys);
787
+ /** The published payload. It's `never` on a subscriber for several keys, where `this.match` reads it. */
788
+ protected get data(): SingleKeyData<Keys>;
789
+ /**
790
+ * Executes the subscriber handler logic.
791
+ */
792
+ abstract execute(): Promise<void>;
793
+ /**
794
+ * Run the case for the published key. Use this only when the subscriber is registered for several
795
+ * keys. On a single-key subscriber, read `this.data` directly.
796
+ *
797
+ * Provide one case per registered key. Each case receives that key's own payload. A missing key or an unknown
798
+ * key is a compile error.
799
+ *
800
+ * @typeParam Ret - The type that every case must return. Leave it out to get the union of what the cases return.
801
+ * @throws A **SeedcordTypeError** with `MatchCaseMissing` when the published key doesn't have a case.
802
+ *
803
+ * @example
804
+ * ```ts
805
+ * import { Subscribe, Subscriber } from '@seedcord/gateway';
806
+ *
807
+ * \@Subscribe(['interactionDispatched'], ['eventDispatched'])
808
+ * class Metrics extends Subscriber<'interactionDispatched' | 'eventDispatched'> {
809
+ * async execute() {
810
+ * await this.match({
811
+ * interactionDispatched: (data) => this.logger.info(data.routeId),
812
+ * eventDispatched: (data) => this.logger.info(data.name)
813
+ * });
814
+ * }
815
+ * }
816
+ * ```
817
+ */
818
+ protected match<Ret = never, Cases extends SubscriberMatchCases<Keys, Ret> = SubscriberMatchCases<Keys, Ret>>(cases: OnlyCases<Cases, SubscriberMatchCases<Keys, Ret>>): Promise<MatchResult<Ret, Cases>>;
819
+ }
820
+ //#endregion
910
821
  //#region src/subscribers/decorators/Subscribe.d.ts
911
822
  /**
912
823
  * Options accepted by the `@Subscribe` decorator.
@@ -919,32 +830,36 @@ interface SubscribeOptions {
919
830
  */
920
831
  readonly frequency?: EventFrequency | undefined;
921
832
  }
833
+ type SubscribeSpec<Key extends SubscriptionKey> = readonly [key: Key, options?: SubscribeOptions];
922
834
  /**
923
- * Registers a subscriber handler class with a specific subscriber event.
835
+ * Registers a subscriber class with one or more subscription keys.
924
836
  *
925
- * Associates the decorated class with a subscriber event type for automatic
926
- * registration and execution when the subscriber is emitted.
837
+ * Supply one `[key, options?]` tuple per key. The subscriber's generic must list the same keys.
927
838
  *
928
- * @typeParam TSubscriber - The subscriber event name to register for
929
- * @param subscriber - The subscriber event name to register for
930
- * @param options - Options to configure the subscriber handler registration.
931
839
  * @decorator
932
840
  * @example
933
- * ```typescript
934
- * \@Subscribe('userJoin')
841
+ * ```ts
842
+ * \@Subscribe(['userJoin'])
935
843
  * class WelcomeHandler extends Subscriber<'userJoin'> {
936
- * async execute() {
937
- * }
844
+ * async execute() {
845
+ * this.logger.info(`${this.data.userId} joined`);
846
+ * }
938
847
  * }
939
848
  * ```
940
849
  * @example
941
850
  * ```ts
942
- * \@Subscribe('userJoin', { frequency: 'once' })
943
- * // or
944
- * \@Subscribe('userJoin')
851
+ * \@Subscribe(['userJoin', { frequency: 'once' }], ['userLeave'])
852
+ * class Presence extends Subscriber<'userJoin' | 'userLeave'> {
853
+ * async execute() {
854
+ * await this.match({
855
+ * userJoin: (data) => this.logger.info(`${data.userId} joined`),
856
+ * userLeave: (data) => this.logger.info(`${data.userId} left`)
857
+ * });
858
+ * }
859
+ * }
945
860
  * ```
946
861
  */
947
- declare function Subscribe<TSubscriber extends SubscriptionKey>(subscriber: TSubscriber, options?: SubscribeOptions): <HandlerCtor extends Constructor<Subscriber<TSubscriber, CoreBase>>>(constructor: HandlerCtor) => void;
862
+ declare function Subscribe<const Defs extends readonly SubscribeSpec<SubscriptionKey>[]>(...defs: Defs): <HandlerCtor extends Constructor<Subscriber<Defs[number][0], CoreBase>>>(constructor: HandlerCtor) => void;
948
863
  //#endregion
949
864
  //#region src/subscribers/bases/WebhookSender.d.ts
950
865
  /** A file attached to a webhook report. */
@@ -989,7 +904,7 @@ interface WebhookReport {
989
904
  * resolves the url, validates it, reuses one sender per url, and sends. Registration skips a reporter
990
905
  * whose variable is unset and warns at boot.
991
906
  *
992
- * @typeParam KeyOfSubscribers - The subscription key this reporter receives
907
+ * @typeParam KeyOfSubscribers - The subscription key or keys that this reporter receives, the same ones as `@Subscribe`
993
908
  * @typeParam TCore - The transport's Core
994
909
  */
995
910
  declare abstract class WebhookLog<KeyOfSubscribers extends SubscriptionKey, TCore extends CoreBase> extends Subscriber<KeyOfSubscribers, TCore> {
@@ -1024,7 +939,7 @@ declare abstract class WebhookLog<KeyOfSubscribers extends SubscriptionKey, TCor
1024
939
  * @decorator
1025
940
  * @example
1026
941
  * ```ts
1027
- * \@Subscribe('memberBanned')
942
+ * \@Subscribe(['memberBanned'])
1028
943
  * \@WebhookUrl('BAN_LOG_WEBHOOK_URL')
1029
944
  * class BanLog extends WebhookLog<'memberBanned'> {
1030
945
  * report() {
@@ -1033,7 +948,7 @@ declare abstract class WebhookLog<KeyOfSubscribers extends SubscriptionKey, TCor
1033
948
  * }
1034
949
  * ```
1035
950
  */
1036
- declare function WebhookUrl(envKey: string): <HandlerCtor extends Constructor<WebhookLog<SubscriptionKey, CoreBase>>>(constructor: HandlerCtor) => void;
951
+ declare function WebhookUrl(envKey: string): <Keys extends SubscriptionKey>(constructor: Constructor<WebhookLog<Keys, CoreBase>>) => void;
1037
952
  //#endregion
1038
- export { SlashRouteBrand as $, Gate as A, CacheFor as At, HandlerOutcome as B, UserContextMenuRegistry as Bt, GateNoticeOptions as C, CommandMeta as Ct, JoinNames as D, RowComponent as Dt, IntersectRequired as E, BuilderComponent as Et, defineGate as F, SlashRouteEntry as Ft, RegisterInteractionMiddleware as G, AnyMiddlewareCtor as H, SerializedReply as I, ContextMenuKind as It, ComponentDefsBrand as J, interactionMiddlewareMetaOf as K, serializeReply as L, MenuCacheFor as Lt, GuildPermissionsContext as M, RouteCache as Mt, RequiredOf as N, SlashOption as Nt, TwoOrMore as O, BuilderType as Ot, defineEffectGate as P, SlashRegistry as Pt, MiddlewareKindsBrand as Q, DispatchResult as R, MessageContextMenuRegistry as Rt, PermissionScope as S, resolveDeployTarget as St, GateFitsWith as T, RegisterCommand as Tt, InteractionMiddlewareMetadata as U, HandlerResult as V, InteractionMiddlewareOptions as W, ContextMenuKindBrand as X, ComponentKindBrand as Y, ContextMenuNamesBrand as Z, RequirePermissions as _, ScopeId as _t, Subscribe as a, InjectedContextMenuMap as at, RequireRoleOptions as b, scopeIdsOf as bt, PageCursor as c, CommandRef as ct, AutocompleteOptions as d, commandRefOf as dt, CommandInfo as et, ChoiceValueOf as f, indexById as ft, RequireBotPermissions as g, GLOBAL_ID as gt, ResolvedEmoji as h, DeployResult as ht, WebhookFile as i, InjectedCommandMap as it, GateContextBase as j, OptionKind as jt, EffectGate as k, RowType as kt, pageCursor as l, LinkedCommands as lt, FocusedField as m, CommandBuilder as mt, WebhookLog as n, ContextMenuInfo as nt, SubscribeOptions as o, installLinks as ot, EntryFor as p, linkCommands as pt, AutocompleteRouteBrand as q, WebhookReport as r, ContextMenus as rt, PAGE_MAX as s, warnLinks as st, WebhookUrl as t, Commands as tt, AutocompletableNames as u, commandKey as ut, RequirePermissionsOptions as v, commandsRoute as vt, pickNotice as w, GuildTarget as wt, PermissionNames as x, skippedNote as xt, RequireRole as y, globalFirst as yt, EventDispatchResult as z, NamesFor as zt };
1039
- //# sourceMappingURL=index-Boo33hh3.d.mts.map
953
+ export { ContextMenuInfo as $, RequiredOf as A, SlashOption as At, InteractionMiddlewareMetadata as B, JoinNames as C, BuilderComponent as Ct, Gate as D, CacheFor as Dt, EffectGate as E, RowType as Et, DispatchResult as F, MessageContextMenuRegistry as Ft, ComponentDefsBrand as G, RegisterInteractionMiddleware as H, EventDispatchResult as I, NamesFor as It, ContextMenuNamesBrand as J, ComponentKindBrand as K, HandlerOutcome as L, UserContextMenuRegistry as Lt, defineGate as M, SlashRouteEntry as Mt, SerializedReply as N, ContextMenuKind as Nt, GateContextBase as O, OptionKind as Ot, serializeReply as P, MenuCacheFor as Pt, Commands as Q, HandlerResult as R, IntersectRequired as S, RegisterCommand as St, AppPermissionsContext as T, BuilderType as Tt, interactionMiddlewareMetaOf as U, InteractionMiddlewareOptions as V, AutocompleteRouteBrand as W, SlashRouteBrand as X, MiddlewareKindsBrand as Y, CommandInfo as Z, ChoiceValueOf as _, scopeIdsOf as _t, Subscribe as a, CommandRef as at, ResolvedEmoji as b, CommandMeta as bt, CaseReturn as c, commandRefOf as ct, runCase as d, CommandBuilder as dt, ContextMenus as et, PAGE_MAX as f, DeployResult as ft, AutocompleteOptions as g, globalFirst as gt, AutocompletableNames as h, commandsRoute as ht, WebhookFile as i, warnLinks as it, defineEffectGate as j, SlashRegistry as jt, GuildPermissionsContext as k, RouteCache as kt, MatchResult as l, indexById as lt, pageCursor as m, ScopeId as mt, WebhookLog as n, InjectedContextMenuMap as nt, SubscribeOptions as o, LinkedCommands as ot, PageCursor as p, GLOBAL_ID as pt, ContextMenuKindBrand as q, WebhookReport as r, installLinks as rt, Subscriber as s, commandKey as st, WebhookUrl as t, InjectedCommandMap as tt, OnlyCases as u, linkCommands as ut, EntryFor as v, skippedNote as vt, TwoOrMore as w, RowComponent as wt, GateFitsWith as x, GuildTarget as xt, FocusedField as y, resolveDeployTarget as yt, AnyMiddlewareCtor as z };
954
+ //# sourceMappingURL=index-DGHng1IN.d.mts.map