@seedcord/core 0.6.0 → 0.8.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.
Files changed (33) hide show
  1. package/dist/{Bus-dGy6xq3F.mjs → Bus-Djm6688b.mjs} +6 -5
  2. package/dist/Bus-Djm6688b.mjs.map +1 -0
  3. package/dist/{Pluggable-B7_EftHj.mjs → Pluggable-BShEwGw9.mjs} +40 -7
  4. package/dist/Pluggable-BShEwGw9.mjs.map +1 -0
  5. package/dist/{Pluggable-C5uxmDb-.d.mts → Pluggable-CAteNmfy.d.mts} +11 -5
  6. package/dist/Plugin-BJfRk0s_.mjs.map +1 -1
  7. package/dist/{Plugin-Bnz61fs_.d.mts → Plugin-D3wIj0xX.d.mts} +8 -5
  8. package/dist/{cursor-8Y2zpF-Y.d.mts → cursor-BPaPFTic.d.mts} +186 -14
  9. package/dist/{cursor-BkI9lxy2.mjs → cursor-CFNKLVlk.mjs} +49 -11
  10. package/dist/cursor-CFNKLVlk.mjs.map +1 -0
  11. package/dist/{index-CD8l15aZ.d.mts → index-BS1hH9Sh.d.mts} +2 -2
  12. package/dist/index.d.mts +16 -35
  13. package/dist/index.mjs +29 -37
  14. package/dist/index.mjs.map +1 -1
  15. package/dist/internal.index.d.mts +79 -8
  16. package/dist/internal.index.mjs +121 -16
  17. package/dist/internal.index.mjs.map +1 -1
  18. package/dist/node-internal.index.d.mts +6 -4
  19. package/dist/node-internal.index.mjs +38 -14
  20. package/dist/node-internal.index.mjs.map +1 -1
  21. package/dist/node.index.d.mts +1 -1
  22. package/dist/node.index.mjs +1 -1
  23. package/dist/{phases-D0a8BV9Z.d.mts → phases-DJf12l5T.d.mts} +48 -9
  24. package/dist/plugin.index.d.mts +1 -1
  25. package/dist/{slashRouteLeaves-C1msUE9Q.mjs → slashRouteLeaves-DTFHzCKN.mjs} +2 -2
  26. package/dist/{slashRouteLeaves-C1msUE9Q.mjs.map → slashRouteLeaves-DTFHzCKN.mjs.map} +1 -1
  27. package/dist/{subscribers-A8lNU5yM.mjs → subscribers-kAQQt_YP.mjs} +32 -4
  28. package/dist/subscribers-kAQQt_YP.mjs.map +1 -0
  29. package/package.json +8 -8
  30. package/dist/Bus-dGy6xq3F.mjs.map +0 -1
  31. package/dist/Pluggable-B7_EftHj.mjs.map +0 -1
  32. package/dist/cursor-BkI9lxy2.mjs.map +0 -1
  33. package/dist/subscribers-A8lNU5yM.mjs.map +0 -1
@@ -1,7 +1,7 @@
1
- import { A as UnregisterSubscriber, B as SubscribeMetadataKey, C as ReplyMethod, D as RegisterDefaults, E as AckTrace, F as GatedMetadataKey, H as Notice, I as InteractionKind, L as InteractionMetadataKey, M as PublishDefault, N as CommandMetadataKey, O as RegisterSubscriber, P as EventMetadataKey, R as InteractionRouteKeys, S as AckState, T as sendTarget, V as WebhookUrlMetadataKey, a as StoredSubscriberCtor, b as ReplyTelemetry, d as DispatchOutcome, i as Bus, j as VerifyWebhooks, k as RegisteredCount, o as SubscriberRegistration, s as busLoggerOf, u as DefaultSubscriptions, w as checkAckLegality, x as reportedWrite, z as MiddlewareMetadataKey } from "./phases-D0a8BV9Z.mjs";
2
- import { A as serializeReply, B as IsNever, C as Gate, F as ContextMenuNamesBrand, G as CacheFor, I as SlashRouteBrand, J as SlashOption, K as OptionKind, L as CommandMeta, M as ComponentDefsBrand, N as ComponentKindBrand, P as ContextMenuKindBrand, Q as MenuCacheFor, V as BuilderComponent, X as SlashRouteEntry, Y as SlashRegistry, Z as ContextMenuKind, _ as pickNotice, c as FocusedField, et as NamesFor, i as AutocompletableNames, j as AutocompleteRouteBrand, k as SerializedReply, m as PermissionNames, n as PageCursor, o as ChoiceValueOf, q as RouteCache, r as pageCursor, s as EntryFor, t as PAGE_MAX, v as GateFitsWith, w as GateContextBase, z as Promisable } from "./cursor-8Y2zpF-Y.mjs";
1
+ import { A as UnregisterSubscriber, B as InteractionMiddlewareMetadataKey, C as ReplyMethod, D as RegisterDefaults, E as AckTrace, F as EventMetadataKey, G as Notice, H as MiddlewareKind, I as EventMiddlewareMetadataKey, L as GatedMetadataKey, M as PublishDefault, O as RegisterSubscriber, P as CommandMetadataKey, R as InteractionKind, S as AckState, T as sendTarget, U as SubscribeMetadataKey, V as InteractionRouteKeys, W as WebhookUrlMetadataKey, a as StoredSubscriberCtor, b as ReplyTelemetry, d as DispatchOutcome, i as Bus, j as VerifyWebhooks, k as RegisteredCount, o as SubscriberRegistration, s as busLoggerOf, u as DefaultSubscriptions, w as checkAckLegality, x as reportedWrite, z as InteractionMetadataKey } from "./phases-DJf12l5T.mjs";
2
+ import { A as serializeReply, B as AutocompleteRouteBrand, C as Gate, F as AnyMiddlewareCtor, G as MiddlewareKindsBrand, H as ComponentKindBrand, I as InteractionMiddlewareMetadata, K as SlashRouteBrand, M as EventDispatchResult, P as HandlerResult, Q as BuilderComponent, U as ContextMenuKindBrand, V as ComponentDefsBrand, W as ContextMenuNamesBrand, X as Promisable, Z as IsNever, _ as pickNotice, at as SlashOption, c as FocusedField, ct as ContextMenuKind, dt as NamesFor, i as AutocompletableNames, it as RouteCache, j as DispatchResult, k as SerializedReply, lt as MenuCacheFor, m as PermissionNames, n as PageCursor, nt as CacheFor, o as ChoiceValueOf, ot as SlashRegistry, q as CommandMeta, r as pageCursor, rt as OptionKind, s as EntryFor, st as SlashRouteEntry, t as PAGE_MAX, v as GateFitsWith, w as GateContextBase, z as interactionMiddlewareMetaOf } from "./cursor-BPaPFTic.mjs";
3
3
  import { n as CommandBuilder, r as DeployResult } from "./basic-Dng__Ena.mjs";
4
- import { a as PluginCtor, i as PluginArgs, n as Initializeable } from "./Plugin-Bnz61fs_.mjs";
4
+ import { i as Initializeable, o as PluginArgs, s as PluginCtor } from "./Plugin-D3wIj0xX.mjs";
5
5
  import { n as contextMenuLeaves, t as ContextMenuLeaves } from "./contextMenuLeaves-COzDYa6c.mjs";
6
6
  import "reflect-metadata";
7
7
  import { AnyCustomId, DecodedParams } from "@seedcord/custom-id";
@@ -9,8 +9,54 @@ import { ContextMenuCommandBuilder, SlashCommandBuilder } from "@discordjs/build
9
9
  import { ChannelType } from "discord-api-types/v10";
10
10
  import { SeedcordErrorCode } from "@seedcord/errors";
11
11
  import { SeedcordError } from "@seedcord/errors/internal";
12
+ import { Logger } from "@seedcord/logger";
12
13
  import { DevChannel, SeedcordCliEvents, SeedcordFrameworkEvents } from "@seedcord/types/internal";
13
14
  import { BotColor, DeferOpts, EpochMs, HmrAware, ReplyResponse, SendOpts } from "@seedcord/types";
15
+ //#region src/dispatch/MiddlewareRegistry.d.ts
16
+ /**
17
+ * A decorated middleware reduced to what the registry orders and filters on. An absent `keys` puts the
18
+ * middleware in every chain.
19
+ *
20
+ * @internal
21
+ */
22
+ interface MiddlewareRegistration<Key extends string> {
23
+ readonly priority: number;
24
+ readonly keys?: readonly Key[];
25
+ }
26
+ /**
27
+ * Reads a transport's decorator metadata off a class. Returns `undefined` for an undecorated one.
28
+ *
29
+ * @internal
30
+ */
31
+ type MiddlewareRegistrationOf<Ctor, Key extends string> = (ctor: Ctor) => MiddlewareRegistration<Key> | undefined;
32
+ /** @internal */
33
+ declare function interactionMiddleware(ctor: AnyMiddlewareCtor): MiddlewareRegistration<MiddlewareKind> | undefined;
34
+ /**
35
+ * Holds the middleware a transport loaded and keeps one chain per key. An interaction keys on its kind
36
+ * and an event on its name. Priority and the key filter behave the same on each.
37
+ *
38
+ * @typeParam Ctor - The transport's middleware constructor type.
39
+ * @typeParam Key - The chain lookup key.
40
+ *
41
+ * @internal
42
+ */
43
+ declare class MiddlewareRegistry<Ctor extends AnyMiddlewareCtor, Key extends string = MiddlewareKind> {
44
+ private readonly registrationOf;
45
+ private readonly entries;
46
+ private chains;
47
+ constructor(registrationOf: MiddlewareRegistrationOf<Ctor, Key>);
48
+ /**
49
+ * Adds a decorated middleware class and returns what it registered with. Returns `undefined` for an
50
+ * undecorated class or one already registered.
51
+ *
52
+ * @throws A **SeedcordError** If another class with the same name is registered.
53
+ */
54
+ register(ctor: Ctor): MiddlewareRegistration<Key> | undefined;
55
+ unregister(ctor: Ctor): void;
56
+ chainFor(key: Key): readonly Ctor[];
57
+ private rebuild;
58
+ }
59
+ //#endregion
14
60
  //#region src/components/botColorHolder.d.ts
15
61
  declare function setBotColor(color: BotColor | undefined): void;
16
62
  //#endregion
@@ -24,9 +70,8 @@ declare function contextMenuRouteOf(kind: ContextMenuKind): InteractionKind;
24
70
  //#endregion
25
71
  //#region src/gates/runGates.d.ts
26
72
  type GateObserver = (gateName: string, elapsedMs: number) => void;
27
- declare function routeIdOf(handlerCtor: object): string | null;
28
73
  declare function runGates(gates: readonly Gate<GateContextBase>[], ctx: GateContextBase, observe?: GateObserver): Promise<void>;
29
- declare function runHandlerGates(handlerCtor: object, ctx: GateContextBase, routeId?: string, observe?: GateObserver): Promise<void>;
74
+ declare function runHandlerGates(handlerCtor: object, ctx: GateContextBase, declaredRoute?: string, observe?: GateObserver): Promise<void>;
30
75
  //#endregion
31
76
  //#region src/gates/slowGate.d.ts
32
77
  /**
@@ -35,7 +80,7 @@ declare function runHandlerGates(handlerCtor: object, ctx: GateContextBase, rout
35
80
  */
36
81
  interface SlowGateMonitor {
37
82
  readonly observe: GateObserver;
38
- report(routeId: string | null): void;
83
+ report(declaredRoute: string | null): void;
39
84
  }
40
85
  declare function slowGateMonitor(): SlowGateMonitor | undefined;
41
86
  //#endregion
@@ -253,14 +298,17 @@ declare class ReportThrottle {
253
298
  *
254
299
  * @internal
255
300
  */
256
- declare function outcomeFor(caught: unknown): DispatchOutcome;
301
+ declare function outcomeFor(caught: unknown): Exclude<DispatchOutcome, 'handled'>;
257
302
  interface DispatchReport {
303
+ readonly dispatchId: string;
258
304
  readonly routeId: string;
259
305
  readonly interactionId: string;
260
306
  readonly kind: `${InteractionKind}`;
261
307
  readonly outcome: DispatchOutcome;
262
308
  /** True when no route matched and the unhandled default ran. */
263
309
  readonly fallback: boolean;
310
+ readonly userId: string | null;
311
+ readonly guildId: string | null;
264
312
  /** `performance.now()` captured as the dispatch began. */
265
313
  readonly startedAt: number;
266
314
  /** {@link queuedMsFor} read at the same moment as `startedAt`. */
@@ -275,5 +323,28 @@ interface DispatchReport {
275
323
  declare function queuedMsFor(interactionId: string): number;
276
324
  declare function reportDispatch(bus: Bus, report: DispatchReport): void;
277
325
  //#endregion
278
- export { type AckState, AckTrace, type AutocompletableNames, AutocompleteRouteBrand, type CacheFor, type ChoiceValueOf, type CommandBuilder, type CommandCtor, type CommandMeta, CommandMetadataKey, ComponentDefsBrand, ComponentDefsKey, ComponentKindBrand, ContextMenuKindBrand, type ContextMenuLeaves, ContextMenuNamesBrand, type DecodedComponentRoute, type DefaultSubscriptions, type DeployResult, type EntryFor, EventMetadataKey, type FocusedField, type GateFitsWith, GateNotice, type GateObserver, GatedMetadataKey, type HasComponentDefs, HasDangerousPermissions, HmrManager, type Initializeable, InteractionMetadataKey, InteractionRouteKeys, type MatchArms, type MenuCacheFor, MiddlewareMetadataKey, MissingPermissions, MissingRole, type NamesFor, NeedsAny, NotAllowed, NotInDm, NotInGuild, NotOwner, NoticeCard, OnCooldown, type OptionKind, type OptionLens, PAGE_MAX, type PageCursor, PermissionNames, type PluginArgs, type PluginCtor, PublishDefault, RegisterDefaults, RegisterSubscriber, RegisteredCount, type ReplyMethod, type ReplyTelemetry, ReportThrottle, type RoutableConstructor, type RouteCache, type SerializedReply, type SingleParams, type SlashOption, type SlashOptions, SlashRouteBrand, type SlashRouteEntry, type SlowGateMonitor, type StoredSubscriberCtor, SubscribeMetadataKey, type SubscriberRegistration, UnregisterSubscriber, VerifyWebhooks, WebhookUrlMetadataKey, accessorStore, areRoutes, asError, busLoggerOf, checkAckLegality, clearStore, contextMenuLeaves, contextMenuRouteOf, decodeComponentRoute, deferFlags, getDevChannel, guardedAccessor, interactionRoutesOf, isCommandClass, isEmojiTuple, outcomeFor, pageCursor, pickNotice, queuedMsFor, reportDispatch, reportedWrite, routeIdOf, runGates, runHandlerGates, sendFlags, sendTarget, serializeReply, setBotColor, setDevChannel, slashRouteLeaves, slowGateMonitor, storeComponentRoute, storeInteractionRoute, translateSerializationError, wrapHot };
326
+ //#region src/dispatch/runAfter.d.ts
327
+ interface Afterable<Result> {
328
+ after?(result: Result): Promise<void>;
329
+ }
330
+ /** @internal */
331
+ declare function resultFor(caught: unknown): DispatchResult;
332
+ /**
333
+ * Builds what an event middleware's `after()` receives. `handlers` is empty exactly when the chain
334
+ * stopped the event.
335
+ *
336
+ * @internal
337
+ */
338
+ declare function eventResultFor(stopped: {
339
+ caught: unknown;
340
+ } | null, handlers: readonly HandlerResult[]): EventDispatchResult;
341
+ /**
342
+ * Calls `after()` on each middleware that ran, newest first. A throw inside one is logged. Every
343
+ * remaining middleware still gets its call.
344
+ *
345
+ * @internal
346
+ */
347
+ declare function runAfter<Result>(ran: readonly Afterable<Result>[], result: Result, logger: Logger): Promise<void>;
348
+ //#endregion
349
+ export { type AckState, AckTrace, type AutocompletableNames, AutocompleteRouteBrand, type CacheFor, type ChoiceValueOf, type CommandBuilder, type CommandCtor, type CommandMeta, CommandMetadataKey, ComponentDefsBrand, ComponentDefsKey, ComponentKindBrand, ContextMenuKindBrand, type ContextMenuLeaves, ContextMenuNamesBrand, type DecodedComponentRoute, type DefaultSubscriptions, type DeployResult, type EntryFor, EventMetadataKey, EventMiddlewareMetadataKey, type FocusedField, type GateFitsWith, GateNotice, type GateObserver, GatedMetadataKey, type HasComponentDefs, HasDangerousPermissions, HmrManager, type Initializeable, InteractionMetadataKey, type InteractionMiddlewareMetadata, InteractionMiddlewareMetadataKey, InteractionRouteKeys, type MatchArms, type MenuCacheFor, MiddlewareKindsBrand, type MiddlewareRegistration, type MiddlewareRegistrationOf, MiddlewareRegistry, MissingPermissions, MissingRole, type NamesFor, NeedsAny, NotAllowed, NotInDm, NotInGuild, NotOwner, NoticeCard, OnCooldown, type OptionKind, type OptionLens, PAGE_MAX, type PageCursor, PermissionNames, type PluginArgs, type PluginCtor, PublishDefault, RegisterDefaults, RegisterSubscriber, RegisteredCount, type ReplyMethod, type ReplyTelemetry, ReportThrottle, type RoutableConstructor, type RouteCache, type SerializedReply, type SingleParams, type SlashOption, type SlashOptions, SlashRouteBrand, type SlashRouteEntry, type SlowGateMonitor, type StoredSubscriberCtor, SubscribeMetadataKey, type SubscriberRegistration, UnregisterSubscriber, VerifyWebhooks, WebhookUrlMetadataKey, accessorStore, areRoutes, asError, busLoggerOf, checkAckLegality, clearStore, contextMenuLeaves, contextMenuRouteOf, decodeComponentRoute, deferFlags, eventResultFor, getDevChannel, guardedAccessor, interactionMiddleware, interactionMiddlewareMetaOf, interactionRoutesOf, isCommandClass, isEmojiTuple, outcomeFor, pageCursor, pickNotice, queuedMsFor, reportDispatch, reportedWrite, resultFor, runAfter, runGates, runHandlerGates, sendFlags, sendTarget, serializeReply, setBotColor, setDevChannel, slashRouteLeaves, slowGateMonitor, storeComponentRoute, storeInteractionRoute, translateSerializationError, wrapHot };
279
350
  //# sourceMappingURL=internal.index.d.mts.map
@@ -1,10 +1,11 @@
1
- import { A as translateSerializationError, B as storeComponentRoute, C as NotInDm, E as OnCooldown, F as checkAckLegality, G as Notice, H as ComponentDefsKey, I as sendTarget, L as areRoutes, N as reportedWrite, P as AckTrace, R as contextMenuRouteOf, S as NotAllowed, T as NotOwner, U as decodeComponentRoute, V as storeInteractionRoute, W as NoticeCard, _ as GateNotice, b as MissingRole, d as pickNotice, f as discardCommits, g as runCommits, h as runCheck, k as serializeReply, n as pageCursor, r as Silence, s as PermissionNames, t as PAGE_MAX, v as HasDangerousPermissions, w as NotInGuild, x as NeedsAny, y as MissingPermissions, z as interactionRoutesOf } from "./cursor-BkI9lxy2.mjs";
2
- import { C as SubscribeMetadataKey, D as guardedAccessor, E as clearStore, M as setBotColor, O as contextMenuLeaves, S as MiddlewareMetadataKey, T as accessorStore, _ as EventMetadataKey, a as RegisterSubscriber, b as InteractionMetadataKey, c as VerifyWebhooks, g as CommandMetadataKey, h as sendFlags, i as RegisterDefaults, m as deferFlags, n as busLoggerOf, o as RegisteredCount, p as ReportThrottle, s as UnregisterSubscriber, v as GatedMetadataKey, w as WebhookUrlMetadataKey, x as InteractionRouteKeys } from "./Bus-dGy6xq3F.mjs";
1
+ import { A as translateSerializationError, B as contextMenuRouteOf, C as NotInDm, E as OnCooldown, F as checkAckLegality, G as decodeComponentRoute, H as storeComponentRoute, I as sendTarget, K as NoticeCard, N as reportedWrite, P as AckTrace, R as interactionMiddlewareMetaOf, S as NotAllowed, T as NotOwner, U as storeInteractionRoute, V as interactionRoutesOf, W as ComponentDefsKey, _ as GateNotice, b as MissingRole, d as pickNotice, f as discardCommits, g as runCommits, h as runCheck, k as serializeReply, n as pageCursor, q as Notice, r as Silence, s as PermissionNames, t as PAGE_MAX, v as HasDangerousPermissions, w as NotInGuild, x as NeedsAny, y as MissingPermissions, z as areRoutes } from "./cursor-CFNKLVlk.mjs";
2
+ import { C as InteractionRouteKeys, D as clearStore, E as accessorStore, N as setBotColor, O as guardedAccessor, S as InteractionMiddlewareMetadataKey, T as WebhookUrlMetadataKey, _ as EventMetadataKey, a as RegisterSubscriber, c as VerifyWebhooks, g as CommandMetadataKey, h as sendFlags, i as RegisterDefaults, k as contextMenuLeaves, m as deferFlags, n as busLoggerOf, o as RegisteredCount, p as ReportThrottle, s as UnregisterSubscriber, v as EventMiddlewareMetadataKey, w as SubscribeMetadataKey, x as InteractionMetadataKey, y as GatedMetadataKey } from "./Bus-Djm6688b.mjs";
3
3
  import { n as asError, t as PublishDefault } from "./publishDefault-BuSZPbjT.mjs";
4
4
  import { n as setDevChannel, t as getDevChannel } from "./devChannel-CQU7hq7Q.mjs";
5
- import { n as isCommandClass, t as slashRouteLeaves } from "./slashRouteLeaves-C1msUE9Q.mjs";
5
+ import { n as isCommandClass, t as slashRouteLeaves } from "./slashRouteLeaves-DTFHzCKN.mjs";
6
6
  import "reflect-metadata";
7
- import { paint } from "@seedcord/errors";
7
+ import { SeedcordErrorCode, paint } from "@seedcord/errors";
8
+ import { SeedcordError } from "@seedcord/errors/internal";
8
9
  import { Logger } from "@seedcord/logger";
9
10
  import { timestampFromSnowflake } from "@seedcord/utils";
10
11
  import { Envapter } from "envapt";
@@ -16,16 +17,75 @@ const ContextMenuKindBrand = Symbol("seedcord:brand:context-menu-kind");
16
17
  const ContextMenuNamesBrand = Symbol("seedcord:brand:context-menu-names");
17
18
  const ComponentKindBrand = Symbol("seedcord:brand:component-kind");
18
19
  const ComponentDefsBrand = Symbol("seedcord:brand:component-defs");
20
+ const MiddlewareKindsBrand = Symbol("seedcord:brand:middleware-kinds");
19
21
 
20
22
  //#endregion
21
- //#region src/gates/runGates.ts
22
- function routeIdOf(handlerCtor) {
23
- for (const [kind, key] of Object.entries(InteractionRouteKeys)) {
24
- const routes = Reflect.getMetadata(key, handlerCtor);
25
- if (routes?.length) return `${kind}:${routes.join(",")}`;
26
- }
27
- return null;
23
+ //#region src/dispatch/MiddlewareRegistry.ts
24
+ /** @internal */
25
+ function interactionMiddleware(ctor) {
26
+ const metadata = interactionMiddlewareMetaOf(ctor);
27
+ if (!metadata) return void 0;
28
+ return {
29
+ priority: metadata.priority,
30
+ ...metadata.kinds && { keys: metadata.kinds }
31
+ };
28
32
  }
33
+ /**
34
+ * Holds the middleware a transport loaded and keeps one chain per key. An interaction keys on its kind
35
+ * and an event on its name. Priority and the key filter behave the same on each.
36
+ *
37
+ * @typeParam Ctor - The transport's middleware constructor type.
38
+ * @typeParam Key - The chain lookup key.
39
+ *
40
+ * @internal
41
+ */
42
+ var MiddlewareRegistry = class {
43
+ registrationOf;
44
+ entries = [];
45
+ chains = /* @__PURE__ */ new Map();
46
+ constructor(registrationOf) {
47
+ this.registrationOf = registrationOf;
48
+ }
49
+ /**
50
+ * Adds a decorated middleware class and returns what it registered with. Returns `undefined` for an
51
+ * undecorated class or one already registered.
52
+ *
53
+ * @throws A **SeedcordError** If another class with the same name is registered.
54
+ */
55
+ register(ctor) {
56
+ const registration = this.registrationOf(ctor);
57
+ if (!registration) return void 0;
58
+ if (this.entries.some((entry) => entry.ctor === ctor)) return void 0;
59
+ if (this.entries.some((entry) => entry.ctor.name === ctor.name)) throw new SeedcordError(SeedcordErrorCode.DuplicateMiddleware, [ctor.name]);
60
+ this.entries.push({
61
+ ctor,
62
+ priority: registration.priority,
63
+ ...registration.keys && { keys: new Set(registration.keys) }
64
+ });
65
+ this.rebuild();
66
+ return registration;
67
+ }
68
+ unregister(ctor) {
69
+ const index = this.entries.findIndex((entry) => entry.ctor === ctor);
70
+ if (index === -1) return;
71
+ this.entries.splice(index, 1);
72
+ this.rebuild();
73
+ }
74
+ chainFor(key) {
75
+ const built = this.chains.get(key);
76
+ if (built) return built;
77
+ const chain = this.entries.filter((entry) => !entry.keys || entry.keys.has(key)).map((entry) => entry.ctor);
78
+ this.chains.set(key, chain);
79
+ return chain;
80
+ }
81
+ rebuild() {
82
+ this.entries.sort((a, b) => a.priority - b.priority);
83
+ this.chains = /* @__PURE__ */ new Map();
84
+ }
85
+ };
86
+
87
+ //#endregion
88
+ //#region src/gates/runGates.ts
29
89
  async function timedCheck(gate, ctx, observe) {
30
90
  const start = performance.now();
31
91
  try {
@@ -42,12 +102,12 @@ async function runGates(gates, ctx, observe) {
42
102
  discardCommits(ctx);
43
103
  }
44
104
  }
45
- async function runHandlerGates(handlerCtor, ctx, routeId, observe) {
105
+ async function runHandlerGates(handlerCtor, ctx, declaredRoute, observe) {
46
106
  const gates = Reflect.getMetadata(GatedMetadataKey, handlerCtor);
47
107
  if (!gates) return;
48
108
  await runGates(gates, {
49
109
  ...ctx,
50
- routeId: routeId ?? routeIdOf(handlerCtor)
110
+ declaredRoute: declaredRoute ?? null
51
111
  }, observe);
52
112
  }
53
113
 
@@ -67,11 +127,11 @@ function slowGateMonitor() {
67
127
  name,
68
128
  ms
69
129
  }),
70
- report(routeId) {
130
+ report(declaredRoute) {
71
131
  const total = readings.reduce((sum, reading) => sum + reading.ms, 0);
72
132
  if (total < SLOW_GATE_MS) return;
73
133
  const shares = readings.toSorted((a, b) => b.ms - a.ms).map((reading) => `${paint.sky.bold(reading.name)} ${paint.mute(`${Math.round(reading.ms)}ms`)}`);
74
- const headline = `gates${routeId === null ? "" : ` for ${paint.sky.bold(routeId)}`} took ${paint.amber(`${Math.round(total)}ms`)} of the 3s ack budget`;
134
+ const headline = `gates${declaredRoute === null ? "" : ` for ${paint.sky.bold(declaredRoute)}`} took ${paint.amber(`${Math.round(total)}ms`)} of the 3s ack budget`;
75
135
  logger$1().utils.block(headline, shares, "warn", (text) => text);
76
136
  }
77
137
  };
@@ -158,11 +218,14 @@ function queuedMsFor(interactionId) {
158
218
  function reportDispatch(bus, report) {
159
219
  const durationMs = performance.now() - report.startedAt;
160
220
  bus[PublishDefault]("interactionDispatched", {
221
+ dispatchId: report.dispatchId,
161
222
  routeId: report.routeId,
162
223
  interactionId: report.interactionId,
163
224
  kind: report.kind,
164
225
  outcome: report.outcome,
165
226
  fallback: report.fallback,
227
+ userId: report.userId,
228
+ guildId: report.guildId,
166
229
  durationMs,
167
230
  queuedMs: report.queuedMs
168
231
  });
@@ -170,5 +233,47 @@ function reportDispatch(bus, report) {
170
233
  }
171
234
 
172
235
  //#endregion
173
- export { AckTrace, AutocompleteRouteBrand, CommandMetadataKey, ComponentDefsBrand, ComponentDefsKey, ComponentKindBrand, ContextMenuKindBrand, ContextMenuNamesBrand, EventMetadataKey, GateNotice, GatedMetadataKey, HasDangerousPermissions, HmrManager, InteractionMetadataKey, InteractionRouteKeys, MiddlewareMetadataKey, MissingPermissions, MissingRole, NeedsAny, NotAllowed, NotInDm, NotInGuild, NotOwner, NoticeCard, OnCooldown, PAGE_MAX, PermissionNames, PublishDefault, RegisterDefaults, RegisterSubscriber, RegisteredCount, ReportThrottle, SlashRouteBrand, SubscribeMetadataKey, UnregisterSubscriber, VerifyWebhooks, WebhookUrlMetadataKey, accessorStore, areRoutes, asError, busLoggerOf, checkAckLegality, clearStore, contextMenuLeaves, contextMenuRouteOf, decodeComponentRoute, deferFlags, getDevChannel, guardedAccessor, interactionRoutesOf, isCommandClass, isEmojiTuple, outcomeFor, pageCursor, pickNotice, queuedMsFor, reportDispatch, reportedWrite, routeIdOf, runGates, runHandlerGates, sendFlags, sendTarget, serializeReply, setBotColor, setDevChannel, slashRouteLeaves, slowGateMonitor, storeComponentRoute, storeInteractionRoute, translateSerializationError, wrapHot };
236
+ //#region src/dispatch/runAfter.ts
237
+ /** @internal */
238
+ function resultFor(caught) {
239
+ return {
240
+ outcome: outcomeFor(caught),
241
+ caught
242
+ };
243
+ }
244
+ /**
245
+ * Builds what an event middleware's `after()` receives. `handlers` is empty exactly when the chain
246
+ * stopped the event.
247
+ *
248
+ * @internal
249
+ */
250
+ function eventResultFor(stopped, handlers) {
251
+ if (!stopped) return {
252
+ outcome: "handled",
253
+ handlers
254
+ };
255
+ return {
256
+ ...resultFor(stopped.caught),
257
+ handlers: []
258
+ };
259
+ }
260
+ /**
261
+ * Calls `after()` on each middleware that ran, newest first. A throw inside one is logged. Every
262
+ * remaining middleware still gets its call.
263
+ *
264
+ * @internal
265
+ */
266
+ async function runAfter(ran, result, logger) {
267
+ for (let index = ran.length - 1; index >= 0; index--) {
268
+ const middleware = ran[index];
269
+ try {
270
+ await middleware?.after?.(result);
271
+ } catch (caught) {
272
+ logger.error(`${middleware?.constructor.name ?? "A middleware"} threw from after()`, asError(caught));
273
+ }
274
+ }
275
+ }
276
+
277
+ //#endregion
278
+ export { AckTrace, AutocompleteRouteBrand, CommandMetadataKey, ComponentDefsBrand, ComponentDefsKey, ComponentKindBrand, ContextMenuKindBrand, ContextMenuNamesBrand, EventMetadataKey, EventMiddlewareMetadataKey, GateNotice, GatedMetadataKey, HasDangerousPermissions, HmrManager, InteractionMetadataKey, InteractionMiddlewareMetadataKey, InteractionRouteKeys, MiddlewareKindsBrand, MiddlewareRegistry, MissingPermissions, MissingRole, NeedsAny, NotAllowed, NotInDm, NotInGuild, NotOwner, NoticeCard, OnCooldown, PAGE_MAX, PermissionNames, PublishDefault, RegisterDefaults, RegisterSubscriber, RegisteredCount, ReportThrottle, SlashRouteBrand, SubscribeMetadataKey, UnregisterSubscriber, VerifyWebhooks, WebhookUrlMetadataKey, accessorStore, areRoutes, asError, busLoggerOf, checkAckLegality, clearStore, contextMenuLeaves, contextMenuRouteOf, decodeComponentRoute, deferFlags, eventResultFor, getDevChannel, guardedAccessor, interactionMiddleware, interactionMiddlewareMetaOf, interactionRoutesOf, isCommandClass, isEmojiTuple, outcomeFor, pageCursor, pickNotice, queuedMsFor, reportDispatch, reportedWrite, resultFor, runAfter, runGates, runHandlerGates, sendFlags, sendTarget, serializeReply, setBotColor, setDevChannel, slashRouteLeaves, slowGateMonitor, storeComponentRoute, storeInteractionRoute, translateSerializationError, wrapHot };
174
279
  //# sourceMappingURL=internal.index.mjs.map
@@ -1 +1 @@
1
- {"version":3,"file":"internal.index.mjs","names":["logger"],"sources":["../src/decorators/brands.ts","../src/gates/runGates.ts","../src/gates/slowGate.ts","../src/miscellaneous/emojiConfig.ts","../src/hmr/wrapHot.ts","../src/hmr/HmrManager.ts","../src/dispatch/dispatchReport.ts"],"sourcesContent":["// A handler base declares these so a route decorator can read what the handler is for. Nothing\n// assigns them at runtime.\n\nexport const SlashRouteBrand: unique symbol = Symbol('seedcord:brand:slash-route');\nexport const AutocompleteRouteBrand: unique symbol = Symbol('seedcord:brand:autocomplete-route');\nexport const ContextMenuKindBrand: unique symbol = Symbol('seedcord:brand:context-menu-kind');\nexport const ContextMenuNamesBrand: unique symbol = Symbol('seedcord:brand:context-menu-names');\nexport const ComponentKindBrand: unique symbol = Symbol('seedcord:brand:component-kind');\nexport const ComponentDefsBrand: unique symbol = Symbol('seedcord:brand:component-defs');\n","import 'reflect-metadata';\n\nimport { GatedMetadataKey, InteractionRouteKeys } from '#src/metadataKeys';\n\nimport { discardCommits, runCheck, runCommits } from './effects';\n\nimport type { Gate, GateContextBase } from './Gate';\n\n// a combinator (`and`/`or`) reports once under its joined name.\nexport type GateObserver = (gateName: string, elapsedMs: number) => void;\n\n// the stable id a route decorator stored, e.g. slash:daily or button:confirm. null for a plain event handler.\nexport function routeIdOf(handlerCtor: object): string | null {\n for (const [kind, key] of Object.entries(InteractionRouteKeys)) {\n // justified: getMetadata returns any, this key only ever stores the route/prefix string array\n const routes = Reflect.getMetadata(key, handlerCtor) as string[] | undefined;\n if (routes?.length) return `${kind}:${routes.join(',')}`;\n }\n return null;\n}\n\nasync function timedCheck(gate: Gate<GateContextBase>, ctx: GateContextBase, observe: GateObserver): Promise<void> {\n const start = performance.now();\n // finally so a refusing gate still reports\n try {\n await runCheck(gate, ctx);\n } finally {\n observe(gate.name, performance.now() - start);\n }\n}\n\n// the first refusal throws out to the dispatcher boundary. an effect gate's commit runs once the\n// whole set passes, and observe never times the commit phase.\nexport async function runGates(\n gates: readonly Gate<GateContextBase>[],\n ctx: GateContextBase,\n observe?: GateObserver\n): Promise<void> {\n try {\n for (const gate of gates) {\n await (observe ? timedCheck(gate, ctx, observe) : runCheck(gate, ctx));\n }\n await runCommits(ctx);\n } finally {\n discardCommits(ctx);\n }\n}\n\n// dispatchers call this before execute, inside the boundary, so a refusal renders or drops. an explicit\n// routeId is for manifest-driven handlers, which carry no route metadata to derive one from.\nexport async function runHandlerGates(\n handlerCtor: object,\n ctx: GateContextBase,\n routeId?: string,\n observe?: GateObserver\n): Promise<void> {\n // justified: getMetadata returns any, and this key only ever stores the @Gated gate array\n const gates = Reflect.getMetadata(GatedMetadataKey, handlerCtor) as readonly Gate<GateContextBase>[] | undefined;\n if (!gates) return;\n await runGates(gates, { ...ctx, routeId: routeId ?? routeIdOf(handlerCtor) }, observe);\n}\n","import { paint } from '@seedcord/errors';\nimport { Logger } from '@seedcord/logger';\nimport { Envapter } from 'envapt';\n\nimport type { GateObserver } from './runGates';\n\n// combined check time slower than this cuts into the 3s interaction-ack budget\nconst SLOW_GATE_MS = 750;\n\n// lazy because the logger reads the environment, which binds after this module loads\nlet gateLogger: Logger | undefined;\nfunction logger(): Logger {\n gateLogger ??= new Logger('Gates', { channel: 'gates' });\n return gateLogger;\n}\n\n/**\n * collects each gate check's elapsed time for one dispatch. `report` warns once when the checks sum\n * past the threshold, naming the total, the route, and each gate's share.\n */\nexport interface SlowGateMonitor {\n readonly observe: GateObserver;\n report(routeId: string | null): void;\n}\n\n// undefined in production, so timedCheck skips the clock there. read per call, the environment binds late\nexport function slowGateMonitor(): SlowGateMonitor | undefined {\n if (Envapter.isProduction) return undefined;\n const readings: { name: string; ms: number }[] = [];\n return {\n observe: (name, ms) => readings.push({ name, ms }),\n report(routeId) {\n const total = readings.reduce((sum, reading) => sum + reading.ms, 0);\n if (total < SLOW_GATE_MS) return;\n const shares = readings\n .toSorted((a, b) => b.ms - a.ms)\n .map((reading) => `${paint.sky.bold(reading.name)} ${paint.mute(`${Math.round(reading.ms)}ms`)}`);\n const route = routeId === null ? '' : ` for ${paint.sky.bold(routeId)}`;\n const headline = `gates${route} took ${paint.amber(`${Math.round(total)}ms`)} of the 3s ack budget`;\n logger().utils.block(headline, shares, 'warn', (text) => text);\n }\n };\n}\n","// narrows a configured emoji value to the [name, guildId] form\nexport function isEmojiTuple(value: unknown): value is readonly [string, string] {\n return Array.isArray(value) && value.length === 2 && typeof value[0] === 'string' && typeof value[1] === 'string';\n}\n","import type { DevChannel } from '@seedcord/types/internal';\n\n// the minimal raw-hot shape, satisfied by both vite's `import.meta.hot` and the CLI's `NormalizedHotChannel`.\ninterface RawHot {\n send(event: string, data?: unknown): void;\n on(event: string, cb: (data: unknown) => void): void;\n}\n\n/** @internal */\nexport function wrapHot<TSend, TRecv>(hot: RawHot): DevChannel<TSend, TRecv> {\n return {\n send: (event, data) => hot.send(event, data),\n // justified: RawHot types every payload as unknown, and the wire contract guarantees it matches TRecv[Key]\n on: (event, cb) => hot.on(event, cb as (data: unknown) => void)\n };\n}\n","// scoped here so import.meta.hot stays untyped in the rest of the package\n/// <reference types=\"vite/client\" />\nimport { paint } from '@seedcord/errors';\nimport { Logger } from '@seedcord/logger';\nimport { Envapter } from 'envapt';\n\nimport { setDevChannel } from './devChannel';\nimport { wrapHot } from './wrapHot';\n\nimport type { HmrAware, HmrUpdateEvent } from '@seedcord/types';\nimport type { SeedcordCliEvents, SeedcordFrameworkEvents } from '@seedcord/types/internal';\n\nexport class HmrManager {\n private readonly logger = new Logger('HMR', { channel: 'hmr' });\n private readonly listeners = new Set<HmrAware>();\n\n /** @internal */\n public init(): void {\n if (!import.meta.hot) return;\n // the only raw `import.meta.hot` read in the framework\n const channel = wrapHot<SeedcordFrameworkEvents, SeedcordCliEvents>(import.meta.hot);\n setDevChannel(channel);\n\n if (Envapter.isDevelopment || Envapter.isTest) {\n this.logger.debug('Enabled');\n channel.on('seedcord:hmr', (payload) => void this.handleUpdate(payload));\n }\n }\n\n /** @internal */\n public register(listener: HmrAware): void {\n this.listeners.add(listener);\n }\n\n private async handleUpdate(event: HmrUpdateEvent): Promise<void> {\n const promises = [...this.listeners].map(async (listener) => {\n try {\n await listener.onHmr(event);\n } catch (error) {\n this.logger.error(`Error handling HMR update in ${paint.sky.bold(listener.constructor.name)}`, error);\n }\n });\n\n await Promise.all(promises);\n }\n}\n","import { paint } from '@seedcord/errors';\nimport { Logger } from '@seedcord/logger';\nimport { timestampFromSnowflake } from '@seedcord/utils';\n\nimport { Notice } from '#stops/Notice';\nimport { Silence } from '#stops/Silence';\nimport { PublishDefault } from '#subscribers/publishDefault';\n\nimport type { InteractionKind } from '#src/metadataKeys';\nimport type { Bus } from '#subscribers/Bus';\nimport type { DispatchOutcome } from '#subscribers/types/Subscriptions';\n\nlet dispatchLogger: Logger | undefined;\nfunction logger(): Logger {\n dispatchLogger ??= new Logger('Dispatch', { channel: 'interactions' });\n return dispatchLogger;\n}\n\n/**\n * Classifies a caught stop for `interactionDispatched` so a gate and a\n * handler label the same stop identically.\n *\n * @internal\n */\nexport function outcomeFor(caught: unknown): DispatchOutcome {\n if (caught instanceof Silence) return 'refused';\n if (caught instanceof Notice) return caught.report ? 'failed' : 'refused';\n return 'failed';\n}\n\ninterface DispatchReport {\n readonly routeId: string;\n readonly interactionId: string;\n readonly kind: `${InteractionKind}`;\n readonly outcome: DispatchOutcome;\n /** True when no route matched and the unhandled default ran. */\n readonly fallback: boolean;\n /** `performance.now()` captured as the dispatch began. */\n readonly startedAt: number;\n /** {@link queuedMsFor} read at the same moment as `startedAt`. */\n readonly queuedMs: number;\n}\n\n/**\n * Discord's interaction creation to now, read from the snowflake. Called at dispatch entry, since\n * a later read would count the handler run into the queue time as well.\n *\n * @internal\n */\nexport function queuedMsFor(interactionId: string): number {\n // telemetry never breaks a dispatch, and BigInt() throws on a malformed id\n try {\n return Date.now() - timestampFromSnowflake(interactionId);\n } catch {\n return 0;\n }\n}\n\nexport function reportDispatch(bus: Bus, report: DispatchReport): void {\n const durationMs = performance.now() - report.startedAt;\n bus[PublishDefault]('interactionDispatched', {\n routeId: report.routeId,\n interactionId: report.interactionId,\n kind: report.kind,\n outcome: report.outcome,\n fallback: report.fallback,\n durationMs,\n queuedMs: report.queuedMs\n });\n\n // a throwing log sink must not stop the publish above\n logger().trace(\n `${paint.sky.bold(report.routeId)} ${report.outcome} ${paint.mute('in')} ${Math.round(durationMs)}ms`\n );\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"internal.index.mjs","names":["logger"],"sources":["../src/decorators/brands.ts","../src/dispatch/MiddlewareRegistry.ts","../src/gates/runGates.ts","../src/gates/slowGate.ts","../src/miscellaneous/emojiConfig.ts","../src/hmr/wrapHot.ts","../src/hmr/HmrManager.ts","../src/dispatch/dispatchReport.ts","../src/dispatch/runAfter.ts"],"sourcesContent":["// phantom, nothing assigns these at runtime\n\nexport const SlashRouteBrand: unique symbol = Symbol('seedcord:brand:slash-route');\nexport const AutocompleteRouteBrand: unique symbol = Symbol('seedcord:brand:autocomplete-route');\nexport const ContextMenuKindBrand: unique symbol = Symbol('seedcord:brand:context-menu-kind');\nexport const ContextMenuNamesBrand: unique symbol = Symbol('seedcord:brand:context-menu-names');\nexport const ComponentKindBrand: unique symbol = Symbol('seedcord:brand:component-kind');\nexport const ComponentDefsBrand: unique symbol = Symbol('seedcord:brand:component-defs');\nexport const MiddlewareKindsBrand: unique symbol = Symbol('seedcord:brand:middleware-kinds');\n","import { SeedcordErrorCode } from '@seedcord/errors';\nimport { SeedcordError } from '@seedcord/errors/internal';\n\nimport { interactionMiddlewareMetaOf } from '#decorators/middleware';\n\nimport type { AnyMiddlewareCtor } from '#decorators/middleware';\nimport type { MiddlewareKind } from '#src/metadataKeys';\n\n/**\n * A decorated middleware reduced to what the registry orders and filters on. An absent `keys` puts the\n * middleware in every chain.\n *\n * @internal\n */\nexport interface MiddlewareRegistration<Key extends string> {\n readonly priority: number;\n readonly keys?: readonly Key[];\n}\n\n/**\n * Reads a transport's decorator metadata off a class. Returns `undefined` for an undecorated one.\n *\n * @internal\n */\nexport type MiddlewareRegistrationOf<Ctor, Key extends string> = (\n ctor: Ctor\n) => MiddlewareRegistration<Key> | undefined;\n\n/** @internal */\nexport function interactionMiddleware(ctor: AnyMiddlewareCtor): MiddlewareRegistration<MiddlewareKind> | undefined {\n const metadata = interactionMiddlewareMetaOf(ctor);\n if (!metadata) return undefined;\n return { priority: metadata.priority, ...(metadata.kinds && { keys: metadata.kinds }) };\n}\n\ninterface Entry<Ctor, Key extends string> {\n readonly ctor: Ctor;\n readonly priority: number;\n readonly keys?: ReadonlySet<Key>;\n}\n\n/**\n * Holds the middleware a transport loaded and keeps one chain per key. An interaction keys on its kind\n * and an event on its name. Priority and the key filter behave the same on each.\n *\n * @typeParam Ctor - The transport's middleware constructor type.\n * @typeParam Key - The chain lookup key.\n *\n * @internal\n */\nexport class MiddlewareRegistry<Ctor extends AnyMiddlewareCtor, Key extends string = MiddlewareKind> {\n private readonly entries: Entry<Ctor, Key>[] = [];\n // an in-flight dispatch keeps walking the array chainFor gave it. never mutate one in place.\n private chains = new Map<Key, readonly Ctor[]>();\n\n public constructor(private readonly registrationOf: MiddlewareRegistrationOf<Ctor, Key>) {}\n\n /**\n * Adds a decorated middleware class and returns what it registered with. Returns `undefined` for an\n * undecorated class or one already registered.\n *\n * @throws A **SeedcordError** If another class with the same name is registered.\n */\n public register(ctor: Ctor): MiddlewareRegistration<Key> | undefined {\n const registration = this.registrationOf(ctor);\n if (!registration) return undefined;\n\n // a double import or an hmr re-scan registers the same class twice\n if (this.entries.some((entry) => entry.ctor === ctor)) return undefined;\n if (this.entries.some((entry) => entry.ctor.name === ctor.name)) {\n throw new SeedcordError(SeedcordErrorCode.DuplicateMiddleware, [ctor.name]);\n }\n\n this.entries.push({\n ctor,\n priority: registration.priority,\n ...(registration.keys && { keys: new Set(registration.keys) })\n });\n this.rebuild();\n return registration;\n }\n\n public unregister(ctor: Ctor): void {\n const index = this.entries.findIndex((entry) => entry.ctor === ctor);\n if (index === -1) return;\n this.entries.splice(index, 1);\n this.rebuild();\n }\n\n public chainFor(key: Key): readonly Ctor[] {\n const built = this.chains.get(key);\n if (built) return built;\n\n const chain = this.entries.filter((entry) => !entry.keys || entry.keys.has(key)).map((entry) => entry.ctor);\n this.chains.set(key, chain);\n return chain;\n }\n\n private rebuild(): void {\n // a stable sort keeps ties in registration order\n this.entries.sort((a, b) => a.priority - b.priority);\n this.chains = new Map();\n }\n}\n","import 'reflect-metadata';\n\nimport { GatedMetadataKey } from '#src/metadataKeys';\n\nimport { discardCommits, runCheck, runCommits } from './effects';\n\nimport type { Gate, GateContextBase } from './Gate';\n\n// a combinator (`and`/`or`) reports once under its joined name.\nexport type GateObserver = (gateName: string, elapsedMs: number) => void;\n\nasync function timedCheck(gate: Gate<GateContextBase>, ctx: GateContextBase, observe: GateObserver): Promise<void> {\n const start = performance.now();\n // finally so a refusing gate still reports\n try {\n await runCheck(gate, ctx);\n } finally {\n observe(gate.name, performance.now() - start);\n }\n}\n\n// the first refusal throws out to the dispatcher boundary. an effect gate's commit runs once the\n// whole set passes, and observe never times the commit phase.\nexport async function runGates(\n gates: readonly Gate<GateContextBase>[],\n ctx: GateContextBase,\n observe?: GateObserver\n): Promise<void> {\n try {\n for (const gate of gates) {\n await (observe ? timedCheck(gate, ctx, observe) : runCheck(gate, ctx));\n }\n await runCommits(ctx);\n } finally {\n discardCommits(ctx);\n }\n}\n\n// dispatchers call this before execute, inside the boundary, so a refusal renders or drops\nexport async function runHandlerGates(\n handlerCtor: object,\n ctx: GateContextBase,\n declaredRoute?: string,\n observe?: GateObserver\n): Promise<void> {\n // justified: getMetadata returns any, and this key only ever stores the @Gated gate array\n const gates = Reflect.getMetadata(GatedMetadataKey, handlerCtor) as readonly Gate<GateContextBase>[] | undefined;\n if (!gates) return;\n await runGates(gates, { ...ctx, declaredRoute: declaredRoute ?? null }, observe);\n}\n","import { paint } from '@seedcord/errors';\nimport { Logger } from '@seedcord/logger';\nimport { Envapter } from 'envapt';\n\nimport type { GateObserver } from './runGates';\n\n// combined check time slower than this cuts into the 3s interaction-ack budget\nconst SLOW_GATE_MS = 750;\n\n// lazy because the logger reads the environment, which binds after this module loads\nlet gateLogger: Logger | undefined;\nfunction logger(): Logger {\n gateLogger ??= new Logger('Gates', { channel: 'gates' });\n return gateLogger;\n}\n\n/**\n * collects each gate check's elapsed time for one dispatch. `report` warns once when the checks sum\n * past the threshold, naming the total, the route, and each gate's share.\n */\nexport interface SlowGateMonitor {\n readonly observe: GateObserver;\n report(declaredRoute: string | null): void;\n}\n\n// undefined in production, so timedCheck skips the clock there. read per call, the environment binds late\nexport function slowGateMonitor(): SlowGateMonitor | undefined {\n if (Envapter.isProduction) return undefined;\n const readings: { name: string; ms: number }[] = [];\n return {\n observe: (name, ms) => readings.push({ name, ms }),\n report(declaredRoute) {\n const total = readings.reduce((sum, reading) => sum + reading.ms, 0);\n if (total < SLOW_GATE_MS) return;\n const shares = readings\n .toSorted((a, b) => b.ms - a.ms)\n .map((reading) => `${paint.sky.bold(reading.name)} ${paint.mute(`${Math.round(reading.ms)}ms`)}`);\n const route = declaredRoute === null ? '' : ` for ${paint.sky.bold(declaredRoute)}`;\n const headline = `gates${route} took ${paint.amber(`${Math.round(total)}ms`)} of the 3s ack budget`;\n logger().utils.block(headline, shares, 'warn', (text) => text);\n }\n };\n}\n","// narrows a configured emoji value to the [name, guildId] form\nexport function isEmojiTuple(value: unknown): value is readonly [string, string] {\n return Array.isArray(value) && value.length === 2 && typeof value[0] === 'string' && typeof value[1] === 'string';\n}\n","import type { DevChannel } from '@seedcord/types/internal';\n\n// the minimal raw-hot shape, satisfied by both vite's `import.meta.hot` and the CLI's `NormalizedHotChannel`.\ninterface RawHot {\n send(event: string, data?: unknown): void;\n on(event: string, cb: (data: unknown) => void): void;\n}\n\n/** @internal */\nexport function wrapHot<TSend, TRecv>(hot: RawHot): DevChannel<TSend, TRecv> {\n return {\n send: (event, data) => hot.send(event, data),\n // justified: RawHot types every payload as unknown, and the wire contract guarantees it matches TRecv[Key]\n on: (event, cb) => hot.on(event, cb as (data: unknown) => void)\n };\n}\n","// scoped here so import.meta.hot stays untyped in the rest of the package\n/// <reference types=\"vite/client\" />\nimport { paint } from '@seedcord/errors';\nimport { Logger } from '@seedcord/logger';\nimport { Envapter } from 'envapt';\n\nimport { setDevChannel } from './devChannel';\nimport { wrapHot } from './wrapHot';\n\nimport type { HmrAware, HmrUpdateEvent } from '@seedcord/types';\nimport type { SeedcordCliEvents, SeedcordFrameworkEvents } from '@seedcord/types/internal';\n\nexport class HmrManager {\n private readonly logger = new Logger('HMR', { channel: 'hmr' });\n private readonly listeners = new Set<HmrAware>();\n\n /** @internal */\n public init(): void {\n if (!import.meta.hot) return;\n // the only raw `import.meta.hot` read in the framework\n const channel = wrapHot<SeedcordFrameworkEvents, SeedcordCliEvents>(import.meta.hot);\n setDevChannel(channel);\n\n if (Envapter.isDevelopment || Envapter.isTest) {\n this.logger.debug('Enabled');\n channel.on('seedcord:hmr', (payload) => void this.handleUpdate(payload));\n }\n }\n\n /** @internal */\n public register(listener: HmrAware): void {\n this.listeners.add(listener);\n }\n\n private async handleUpdate(event: HmrUpdateEvent): Promise<void> {\n const promises = [...this.listeners].map(async (listener) => {\n try {\n await listener.onHmr(event);\n } catch (error) {\n this.logger.error(`Error handling HMR update in ${paint.sky.bold(listener.constructor.name)}`, error);\n }\n });\n\n await Promise.all(promises);\n }\n}\n","import { paint } from '@seedcord/errors';\nimport { Logger } from '@seedcord/logger';\nimport { timestampFromSnowflake } from '@seedcord/utils';\n\nimport { Notice } from '#stops/Notice';\nimport { Silence } from '#stops/Silence';\nimport { PublishDefault } from '#subscribers/publishDefault';\n\nimport type { InteractionKind } from '#src/metadataKeys';\nimport type { Bus } from '#subscribers/Bus';\nimport type { DispatchOutcome } from '#subscribers/types/Subscriptions';\n\nlet dispatchLogger: Logger | undefined;\nfunction logger(): Logger {\n dispatchLogger ??= new Logger('Dispatch', { channel: 'interactions' });\n return dispatchLogger;\n}\n\n/**\n * Classifies a caught stop for `interactionDispatched` so a gate and a\n * handler label the same stop identically.\n *\n * @internal\n */\nexport function outcomeFor(caught: unknown): Exclude<DispatchOutcome, 'handled'> {\n if (caught instanceof Silence) return 'refused';\n if (caught instanceof Notice) return caught.report ? 'failed' : 'refused';\n return 'failed';\n}\n\ninterface DispatchReport {\n readonly dispatchId: string;\n readonly routeId: string;\n readonly interactionId: string;\n readonly kind: `${InteractionKind}`;\n readonly outcome: DispatchOutcome;\n /** True when no route matched and the unhandled default ran. */\n readonly fallback: boolean;\n readonly userId: string | null;\n readonly guildId: string | null;\n /** `performance.now()` captured as the dispatch began. */\n readonly startedAt: number;\n /** {@link queuedMsFor} read at the same moment as `startedAt`. */\n readonly queuedMs: number;\n}\n\n/**\n * Discord's interaction creation to now, read from the snowflake. Called at dispatch entry, since\n * a later read would count the handler run into the queue time as well.\n *\n * @internal\n */\nexport function queuedMsFor(interactionId: string): number {\n // timestampFromSnowflake throws on a malformed id\n try {\n return Date.now() - timestampFromSnowflake(interactionId);\n } catch {\n return 0;\n }\n}\n\nexport function reportDispatch(bus: Bus, report: DispatchReport): void {\n const durationMs = performance.now() - report.startedAt;\n bus[PublishDefault]('interactionDispatched', {\n dispatchId: report.dispatchId,\n routeId: report.routeId,\n interactionId: report.interactionId,\n kind: report.kind,\n outcome: report.outcome,\n fallback: report.fallback,\n userId: report.userId,\n guildId: report.guildId,\n durationMs,\n queuedMs: report.queuedMs\n });\n\n // a throwing log sink must not stop the publish above\n logger().trace(\n `${paint.sky.bold(report.routeId)} ${report.outcome} ${paint.mute('in')} ${Math.round(durationMs)}ms`\n );\n}\n","import { asError } from '#stops/asError';\n\nimport { outcomeFor } from './dispatchReport';\n\nimport type { DispatchResult, EventDispatchResult, HandlerResult } from './dispatchResult';\nimport type { Logger } from '@seedcord/logger';\n\ninterface Afterable<Result> {\n after?(result: Result): Promise<void>;\n}\n\n/** @internal */\nexport function resultFor(caught: unknown): DispatchResult {\n return { outcome: outcomeFor(caught), caught };\n}\n\n/**\n * Builds what an event middleware's `after()` receives. `handlers` is empty exactly when the chain\n * stopped the event.\n *\n * @internal\n */\nexport function eventResultFor(\n stopped: { caught: unknown } | null,\n handlers: readonly HandlerResult[]\n): EventDispatchResult {\n if (!stopped) return { outcome: 'handled', handlers };\n return { ...resultFor(stopped.caught), handlers: [] };\n}\n\n/**\n * Calls `after()` on each middleware that ran, newest first. A throw inside one is logged. Every\n * remaining middleware still gets its call.\n *\n * @internal\n */\nexport async function runAfter<Result>(\n ran: readonly Afterable<Result>[],\n result: Result,\n logger: Logger\n): Promise<void> {\n for (let index = ran.length - 1; index >= 0; index--) {\n const middleware = ran[index];\n try {\n await middleware?.after?.(result);\n } catch (caught) {\n logger.error(`${middleware?.constructor.name ?? 'A middleware'} threw from after()`, asError(caught));\n }\n }\n}\n"],"mappings":"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"}
@@ -1,9 +1,9 @@
1
- import { i as Bus, n as StartupPhase, r as CoreBase, t as ShutdownPhase } from "./phases-D0a8BV9Z.mjs";
1
+ import { i as Bus, n as StartupPhase, r as CoreBase, t as ShutdownPhase } from "./phases-DJf12l5T.mjs";
2
2
  import { n as CommandBuilder, r as DeployResult } from "./basic-Dng__Ena.mjs";
3
- import "./index-CD8l15aZ.mjs";
4
- import { n as Initializeable } from "./Plugin-Bnz61fs_.mjs";
3
+ import "./index-BS1hH9Sh.mjs";
4
+ import { i as Initializeable } from "./Plugin-D3wIj0xX.mjs";
5
5
  import { t as ContextMenuLeaves } from "./contextMenuLeaves-COzDYa6c.mjs";
6
- import { a as LifecycleTask, i as CoordinatedShutdown, n as shutdownOf } from "./Pluggable-C5uxmDb-.mjs";
6
+ import { a as LifecycleTask, i as CoordinatedShutdown, n as shutdownOf } from "./Pluggable-CAteNmfy.mjs";
7
7
  import { Logger } from "@seedcord/logger";
8
8
  import { IncomingMessage, ServerResponse } from "node:http";
9
9
  import { HealthCheckConfig, HealthCheckOption, HmrAware, HmrUpdateEvent } from "@seedcord/types";
@@ -75,6 +75,7 @@ declare class CommandRegistry implements Initializeable, HmrAware {
75
75
  private readonly pendingEvents;
76
76
  private readonly injector;
77
77
  private readonly dir;
78
+ private readonly configuredGuilds;
78
79
  constructor(core: CoreBase);
79
80
  private getArtifacts;
80
81
  /** @internal */
@@ -89,6 +90,7 @@ declare class CommandRegistry implements Initializeable, HmrAware {
89
90
  private unregisterCommand;
90
91
  /** @internal */
91
92
  setCommands(): Promise<DeployResult>;
93
+ private putGuild;
92
94
  private put;
93
95
  /** Every registered builder, global first. A guild command in N guilds appears once per guild. @internal */
94
96
  allCommands(): CommandBuilder[];
@@ -1,11 +1,11 @@
1
- import { C as SubscribeMetadataKey, O as contextMenuLeaves, a as RegisterSubscriber, c as VerifyWebhooks, f as Subscriber, g as CommandMetadataKey, i as RegisterDefaults, n as busLoggerOf, o as RegisteredCount, r as registrationFor, s as UnregisterSubscriber } from "./Bus-dGy6xq3F.mjs";
2
- import { t as CommandInjector } from "./subscribers-A8lNU5yM.mjs";
1
+ import { a as RegisterSubscriber, c as VerifyWebhooks, f as Subscriber, g as CommandMetadataKey, i as RegisterDefaults, k as contextMenuLeaves, n as busLoggerOf, o as RegisteredCount, r as registrationFor, s as UnregisterSubscriber, w as SubscribeMetadataKey } from "./Bus-Djm6688b.mjs";
2
+ import { r as CommandInjector, t as ConfigGuilds } from "./subscribers-kAQQt_YP.mjs";
3
3
  import { t as PublishDefault } from "./publishDefault-BuSZPbjT.mjs";
4
4
  import { n as StartupPhase, t as ShutdownPhase } from "./phases-BauKJOTT.mjs";
5
5
  import { t as getDevChannel } from "./devChannel-CQU7hq7Q.mjs";
6
- import { n as isCommandClass, t as slashRouteLeaves } from "./slashRouteLeaves-C1msUE9Q.mjs";
6
+ import { n as isCommandClass, t as slashRouteLeaves } from "./slashRouteLeaves-DTFHzCKN.mjs";
7
7
  import { t as HmrModuleHandler } from "./HmrModuleHandler-9AUfKXRl.mjs";
8
- import { i as settleWithin, n as shutdownOf, r as assertNodeVersion } from "./Pluggable-B7_EftHj.mjs";
8
+ import { i as settleWithin, n as shutdownOf, r as assertNodeVersion } from "./Pluggable-BShEwGw9.mjs";
9
9
  import { SlashCommandBuilder } from "@discordjs/builders";
10
10
  import { Routes } from "discord-api-types/v10";
11
11
  import { SeedcordErrorCode, paint } from "@seedcord/errors";
@@ -180,6 +180,21 @@ var SubscriberLoader = class {
180
180
  }
181
181
  };
182
182
 
183
+ //#endregion
184
+ //#region src/commands/deployTarget.ts
185
+ function resolveDeployTarget(meta, configured, commandName) {
186
+ if (meta.scope === "global") return { scope: "global" };
187
+ if (meta.scope === "config") return configured.length > 0 ? guildTarget(configured) : { scope: "global" };
188
+ if (configured.length === 0 && meta.guilds.includes("config")) throw new SeedcordError(SeedcordErrorCode.CoreCommandGuildsEmpty, [commandName]);
189
+ return guildTarget(meta.guilds.flatMap((target) => target === "config" ? configured : target));
190
+ }
191
+ function guildTarget(guilds) {
192
+ return {
193
+ scope: "guild",
194
+ guilds: [...new Set(guilds)]
195
+ };
196
+ }
197
+
183
198
  //#endregion
184
199
  //#region src/node/commands/CommandRegistry.ts
185
200
  function indexById(commands) {
@@ -203,14 +218,16 @@ var CommandRegistry = class {
203
218
  pendingEvents = /* @__PURE__ */ new Map();
204
219
  injector = new CommandInjector();
205
220
  dir;
221
+ configuredGuilds;
206
222
  constructor(core) {
207
223
  this.core = core;
208
- const dir = core.config.bot.commands.path;
209
- if (!dir) throw new SeedcordError(SeedcordErrorCode.CoreControllerPathMissing, ["CommandRegistry", "commands"]);
210
- this.dir = dir;
224
+ const { commands } = core.config.bot;
225
+ if (!commands.path) throw new SeedcordError(SeedcordErrorCode.CoreControllerPathMissing, ["CommandRegistry", "commands"]);
226
+ this.dir = commands.path;
227
+ this.configuredGuilds = commands.guilds ?? [];
211
228
  if (!Envapter.isDevelopment && !Envapter.isTest) return;
212
229
  this.hmrHandler = new HmrModuleHandler({
213
- handlersDir: dir,
230
+ handlersDir: this.dir,
214
231
  isHandler: isCommandClass,
215
232
  registerHandler: this.registerCommand.bind(this),
216
233
  unregisterHandler: this.unregisterCommand.bind(this),
@@ -285,16 +302,16 @@ var CommandRegistry = class {
285
302
  if (!meta) return;
286
303
  const comp = new Ctor().component;
287
304
  const kind = comp instanceof SlashCommandBuilder ? "slash command" : "context menu";
288
- if (meta.scope === "global") this.globalCommands.push(comp);
289
- else for (const g of meta.guilds) {
305
+ const target = resolveDeployTarget(meta, this.configuredGuilds, Ctor.name);
306
+ if (target.scope === "global") this.globalCommands.push(comp);
307
+ else for (const g of target.guilds) {
290
308
  const arr = this.guildCommands.get(g) ?? [];
291
309
  arr.push(comp);
292
310
  this.guildCommands.set(g, arr);
293
311
  }
294
312
  this.ctorToCommand.set(Ctor, {
295
313
  name: comp.name,
296
- scope: meta.scope,
297
- ...meta.scope === "guild" && { guilds: meta.guilds }
314
+ ...target
298
315
  });
299
316
  if (this.loading) this.loadedCommands.push({
300
317
  name: comp.name,
@@ -308,7 +325,7 @@ var CommandRegistry = class {
308
325
  if (info.scope === "global") {
309
326
  const idx = this.globalCommands.findIndex((c) => c.name === info.name);
310
327
  if (idx !== -1) this.globalCommands.splice(idx, 1);
311
- } else for (const g of info.guilds ?? []) {
328
+ } else for (const g of info.guilds) {
312
329
  const arr = this.guildCommands.get(g);
313
330
  if (arr) {
314
331
  const idx = arr.findIndex((c) => c.name === info.name);
@@ -330,7 +347,7 @@ var CommandRegistry = class {
330
347
  this.logger.utils.block(`Deployed ${this.globalCommands.length} global ${tag}`, this.logger.utils.wrap(this.globalCommands.map((command) => command.name)), "info");
331
348
  }
332
349
  for (const [guildId, commands] of this.guildCommands.entries()) {
333
- const deployed = await this.put(Routes.applicationGuildCommands(appId, guildId), commands);
350
+ const deployed = await this.putGuild(appId, guildId, commands);
334
351
  result.guilds.set(guildId, indexById(deployed));
335
352
  const tag = commands.length === 1 ? "command" : "commands";
336
353
  this.logger.utils.block(`Deployed ${paint.iris.bold(commands.length)} ${tag} to ${paint.sky.bold(guildId)}`, this.logger.utils.wrap(commands.map((command) => command.name)), "info");
@@ -339,6 +356,13 @@ var CommandRegistry = class {
339
356
  this.core.bus[PublishDefault]("commandsDeployed", deployedPayload(result));
340
357
  return result;
341
358
  }
359
+ async putGuild(appId, guildId, commands) {
360
+ try {
361
+ return await this.put(Routes.applicationGuildCommands(appId, guildId), commands);
362
+ } catch (error) {
363
+ throw new SeedcordError(SeedcordErrorCode.CoreCommandGuildDeployFailed, [guildId], { cause: error });
364
+ }
365
+ }
342
366
  async put(route, commands) {
343
367
  const body = commands.map((command) => command.toJSON());
344
368
  const { rest } = this.core;