@seedcord/core 0.11.0 → 0.12.0-next.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (54) hide show
  1. package/README.md +3 -3
  2. package/dist/Bus-VJw-QpoY.mjs +986 -0
  3. package/dist/Bus-VJw-QpoY.mjs.map +1 -0
  4. package/dist/{CoordinatedShutdown-H5YjZn8A.d.mts → CoordinatedShutdown-fPKLWjc5.d.mts} +13 -4
  5. package/dist/{CoordinatedStartup-CGnJrqcl.mjs → CoordinatedStartup-Zw_Lk7Zk.mjs} +22 -20
  6. package/dist/CoordinatedStartup-Zw_Lk7Zk.mjs.map +1 -0
  7. package/dist/Generated-ByC53_A2.d.mts +26 -0
  8. package/dist/HmrModuleHandler-Btgu8r96.mjs +165 -0
  9. package/dist/HmrModuleHandler-Btgu8r96.mjs.map +1 -0
  10. package/dist/{PluginHost-DB4j8unu.d.mts → PluginHost-DFnkIldj.d.mts} +43 -15
  11. package/dist/PluginHost-DTtxWRiL.mjs +552 -0
  12. package/dist/PluginHost-DTtxWRiL.mjs.map +1 -0
  13. package/dist/{PluginLifecycle-AhQ46NIr.mjs → PluginLifecycle-hx5zUc-P.mjs} +4 -4
  14. package/dist/PluginLifecycle-hx5zUc-P.mjs.map +1 -0
  15. package/dist/{ServerHost-DYeh0KTk.d.mts → ServerHost-B5o3ScCF.d.mts} +7 -6
  16. package/dist/{ServerHost-CtmAhEXn.mjs → ServerHost-CDaEDR4C.mjs} +17 -6
  17. package/dist/{ServerHost-CtmAhEXn.mjs.map → ServerHost-CDaEDR4C.mjs.map} +1 -1
  18. package/dist/bindViteEnv-NFzvdqlT.mjs +32 -0
  19. package/dist/{bindViteEnv-DWq5rftJ.mjs.map → bindViteEnv-NFzvdqlT.mjs.map} +1 -1
  20. package/dist/env-internal.index.mjs +1 -1
  21. package/dist/hmr.index.d.mts +20 -20
  22. package/dist/hmr.index.mjs +1 -1
  23. package/dist/{index-Du71Xx69.d.mts → index-DGHng1IN.d.mts} +115 -201
  24. package/dist/index.d.mts +321 -74
  25. package/dist/index.mjs +323 -102
  26. package/dist/index.mjs.map +1 -1
  27. package/dist/internal.index.d.mts +223 -83
  28. package/dist/internal.index.mjs +573 -74
  29. package/dist/internal.index.mjs.map +1 -1
  30. package/dist/node-internal.index.d.mts +8 -4
  31. package/dist/node-internal.index.mjs +19 -6
  32. package/dist/node-internal.index.mjs.map +1 -1
  33. package/dist/node.index.d.mts +3 -3
  34. package/dist/node.index.mjs +22 -28
  35. package/dist/node.index.mjs.map +1 -1
  36. package/dist/{phases-FTM69J3a.d.mts → phases-C2QCSqRu.d.mts} +164 -55
  37. package/dist/plugin.index.d.mts +3 -2
  38. package/dist/plugin.index.mjs +1 -1
  39. package/dist/subscribers-CGuVjbrg.mjs +982 -0
  40. package/dist/subscribers-CGuVjbrg.mjs.map +1 -0
  41. package/dist/{bindViteEnv-DWq5rftJ.mjs → usingCtx-Cf2cZi96.mjs} +1 -31
  42. package/package.json +14 -11
  43. package/dist/CoordinatedStartup-CGnJrqcl.mjs.map +0 -1
  44. package/dist/HmrModuleHandler-8ZC-UuZh.mjs +0 -162
  45. package/dist/HmrModuleHandler-8ZC-UuZh.mjs.map +0 -1
  46. package/dist/PluginHost-B8kxLOeb.mjs +0 -245
  47. package/dist/PluginHost-B8kxLOeb.mjs.map +0 -1
  48. package/dist/PluginLifecycle-AhQ46NIr.mjs.map +0 -1
  49. package/dist/phases-BauKJOTT.mjs +0 -31
  50. package/dist/phases-BauKJOTT.mjs.map +0 -1
  51. package/dist/publishDefault-D3dAyzZ9.mjs +0 -7
  52. package/dist/publishDefault-D3dAyzZ9.mjs.map +0 -1
  53. package/dist/subscribers-Ce3SWy23.mjs +0 -1913
  54. package/dist/subscribers-Ce3SWy23.mjs.map +0 -1
@@ -0,0 +1,986 @@
1
+ import "reflect-metadata";
2
+ import { ActionRowBuilder, ButtonBuilder, ChannelSelectMenuBuilder, CheckboxBuilder, CheckboxGroupBuilder, CheckboxGroupOptionBuilder, ContainerBuilder, ContextMenuCommandBuilder, EmbedBuilder, FileBuilder, FileUploadBuilder, LabelBuilder, MediaGalleryBuilder, MentionableSelectMenuBuilder, ModalBuilder, RadioGroupBuilder, RadioGroupOptionBuilder, RoleSelectMenuBuilder, SectionBuilder, SeparatorBuilder, SlashCommandBuilder, SlashCommandSubcommandBuilder, SlashCommandSubcommandGroupBuilder, StringSelectMenuBuilder, StringSelectMenuOptionBuilder, TextDisplayBuilder, TextInputBuilder, UserSelectMenuBuilder } from "@discordjs/builders";
3
+ import { InteractionContextType, MessageFlags, Routes, SeparatorSpacingSize } from "discord-api-types/v10";
4
+ import { SeedcordErrorCode, paint } from "@seedcord/errors";
5
+ import { SeedcordError, SeedcordRangeError, SeedcordTypeError } from "@seedcord/errors/internal";
6
+ import { Logger } from "@seedcord/logger";
7
+ import { pluralize } from "@seedcord/utils/internal";
8
+ import { filterCirculars, stripAnsi, timestampFromSnowflake } from "@seedcord/utils";
9
+ import { TypedEventEmitter } from "@seedcord/event-emitter";
10
+ import { Envapter } from "envapt";
11
+ import { DiscordAPIError, REST } from "@discordjs/rest";
12
+
13
+ //#region src/components/botColorHolder.ts
14
+ const DEFAULT_COLOR = "Default";
15
+ let readBotColor = () => void 0;
16
+ function bindBotColor(read) {
17
+ readBotColor = read;
18
+ }
19
+ function getBotColor() {
20
+ return readBotColor() ?? DEFAULT_COLOR;
21
+ }
22
+
23
+ //#endregion
24
+ //#region src/components/builderTypes.ts
25
+ /**
26
+ * Available Discord.js builder classes for use with BuilderComponent for commands, embeds, modals, etc.
27
+ *
28
+ * @internal
29
+ */
30
+ const BuilderTypes = {
31
+ command: SlashCommandBuilder,
32
+ context_menu: ContextMenuCommandBuilder,
33
+ subcommand: SlashCommandSubcommandBuilder,
34
+ group: SlashCommandSubcommandGroupBuilder,
35
+ embed: EmbedBuilder,
36
+ modal: ModalBuilder,
37
+ label: LabelBuilder,
38
+ text_input: TextInputBuilder,
39
+ file_upload: FileUploadBuilder,
40
+ checkbox: CheckboxBuilder,
41
+ checkbox_group: CheckboxGroupBuilder,
42
+ checkbox_group_option: CheckboxGroupOptionBuilder,
43
+ radio_group: RadioGroupBuilder,
44
+ radio_group_option: RadioGroupOptionBuilder,
45
+ button: ButtonBuilder,
46
+ menu_string: StringSelectMenuBuilder,
47
+ menu_option_string: StringSelectMenuOptionBuilder,
48
+ menu_user: UserSelectMenuBuilder,
49
+ menu_channel: ChannelSelectMenuBuilder,
50
+ menu_mentionable: MentionableSelectMenuBuilder,
51
+ menu_role: RoleSelectMenuBuilder,
52
+ container: ContainerBuilder,
53
+ text_display: TextDisplayBuilder,
54
+ file: FileBuilder,
55
+ media: MediaGalleryBuilder,
56
+ section: SectionBuilder,
57
+ separator: SeparatorBuilder
58
+ };
59
+ /**
60
+ * Available Discord.js action row classes for use with RowComponent for Select Menus and Buttons
61
+ *
62
+ * @internal
63
+ */
64
+ const RowTypes = {
65
+ button: ActionRowBuilder,
66
+ menu_string: ActionRowBuilder,
67
+ menu_user: ActionRowBuilder,
68
+ menu_channel: ActionRowBuilder,
69
+ menu_mentionable: ActionRowBuilder,
70
+ menu_role: ActionRowBuilder
71
+ };
72
+
73
+ //#endregion
74
+ //#region src/components/colors.ts
75
+ const Colors = {
76
+ Aqua: 1752220,
77
+ Blue: 3447003,
78
+ Blurple: 5793266,
79
+ DarkAqua: 1146986,
80
+ DarkBlue: 2123412,
81
+ DarkButNotBlack: 2895667,
82
+ DarkerGrey: 8359053,
83
+ DarkGold: 12745742,
84
+ DarkGreen: 2067276,
85
+ DarkGrey: 9936031,
86
+ DarkNavy: 2899536,
87
+ DarkOrange: 11027200,
88
+ DarkPurple: 7419530,
89
+ DarkRed: 10038562,
90
+ DarkVividPink: 11342935,
91
+ Default: 0,
92
+ Fuchsia: 15418782,
93
+ Gold: 15844367,
94
+ Green: 5763719,
95
+ Grey: 9807270,
96
+ Greyple: 10070709,
97
+ LightGrey: 12370112,
98
+ LuminousVividPink: 15277667,
99
+ Navy: 3426654,
100
+ NotQuiteBlack: 2303786,
101
+ Orange: 15105570,
102
+ Purple: 10181046,
103
+ Red: 15548997,
104
+ White: 16777215,
105
+ Yellow: 16705372
106
+ };
107
+
108
+ //#endregion
109
+ //#region src/components/resolveColor.ts
110
+ const MAX_COLOR = 16777215;
111
+ const RED_SHIFT = 16;
112
+ const GREEN_SHIFT = 8;
113
+ const palette = Colors;
114
+ /**
115
+ * Resolves a {@link BotColor} to the integer Discord expects.
116
+ *
117
+ * Ported from discord.js (Apache-2.0), `resolveColor` in src/util/Util.js.
118
+ */
119
+ function resolveColor(color) {
120
+ let resolved;
121
+ if (typeof color === "string") {
122
+ if (color === "Random") return Math.floor(Math.random() * 16777216);
123
+ if (color === "Default") return 0;
124
+ if (/^#?[\da-f]{6}$/i.test(color)) return Number.parseInt(color.replace("#", ""), 16);
125
+ resolved = palette[color];
126
+ } else if (typeof color === "number") resolved = color;
127
+ else resolved = (color[0] << RED_SHIFT) + (color[1] << GREEN_SHIFT) + color[2];
128
+ if (resolved === void 0 || !Number.isSafeInteger(resolved)) throw new SeedcordTypeError(SeedcordErrorCode.ColorUnresolvable, [JSON.stringify(color)]);
129
+ if (resolved < 0 || resolved > MAX_COLOR) throw new SeedcordRangeError(SeedcordErrorCode.ColorOutOfRange, [resolved]);
130
+ return resolved;
131
+ }
132
+
133
+ //#endregion
134
+ //#region src/components/Component.ts
135
+ /** @internal */
136
+ var BaseComponent = class {
137
+ _component;
138
+ constructor(ComponentClass) {
139
+ this._component = new ComponentClass();
140
+ }
141
+ /**
142
+ * The wrapped builder, for calling Discord.js methods like setTitle() and setDescription() inside a subclass.
143
+ *
144
+ * @example this.instance.setTitle('My Modal')
145
+ */
146
+ get instance() {
147
+ return this._component;
148
+ }
149
+ };
150
+ /**
151
+ * Base class for Discord.js builder components
152
+ *
153
+ * Wraps Discord.js builders (SlashCommandBuilder, EmbedBuilder, etc.) with
154
+ * Seedcord-specific defaults and helper methods.
155
+ *
156
+ * @typeParam BuilderKey - The type of Discord.js builder being wrapped
157
+ */
158
+ var BuilderComponent = class extends BaseComponent {
159
+ type;
160
+ colorApplied = false;
161
+ constructor(type) {
162
+ const ComponentClass = BuilderTypes[type];
163
+ super(ComponentClass);
164
+ this.type = type;
165
+ if (this.instance instanceof SlashCommandBuilder || this.instance instanceof ContextMenuCommandBuilder) this.instance.setContexts(InteractionContextType.Guild);
166
+ }
167
+ get component() {
168
+ this.applyBotColor();
169
+ return this.instance;
170
+ }
171
+ applyBotColor() {
172
+ if (this.colorApplied) return;
173
+ this.colorApplied = true;
174
+ const color = getBotColor();
175
+ if (this.instance instanceof EmbedBuilder) {
176
+ if (this.instance.data.color === void 0) this.instance.setColor(resolveColor(color));
177
+ } else if (this.instance instanceof ContainerBuilder) {
178
+ const accent = this.instance.data.accent_color;
179
+ if (accent === null || accent === void 0) this.instance.setAccentColor(color === "Default" ? void 0 : resolveColor(color));
180
+ }
181
+ }
182
+ };
183
+ const BUILDER_COMPONENT = Symbol.for("seedcord:core:builder-component");
184
+ Object.defineProperty(BuilderComponent.prototype, BUILDER_COMPONENT, { value: true });
185
+ /** @internal */
186
+ function isBuilderComponentClass(value) {
187
+ if (typeof value !== "function") return false;
188
+ const proto = value.prototype;
189
+ return typeof proto === "object" && proto !== null && BUILDER_COMPONENT in proto;
190
+ }
191
+ /**
192
+ * Base class for Discord action row components
193
+ *
194
+ * Wraps Discord.js action row builder with Seedcord-specific defaults and helper methods.
195
+ *
196
+ * @typeParam RowKey - The Discord.js action row type being wrapped
197
+ */
198
+ var RowComponent = class extends BaseComponent {
199
+ type;
200
+ constructor(type) {
201
+ const ComponentClass = RowTypes[type];
202
+ super(ComponentClass);
203
+ this.type = type;
204
+ }
205
+ get component() {
206
+ return this.instance;
207
+ }
208
+ };
209
+
210
+ //#endregion
211
+ //#region src/metadataKeys.ts
212
+ /**
213
+ * Every interaction kind seedcord routes.
214
+ */
215
+ let InteractionKind = /* @__PURE__ */ function(InteractionKind) {
216
+ InteractionKind["Slash"] = "slash";
217
+ InteractionKind["Button"] = "button";
218
+ InteractionKind["Modal"] = "modal";
219
+ InteractionKind["StringMenu"] = "stringMenu";
220
+ InteractionKind["UserMenu"] = "userMenu";
221
+ InteractionKind["RoleMenu"] = "roleMenu";
222
+ InteractionKind["ChannelMenu"] = "channelMenu";
223
+ InteractionKind["MentionableMenu"] = "mentionableMenu";
224
+ InteractionKind["MessageContextMenu"] = "messageContextMenu";
225
+ InteractionKind["UserContextMenu"] = "userContextMenu";
226
+ InteractionKind["Autocomplete"] = "autocomplete";
227
+ return InteractionKind;
228
+ }({});
229
+ const CommandMetadataKey = Symbol.for("seedcord:command:metadata");
230
+ const InteractionMetadataKey = Symbol.for("seedcord:interaction:metadata");
231
+ const InteractionMiddlewareMetadataKey = Symbol.for("seedcord:middleware:interaction:metadata");
232
+ const EventMiddlewareMetadataKey = Symbol.for("seedcord:middleware:event:metadata");
233
+ const SubscribeMetadataKey = Symbol.for("seedcord:subscribe:metadata");
234
+ const EventMetadataKey = Symbol.for("seedcord:event:metadata");
235
+ const GatedMetadataKey = Symbol.for("seedcord:gated:metadata");
236
+ const WebhookUrlMetadataKey = Symbol.for("seedcord:webhook-url:metadata");
237
+ const InteractionRouteKeys = {
238
+ ["slash"]: Symbol.for("seedcord:interaction:slash"),
239
+ ["button"]: Symbol.for("seedcord:interaction:button"),
240
+ ["modal"]: Symbol.for("seedcord:interaction:modal"),
241
+ ["stringMenu"]: Symbol.for("seedcord:interaction:string-menu"),
242
+ ["userMenu"]: Symbol.for("seedcord:interaction:user-menu"),
243
+ ["roleMenu"]: Symbol.for("seedcord:interaction:role-menu"),
244
+ ["channelMenu"]: Symbol.for("seedcord:interaction:channel-menu"),
245
+ ["mentionableMenu"]: Symbol.for("seedcord:interaction:mentionable-menu"),
246
+ ["messageContextMenu"]: Symbol.for("seedcord:interaction:message-context-menu"),
247
+ ["userContextMenu"]: Symbol.for("seedcord:interaction:user-context-menu"),
248
+ ["autocomplete"]: Symbol.for("seedcord:interaction:autocomplete")
249
+ };
250
+
251
+ //#endregion
252
+ //#region src/decorators/assertUniqueKeys.ts
253
+ function assertUniqueKeys(decorator, ctor, keys, fix = "Remove one of the two tuples.") {
254
+ const seen = /* @__PURE__ */ new Set();
255
+ for (const key of keys) {
256
+ if (seen.has(key)) throw new SeedcordTypeError(SeedcordErrorCode.DecoratorDuplicateKey, [
257
+ decorator,
258
+ ctor.name,
259
+ key,
260
+ fix
261
+ ]);
262
+ seen.add(key);
263
+ }
264
+ }
265
+
266
+ //#endregion
267
+ //#region src/subscribers/publishDefault.ts
268
+ /** @internal PublishableKey excludes this key so publish() never emits it externally. */
269
+ const PublishDefault = Symbol("seedcord:bus:publish-default");
270
+
271
+ //#endregion
272
+ //#region src/lifecycle/phases.ts
273
+ /**
274
+ * Startup phases, in run order.
275
+ */
276
+ let StartupPhase = /* @__PURE__ */ function(StartupPhase) {
277
+ /** Nothing connected. Plugin init runs here by default. */
278
+ StartupPhase[StartupPhase["Configuration"] = 1] = "Configuration";
279
+ /** The gateway session opens, and http runs its REST-only startup work. */
280
+ StartupPhase[StartupPhase["Login"] = 2] = "Login";
281
+ /** Handlers are live and interactions dispatch. */
282
+ StartupPhase[StartupPhase["Ready"] = 3] = "Ready";
283
+ return StartupPhase;
284
+ }({});
285
+ /**
286
+ * Shutdown phases, in run order.
287
+ */
288
+ let ShutdownPhase = /* @__PURE__ */ function(ShutdownPhase) {
289
+ /** Stop taking new interactions and requests. */
290
+ ShutdownPhase[ShutdownPhase["Unbind"] = 1] = "Unbind";
291
+ /** Waits for in-flight work to settle and stops internal services. */
292
+ ShutdownPhase[ShutdownPhase["Drain"] = 2] = "Drain";
293
+ /** External resources close. Plugin dispose runs here by default. */
294
+ ShutdownPhase[ShutdownPhase["Disconnect"] = 3] = "Disconnect";
295
+ /** The client disconnects last so in-flight handlers can still reference it during drain. */
296
+ ShutdownPhase[ShutdownPhase["Logout"] = 4] = "Logout";
297
+ /**
298
+ * Resources seedcord holds for the bot, like the `config.kv` driver, close after every plugin
299
+ * has stopped. Only seedcord runs work here.
300
+ */
301
+ ShutdownPhase[ShutdownPhase["Release"] = 5] = "Release";
302
+ return ShutdownPhase;
303
+ }({});
304
+
305
+ //#endregion
306
+ //#region src/kv/MemoryDriver.ts
307
+ const SWEEP_GAP_MS = 6e4;
308
+ /**
309
+ * The default `config.kv`. It keeps every key in a `Map` and loses them on restart. On serverless,
310
+ * each running copy keeps its own.
311
+ */
312
+ var MemoryDriver = class {
313
+ #entries = /* @__PURE__ */ new Map();
314
+ #lastSweep = Date.now();
315
+ get(key) {
316
+ this.#maybeSweep();
317
+ return this.#live(key)?.value;
318
+ }
319
+ set(key, value, ttlMs) {
320
+ this.#maybeSweep();
321
+ this.#entries.set(key, {
322
+ value,
323
+ expiresAt: ttlMs === void 0 ? void 0 : Date.now() + ttlMs
324
+ });
325
+ }
326
+ delete(key) {
327
+ this.#entries.delete(key);
328
+ }
329
+ *entries(prefix) {
330
+ this.#maybeSweep();
331
+ for (const key of this.#entries.keys()) {
332
+ const entry = key.startsWith(prefix) ? this.#live(key) : void 0;
333
+ if (entry) yield [key, entry.value];
334
+ }
335
+ }
336
+ [Symbol.dispose]() {
337
+ this.#entries.clear();
338
+ }
339
+ #live(key) {
340
+ const entry = this.#entries.get(key);
341
+ if (entry?.expiresAt !== void 0 && entry.expiresAt <= Date.now()) {
342
+ this.#entries.delete(key);
343
+ return;
344
+ }
345
+ return entry;
346
+ }
347
+ #maybeSweep() {
348
+ const now = Date.now();
349
+ if (now - this.#lastSweep < SWEEP_GAP_MS) return;
350
+ this.#lastSweep = now;
351
+ for (const key of this.#entries.keys()) this.#live(key);
352
+ }
353
+ };
354
+
355
+ //#endregion
356
+ //#region src/dispatch/OnceTracker.ts
357
+ var OnceTracker = class {
358
+ spent = /* @__PURE__ */ new WeakMap();
359
+ isSpent(entry, key) {
360
+ return entry.frequency === "once" && (this.spent.get(entry.ctor)?.has(key) ?? false);
361
+ }
362
+ claim(entry, key) {
363
+ if (entry.frequency !== "once") return true;
364
+ const keys = this.spent.get(entry.ctor) ?? /* @__PURE__ */ new Set();
365
+ if (keys.has(key)) return false;
366
+ this.spent.set(entry.ctor, keys.add(key));
367
+ return true;
368
+ }
369
+ };
370
+
371
+ //#endregion
372
+ //#region src/reply/flags.ts
373
+ function sendFlags(opts) {
374
+ let flags = MessageFlags.IsComponentsV2;
375
+ if (opts?.ephemeral ?? true) flags |= MessageFlags.Ephemeral;
376
+ if (opts?.silent) flags |= MessageFlags.SuppressNotifications;
377
+ return flags;
378
+ }
379
+ function deferFlags(opts) {
380
+ return opts?.ephemeral ?? true ? MessageFlags.Ephemeral : 0;
381
+ }
382
+
383
+ //#endregion
384
+ //#region src/subscribers/ReportThrottle.ts
385
+ var ReportThrottle = class ReportThrottle {
386
+ windowMs;
387
+ now;
388
+ static perCore = /* @__PURE__ */ new WeakMap();
389
+ static for(core) {
390
+ let throttle = ReportThrottle.perCore.get(core);
391
+ if (!throttle) {
392
+ throttle = new ReportThrottle();
393
+ ReportThrottle.perCore.set(core, throttle);
394
+ }
395
+ return throttle;
396
+ }
397
+ windows = /* @__PURE__ */ new Map();
398
+ constructor(windowMs = 6e4, now = () => Date.now()) {
399
+ this.windowMs = windowMs;
400
+ this.now = now;
401
+ }
402
+ take(key) {
403
+ const window = this.windows.get(key);
404
+ if (window && this.now() - window.lastSentAt < this.windowMs) {
405
+ window.suppressedSinceSend += 1;
406
+ return null;
407
+ }
408
+ const suppressed = window?.suppressedSinceSend ?? 0;
409
+ this.windows.set(key, {
410
+ lastSentAt: this.now(),
411
+ suppressedSinceSend: 0
412
+ });
413
+ return suppressed;
414
+ }
415
+ restore(key, suppressed) {
416
+ const window = this.windows.get(key);
417
+ if (window) window.suppressedSinceSend += suppressed + 1;
418
+ }
419
+ };
420
+
421
+ //#endregion
422
+ //#region src/handlers/runCase.ts
423
+ async function runCase(handler, cases, kind, value, ...args) {
424
+ const callback = Object.hasOwn(cases, value) ? cases[value] : void 0;
425
+ if (typeof callback !== "function") throw new SeedcordTypeError(SeedcordErrorCode.MatchCaseMissing, [
426
+ handler.constructor.name,
427
+ kind,
428
+ value
429
+ ]);
430
+ return await callback(...args);
431
+ }
432
+
433
+ //#endregion
434
+ //#region src/subscribers/Subscriber.ts
435
+ /**
436
+ * Abstract base class for handling application subscribers.
437
+ *
438
+ * Pass the subscription key(s) as the generic, the same one(s) as `@Subscribe`. On a single-key subscriber, read
439
+ * `this.data` directly. On a subscriber for several keys, branch with `this.match`, keyed by subscription key.
440
+ *
441
+ * @typeParam Keys - One or more subscription keys, like `'userJoin'` or `'userJoin' | 'userLeave'`.
442
+ * @typeParam TCore - The transport's Core
443
+ */
444
+ var Subscriber = class {
445
+ core;
446
+ key;
447
+ logger;
448
+ #payload;
449
+ constructor(payload, core, key) {
450
+ this.core = core;
451
+ this.key = key;
452
+ this.#payload = payload;
453
+ this.logger = new Logger(this.constructor.name, { channel: "subscribers" });
454
+ }
455
+ /** The published payload. It's `never` on a subscriber for several keys, where `this.match` reads it. */
456
+ get data() {
457
+ return this.#payload;
458
+ }
459
+ /**
460
+ * Run the case for the published key. Use this only when the subscriber is registered for several
461
+ * keys. On a single-key subscriber, read `this.data` directly.
462
+ *
463
+ * Provide one case per registered key. Each case receives that key's own payload. A missing key or an unknown
464
+ * key is a compile error.
465
+ *
466
+ * @typeParam Ret - The type that every case must return. Leave it out to get the union of what the cases return.
467
+ * @throws A **SeedcordTypeError** with `MatchCaseMissing` when the published key doesn't have a case.
468
+ *
469
+ * @example
470
+ * ```ts
471
+ * import { Subscribe, Subscriber } from '@seedcord/gateway';
472
+ *
473
+ * \@Subscribe(['interactionDispatched'], ['eventDispatched'])
474
+ * class Metrics extends Subscriber<'interactionDispatched' | 'eventDispatched'> {
475
+ * async execute() {
476
+ * await this.match({
477
+ * interactionDispatched: (data) => this.logger.info(data.routeId),
478
+ * eventDispatched: (data) => this.logger.info(data.name)
479
+ * });
480
+ * }
481
+ * }
482
+ * ```
483
+ */
484
+ async match(cases) {
485
+ return await runCase(this, cases, "subscription key", this.key, this.#payload);
486
+ }
487
+ };
488
+
489
+ //#endregion
490
+ //#region src/subscribers/bases/webhookHelpers.ts
491
+ const DISCORD_WEBHOOK_REGEX = new RegExp(String.raw`^https?:\/\/(?:canary\.|ptb\.)?discord(?:app)?\.com\/api\/webhooks\/\d+\/[\w$-]+$`);
492
+ function isDiscordWebhookUrl(value) {
493
+ return URL.canParse(value) && DISCORD_WEBHOOK_REGEX.test(value);
494
+ }
495
+ function jsonAttachment(name, description, data) {
496
+ const content = filterCirculars(data);
497
+ return {
498
+ name,
499
+ description,
500
+ data: new TextEncoder().encode(JSON.stringify(content, void 0, 2))
501
+ };
502
+ }
503
+ function breakBackticks(text) {
504
+ return text.replaceAll("```", "`​`​`");
505
+ }
506
+ const REPORT_BUDGET = 1800;
507
+ function body(value) {
508
+ const raw = Error.isError(value) ? value.stack ?? `${value.name}: ${value.message}` : String(value);
509
+ return breakBackticks(stripAnsi(raw));
510
+ }
511
+ function clamp(text, budget) {
512
+ if (text.length <= budget) return text;
513
+ return budget > 0 ? `${text.slice(0, budget - 1)}…` : "";
514
+ }
515
+ function omittedLine(count) {
516
+ return `\n\nand ${count} more ${pluralize(count, "failure")}`;
517
+ }
518
+ function memberReport(members, budget) {
519
+ const room = Math.max(budget - omittedLine(members.length).length, 0);
520
+ const share = Math.floor(room / members.length);
521
+ let report = "";
522
+ let shown = 0;
523
+ for (const [index, member] of members.entries()) {
524
+ const heading = `\n\nFailure ${index + 1} of ${members.length}:\n`;
525
+ const entry = heading + clamp(body(member), Math.max(share - heading.length, 0));
526
+ if (report.length + entry.length > room) break;
527
+ report += entry;
528
+ shown++;
529
+ }
530
+ const hidden = members.length - shown;
531
+ return hidden > 0 ? report + omittedLine(hidden) : report;
532
+ }
533
+ function errorReport(error) {
534
+ let head = body(error);
535
+ if (Error.isError(error.cause)) head += `\n\nCaused by:\n${body(error.cause)}`;
536
+ const members = error instanceof AggregateError ? error.errors : [];
537
+ if (members.length === 0) return clamp(head, REPORT_BUDGET);
538
+ const report = clamp(head, REPORT_BUDGET - omittedLine(members.length).length);
539
+ return report + memberReport(members, REPORT_BUDGET - report.length);
540
+ }
541
+ var WebhookSeparator = class extends BuilderComponent {
542
+ constructor() {
543
+ super("separator");
544
+ this.instance.setSpacing(SeparatorSpacingSize.Small).setDivider(true);
545
+ }
546
+ };
547
+
548
+ //#endregion
549
+ //#region src/subscribers/bases/WebhookSender.ts
550
+ const HTTP_NOT_FOUND = 404;
551
+ const HTTP_UNAUTHORIZED = 401;
552
+ var WebhookSender = class {
553
+ rest = new REST();
554
+ route;
555
+ constructor(url) {
556
+ const match = /\/webhooks\/(\d+)\/([\w$-]+)$/.exec(url);
557
+ if (!match?.[1] || !match[2]) throw new SeedcordTypeError(SeedcordErrorCode.CoreWebhookUrlInvalid, [new.target.name]);
558
+ this.route = Routes.webhook(match[1], match[2]);
559
+ }
560
+ async verify() {
561
+ try {
562
+ await this.rest.get(this.route, { auth: false });
563
+ return "ok";
564
+ } catch (error) {
565
+ return error instanceof DiscordAPIError && (error.status === HTTP_NOT_FOUND || error.status === HTTP_UNAUTHORIZED) ? "missing" : "unreachable";
566
+ }
567
+ }
568
+ async send(options) {
569
+ const { flags, username, avatarUrl, components, files } = options;
570
+ await this.rest.post(this.route, {
571
+ auth: false,
572
+ query: new URLSearchParams({
573
+ with_components: "true",
574
+ wait: "true"
575
+ }),
576
+ body: {
577
+ flags,
578
+ username,
579
+ ...avatarUrl && { avatar_url: avatarUrl },
580
+ components: components.map((component) => component.toJSON()),
581
+ ...files?.length && { attachments: files.map((file, id) => ({
582
+ id,
583
+ filename: file.name,
584
+ description: file.description
585
+ })) }
586
+ },
587
+ ...files?.length && { files: files.map((file) => ({
588
+ name: file.name,
589
+ data: file.data
590
+ })) }
591
+ });
592
+ }
593
+ };
594
+
595
+ //#endregion
596
+ //#region src/subscribers/bases/WebhookLog.ts
597
+ /**
598
+ * Base class for subscribers that deliver their event to a Discord webhook.
599
+ *
600
+ * Declare the url's environment variable with `@WebhookUrl` and implement {@link report}. The base
601
+ * resolves the url, validates it, reuses one sender per url, and sends. Registration skips a reporter
602
+ * whose variable is unset and warns at boot.
603
+ *
604
+ * @typeParam KeyOfSubscribers - The subscription key or keys that this reporter receives, the same ones as `@Subscribe`
605
+ * @typeParam TCore - The transport's Core
606
+ */
607
+ var WebhookLog = class WebhookLog extends Subscriber {
608
+ static senders = /* @__PURE__ */ new Map();
609
+ /**
610
+ * Events sharing this key collapse into one card per minute. Return `null` to send every event.
611
+ */
612
+ throttleKey() {
613
+ return null;
614
+ }
615
+ async execute() {
616
+ const envKey = WebhookLog.envKeyOf(this.constructor);
617
+ const url = WebhookLog.urlOf(envKey);
618
+ if (url === null) throw new SeedcordError(SeedcordErrorCode.ConfigMissingEnv, [envKey]);
619
+ const key = this.throttleKey();
620
+ const throttle = ReportThrottle.for(this.core);
621
+ const suppressed = key === null ? 0 : throttle.take(key);
622
+ if (suppressed === null) {
623
+ this.logger.trace(`counted a repeat into the next ${paint.sky.bold(this.constructor.name)} card`);
624
+ return;
625
+ }
626
+ try {
627
+ const { username, avatarUrl, components, files } = await this.report(suppressed);
628
+ await WebhookLog.senderFor(url).send({
629
+ flags: sendFlags({ ephemeral: false }),
630
+ username: username ?? this.constructor.name,
631
+ avatarUrl,
632
+ components,
633
+ files
634
+ });
635
+ } catch (error) {
636
+ if (key !== null) throttle.restore(key, suppressed);
637
+ this.logger.error("Webhook report failed", error);
638
+ }
639
+ }
640
+ /** @internal */
641
+ static senderFor(url) {
642
+ let sender = WebhookLog.senders.get(url);
643
+ if (!sender) {
644
+ sender = new WebhookSender(url);
645
+ WebhookLog.senders.set(url, sender);
646
+ }
647
+ return sender;
648
+ }
649
+ /** @internal */
650
+ static envKeyOf(ctor) {
651
+ const envKey = Reflect.getMetadata(WebhookUrlMetadataKey, ctor);
652
+ if (!envKey) throw new SeedcordTypeError(SeedcordErrorCode.DecoratorWebhookUrlMissing, [ctor.name]);
653
+ return envKey;
654
+ }
655
+ /** @internal Returns null when the env var is unset, throws when set but malformed. */
656
+ static urlOf(envKey) {
657
+ const raw = Envapter.get(envKey)?.trim();
658
+ if (!raw) return null;
659
+ if (!isDiscordWebhookUrl(raw)) throw new SeedcordError(SeedcordErrorCode.ConfigInvalidEnv, [envKey, "the webhook URL from the channel's Integrations settings"]);
660
+ return raw;
661
+ }
662
+ };
663
+
664
+ //#endregion
665
+ //#region src/subscribers/decorators/Subscribe.ts
666
+ /**
667
+ * Registers a subscriber class with one or more subscription keys.
668
+ *
669
+ * Supply one `[key, options?]` tuple per key. The subscriber's generic must list the same keys.
670
+ *
671
+ * @decorator
672
+ * @example
673
+ * ```ts
674
+ * \@Subscribe(['userJoin'])
675
+ * class WelcomeHandler extends Subscriber<'userJoin'> {
676
+ * async execute() {
677
+ * this.logger.info(`${this.data.userId} joined`);
678
+ * }
679
+ * }
680
+ * ```
681
+ * @example
682
+ * ```ts
683
+ * \@Subscribe(['userJoin', { frequency: 'once' }], ['userLeave'])
684
+ * class Presence extends Subscriber<'userJoin' | 'userLeave'> {
685
+ * async execute() {
686
+ * await this.match({
687
+ * userJoin: (data) => this.logger.info(`${data.userId} joined`),
688
+ * userLeave: (data) => this.logger.info(`${data.userId} left`)
689
+ * });
690
+ * }
691
+ * }
692
+ * ```
693
+ */
694
+ function Subscribe(...defs) {
695
+ return function(constructor) {
696
+ const entries = defs.map(([key, options]) => ({
697
+ key,
698
+ frequency: options?.frequency ?? "on"
699
+ }));
700
+ assertUniqueKeys("@Subscribe", constructor, entries.map((entry) => entry.key));
701
+ Reflect.defineMetadata(SubscribeMetadataKey, entries, constructor);
702
+ };
703
+ }
704
+
705
+ //#endregion
706
+ //#region src/subscribers/decorators/WebhookUrl.ts
707
+ /**
708
+ * Declares the environment variable a {@link WebhookLog} reporter reads its webhook url from.
709
+ *
710
+ * An unset variable disables the reporter with a boot warning. A set but malformed value throws
711
+ * at boot.
712
+ *
713
+ * @param envKey - The environment variable name holding the webhook url
714
+ * @decorator
715
+ * @example
716
+ * ```ts
717
+ * \@Subscribe(['memberBanned'])
718
+ * \@WebhookUrl('BAN_LOG_WEBHOOK_URL')
719
+ * class BanLog extends WebhookLog<'memberBanned'> {
720
+ * report() {
721
+ * return { components: [new BanCard(this.data).component] };
722
+ * }
723
+ * }
724
+ * ```
725
+ */
726
+ function WebhookUrl(envKey) {
727
+ return function(constructor) {
728
+ Reflect.defineMetadata(WebhookUrlMetadataKey, envKey, constructor);
729
+ };
730
+ }
731
+
732
+ //#endregion
733
+ //#region \0@oxc-project+runtime@0.152.0/helpers/esm/decorate.js
734
+ function __decorate(decorators, target, key, desc) {
735
+ var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
736
+ if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
737
+ else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
738
+ return c > 3 && r && Object.defineProperty(target, key, r), r;
739
+ }
740
+
741
+ //#endregion
742
+ //#region src/subscribers/default/HandledException.ts
743
+ let HandledException = class HandledException extends WebhookLog {
744
+ throttleKey() {
745
+ return `handledException:${this.data.origin}:${this.data.denial.name}`;
746
+ }
747
+ report(suppressed) {
748
+ return {
749
+ username: "Handled Exception",
750
+ components: [new HandledExceptionContainer(this.data, suppressed).component],
751
+ files: [jsonAttachment("source.json", "The raw source the fault came from", this.data.source.raw)]
752
+ };
753
+ }
754
+ };
755
+ HandledException = __decorate([Subscribe(["handledException"]), WebhookUrl("HANDLED_EXCEPTION_WEBHOOK_URL")], HandledException);
756
+ var HandledExceptionContainer = class extends BuilderComponent {
757
+ constructor(data, suppressed) {
758
+ super("container");
759
+ const { denial, uuid, source } = data;
760
+ this.instance.addTextDisplayComponents((text) => text.setContent(faultSummary(denial, source))).addSeparatorComponents(new WebhookSeparator().component).addTextDisplayComponents((text) => text.setContent(`### UUID \`${uuid}\`\n\`\`\`${breakBackticks(causeStack(denial))}\`\`\``)).addSeparatorComponents(new WebhookSeparator().component).addTextDisplayComponents((text) => text.setContent("### Source")).addFileComponents((file) => file.setURL("attachment://source.json"));
761
+ this.addSuppressedCount(suppressed);
762
+ }
763
+ addSuppressedCount(suppressed) {
764
+ if (suppressed === 0) return;
765
+ this.instance.addTextDisplayComponents((text) => text.setContent(`-# ${suppressed} more of these since the last report`));
766
+ }
767
+ };
768
+ function faultSummary(denial, source) {
769
+ const head = stripAnsi(`### A handled fault was reported: \`${denial.name}\`\n**Message:** ${denial.message}\n`);
770
+ if (source.kind === "event") return `${head}**Event:** ${source.eventName}\n**Handler:** ${source.handler}\n**User ID:** \`${source.userId ?? "n/a"}\`\n**Guild ID:** \`${source.guildId ?? "n/a"}\`\n**Channel ID:** \`${source.channelId ?? "n/a"}\``;
771
+ return `${head}**Kind:** ${source.interactionKind}\n**Command:** ${source.command ?? "n/a"}\n**CustomId:** ${source.customId ?? "n/a"}\n**User ID:** \`${source.userId}\`\n**Guild ID:** \`${source.guildId ?? "n/a"}\`\n**Channel ID:** \`${source.channelId ?? "n/a"}\`\n**Interaction ID:** \`${source.interactionId}\``;
772
+ }
773
+ function causeStack(denial) {
774
+ const { cause } = denial;
775
+ if (Error.isError(cause)) return stripAnsi(cause.stack ?? cause.message);
776
+ if (typeof cause === "string") return stripAnsi(cause);
777
+ if (typeof cause === "bigint" || typeof cause === "symbol" || typeof cause === "function") return cause.toString();
778
+ if (cause !== void 0) try {
779
+ return JSON.stringify(cause);
780
+ } catch {
781
+ return "[unserializable cause]";
782
+ }
783
+ return stripAnsi(denial.stack ?? "No cause recorded.");
784
+ }
785
+
786
+ //#endregion
787
+ //#region src/subscribers/default/UnknownException.ts
788
+ let UnknownException = class UnknownException extends WebhookLog {
789
+ throttleKey() {
790
+ return `unknownException:${this.data.origin}:${nameOf(this.data.error)}`;
791
+ }
792
+ report(suppressed) {
793
+ const { metadata } = this.data;
794
+ return {
795
+ username: "Unknown Exception",
796
+ components: [new UnhandledErrorContainer(this.data, suppressed).component],
797
+ files: metadata ? [jsonAttachment("metadata.json", "Metadata associated with the error", metadata)] : void 0
798
+ };
799
+ }
800
+ };
801
+ UnknownException = __decorate([Subscribe(["unknownException"]), WebhookUrl("UNKNOWN_EXCEPTION_WEBHOOK_URL")], UnknownException);
802
+ function nameOf(error) {
803
+ return error.name === "Error" ? error.constructor.name : error.name;
804
+ }
805
+ var UnhandledErrorContainer = class extends BuilderComponent {
806
+ constructor(data, suppressed) {
807
+ super("container");
808
+ const { uuid, error, guild, user, metadata } = data;
809
+ this.instance.addTextDisplayComponents((text) => text.setContent(`### An unknown exception was thrown\n**Guild ID:** \`${guild?.id ?? "Not used in a guild"}\`\n**Guild Name:** ${guild?.name ?? "Not used in a guild"}\n**User ID:** \`${user?.id ?? "Missing user info"}\`\n**Username:** ${user?.username ?? "Missing user info"}\n`)).addSeparatorComponents(new WebhookSeparator().component).addTextDisplayComponents((text) => text.setContent(`### UUID \`${uuid}\`\n\`\`\`${errorReport(error)}\`\`\``));
810
+ this.addTimestampsIfAvailable(error);
811
+ this.addMetadataIfAvailable(metadata);
812
+ this.addSuppressedCount(suppressed);
813
+ }
814
+ addSuppressedCount(suppressed) {
815
+ if (suppressed === 0) return;
816
+ this.instance.addTextDisplayComponents((text) => text.setContent(`-# ${suppressed} more of these since the last report`));
817
+ }
818
+ addTimestampsIfAvailable(error) {
819
+ if (!(error instanceof DiscordAPIError)) return;
820
+ const now = Date.now();
821
+ const snowflake = error.url.match(/\/interactions\/(\d+)\//)?.[1];
822
+ if (!snowflake) return void 0;
823
+ const interactionTs = timestampFromSnowflake(snowflake);
824
+ const diff = now - interactionTs;
825
+ const seconds = Math.floor(diff / 1e3);
826
+ const millis = diff % 1e3;
827
+ this.instance.addSeparatorComponents(new WebhookSeparator().component).addTextDisplayComponents((text) => text.setContent(`### Timestamps\n- **\`Interaction sent\` :** ${new Date(interactionTs).toISOString()} (${interactionTs})\n- **\`Error logged \` :** ${new Date(now).toISOString()} (${now})\n- **\`Offset \` :** ${seconds}s ${millis}ms`));
828
+ }
829
+ addMetadataIfAvailable(metadata) {
830
+ if (!metadata) return;
831
+ this.instance.addSeparatorComponents(new WebhookSeparator().component).addTextDisplayComponents((text) => text.setContent("### Metadata")).addFileComponents((file) => file.setURL("attachment://metadata.json"));
832
+ }
833
+ };
834
+
835
+ //#endregion
836
+ //#region src/subscribers/slots.ts
837
+ const RegisterDefaults = Symbol("seedcord:bus:register-defaults");
838
+ const VerifyWebhooks = Symbol("seedcord:bus:verify-webhooks");
839
+ const RegisteredCount = Symbol("seedcord:bus:registered-count");
840
+ const RegisterSubscriber = Symbol("seedcord:bus:register");
841
+ const UnregisterSubscriber = Symbol("seedcord:bus:unregister");
842
+
843
+ //#endregion
844
+ //#region src/subscribers/Bus.ts
845
+ const loggerSlot = Symbol("seedcord:bus:logger");
846
+ /**
847
+ * Registers and dispatches subscribers. Subscribers run on a programmatic or framework publish.
848
+ * Accessed via `core.bus`. Do not construct it directly.
849
+ */
850
+ var Bus = class extends TypedEventEmitter {
851
+ core;
852
+ logger = new Logger("Subscribers", { channel: "subscribers" });
853
+ /** @internal */
854
+ [loggerSlot] = this.logger;
855
+ subscribersMap = /* @__PURE__ */ new Map();
856
+ onceRuns = new OnceTracker();
857
+ webhookProbes = /* @__PURE__ */ new Map();
858
+ constructor(core) {
859
+ super();
860
+ this.core = core;
861
+ }
862
+ /** @internal keep this inside start(). a bad webhook url thrown outside it never resets the singleton guard */
863
+ [RegisterDefaults]() {
864
+ for (const ctor of [UnknownException, HandledException]) this[RegisterSubscriber](ctor);
865
+ }
866
+ /** @internal */
867
+ [RegisterSubscriber](ctor) {
868
+ if (!this.probeWebhook(ctor)) return;
869
+ for (const registration of registrationsFor(ctor)) {
870
+ let registrations = this.subscribersMap.get(registration.key);
871
+ if (!registrations) {
872
+ registrations = [];
873
+ this.subscribersMap.set(registration.key, registrations);
874
+ }
875
+ registrations.push(registration);
876
+ }
877
+ }
878
+ /** @internal */
879
+ [UnregisterSubscriber](ctor, keys) {
880
+ for (const key of keys ?? [...this.subscribersMap.keys()]) {
881
+ const registrations = this.subscribersMap.get(key);
882
+ if (!registrations) continue;
883
+ const index = registrations.findIndex((entry) => entry.ctor === ctor);
884
+ if (index !== -1) registrations.splice(index, 1);
885
+ }
886
+ }
887
+ /** @internal */
888
+ get [RegisteredCount]() {
889
+ return new Set([...this.subscribersMap.values()].flat().map((entry) => entry.ctor)).size;
890
+ }
891
+ /** @internal registration already skipped every reporter with no url set */
892
+ async [VerifyWebhooks]() {
893
+ const missing = [];
894
+ await Promise.all([...this.webhookProbes].map(async ([url, envKeys]) => {
895
+ const result = await WebhookLog.senderFor(url).verify();
896
+ if (result === "missing") missing.push(...envKeys);
897
+ else if (result === "unreachable") this.logger.warn(`could not verify webhook at ${envKeys.map((key) => paint.amber.bold(key)).join(", ")}`);
898
+ }));
899
+ if (missing.length > 0) throw new SeedcordError(SeedcordErrorCode.ConfigWebhookNotFound, [missing.join(", ")]);
900
+ }
901
+ probeWebhook(ctor) {
902
+ if (!(ctor.prototype instanceof WebhookLog)) return true;
903
+ const envKey = WebhookLog.envKeyOf(ctor);
904
+ const url = WebhookLog.urlOf(envKey);
905
+ if (url === null) {
906
+ this.logger.warn(`${paint.sky.bold(ctor.name)} is off because ${paint.amber.bold(envKey)} is not set`);
907
+ return false;
908
+ }
909
+ const envKeys = this.webhookProbes.get(url) ?? [];
910
+ if (!envKeys.includes(envKey)) envKeys.push(envKey);
911
+ this.webhookProbes.set(url, envKeys);
912
+ return true;
913
+ }
914
+ /**
915
+ * Publishes an event to its subscribers and native listeners.
916
+ *
917
+ * Fire-and-forget. Subscriber handlers run asynchronously and this returns before they finish.
918
+ * Errors thrown by a subscriber or by an `on()` listener are caught and logged, never surfaced
919
+ * here. One throwing listener does not stop the others. Subscribers on one key run concurrently
920
+ * and carry no ordering guarantee. A `'once'` subscriber is marked as executed when it starts,
921
+ * even if it throws. It never runs twice.
922
+ *
923
+ * The framework's own keys are excluded. Subscribe to those and listen with `on`. The framework
924
+ * is their only publisher.
925
+ *
926
+ * @param event - The subscription key to publish
927
+ * @param data - Payload passed to each subscriber
928
+ * @returns Whether the underlying emitter had native listeners for the event
929
+ */
930
+ publish(event, data) {
931
+ return this.dispatchKey(event, data);
932
+ }
933
+ /**
934
+ * @deprecated Call {@link Bus.publish} to reach subscribers and listeners together.
935
+ * @throws A **SeedcordTypeError** every time, with `CoreBusEmitUnavailable`. `emit` reaches only the `on()` listeners.
936
+ */
937
+ emit(event, ..._args) {
938
+ throw new SeedcordTypeError(SeedcordErrorCode.CoreBusEmitUnavailable, [String(event)]);
939
+ }
940
+ /** @internal the framework's own publish path, keyed by a symbol no public entry exports */
941
+ [PublishDefault](event, data) {
942
+ return this.dispatchKey(event, data);
943
+ }
944
+ dispatchKey(event, data) {
945
+ this.processSubscriber(event, data);
946
+ return this.emitSafe(event, ...[data]);
947
+ }
948
+ onListenerError(error, event) {
949
+ this.logger.error(`listener for ${String(event)} threw`, error);
950
+ }
951
+ async processSubscriber(subscriberName, data) {
952
+ const registrations = this.subscribersMap.get(subscriberName);
953
+ if (!registrations) return;
954
+ const pending = registrations.reduce((acc, entry) => {
955
+ if (!this.onceRuns.claim(entry, subscriberName)) return acc;
956
+ acc.push(this.runSubscriber(entry, subscriberName, data));
957
+ return acc;
958
+ }, []);
959
+ await Promise.allSettled(pending);
960
+ }
961
+ async runSubscriber(entry, subscriberName, data) {
962
+ let name = "<unresolved>";
963
+ try {
964
+ const Ctor = entry.ctor;
965
+ name = Ctor.name;
966
+ await new Ctor(data, this.core, subscriberName).execute();
967
+ } catch (err) {
968
+ this.logger.error(`Error in subscriber ${paint.mute(String(subscriberName))} handler ${paint.sky.bold(name)}:`, err);
969
+ }
970
+ }
971
+ };
972
+ function registrationsFor(ctor) {
973
+ return Reflect.getMetadata(SubscribeMetadataKey, ctor).map(({ key, frequency }) => ({
974
+ key,
975
+ frequency,
976
+ ctor
977
+ }));
978
+ }
979
+ /** @internal */
980
+ function busLoggerOf(bus) {
981
+ return bus[loggerSlot];
982
+ }
983
+
984
+ //#endregion
985
+ export { InteractionRouteKeys as A, CommandMetadataKey as C, InteractionKind as D, GatedMetadataKey as E, isBuilderComponentClass as F, bindBotColor as I, WebhookUrlMetadataKey as M, BuilderComponent as N, InteractionMetadataKey as O, RowComponent as P, assertUniqueKeys as S, EventMiddlewareMetadataKey as T, OnceTracker as _, RegisterSubscriber as a, StartupPhase as b, VerifyWebhooks as c, WebhookLog as d, Subscriber as f, sendFlags as g, deferFlags as h, RegisterDefaults as i, SubscribeMetadataKey as j, InteractionMiddlewareMetadataKey as k, WebhookUrl as l, ReportThrottle as m, busLoggerOf as n, RegisteredCount as o, runCase as p, registrationsFor as r, UnregisterSubscriber as s, Bus as t, Subscribe as u, MemoryDriver as v, EventMetadataKey as w, PublishDefault as x, ShutdownPhase as y };
986
+ //# sourceMappingURL=Bus-VJw-QpoY.mjs.map