@seedcord/gateway 0.5.0 → 0.6.0

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/index.mjs CHANGED
@@ -1,6 +1,6 @@
1
1
  import "reflect-metadata";
2
2
  import { WinstonConsoleSink, WinstonFileSink, installNodeDefaults } from "@seedcord/logger/node";
3
- import { EventMetadataKey, GateNotice, GatedMetadataKey, HmrManager, InteractionMetadataKey, MiddlewareMetadataKey, MissingPermissions, NoticeCard, PermissionNames, PublishDefault, accessorStore, areRoutes, asError, busLoggerOf, clearStore, decodeComponentRoute, deferFlags, guardedAccessor, interactionRoutesOf, isEmojiTuple, outcomeFor, pickNotice, queuedMsFor, reportDispatch, reportedWrite, routeIdOf, runHandlerGates, sendFlags, setBotColor, slowGateMonitor } from "@seedcord/core/internal";
3
+ import { EventMetadataKey, EventMiddlewareMetadataKey, GateNotice, GatedMetadataKey, HmrManager, InteractionMetadataKey, InteractionMiddlewareMetadataKey, MiddlewareRegistry, MissingPermissions, NoticeCard, PermissionNames, PublishDefault, accessorStore, areRoutes, asError, busLoggerOf, clearStore, decodeComponentRoute, deferFlags, eventResultFor, guardedAccessor, interactionMiddleware, interactionRoutesOf, isEmojiTuple, outcomeFor, pickNotice, queuedMsFor, reportDispatch, reportedWrite, resultFor, runAfter, runHandlerGates, sendFlags, setBotColor, slowGateMonitor } from "@seedcord/core/internal";
4
4
  import { SeedcordErrorCode, paint } from "@seedcord/errors";
5
5
  import { SeedcordError, SeedcordTypeError, validateDiscordToken } from "@seedcord/errors/internal";
6
6
  import { ArraySourceBase, BaseHandler as BaseHandler$1, BaseReplySender, BuilderComponent, Bus, CursorSourceBase, CustomId, DispatchContext, Fault, InteractionKind, Notice, PaginatorBase, RepliableHandler as RepliableHandler$1, ResolvedEmoji, RowComponent, Silence, Subscriber as Subscriber$1, WebhookLog as WebhookLog$1, assertPermissions, defineGate } from "@seedcord/core";
@@ -10,7 +10,7 @@ import { ButtonBuilder, TextDisplayBuilder } from "@discordjs/builders";
10
10
  import { buildSlashRoute } from "@seedcord/utils/internal";
11
11
  import { Plugin as Plugin$1 } from "@seedcord/core/plugin";
12
12
  import { CoordinatedShutdown, CoordinatedStartup, Pluggable } from "@seedcord/core/node";
13
- import { CommandRegistry, HealthCheck, ShutdownPhase, StartupPhase, SubscriberLoader, settleWithin, shutdownOf } from "@seedcord/core/node/internal";
13
+ import { CommandRegistry, DRAIN_TASK_TIMEOUT_MS, DRAIN_WINDOW_MS, HealthCheck, ShutdownPhase, StartupPhase, SubscriberLoader, drainInFlight, shutdownOf } from "@seedcord/core/node/internal";
14
14
  import { MemoryRateLimiter } from "@seedcord/rate-limiter";
15
15
  import { HostAugmentTarget, HostVersion, SeedcordBrand } from "@seedcord/types/internal";
16
16
  import { Envapter } from "envapt";
@@ -132,45 +132,39 @@ function Gated(...gates) {
132
132
  //#endregion
133
133
  //#region src/bot/decorators/Middlewares.ts
134
134
  /**
135
- * Middleware types supported by Seedcord
136
- */
137
- let MiddlewareType = /* @__PURE__ */ function(MiddlewareType) {
138
- MiddlewareType["Interaction"] = "interaction";
139
- MiddlewareType["Event"] = "event";
140
- return MiddlewareType;
141
- }({});
142
- /**
143
- * Decorator used to register middleware with priority ordering. The lower the priority number, the earlier it runs.
144
- *
145
- * Interaction middleware cannot specify event filters.
146
- *
147
- * @param type - Middleware kind from {@link MiddlewareType}
148
- * @param priority - Ordering value where lower runs earlier. {@default `0` }
149
- * @param options - Additional registration options
135
+ * Registers an event middleware. It runs before the handlers for every event it lists.
150
136
  *
137
+ * @param options - The events this middleware runs on and its ordering.
151
138
  * @decorator
152
139
  *
153
140
  * @example
154
141
  * ```ts
155
- * \@Middleware(MiddlewareType.Event, 10, { events: [Events.MessageCreate, Events.MessageUpdate] })
156
- * class MyEventMiddleware extends EventMiddleware {}
142
+ * \@RegisterEventMiddleware({ events: [Events.MessageCreate], priority: 10 })
143
+ * class Audit extends EventMiddleware<Events.MessageCreate> {
144
+ * async execute() {
145
+ * this.logger.info(this.event[0].id);
146
+ * }
147
+ * }
157
148
  * ```
158
- * @throws A **SeedcordTypeError** If priority is not a finite number
159
- * @throws A **SeedcordError** If interaction middleware specifies event filters
149
+ *
150
+ * @throws A **SeedcordTypeError** If `priority` is not a finite number.
160
151
  */
161
- function Middleware(type, priority = 0, options = {}) {
162
- return (ctor) => {
163
- const normalizedPriority = Number(priority);
164
- if (!Number.isFinite(normalizedPriority)) throw new SeedcordTypeError(SeedcordErrorCode.DecoratorInvalidMiddlewarePriority);
165
- if (type === "interaction" && Array.isArray(options.events) && options.events.length > 0) throw new SeedcordTypeError(SeedcordErrorCode.DecoratorInteractionEventFilter);
152
+ function RegisterEventMiddleware(options = {}) {
153
+ return function(ctor) {
154
+ const priority = Number(options.priority ?? 0);
155
+ if (!Number.isFinite(priority)) throw new SeedcordTypeError(SeedcordErrorCode.DecoratorInvalidMiddlewarePriority);
156
+ if (options.events?.length === 0) throw new SeedcordTypeError(SeedcordErrorCode.DecoratorEmptyMiddlewareFilter, ["events"]);
166
157
  const metadata = {
167
- priority: normalizedPriority,
168
- type,
169
- ...options.events && options.events.length > 0 && { events: options.events }
158
+ priority,
159
+ ...options.events && { events: options.events }
170
160
  };
171
- Reflect.defineMetadata(MiddlewareMetadataKey, metadata, ctor);
161
+ Reflect.defineMetadata(EventMiddlewareMetadataKey, metadata, ctor);
172
162
  };
173
163
  }
164
+ /** @internal */
165
+ function eventMiddlewareMetaOf(constructor) {
166
+ return Reflect.getMetadata(EventMiddlewareMetadataKey, constructor);
167
+ }
174
168
 
175
169
  //#endregion
176
170
  //#region src/bot/gates/catalog/IgnoreBots.ts
@@ -502,34 +496,6 @@ async function getConfirmation(handler, prompt, options) {
502
496
  return confirmed;
503
497
  }
504
498
 
505
- //#endregion
506
- //#region src/handlers/BaseHandler.ts
507
- /**
508
- * The gateway base, fixes the core base's `TCore` to the gateway {@link Core} and adds the `getEvent`
509
- * gate hook.
510
- */
511
- var BaseHandler = class extends BaseHandler$1 {
512
- getEvent() {
513
- return this.event;
514
- }
515
- };
516
-
517
- //#endregion
518
- //#region src/handlers/interaction/InteractionMiddleware.ts
519
- /**
520
- * Base class for interaction middleware
521
- *
522
- * Middleware runs before interaction handlers and can modify behavior or block execution.
523
- * Unlike handlers, middleware should not send responses directly.
524
- *
525
- * @typeParam Repliable - The interaction type this middleware processes
526
- */
527
- var InteractionMiddleware = class extends BaseHandler {
528
- constructor(event, core) {
529
- super(event, core, void 0, "interactions");
530
- }
531
- };
532
-
533
499
  //#endregion
534
500
  //#region src/bot/ReplySender.ts
535
501
  function isReplyFile(file) {
@@ -553,9 +519,10 @@ function seedState(interaction) {
553
519
  /** Writes interaction responses through discord.js. The repliable handler bases and the dispatcher construct it. */
554
520
  var ReplySender = class extends BaseReplySender {
555
521
  interaction;
556
- constructor(interaction, routeId, bus) {
557
- super(routeId, {
522
+ constructor(interaction, dispatch, bus) {
523
+ super({
558
524
  bus,
525
+ dispatch,
559
526
  interactionId: interaction.id
560
527
  }, seedState(interaction));
561
528
  this.interaction = interaction;
@@ -665,8 +632,35 @@ var RepliableHandler = class extends RepliableHandler$1 {
665
632
  getEvent() {
666
633
  return this.event;
667
634
  }
668
- buildSender(event, core, routeId) {
669
- return new ReplySender(event, routeId, core.bus);
635
+ buildSender(event, core, dispatch) {
636
+ return new ReplySender(event, dispatch, core.bus);
637
+ }
638
+ };
639
+
640
+ //#endregion
641
+ //#region src/handlers/interaction/InteractionMiddleware.ts
642
+ /**
643
+ * Base class for interaction middleware.
644
+ *
645
+ * Middleware runs after the dispatcher builds the handler and before the gates, over the handler's own
646
+ * reply surface. Name the kinds in the generic and list the same ones in `{ kinds }`. Several kinds give
647
+ * `this.event` a union. The discord.js guards narrow it. A catchall reads every repliable kind.
648
+ *
649
+ * @typeParam Kind - One or more {@link InteractionKind} members. Defaults to every repliable kind.
650
+ *
651
+ * @example
652
+ * ```ts
653
+ * \@RegisterInteractionMiddleware({ kinds: [InteractionKind.Button] })
654
+ * class Audit extends InteractionMiddleware<InteractionKind.Button> {
655
+ * async execute() {
656
+ * this.logger.info(this.event.customId);
657
+ * }
658
+ * }
659
+ * ```
660
+ */
661
+ var InteractionMiddleware = class extends RepliableHandler {
662
+ constructor(event, core, dispatch, sender) {
663
+ super(event, core, dispatch, sender);
670
664
  }
671
665
  };
672
666
 
@@ -895,6 +889,18 @@ var MessageContextMenuHandler = class extends InteractionHandler {
895
889
  }
896
890
  };
897
891
 
892
+ //#endregion
893
+ //#region src/handlers/BaseHandler.ts
894
+ /**
895
+ * The gateway base, fixes the core base's `TCore` to the gateway {@link Core} and adds the `getEvent`
896
+ * gate hook.
897
+ */
898
+ var BaseHandler = class extends BaseHandler$1 {
899
+ getEvent() {
900
+ return this.event;
901
+ }
902
+ };
903
+
898
904
  //#endregion
899
905
  //#region src/handlers/interaction/AutocompleteHandler.ts
900
906
  /**
@@ -972,11 +978,11 @@ var AutocompleteHandler = class extends BaseHandler {
972
978
  }
973
979
  /** Send autocomplete suggestions, callback type 8. Prefer {@link match}, which restricts each field's choices to its declared type. */
974
980
  async respond(choices) {
975
- const routeId = this.dispatch?.routeId ?? "autocomplete";
976
981
  await reportedWrite({
977
982
  bus: this.core.bus,
983
+ dispatch: this.dispatch,
978
984
  interactionId: this.event.id
979
- }, routeId, "respond", () => this.event.respond(choices));
985
+ }, "respond", () => this.event.respond(choices));
980
986
  }
981
987
  /** The firing command route, for a field whose completion differs per registered command. */
982
988
  get route() {
@@ -1314,8 +1320,8 @@ var MentionableMenuHandler = class extends SelectMenuHandler {
1314
1320
  */
1315
1321
  var EventHandler = class extends BaseHandler {
1316
1322
  firedEvent;
1317
- constructor(event, core, eventName) {
1318
- super(event, core, void 0, "events");
1323
+ constructor(event, core, dispatch, eventName) {
1324
+ super(event, core, dispatch, "events");
1319
1325
  this.firedEvent = eventName;
1320
1326
  }
1321
1327
  /**
@@ -1367,8 +1373,8 @@ var EventHandler = class extends BaseHandler {
1367
1373
  */
1368
1374
  var EventMiddleware = class extends BaseHandler {
1369
1375
  firedEvent;
1370
- constructor(event, core, eventName) {
1371
- super(event, core, void 0, "events");
1376
+ constructor(event, core, dispatch, eventName) {
1377
+ super(event, core, dispatch, "events");
1372
1378
  this.firedEvent = eventName;
1373
1379
  }
1374
1380
  /**
@@ -1528,13 +1534,14 @@ function deriveEventActor(args) {
1528
1534
 
1529
1535
  //#endregion
1530
1536
  //#region src/bot/gates/runGates.ts
1531
- function interactionGateContext(interaction, core) {
1537
+ function interactionGateContext(interaction, core, dispatch) {
1532
1538
  const rawMember = interaction.member;
1533
1539
  const memberRoleIds = rawMember instanceof GuildMember ? [...rawMember.roles.cache.keys()].filter((id) => id !== interaction.guildId) : rawMember?.roles ?? [];
1534
1540
  return {
1535
1541
  kind: "interaction",
1536
1542
  interaction,
1537
1543
  core,
1544
+ dispatch,
1538
1545
  user: interaction.user,
1539
1546
  guild: interaction.guild,
1540
1547
  member: rawMember instanceof GuildMember ? rawMember : null,
@@ -1546,14 +1553,15 @@ function interactionGateContext(interaction, core) {
1546
1553
  appPermissions: interaction.appPermissions.bitfield,
1547
1554
  memberGuildPermissions: rawMember instanceof GuildMember ? rawMember.permissions.bitfield : null,
1548
1555
  appGuildPermissions: interaction.guild?.members.me?.permissions.bitfield ?? null,
1549
- routeId: null
1556
+ declaredRoute: null
1550
1557
  };
1551
1558
  }
1552
- function eventGateContext(eventName, args, core) {
1559
+ function eventGateContext(eventName, args, core, dispatch) {
1553
1560
  const actor = deriveEventActor(args);
1554
1561
  return {
1555
1562
  kind: "event",
1556
1563
  core,
1564
+ dispatch,
1557
1565
  eventName,
1558
1566
  payload: args,
1559
1567
  user: actor.user,
@@ -1567,54 +1575,62 @@ function eventGateContext(eventName, args, core) {
1567
1575
  appPermissions: null,
1568
1576
  memberGuildPermissions: actor.member?.permissions.bitfield ?? null,
1569
1577
  appGuildPermissions: actor.guild?.members.me?.permissions.bitfield ?? null,
1570
- routeId: null
1578
+ declaredRoute: null
1571
1579
  };
1572
1580
  }
1573
1581
 
1574
1582
  //#endregion
1575
1583
  //#region src/miscellaneous/extractErrorResponse.ts
1576
1584
  const logger$2 = new Logger("ErrorsHandling", { channel: "errors" });
1577
- function extractErrorResponse(error, core, origin) {
1585
+ function extractErrorResponse(error, core, fault) {
1578
1586
  const uuid = crypto.randomUUID();
1587
+ const { dispatch } = fault;
1579
1588
  const developerUsername = core.config.notifications?.developerUsername;
1580
- const ctx = developerUsername === void 0 ? { uuid } : {
1589
+ const ctx = developerUsername === void 0 ? {
1590
+ uuid,
1591
+ dispatch
1592
+ } : {
1581
1593
  uuid,
1582
- developerUsername
1594
+ developerUsername,
1595
+ dispatch
1583
1596
  };
1584
1597
  if (error instanceof Notice) {
1585
- if (error.report) reportFault(error, core, origin, uuid);
1598
+ if (error.report) reportFault(error, core, fault, uuid);
1586
1599
  return {
1587
1600
  uuid,
1588
1601
  response: error.render(ctx)
1589
1602
  };
1590
1603
  }
1591
- reportRawFault(error, core, origin, uuid);
1592
- const override = core.config.errors?.defaultError;
1604
+ reportRawFault(error, core, fault, uuid);
1605
+ const Override = core.config.errors?.defaultError;
1593
1606
  return {
1594
1607
  uuid,
1595
- response: override ? new override(uuid).render(ctx) : new Fault().render(ctx)
1608
+ response: Override ? new Override(uuid).render(ctx) : new Fault().render(ctx)
1596
1609
  };
1597
1610
  }
1598
- function reportFault(denial, core, origin, uuid) {
1611
+ function reportFault(denial, core, fault, uuid) {
1599
1612
  logger$2.error(`${denial.name}: ${uuid}`, denial);
1600
- if (origin.interaction) core.bus[PublishDefault]("handledException", {
1613
+ if (fault.interaction) core.bus[PublishDefault]("handledException", {
1601
1614
  denial,
1602
1615
  uuid,
1603
- routeId: origin.routeId,
1604
- source: buildInteractionSource(origin.interaction)
1616
+ dispatchId: fault.dispatch.id,
1617
+ origin: fault.origin,
1618
+ source: buildInteractionSource(fault.interaction)
1605
1619
  });
1606
- else if (origin.event) core.bus[PublishDefault]("handledException", {
1620
+ else if (fault.event) core.bus[PublishDefault]("handledException", {
1607
1621
  denial,
1608
1622
  uuid,
1609
- routeId: origin.routeId,
1610
- source: buildEventSource(origin.event, origin)
1623
+ dispatchId: fault.dispatch.id,
1624
+ origin: fault.origin,
1625
+ source: buildEventSource(fault.event, fault)
1611
1626
  });
1612
1627
  else core.bus[PublishDefault]("unknownException", {
1613
1628
  uuid,
1629
+ dispatchId: fault.dispatch.id,
1614
1630
  error: denial,
1615
- routeId: origin.routeId,
1616
- ...scalarActors(origin),
1617
- metadata: metadataFor(origin)
1631
+ origin: fault.origin,
1632
+ ...scalarActors(fault),
1633
+ metadata: metadataFor(fault)
1618
1634
  });
1619
1635
  }
1620
1636
  function causeLine(error) {
@@ -1623,44 +1639,45 @@ function causeLine(error) {
1623
1639
  const [first] = cause.message.split("\n");
1624
1640
  return `\ncaused by ${cause.name}: ${first ?? cause.message}`;
1625
1641
  }
1626
- function reportRawFault(error, core, origin, uuid) {
1642
+ function reportRawFault(error, core, fault, uuid) {
1627
1643
  if (core.config.errors?.errorStack ?? false) logger$2.error(uuid, error);
1628
1644
  else logger$2.error(`${uuid} | ${error.message}${causeLine(error)}`);
1629
1645
  core.bus[PublishDefault]("unknownException", {
1630
1646
  uuid,
1647
+ dispatchId: fault.dispatch.id,
1631
1648
  error,
1632
- routeId: origin.routeId,
1633
- ...scalarActors(origin),
1634
- metadata: metadataFor(origin)
1649
+ origin: fault.origin,
1650
+ ...scalarActors(fault),
1651
+ metadata: metadataFor(fault)
1635
1652
  });
1636
1653
  }
1637
- function scalarActors(origin) {
1654
+ function scalarActors(fault) {
1638
1655
  return {
1639
- guild: origin.guild ? {
1640
- id: origin.guild.id,
1641
- name: origin.guild.name
1656
+ guild: fault.guild ? {
1657
+ id: fault.guild.id,
1658
+ name: fault.guild.name
1642
1659
  } : void 0,
1643
- user: origin.user ? {
1644
- id: origin.user.id,
1645
- username: origin.user.username
1660
+ user: fault.user ? {
1661
+ id: fault.user.id,
1662
+ username: fault.user.username
1646
1663
  } : void 0
1647
1664
  };
1648
1665
  }
1649
- function metadataFor(origin) {
1650
- if (origin.event) return {
1651
- eventName: origin.event.name,
1652
- handler: origin.event.handler,
1653
- args: origin.event.args
1666
+ function metadataFor(fault) {
1667
+ if (fault.event) return {
1668
+ eventName: fault.event.name,
1669
+ handler: fault.event.handler,
1670
+ args: fault.event.args
1654
1671
  };
1655
- return origin.metadata;
1672
+ return fault.metadata;
1656
1673
  }
1657
- function buildEventSource(event, origin) {
1674
+ function buildEventSource(event, fault) {
1658
1675
  return {
1659
1676
  kind: "event",
1660
1677
  eventName: event.name,
1661
1678
  handler: event.handler,
1662
- userId: origin.user?.id ?? null,
1663
- guildId: origin.guild?.id ?? null,
1679
+ userId: fault.user?.id ?? null,
1680
+ guildId: fault.guild?.id ?? null,
1664
1681
  channelId: event.channelId,
1665
1682
  raw: event.args
1666
1683
  };
@@ -1691,7 +1708,8 @@ function interactionKind(interaction) {
1691
1708
  //#endregion
1692
1709
  //#region src/bot/handleEventFault.ts
1693
1710
  const logger$1 = new Logger("Faults", { channel: "errors" });
1694
- function handleEventFault(caught, eventName, handlerName, args, core) {
1711
+ function handleEventFault(caught, fault, core) {
1712
+ const { eventName, handlerName, args, dispatch } = fault;
1695
1713
  if (caught instanceof Silence) {
1696
1714
  if (caught.reason !== void 0 && (core.config.errors?.logSilences ?? true)) logger$1.debug(`Silence: ${caught.reason}`);
1697
1715
  return;
@@ -1711,14 +1729,45 @@ function handleEventFault(caught, eventName, handlerName, args, core) {
1711
1729
  args,
1712
1730
  channelId: actor.channelId
1713
1731
  },
1714
- routeId: `event:${eventName}:${handlerName}`,
1732
+ origin: `event:${eventName}:${handlerName}`,
1733
+ dispatch,
1715
1734
  guild: actor.guild,
1716
1735
  user: actor.user
1717
1736
  });
1718
1737
  }
1719
1738
 
1739
+ //#endregion
1740
+ //#region src/bot/reportEventDispatch.ts
1741
+ function reportEventDispatching(core, dispatchId, name, args) {
1742
+ core.bus[PublishDefault]("eventDispatching", {
1743
+ dispatchId,
1744
+ name,
1745
+ args
1746
+ });
1747
+ }
1748
+ function reportEventDispatched(core, dispatchId, name, result, startedAt) {
1749
+ core.bus[PublishDefault]("eventDispatched", {
1750
+ dispatchId,
1751
+ name,
1752
+ outcome: result.outcome,
1753
+ handlers: result.handlers.map(({ handler, outcome }) => ({
1754
+ handler,
1755
+ outcome
1756
+ })),
1757
+ durationMs: performance.now() - startedAt
1758
+ });
1759
+ }
1760
+
1720
1761
  //#endregion
1721
1762
  //#region src/bot/controllers/EventDispatcher.ts
1763
+ const eventMiddleware = (ctor) => {
1764
+ const metadata = eventMiddlewareMetaOf(ctor);
1765
+ if (!metadata) return void 0;
1766
+ return {
1767
+ priority: metadata.priority,
1768
+ ...metadata.events && { keys: metadata.events }
1769
+ };
1770
+ };
1722
1771
  /**
1723
1772
  * Interactions route through InteractionDispatcher.
1724
1773
  *
@@ -1729,7 +1778,7 @@ var EventDispatcher = class {
1729
1778
  logger = new Logger("Events", { channel: "events" });
1730
1779
  isInitialized = false;
1731
1780
  eventMap = /* @__PURE__ */ new Map();
1732
- middlewares = [];
1781
+ middlewares = new MiddlewareRegistry(eventMiddleware);
1733
1782
  executedOnceHandlers = /* @__PURE__ */ new Set();
1734
1783
  attachedEvents = /* @__PURE__ */ new Set();
1735
1784
  inFlight = /* @__PURE__ */ new Set();
@@ -1825,35 +1874,32 @@ var EventDispatcher = class {
1825
1874
  }
1826
1875
  }
1827
1876
  unregisterMiddleware(middlewareCtor) {
1828
- const index = this.middlewares.findIndex((entry) => entry.ctor === middlewareCtor);
1829
- if (index !== -1) this.middlewares.splice(index, 1);
1877
+ this.middlewares.unregister(middlewareCtor);
1830
1878
  }
1831
1879
  registerMiddleware(middlewareCtor, relativePath) {
1832
- const metadata = Reflect.getMetadata(MiddlewareMetadataKey, middlewareCtor);
1833
- if (metadata?.type !== "event") return;
1834
- if (this.middlewares.some((entry) => entry.ctor === middlewareCtor)) return;
1835
- this.middlewares.push({
1836
- ctor: middlewareCtor,
1837
- priority: metadata.priority,
1838
- ...metadata.events && { events: metadata.events }
1839
- });
1840
- this.middlewares.sort((a, b) => a.priority - b.priority);
1841
- if (this.loading) this.loadedMiddlewares.push({
1842
- name: `${middlewareCtor.name} (${metadata.priority})`,
1880
+ const registration = this.middlewares.register(middlewareCtor);
1881
+ if (!registration || !this.loading) return;
1882
+ const scope = registration.keys ? `${registration.priority}, ${registration.keys.join(", ")}` : registration.priority;
1883
+ this.loadedMiddlewares.push({
1884
+ name: `${middlewareCtor.name} (${String(scope)})`,
1843
1885
  from: formatFilePath(relativePath)
1844
1886
  });
1845
1887
  }
1846
- async runMiddlewares(eventName, args) {
1847
- for (const { ctor, events } of this.middlewares) {
1848
- if (events && !events.includes(eventName)) continue;
1849
- try {
1850
- await new ctor(args, this.core, eventName).execute();
1851
- } catch (caught) {
1852
- handleEventFault(caught, String(eventName), ctor.name, args, this.core);
1853
- return false;
1854
- }
1888
+ async runMiddlewares(eventName, args, dispatch, ran) {
1889
+ for (const Middleware of this.middlewares.chainFor(eventName)) try {
1890
+ const middleware = new Middleware(args, this.core, dispatch, eventName);
1891
+ ran.push(middleware);
1892
+ await middleware.execute();
1893
+ } catch (caught) {
1894
+ handleEventFault(caught, {
1895
+ eventName: String(eventName),
1896
+ handlerName: Middleware.name,
1897
+ args,
1898
+ dispatch
1899
+ }, this.core);
1900
+ return { caught };
1855
1901
  }
1856
- return true;
1902
+ return null;
1857
1903
  }
1858
1904
  isEventHandlerClass(obj) {
1859
1905
  if (typeof obj !== "function") return false;
@@ -1861,7 +1907,7 @@ var EventDispatcher = class {
1861
1907
  }
1862
1908
  isMiddlewareClass(obj) {
1863
1909
  if (typeof obj !== "function") return false;
1864
- return obj.prototype instanceof EventMiddleware && Reflect.hasMetadata(MiddlewareMetadataKey, obj);
1910
+ return obj.prototype instanceof EventMiddleware && Reflect.hasMetadata(EventMiddlewareMetadataKey, obj);
1865
1911
  }
1866
1912
  registerHandler(handlerClass, relativePath) {
1867
1913
  const raw = Reflect.getMetadata(EventMetadataKey, handlerClass);
@@ -1898,10 +1944,6 @@ var EventDispatcher = class {
1898
1944
  this.logger.debug(`Attaching ${paint.sky.bold(eventName)} to the client with ${paint.mute(handlerEntries?.length ?? 0)} handler(s)`);
1899
1945
  this.core.bot.client.on(eventName, (...args) => {
1900
1946
  if (this.draining) return;
1901
- this.core.bus[PublishDefault]("eventDispatching", {
1902
- name: eventName,
1903
- args
1904
- });
1905
1947
  const run = this.processEvent(eventName, args).catch((caught) => {
1906
1948
  const error = asError(caught);
1907
1949
  this.logger.error(`[${paint.coral.bold("UNHANDLED ERROR AT ROOT")}] ${error.name}`, error.stack);
@@ -1915,31 +1957,57 @@ var EventDispatcher = class {
1915
1957
  this.draining = true;
1916
1958
  }
1917
1959
  drain(timeoutMs) {
1918
- return settleWithin(Promise.allSettled(this.inFlight), timeoutMs);
1960
+ return drainInFlight(this.inFlight, timeoutMs, this.logger, "Events");
1919
1961
  }
1920
1962
  async processEvent(eventName, args) {
1963
+ const startedAt = performance.now();
1921
1964
  const handlerEntries = this.eventMap.get(eventName);
1922
1965
  if (!handlerEntries || handlerEntries.length === 0) return;
1923
1966
  const handlersToExecute = handlerEntries.filter((entry) => entry.frequency !== "once" || !this.executedOnceHandlers.has(entry.ctor));
1924
1967
  if (handlersToExecute.length === 0) return;
1925
- if (!await this.runMiddlewares(eventName, args)) return;
1926
- for (const entry of handlersToExecute) {
1927
- if (entry.frequency === "once") {
1928
- if (this.executedOnceHandlers.has(entry.ctor)) continue;
1929
- this.executedOnceHandlers.add(entry.ctor);
1968
+ const dispatch = new DispatchContext(`event:${String(eventName)}`);
1969
+ reportEventDispatching(this.core, dispatch.id, eventName, args);
1970
+ const ran = [];
1971
+ const handlers = [];
1972
+ let stopped = null;
1973
+ try {
1974
+ stopped = await this.runMiddlewares(eventName, args, dispatch, ran);
1975
+ if (stopped) return;
1976
+ for (const entry of handlersToExecute) {
1977
+ if (entry.frequency === "once") {
1978
+ if (this.executedOnceHandlers.has(entry.ctor)) continue;
1979
+ this.executedOnceHandlers.add(entry.ctor);
1980
+ }
1981
+ handlers.push(await this.processHandler(eventName, entry.ctor, args, dispatch));
1930
1982
  }
1931
- await this.processHandler(eventName, entry.ctor, args);
1983
+ } finally {
1984
+ const result = eventResultFor(stopped, handlers);
1985
+ reportEventDispatched(this.core, dispatch.id, eventName, result, startedAt);
1986
+ await runAfter(ran, result, this.logger);
1932
1987
  }
1933
1988
  }
1934
- async processHandler(eventName, ctor, args) {
1989
+ async processHandler(eventName, Ctor, args, dispatch) {
1935
1990
  try {
1936
- this.logger.debug(`Processing ${paint.sky.bold(eventName)} with ${paint.mute(ctor.name)}`);
1937
- const handler = new ctor(args, this.core, eventName);
1938
- const eventCtx = eventGateContext(eventName, args, this.core);
1939
- await runHandlerGates(ctor, eventCtx);
1991
+ this.logger.debug(`Processing ${paint.sky.bold(eventName)} with ${paint.mute(Ctor.name)}`);
1992
+ const handler = new Ctor(args, this.core, dispatch, eventName);
1993
+ const eventCtx = eventGateContext(eventName, args, this.core, dispatch);
1994
+ await runHandlerGates(Ctor, eventCtx);
1940
1995
  await handler.execute();
1996
+ return {
1997
+ handler: Ctor.name,
1998
+ outcome: "handled"
1999
+ };
1941
2000
  } catch (caught) {
1942
- handleEventFault(caught, String(eventName), ctor.name, args, this.core);
2001
+ handleEventFault(caught, {
2002
+ eventName: String(eventName),
2003
+ handlerName: Ctor.name,
2004
+ args,
2005
+ dispatch
2006
+ }, this.core);
2007
+ return {
2008
+ handler: Ctor.name,
2009
+ ...resultFor(caught)
2010
+ };
1943
2011
  }
1944
2012
  }
1945
2013
  };
@@ -1977,7 +2045,8 @@ const HARMLESS_API_CODES = /* @__PURE__ */ new Set([
1977
2045
  //#endregion
1978
2046
  //#region src/bot/handleInteractionFault.ts
1979
2047
  const logger = new Logger("Faults", { channel: "errors" });
1980
- async function handleInteractionFault(caught, interaction, core, routeId, sender) {
2048
+ async function handleInteractionFault(caught, interaction, core, dispatch, sender) {
2049
+ const { routeId } = dispatch;
1981
2050
  if (caught instanceof Silence) {
1982
2051
  if (caught.reason !== void 0 && (core.config.errors?.logSilences ?? true)) logger.debug(`Silence: ${caught.reason}`);
1983
2052
  return;
@@ -1990,30 +2059,33 @@ async function handleInteractionFault(caught, interaction, core, routeId, sender
1990
2059
  }
1991
2060
  if (interaction.isAutocomplete()) {
1992
2061
  extractErrorResponse(error, core, {
1993
- routeId,
2062
+ origin: routeId,
2063
+ dispatch,
1994
2064
  guild: interaction.guild,
1995
2065
  user: interaction.user,
1996
2066
  metadata: interaction
1997
2067
  });
1998
- await sendEmptyChoices(interaction, core, routeId);
2068
+ await sendEmptyChoices(interaction, core, dispatch);
1999
2069
  return;
2000
2070
  }
2001
2071
  const { response } = extractErrorResponse(error, core, {
2002
2072
  interaction,
2003
- routeId,
2073
+ origin: routeId,
2074
+ dispatch,
2004
2075
  guild: interaction.guild,
2005
2076
  user: interaction.user,
2006
2077
  metadata: interaction
2007
2078
  });
2008
- await sendGuarded(sender ?? new ReplySender(interaction, routeId, core.bus), response, error instanceof Notice ? error.ephemeral : true);
2079
+ await sendGuarded(sender ?? new ReplySender(interaction, dispatch, core.bus), response, error instanceof Notice ? error.ephemeral : true);
2009
2080
  }
2010
- async function sendEmptyChoices(interaction, core, routeId) {
2081
+ async function sendEmptyChoices(interaction, core, dispatch) {
2011
2082
  const telemetry = {
2012
2083
  bus: core.bus,
2084
+ dispatch,
2013
2085
  interactionId: interaction.id
2014
2086
  };
2015
2087
  try {
2016
- await reportedWrite(telemetry, routeId, "respond", () => interaction.respond([]));
2088
+ await reportedWrite(telemetry, "respond", () => interaction.respond([]));
2017
2089
  } catch (error) {
2018
2090
  logger.debug(`autocomplete empty-choices send failed: ${String(error)}`);
2019
2091
  }
@@ -2051,7 +2123,7 @@ var InteractionDispatcher = class {
2051
2123
  };
2052
2124
  handlerFiles = /* @__PURE__ */ new Map();
2053
2125
  keysToIgnore = /* @__PURE__ */ new Set();
2054
- middlewares = [];
2126
+ middlewares = new MiddlewareRegistry(interactionMiddleware);
2055
2127
  inFlight = /* @__PURE__ */ new Set();
2056
2128
  draining = false;
2057
2129
  loading = false;
@@ -2150,19 +2222,15 @@ var InteractionDispatcher = class {
2150
2222
  });
2151
2223
  }
2152
2224
  registerMiddleware(middlewareCtor, relativePath) {
2153
- const metadata = Reflect.getMetadata(MiddlewareMetadataKey, middlewareCtor);
2154
- if (metadata?.type !== "interaction") return;
2155
- if (this.middlewares.some((entry) => entry.ctor === middlewareCtor)) return;
2156
- if (this.middlewares.some((entry) => entry.ctor.name === middlewareCtor.name)) throw new SeedcordError(SeedcordErrorCode.InteractionDuplicateMiddleware, [middlewareCtor.name]);
2157
- this.middlewares.push({
2158
- ctor: middlewareCtor,
2159
- priority: metadata.priority
2160
- });
2161
- this.middlewares.sort((a, b) => a.priority - b.priority);
2162
- if (this.loading) this.loadedMiddlewares.push({
2163
- name: `${middlewareCtor.name} (${metadata.priority})`,
2164
- from: formatFilePath(relativePath)
2165
- });
2225
+ const metadata = this.middlewares.register(middlewareCtor);
2226
+ if (!metadata) return;
2227
+ if (this.loading) {
2228
+ const scope = metadata.keys ? `${metadata.priority}, ${metadata.keys.join(", ")}` : metadata.priority;
2229
+ this.loadedMiddlewares.push({
2230
+ name: `${middlewareCtor.name} (${String(scope)})`,
2231
+ from: formatFilePath(relativePath)
2232
+ });
2233
+ }
2166
2234
  }
2167
2235
  isHandlerClass(obj) {
2168
2236
  if (typeof obj !== "function") return false;
@@ -2170,7 +2238,7 @@ var InteractionDispatcher = class {
2170
2238
  }
2171
2239
  isMiddlewareClass(obj) {
2172
2240
  if (typeof obj !== "function") return false;
2173
- return obj.prototype instanceof InteractionMiddleware && Reflect.hasMetadata(MiddlewareMetadataKey, obj);
2241
+ return obj.prototype instanceof InteractionMiddleware && Reflect.hasMetadata(InteractionMiddlewareMetadataKey, obj);
2174
2242
  }
2175
2243
  registerHandler(handlerClass, relativePath) {
2176
2244
  const from = formatFilePath(relativePath);
@@ -2203,8 +2271,7 @@ var InteractionDispatcher = class {
2203
2271
  for (const { routeType, routes } of artifacts ?? this.getArtifacts(handlerClass)) for (const route of routes) this.maps[routeType].delete(route);
2204
2272
  }
2205
2273
  unregisterMiddleware(middlewareCtor) {
2206
- const index = this.middlewares.findIndex((entry) => entry.ctor === middlewareCtor);
2207
- if (index !== -1) this.middlewares.splice(index, 1);
2274
+ this.middlewares.unregister(middlewareCtor);
2208
2275
  }
2209
2276
  attachToClient() {
2210
2277
  this.core.bot.client.on(Events.InteractionCreate, (interaction) => {
@@ -2223,7 +2290,7 @@ var InteractionDispatcher = class {
2223
2290
  this.draining = true;
2224
2291
  }
2225
2292
  drain(timeoutMs) {
2226
- return settleWithin(Promise.allSettled(this.inFlight), timeoutMs);
2293
+ return drainInFlight(this.inFlight, timeoutMs, this.logger, "Interactions");
2227
2294
  }
2228
2295
  async handleCustomIdInteraction(interaction, kind) {
2229
2296
  if ([...this.keysToIgnore].some((matcher) => matcher.owns(interaction.customId))) return;
@@ -2236,43 +2303,49 @@ var InteractionDispatcher = class {
2236
2303
  const startedAt = performance.now();
2237
2304
  const queuedMs = queuedMsFor(interaction.id);
2238
2305
  const matched = this.maps[kind].get(key);
2239
- let routeId = `${kind}:${key || "unrouted"}`;
2240
- let reported = false;
2241
- const report = (outcome) => {
2242
- if (reported) return;
2243
- reported = true;
2244
- reportDispatch(this.core.bus, {
2245
- routeId,
2246
- interactionId: interaction.id,
2247
- kind,
2248
- outcome,
2249
- startedAt,
2250
- fallback: !matched,
2251
- queuedMs
2252
- });
2253
- };
2306
+ const HandlerCtor = matched ?? fallback;
2307
+ const dispatch = new DispatchContext(`${kind}:${key || "unrouted"}`);
2308
+ const report = this.reporterFor({
2309
+ interaction,
2310
+ kind,
2311
+ fallback: !matched,
2312
+ startedAt,
2313
+ queuedMs,
2314
+ dispatchId: dispatch.id,
2315
+ routeId: dispatch.routeId
2316
+ });
2254
2317
  let sender;
2318
+ const ran = [];
2319
+ let result = { outcome: "handled" };
2255
2320
  try {
2256
- if (!interaction.isAutocomplete()) await this.runMiddlewares(interaction);
2257
- const HandlerCtor = matched ?? fallback;
2258
- const dispatch = new DispatchContext(routeIdOf(HandlerCtor) ?? routeId);
2259
- routeId = dispatch.routeId ?? routeId;
2260
2321
  const handler = this.buildHandler(HandlerCtor, interaction, dispatch, key, !matched);
2261
2322
  if (handler instanceof RepliableHandler) sender = handler.sender;
2262
- const refusal = interaction.isAutocomplete() ? null : await this.gateRefusal(HandlerCtor, interaction, dispatch.routeId);
2323
+ const refusal = await this.refusalBeforeHandler({
2324
+ HandlerCtor,
2325
+ kind,
2326
+ interaction,
2327
+ dispatch,
2328
+ sender,
2329
+ ran,
2330
+ declaredRoute: matched ? dispatch.routeId : void 0
2331
+ });
2263
2332
  if (refusal) {
2264
- await this.answer(refusal.caught, interaction, routeId, sender, report);
2333
+ result = resultFor(refusal.caught);
2334
+ await this.answer(refusal.caught, interaction, dispatch, sender, report);
2265
2335
  return;
2266
2336
  }
2267
2337
  await handler.execute();
2268
2338
  report("handled");
2269
2339
  } catch (caught) {
2270
- await this.answer(caught, interaction, routeId, sender, report);
2340
+ if (result.outcome === "handled") result = resultFor(caught);
2341
+ await this.answer(caught, interaction, dispatch, sender, report);
2342
+ } finally {
2343
+ await runAfter(ran, result, this.logger);
2271
2344
  }
2272
2345
  }
2273
- async answer(caught, interaction, routeId, sender, report) {
2346
+ async answer(caught, interaction, dispatch, sender, report) {
2274
2347
  try {
2275
- await handleInteractionFault(caught, interaction, this.core, routeId, sender);
2348
+ await handleInteractionFault(caught, interaction, this.core, dispatch, sender);
2276
2349
  } finally {
2277
2350
  report(outcomeFor(caught));
2278
2351
  }
@@ -2282,18 +2355,51 @@ var InteractionDispatcher = class {
2282
2355
  this.logger.debug(`Processing ${paint.sky.bold(key)} with ${paint.mute(HandlerCtor.name)}`);
2283
2356
  return new HandlerCtor(interaction, this.core, dispatch);
2284
2357
  }
2285
- async runMiddlewares(interaction) {
2286
- for (const { ctor } of this.middlewares) await new ctor(interaction, this.core).execute();
2358
+ reporterFor(row) {
2359
+ let reported = false;
2360
+ return (outcome) => {
2361
+ if (reported) return;
2362
+ reported = true;
2363
+ reportDispatch(this.core.bus, {
2364
+ dispatchId: row.dispatchId,
2365
+ routeId: row.routeId,
2366
+ interactionId: row.interaction.id,
2367
+ kind: row.kind,
2368
+ outcome,
2369
+ startedAt: row.startedAt,
2370
+ fallback: row.fallback,
2371
+ userId: row.interaction.user.id,
2372
+ guildId: row.interaction.guildId,
2373
+ queuedMs: row.queuedMs
2374
+ });
2375
+ };
2376
+ }
2377
+ async refusalBeforeHandler(step) {
2378
+ const { HandlerCtor, kind, interaction, dispatch, sender, ran, declaredRoute } = step;
2379
+ if (kind !== InteractionKind.Autocomplete && sender) try {
2380
+ await this.runMiddlewares(kind, interaction, dispatch, sender, ran);
2381
+ } catch (caught) {
2382
+ return { caught };
2383
+ }
2384
+ if (interaction.isAutocomplete()) return null;
2385
+ return this.gateRefusal(HandlerCtor, interaction, dispatch, declaredRoute);
2386
+ }
2387
+ async runMiddlewares(kind, interaction, dispatch, sender, ran) {
2388
+ for (const Middleware of this.middlewares.chainFor(kind)) {
2389
+ const middleware = new Middleware(interaction, this.core, dispatch, sender);
2390
+ ran.push(middleware);
2391
+ await middleware.execute();
2392
+ }
2287
2393
  }
2288
- async gateRefusal(HandlerCtor, interaction, routeId) {
2394
+ async gateRefusal(HandlerCtor, interaction, dispatch, declaredRoute) {
2289
2395
  const monitor = slowGateMonitor();
2290
2396
  try {
2291
- await runHandlerGates(HandlerCtor, interactionGateContext(interaction, this.core), routeId ?? void 0, monitor?.observe);
2397
+ await runHandlerGates(HandlerCtor, interactionGateContext(interaction, this.core, dispatch), declaredRoute, monitor?.observe);
2292
2398
  return null;
2293
2399
  } catch (caught) {
2294
2400
  return { caught };
2295
2401
  } finally {
2296
- monitor?.report(routeId);
2402
+ monitor?.report(declaredRoute ?? null);
2297
2403
  }
2298
2404
  }
2299
2405
  async handleInteraction(interaction) {
@@ -2396,8 +2502,6 @@ function __decorate(decorators, target, key, desc) {
2396
2502
 
2397
2503
  //#endregion
2398
2504
  //#region src/bot/Bot.ts
2399
- const DISPATCH_DRAIN_TIMEOUT_MS = 5e3;
2400
- const DRAIN_TASK_TIMEOUT_MS = 6e3;
2401
2505
  const UNBIND_TIMEOUT_MS = 2e3;
2402
2506
  const LOGOUT_TIMEOUT_MS = 2e3;
2403
2507
  const loggerSlot = Symbol("seedcord:bot:logger");
@@ -2460,7 +2564,7 @@ var Bot = class {
2460
2564
  this.#events?.stopAccepting();
2461
2565
  }
2462
2566
  async #drain() {
2463
- await Promise.allSettled([this.#interactions?.drain(DISPATCH_DRAIN_TIMEOUT_MS), this.#events?.drain(DISPATCH_DRAIN_TIMEOUT_MS)]);
2567
+ await Promise.allSettled([this.#interactions?.drain(DRAIN_WINDOW_MS), this.#events?.drain(DRAIN_WINDOW_MS)]);
2464
2568
  }
2465
2569
  /** @internal */
2466
2570
  async [initSlot]() {
@@ -2510,7 +2614,7 @@ function initBot(bot) {
2510
2614
  //#endregion
2511
2615
  //#region src/version.ts
2512
2616
  /** Package version */
2513
- const version = "0.5.0";
2617
+ const version = "0.6.0";
2514
2618
 
2515
2619
  //#endregion
2516
2620
  //#region src/Seedcord.ts
@@ -2546,7 +2650,7 @@ var Seedcord = class Seedcord extends Pluggable {
2546
2650
  * @throws A **SeedcordError** When attempting to create multiple instances (singleton)
2547
2651
  */
2548
2652
  constructor(config) {
2549
- super(new CoordinatedShutdown(), new CoordinatedStartup());
2653
+ super(new CoordinatedShutdown(config.lifecycle?.shutdownDeadline), new CoordinatedStartup());
2550
2654
  this.config = config;
2551
2655
  installNodeDefaults(config.logger);
2552
2656
  setBotColor(config.botColor);
@@ -2614,5 +2718,5 @@ var Seedcord = class Seedcord extends Pluggable {
2614
2718
  };
2615
2719
 
2616
2720
  //#endregion
2617
- export { ArraySource, AutocompleteHandler, BaseHandler, ButtonHandler, ChannelMenuHandler, ComponentHandler, CursorSource, Emojis, EventHandler, EventMiddleware, Gated, GatewayEmoji, IgnoreBots, InteractionHandler, InteractionMiddleware, MentionableMenuHandler, MessageContextMenuHandler, Middleware, MiddlewareType, ModalHandler, Nsfw, Paginator, Plugin, RegisterEvent, RepliableHandler, ReplySender, RoleMenuHandler, Seedcord, SelectMenuHandler, SlashHandler, StringMenuHandler, Subscriber, UserContextMenuHandler, UserMenuHandler, WebhookLog, WinstonConsoleSink, WinstonFileSink, checkBotPermissions, checkPermissions, getConfirmation, hasPermsToAssign, version };
2721
+ export { ArraySource, AutocompleteHandler, BaseHandler, ButtonHandler, ChannelMenuHandler, ComponentHandler, CursorSource, Emojis, EventHandler, EventMiddleware, Gated, GatewayEmoji, IgnoreBots, InteractionHandler, InteractionMiddleware, MentionableMenuHandler, MessageContextMenuHandler, ModalHandler, Nsfw, Paginator, Plugin, RegisterEvent, RegisterEventMiddleware, RepliableHandler, ReplySender, RoleMenuHandler, Seedcord, SelectMenuHandler, SlashHandler, StringMenuHandler, Subscriber, UserContextMenuHandler, UserMenuHandler, WebhookLog, WinstonConsoleSink, WinstonFileSink, checkBotPermissions, checkPermissions, getConfirmation, hasPermsToAssign, version };
2618
2722
  //# sourceMappingURL=index.mjs.map