@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.
- package/dist/{Bus-dGy6xq3F.mjs → Bus-Djm6688b.mjs} +6 -5
- package/dist/Bus-Djm6688b.mjs.map +1 -0
- package/dist/{Pluggable-B7_EftHj.mjs → Pluggable-BShEwGw9.mjs} +40 -7
- package/dist/Pluggable-BShEwGw9.mjs.map +1 -0
- package/dist/{Pluggable-C5uxmDb-.d.mts → Pluggable-CAteNmfy.d.mts} +11 -5
- package/dist/Plugin-BJfRk0s_.mjs.map +1 -1
- package/dist/{Plugin-Bnz61fs_.d.mts → Plugin-D3wIj0xX.d.mts} +8 -5
- package/dist/{cursor-8Y2zpF-Y.d.mts → cursor-BPaPFTic.d.mts} +186 -14
- package/dist/{cursor-BkI9lxy2.mjs → cursor-CFNKLVlk.mjs} +49 -11
- package/dist/cursor-CFNKLVlk.mjs.map +1 -0
- package/dist/{index-CD8l15aZ.d.mts → index-BS1hH9Sh.d.mts} +2 -2
- package/dist/index.d.mts +16 -35
- package/dist/index.mjs +29 -37
- package/dist/index.mjs.map +1 -1
- package/dist/internal.index.d.mts +79 -8
- package/dist/internal.index.mjs +121 -16
- package/dist/internal.index.mjs.map +1 -1
- package/dist/node-internal.index.d.mts +6 -4
- package/dist/node-internal.index.mjs +38 -14
- package/dist/node-internal.index.mjs.map +1 -1
- package/dist/node.index.d.mts +1 -1
- package/dist/node.index.mjs +1 -1
- package/dist/{phases-D0a8BV9Z.d.mts → phases-DJf12l5T.d.mts} +48 -9
- package/dist/plugin.index.d.mts +1 -1
- package/dist/{slashRouteLeaves-C1msUE9Q.mjs → slashRouteLeaves-DTFHzCKN.mjs} +2 -2
- package/dist/{slashRouteLeaves-C1msUE9Q.mjs.map → slashRouteLeaves-DTFHzCKN.mjs.map} +1 -1
- package/dist/{subscribers-A8lNU5yM.mjs → subscribers-kAQQt_YP.mjs} +32 -4
- package/dist/subscribers-kAQQt_YP.mjs.map +1 -0
- package/package.json +8 -8
- package/dist/Bus-dGy6xq3F.mjs.map +0 -1
- package/dist/Pluggable-B7_EftHj.mjs.map +0 -1
- package/dist/cursor-BkI9lxy2.mjs.map +0 -1
- package/dist/subscribers-A8lNU5yM.mjs.map +0 -1
|
@@ -265,7 +265,8 @@ let InteractionKind = /* @__PURE__ */ function(InteractionKind) {
|
|
|
265
265
|
}({});
|
|
266
266
|
const CommandMetadataKey = Symbol.for("seedcord:command:metadata");
|
|
267
267
|
const InteractionMetadataKey = Symbol.for("seedcord:interaction:metadata");
|
|
268
|
-
const
|
|
268
|
+
const InteractionMiddlewareMetadataKey = Symbol.for("seedcord:middleware:interaction:metadata");
|
|
269
|
+
const EventMiddlewareMetadataKey = Symbol.for("seedcord:middleware:event:metadata");
|
|
269
270
|
const SubscribeMetadataKey = Symbol.for("seedcord:subscribe:metadata");
|
|
270
271
|
const EventMetadataKey = Symbol.for("seedcord:event:metadata");
|
|
271
272
|
const GatedMetadataKey = Symbol.for("seedcord:gated:metadata");
|
|
@@ -617,7 +618,7 @@ function __decorate(decorators, target, key, desc) {
|
|
|
617
618
|
//#region src/subscribers/default/HandledException.ts
|
|
618
619
|
let HandledException = class HandledException extends WebhookLog {
|
|
619
620
|
throttleKey() {
|
|
620
|
-
return `handledException:${this.data.
|
|
621
|
+
return `handledException:${this.data.origin}:${this.data.denial.name}`;
|
|
621
622
|
}
|
|
622
623
|
report(suppressed) {
|
|
623
624
|
return {
|
|
@@ -662,7 +663,7 @@ function causeStack(denial) {
|
|
|
662
663
|
//#region src/subscribers/default/UnknownException.ts
|
|
663
664
|
let UnknownException = class UnknownException extends WebhookLog {
|
|
664
665
|
throttleKey() {
|
|
665
|
-
return `unknownException:${this.data.
|
|
666
|
+
return `unknownException:${this.data.origin}:${nameOf(this.data.error)}`;
|
|
666
667
|
}
|
|
667
668
|
report(suppressed) {
|
|
668
669
|
const { metadata } = this.data;
|
|
@@ -865,5 +866,5 @@ function busLoggerOf(bus) {
|
|
|
865
866
|
}
|
|
866
867
|
|
|
867
868
|
//#endregion
|
|
868
|
-
export {
|
|
869
|
-
//# sourceMappingURL=Bus-
|
|
869
|
+
export { BuilderComponent as A, InteractionRouteKeys as C, clearStore as D, accessorStore as E, isBuilderComponentClass as M, setBotColor as N, guardedAccessor as O, InteractionMiddlewareMetadataKey as S, WebhookUrlMetadataKey as T, EventMetadataKey as _, RegisterSubscriber as a, InteractionKind as b, VerifyWebhooks as c, WebhookLog as d, Subscriber as f, CommandMetadataKey as g, sendFlags as h, RegisterDefaults as i, RowComponent as j, contextMenuLeaves as k, WebhookUrl as l, deferFlags as m, busLoggerOf as n, RegisteredCount as o, ReportThrottle as p, registrationFor as r, UnregisterSubscriber as s, Bus as t, Subscribe as u, EventMiddlewareMetadataKey as v, SubscribeMetadataKey as w, InteractionMetadataKey as x, GatedMetadataKey as y };
|
|
870
|
+
//# sourceMappingURL=Bus-Djm6688b.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"Bus-Djm6688b.mjs","names":[],"sources":["../src/components/botColorHolder.ts","../src/components/builderTypes.ts","../src/components/colors.ts","../src/components/resolveColor.ts","../src/components/Component.ts","../src/commands/contextMenuLeaves.ts","../src/miscellaneous/guarded.ts","../src/metadataKeys.ts","../src/reply/flags.ts","../src/subscribers/ReportThrottle.ts","../src/subscribers/Subscriber.ts","../src/subscribers/bases/webhookHelpers.ts","../src/subscribers/bases/WebhookSender.ts","../src/subscribers/bases/WebhookLog.ts","../src/subscribers/decorators/Subscribe.ts","../src/subscribers/decorators/WebhookUrl.ts","../src/subscribers/default/HandledException.ts","../src/subscribers/default/UnknownException.ts","../src/subscribers/slots.ts","../src/subscribers/Bus.ts"],"sourcesContent":["import type { BotColor } from '@seedcord/types';\n\n// must be 'Default' so the container guard in applyBotColor leaves the accent unset\nconst DEFAULT_COLOR: BotColor = 'Default';\n\nlet current: BotColor = DEFAULT_COLOR;\n\nexport function setBotColor(color: BotColor | undefined): void {\n current = color ?? DEFAULT_COLOR;\n}\n\nexport function getBotColor(): BotColor {\n return current;\n}\n","import {\n ActionRowBuilder,\n ButtonBuilder,\n ChannelSelectMenuBuilder,\n CheckboxBuilder,\n CheckboxGroupBuilder,\n CheckboxGroupOptionBuilder,\n ContainerBuilder,\n ContextMenuCommandBuilder,\n EmbedBuilder,\n FileBuilder,\n FileUploadBuilder,\n LabelBuilder,\n MediaGalleryBuilder,\n MentionableSelectMenuBuilder,\n ModalBuilder,\n RadioGroupBuilder,\n RadioGroupOptionBuilder,\n RoleSelectMenuBuilder,\n SectionBuilder,\n SeparatorBuilder,\n SlashCommandBuilder,\n SlashCommandSubcommandBuilder,\n SlashCommandSubcommandGroupBuilder,\n StringSelectMenuBuilder,\n StringSelectMenuOptionBuilder,\n TextDisplayBuilder,\n TextInputBuilder,\n UserSelectMenuBuilder\n} from '@discordjs/builders';\n\n/**\n * Available Discord.js builder classes for use with BuilderComponent for commands, embeds, modals, etc.\n *\n * @internal\n */\nexport const BuilderTypes = {\n // Command Components\n command: SlashCommandBuilder,\n context_menu: ContextMenuCommandBuilder,\n subcommand: SlashCommandSubcommandBuilder,\n group: SlashCommandSubcommandGroupBuilder,\n\n // Embed Components\n embed: EmbedBuilder,\n\n // Modal Components\n modal: ModalBuilder,\n label: LabelBuilder,\n text_input: TextInputBuilder,\n file_upload: FileUploadBuilder,\n checkbox: CheckboxBuilder,\n checkbox_group: CheckboxGroupBuilder,\n checkbox_group_option: CheckboxGroupOptionBuilder,\n radio_group: RadioGroupBuilder,\n radio_group_option: RadioGroupOptionBuilder,\n\n // Action Row Components\n button: ButtonBuilder,\n menu_string: StringSelectMenuBuilder,\n menu_option_string: StringSelectMenuOptionBuilder,\n menu_user: UserSelectMenuBuilder,\n menu_channel: ChannelSelectMenuBuilder,\n menu_mentionable: MentionableSelectMenuBuilder,\n menu_role: RoleSelectMenuBuilder,\n\n // ComponentsV2\n container: ContainerBuilder,\n text_display: TextDisplayBuilder,\n file: FileBuilder,\n media: MediaGalleryBuilder,\n section: SectionBuilder,\n separator: SeparatorBuilder\n};\n\n/**\n * Available Discord.js action row classes for use with RowComponent for Select Menus and Buttons\n *\n * @internal\n */\nexport const RowTypes: {\n button: typeof ActionRowBuilder<ButtonBuilder>;\n menu_string: typeof ActionRowBuilder<StringSelectMenuBuilder>;\n menu_user: typeof ActionRowBuilder<UserSelectMenuBuilder>;\n menu_channel: typeof ActionRowBuilder<ChannelSelectMenuBuilder>;\n menu_mentionable: typeof ActionRowBuilder<MentionableSelectMenuBuilder>;\n menu_role: typeof ActionRowBuilder<RoleSelectMenuBuilder>;\n} = {\n button: ActionRowBuilder<ButtonBuilder>,\n menu_string: ActionRowBuilder<StringSelectMenuBuilder>,\n menu_user: ActionRowBuilder<UserSelectMenuBuilder>,\n menu_channel: ActionRowBuilder<ChannelSelectMenuBuilder>,\n menu_mentionable: ActionRowBuilder<MentionableSelectMenuBuilder>,\n menu_role: ActionRowBuilder<RoleSelectMenuBuilder>\n};\n\n/**\n * Available Discord.js builder types for use with BuilderComponent\n */\nexport type BuilderType = keyof typeof BuilderTypes;\n\n/**\n * @internal\n */\nexport type InstantiatedBuilder<BuilderKey extends BuilderType> = InstanceType<(typeof BuilderTypes)[BuilderKey]>;\n\n/**\n * Available Discord.js action row types for use with RowComponent\n */\nexport type RowType = keyof typeof RowTypes;\n\n/**\n * @internal\n */\nexport type InstantiatedActionRow<RowKey extends RowType> = InstanceType<(typeof RowTypes)[RowKey]>;\n","import type { ColorName, TypedOmit } from '@seedcord/types';\n\n// ported verbatim from discord.js (Apache-2.0), src/util/Colors.js\nexport const Colors: TypedOmit<Record<ColorName, number>, 'Random'> = {\n Aqua: 0x1a_bc_9c,\n Blue: 0x34_98_db,\n Blurple: 0x58_65_f2,\n DarkAqua: 0x11_80_6a,\n DarkBlue: 0x20_66_94,\n DarkButNotBlack: 0x2c_2f_33,\n DarkerGrey: 0x7f_8c_8d,\n DarkGold: 0xc2_7c_0e,\n DarkGreen: 0x1f_8b_4c,\n DarkGrey: 0x97_9c_9f,\n DarkNavy: 0x2c_3e_50,\n DarkOrange: 0xa8_43_00,\n DarkPurple: 0x71_36_8a,\n DarkRed: 0x99_2d_22,\n DarkVividPink: 0xad_14_57,\n Default: 0x00_00_00,\n Fuchsia: 0xeb_45_9e,\n Gold: 0xf1_c4_0f,\n Green: 0x57_f2_87,\n Grey: 0x95_a5_a6,\n Greyple: 0x99_aa_b5,\n LightGrey: 0xbc_c0_c0,\n LuminousVividPink: 0xe9_1e_63,\n Navy: 0x34_49_5e,\n NotQuiteBlack: 0x23_27_2a,\n Orange: 0xe6_7e_22,\n Purple: 0x9b_59_b6,\n Red: 0xed_42_45,\n White: 0xff_ff_ff,\n Yellow: 0xfe_e7_5c\n} as const;\n","import { SeedcordErrorCode } from '@seedcord/errors';\nimport { SeedcordRangeError, SeedcordTypeError } from '@seedcord/errors/internal';\n\nimport { Colors } from './colors';\n\nimport type { BotColor } from '@seedcord/types';\n\nconst MAX_COLOR = 0xff_ff_ff;\nconst RED_SHIFT = 16;\nconst GREEN_SHIFT = 8;\n\nconst palette: Record<string, number | undefined> = Colors;\n\n/**\n * Resolves a {@link BotColor} to the integer Discord expects.\n *\n * Ported from discord.js (Apache-2.0), `resolveColor` in src/util/Util.js.\n */\nexport function resolveColor(color: BotColor): number {\n let resolved: number | undefined;\n\n if (typeof color === 'string') {\n if (color === 'Random') return Math.floor(Math.random() * (MAX_COLOR + 1));\n if (color === 'Default') return 0;\n if (/^#?[\\da-f]{6}$/i.test(color)) return Number.parseInt(color.replace('#', ''), 16);\n resolved = palette[color];\n } else if (typeof color === 'number') {\n resolved = color;\n } else {\n resolved = (color[0] << RED_SHIFT) + (color[1] << GREEN_SHIFT) + color[2];\n }\n\n if (resolved === undefined || !Number.isSafeInteger(resolved)) {\n throw new SeedcordTypeError(SeedcordErrorCode.ColorUnresolvable, [JSON.stringify(color)]);\n }\n if (resolved < 0 || resolved > MAX_COLOR) {\n throw new SeedcordRangeError(SeedcordErrorCode.ColorOutOfRange);\n }\n\n return resolved;\n}\n","import { ContainerBuilder, ContextMenuCommandBuilder, EmbedBuilder, SlashCommandBuilder } from '@discordjs/builders';\nimport { InteractionContextType } from 'discord-api-types/v10';\n\nimport { getBotColor } from './botColorHolder';\nimport { BuilderTypes, RowTypes } from './builderTypes';\nimport { resolveColor } from './resolveColor';\n\nimport type { BuilderType, InstantiatedActionRow, InstantiatedBuilder, RowType } from './builderTypes';\n\n/** @internal */\nabstract class BaseComponent<TComponent> {\n private readonly _component: TComponent;\n\n protected constructor(ComponentClass: new () => TComponent) {\n this._component = new ComponentClass();\n }\n\n /**\n * Returns the live builder, ready to send in a Discord message or nest in another component.\n *\n * Reading this can apply the bot color (see {@link BuilderComponent}), so it has a side effect.\n * @example new SomeComponent().component\n */\n public abstract get component(): InstantiatedBuilder<BuilderType> | InstantiatedActionRow<RowType>;\n\n /**\n * The wrapped builder, for calling Discord.js methods like setTitle() and setDescription() inside a subclass.\n *\n * @example this.instance.setTitle('My Modal')\n */\n protected get instance(): TComponent {\n return this._component;\n }\n}\n\n/**\n * Base class for Discord.js builder components\n *\n * Wraps Discord.js builders (SlashCommandBuilder, EmbedBuilder, etc.) with\n * Seedcord-specific defaults and helper methods.\n *\n * @typeParam BuilderKey - The type of Discord.js builder being wrapped\n */\nexport abstract class BuilderComponent<BuilderKey extends BuilderType> extends BaseComponent<\n InstantiatedBuilder<BuilderKey>\n> {\n private colorApplied = false;\n\n protected constructor(public readonly type: BuilderKey) {\n const ComponentClass = BuilderTypes[type] as unknown;\n super(ComponentClass as new () => InstantiatedBuilder<BuilderKey>);\n\n if (this.instance instanceof SlashCommandBuilder || this.instance instanceof ContextMenuCommandBuilder) {\n this.instance.setContexts(InteractionContextType.Guild);\n }\n }\n\n get component(): InstantiatedBuilder<BuilderKey> {\n this.applyBotColor();\n return this.instance;\n }\n\n // Resolving in the constructor would capture the default for a component built before setBotColor()\n // ran. The unset check keeps a color the subclass set for itself.\n private applyBotColor(): void {\n if (this.colorApplied) return;\n this.colorApplied = true;\n\n const color = getBotColor();\n if (this.instance instanceof EmbedBuilder) {\n if (this.instance.data.color === undefined) this.instance.setColor(resolveColor(color));\n } else if (this.instance instanceof ContainerBuilder) {\n const accent = this.instance.data.accent_color;\n if (accent === null || accent === undefined) {\n this.instance.setAccentColor(color === 'Default' ? undefined : resolveColor(color));\n }\n }\n }\n}\n\n// instanceof fails across the cli's separate module registry\nconst BUILDER_COMPONENT = Symbol.for('seedcord:core:builder-component');\n\nObject.defineProperty(BuilderComponent.prototype, BUILDER_COMPONENT, { value: true });\n\n/** @internal */\nexport function isBuilderComponentClass(value: unknown): value is new () => BuilderComponent<BuilderType> {\n if (typeof value !== 'function') return false;\n const proto: unknown = (value as { prototype?: unknown }).prototype;\n return typeof proto === 'object' && proto !== null && BUILDER_COMPONENT in proto;\n}\n\n/**\n * Base class for Discord action row components\n *\n * Wraps Discord.js action row builder with Seedcord-specific defaults and helper methods.\n *\n * @typeParam RowKey - The Discord.js action row type being wrapped\n */\nexport abstract class RowComponent<RowKey extends RowType> extends BaseComponent<InstantiatedActionRow<RowKey>> {\n protected constructor(public readonly type: RowKey) {\n const ComponentClass = RowTypes[type] as unknown;\n super(ComponentClass as new () => InstantiatedActionRow<RowKey>);\n }\n\n get component(): InstantiatedActionRow<RowKey> {\n return this.instance;\n }\n}\n","import { ContextMenuCommandBuilder } from '@discordjs/builders';\nimport { ApplicationCommandType } from 'discord-api-types/v10';\n\nimport type { SlashCommandBuilder } from '@discordjs/builders';\n\n/** @internal */\nexport interface ContextMenuLeaves {\n readonly user: Set<string>;\n readonly message: Set<string>;\n}\n\n/**\n * dedupes names across builders, since the same builder repeats once per guild.\n * a user and a message command can share a name.\n *\n * @internal\n */\nexport function contextMenuLeaves(\n commands: Iterable<SlashCommandBuilder | ContextMenuCommandBuilder>\n): ContextMenuLeaves {\n const user = new Set<string>();\n const message = new Set<string>();\n for (const command of commands) {\n if (!(command instanceof ContextMenuCommandBuilder)) continue;\n if (command.type === ApplicationCommandType.User) user.add(command.name);\n else message.add(command.name);\n }\n return { user, message };\n}\n","import { SeedcordErrorCode } from '@seedcord/errors';\nimport { SeedcordError } from '@seedcord/errors/internal';\n\n// null-prototype, on a plain object a key named __proto__ sets the store's prototype and adds no entry\nexport function accessorStore<Value>(): Record<string, Value> {\n return Object.create(null) as Record<string, Value>;\n}\n\n// empties a store in place, keeping the reference the accessor's Proxy wraps\nexport function clearStore(store: Record<string, unknown>): void {\n for (const key of Object.keys(store)) Reflect.deleteProperty(store, key);\n}\n\n// handler modules import before startup fills the record, and an unguarded read there returns undefined and renders as an empty string\nexport function guardedAccessor<Value>(accessor: string, storage: Record<string, Value>): Record<string, Value> {\n return new Proxy(storage, {\n get(target, key, receiver): unknown {\n // hasOwn, since `in` walks the prototype and would read a key like `constructor` as resolved\n if (typeof key !== 'string' || Object.hasOwn(target, key)) return Reflect.get(target, key, receiver);\n throw new SeedcordError(SeedcordErrorCode.CoreAccessorUnresolved, [accessor, key]);\n }\n });\n}\n","import type { TypedExclude } from '@seedcord/types';\n\n/**\n * Every interaction kind seedcord routes.\n */\nexport enum InteractionKind {\n Slash = 'slash',\n Button = 'button',\n Modal = 'modal',\n StringMenu = 'stringMenu',\n UserMenu = 'userMenu',\n RoleMenu = 'roleMenu',\n ChannelMenu = 'channelMenu',\n MentionableMenu = 'mentionableMenu',\n MessageContextMenu = 'messageContextMenu',\n UserContextMenu = 'userContextMenu',\n Autocomplete = 'autocomplete'\n}\n\n/**\n * The interaction kinds a middleware can filter on. Autocomplete carries no reply target.\n */\nexport type MiddlewareKind = TypedExclude<InteractionKind, InteractionKind.Autocomplete>;\n\n// Symbol.for so a second copy of this module reads the same slots\nexport const CommandMetadataKey = Symbol.for('seedcord:command:metadata');\nexport const InteractionMetadataKey = Symbol.for('seedcord:interaction:metadata');\nexport const InteractionMiddlewareMetadataKey = Symbol.for('seedcord:middleware:interaction:metadata');\nexport const EventMiddlewareMetadataKey = Symbol.for('seedcord:middleware:event:metadata');\nexport const SubscribeMetadataKey = Symbol.for('seedcord:subscribe:metadata');\nexport const EventMetadataKey = Symbol.for('seedcord:event:metadata');\nexport const GatedMetadataKey = Symbol.for('seedcord:gated:metadata');\nexport const WebhookUrlMetadataKey = Symbol.for('seedcord:webhook-url:metadata');\n\nexport const InteractionRouteKeys: Record<InteractionKind, symbol> = {\n [InteractionKind.Slash]: Symbol.for('seedcord:interaction:slash'),\n [InteractionKind.Button]: Symbol.for('seedcord:interaction:button'),\n [InteractionKind.Modal]: Symbol.for('seedcord:interaction:modal'),\n [InteractionKind.StringMenu]: Symbol.for('seedcord:interaction:string-menu'),\n [InteractionKind.UserMenu]: Symbol.for('seedcord:interaction:user-menu'),\n [InteractionKind.RoleMenu]: Symbol.for('seedcord:interaction:role-menu'),\n [InteractionKind.ChannelMenu]: Symbol.for('seedcord:interaction:channel-menu'),\n [InteractionKind.MentionableMenu]: Symbol.for('seedcord:interaction:mentionable-menu'),\n [InteractionKind.MessageContextMenu]: Symbol.for('seedcord:interaction:message-context-menu'),\n [InteractionKind.UserContextMenu]: Symbol.for('seedcord:interaction:user-context-menu'),\n [InteractionKind.Autocomplete]: Symbol.for('seedcord:interaction:autocomplete')\n};\n","import { MessageFlags } from 'discord-api-types/v10';\n\nimport type { DeferOpts, SendOpts } from '@seedcord/types';\n\n// IsComponentsV2 is unconditional, since every framework reply is v2\nexport function sendFlags(opts?: SendOpts): number {\n let flags = MessageFlags.IsComponentsV2;\n if (opts?.ephemeral ?? true) flags |= MessageFlags.Ephemeral;\n if (opts?.silent) flags |= MessageFlags.SuppressNotifications;\n return flags;\n}\n\n// a deferral carries no content\nexport function deferFlags(opts?: DeferOpts): number {\n return (opts?.ephemeral ?? true) ? MessageFlags.Ephemeral : 0;\n}\n","interface ThrottleWindow {\n lastSentAt: number;\n suppressedSinceSend: number;\n}\n\nexport class ReportThrottle {\n private static readonly perCore = new WeakMap<object, ReportThrottle>();\n\n public static for(core: object): ReportThrottle {\n let throttle = ReportThrottle.perCore.get(core);\n if (!throttle) {\n throttle = new ReportThrottle();\n ReportThrottle.perCore.set(core, throttle);\n }\n return throttle;\n }\n\n private readonly windows = new Map<string, ThrottleWindow>();\n\n constructor(\n private readonly windowMs = 60_000,\n private readonly now: () => number = () => Date.now()\n ) {}\n\n // null while the window is open, otherwise how many were dropped since the last send\n public take(key: string): number | null {\n const window = this.windows.get(key);\n if (window && this.now() - window.lastSentAt < this.windowMs) {\n window.suppressedSinceSend += 1;\n return null;\n }\n\n const suppressed = window?.suppressedSinceSend ?? 0;\n this.windows.set(key, { lastSentAt: this.now(), suppressedSinceSend: 0 });\n return suppressed;\n }\n\n // gives a failed send's count to the open window, plus the one card that never went out\n public restore(key: string, suppressed: number): void {\n const window = this.windows.get(key);\n if (window) window.suppressedSinceSend += suppressed + 1;\n }\n}\n","import { Logger } from '@seedcord/logger';\n\nimport type { CoreBase } from '#interfaces/CoreBase';\nimport type { SubscriptionKey, AllSubscriptions } from './types/Subscriptions';\n\n/**\n * Abstract base class for handling application subscribers.\n *\n * Provides type-safe access to subscribers data and the core framework instance.\n * Extend this class to create custom subscriber handlers.\n *\n * @typeParam KeyOfSubscribers - The specific subscriber type this handler processes\n * @typeParam TCore - The transport's Core\n */\nexport abstract class Subscriber<KeyOfSubscribers extends SubscriptionKey, TCore extends CoreBase> {\n protected readonly logger: Logger;\n\n /**\n * Instantiates the subscriber with event data and core.\n *\n * @param data - The subscriber event data\n * @param core - The core framework instance\n */\n constructor(\n protected readonly data: AllSubscriptions[KeyOfSubscribers],\n protected readonly core: TCore\n ) {\n this.logger = new Logger(this.constructor.name, { channel: 'subscribers' });\n }\n\n /**\n * Executes the subscriber handler logic.\n */\n abstract execute(): Promise<void>;\n}\n","import { filterCirculars, stripAnsi } from '@seedcord/utils';\nimport { SeparatorSpacingSize } from 'discord-api-types/v10';\n\nimport { BuilderComponent } from '#components/Component';\n\nimport type { WebhookFile } from './WebhookSender';\n\nconst DISCORD_WEBHOOK_REGEX = new RegExp(\n String.raw`^https?:\\/\\/(?:canary\\.|ptb\\.)?discord(?:app)?\\.com\\/api\\/webhooks\\/\\d+\\/[\\w$-]+$`\n);\n\nexport function isDiscordWebhookUrl(value: string): boolean {\n return URL.canParse(value) && DISCORD_WEBHOOK_REGEX.test(value);\n}\n\nexport function jsonAttachment(name: string, description: string, data: unknown): WebhookFile {\n const content = filterCirculars(data);\n return { name, description, data: new TextEncoder().encode(JSON.stringify(content, undefined, 2)) };\n}\n\n// the report goes inside a ``` code block, so a stray ``` in the text would close it early\nexport function breakBackticks(text: string): string {\n return text.replaceAll('```', '```');\n}\n\n// discord caps a message at 2000 characters. the backticks and heading take the rest.\nconst REPORT_BUDGET = 1800;\n\n// breakBackticks grows the text by 2 chars per run\nfunction body(value: unknown): string {\n const raw = Error.isError(value) ? (value.stack ?? `${value.name}: ${value.message}`) : String(value);\n return breakBackticks(stripAnsi(raw));\n}\n\nfunction clamp(text: string, budget: number): string {\n if (text.length <= budget) return text;\n return budget > 0 ? `${text.slice(0, budget - 1)}…` : '';\n}\n\nfunction omittedLine(count: number): string {\n return `\\n\\nand ${count} more failure${count === 1 ? '' : 's'}`;\n}\n\nfunction memberReport(members: readonly unknown[], budget: number): string {\n // the dropped-count line has to fit even when every member is dropped\n const room = Math.max(budget - omittedLine(members.length).length, 0);\n const share = Math.floor(room / members.length);\n\n let report = '';\n let shown = 0;\n for (const [index, member] of members.entries()) {\n const heading = `\\n\\nFailure ${index + 1} of ${members.length}:\\n`;\n const entry = heading + clamp(body(member), Math.max(share - heading.length, 0));\n if (report.length + entry.length > room) break;\n report += entry;\n shown++;\n }\n\n const hidden = members.length - shown;\n return hidden > 0 ? report + omittedLine(hidden) : report;\n}\n\n// ansi codes are stripped because discord won't render them\nexport function errorReport(error: Error): string {\n let head = body(error);\n if (Error.isError(error.cause)) head += `\\n\\nCaused by:\\n${body(error.cause)}`;\n\n const members: unknown[] = error instanceof AggregateError ? error.errors : [];\n if (members.length === 0) return clamp(head, REPORT_BUDGET);\n\n // a long parent stack would otherwise push the dropped-count line off the end\n const report = clamp(head, REPORT_BUDGET - omittedLine(members.length).length);\n return report + memberReport(members, REPORT_BUDGET - report.length);\n}\n\nexport class WebhookSeparator extends BuilderComponent<'separator'> {\n constructor() {\n super('separator');\n this.instance.setSpacing(SeparatorSpacingSize.Small).setDivider(true);\n }\n}\n","import { DiscordAPIError, REST } from '@discordjs/rest';\nimport { SeedcordErrorCode } from '@seedcord/errors';\nimport { SeedcordTypeError } from '@seedcord/errors/internal';\nimport { Routes } from 'discord-api-types/v10';\n\nimport type { ReplyFile } from '@seedcord/types';\nimport type { APIMessageTopLevelComponent } from 'discord-api-types/v10';\n\nconst HTTP_NOT_FOUND = 404;\nconst HTTP_UNAUTHORIZED = 401;\n\n/** A file attached to a webhook report. */\nexport interface WebhookFile extends ReplyFile {\n readonly description: string;\n}\n\nexport interface WebhookSendOptions {\n flags: number;\n username: string;\n avatarUrl?: string | undefined;\n components: readonly { toJSON(): APIMessageTopLevelComponent }[];\n files?: readonly WebhookFile[] | undefined;\n}\n\nexport class WebhookSender {\n private readonly rest = new REST();\n private readonly route: `/${string}`;\n\n constructor(url: string) {\n const match = /\\/webhooks\\/(\\d+)\\/([\\w$-]+)$/.exec(url);\n if (!match?.[1] || !match[2])\n throw new SeedcordTypeError(SeedcordErrorCode.ConfigWebhookUrlInvalid, ['The url']);\n this.route = Routes.webhook(match[1], match[2]);\n }\n\n // missing means discord answered 404 or 401\n async verify(): Promise<'ok' | 'missing' | 'unreachable'> {\n try {\n await this.rest.get(this.route, { auth: false });\n return 'ok';\n } catch (error) {\n const gone =\n error instanceof DiscordAPIError &&\n (error.status === HTTP_NOT_FOUND || error.status === HTTP_UNAUTHORIZED);\n return gone ? 'missing' : 'unreachable';\n }\n }\n\n async send(options: WebhookSendOptions): Promise<void> {\n const { flags, username, avatarUrl, components, files } = options;\n await this.rest.post(this.route, {\n auth: false,\n // with_components=true makes discord accept a components-v2 payload. wait=true makes it\n // return payload errors in the response\n query: new URLSearchParams({ with_components: 'true', wait: 'true' }),\n body: {\n flags,\n username,\n ...(avatarUrl && { avatar_url: avatarUrl }),\n components: components.map((component) => component.toJSON()),\n ...(files?.length && {\n attachments: files.map((file, id) => ({ id, filename: file.name, description: file.description }))\n })\n },\n ...(files?.length && { files: files.map((file) => ({ name: file.name, data: file.data })) })\n });\n }\n}\n","import { SeedcordErrorCode, paint } from '@seedcord/errors';\nimport { SeedcordError, SeedcordTypeError } from '@seedcord/errors/internal';\nimport { Converters, Envapter } from 'envapt';\n\nimport { sendFlags } from '#reply/flags';\nimport { WebhookUrlMetadataKey } from '#src/metadataKeys';\n\nimport { ReportThrottle } from '../ReportThrottle';\nimport { Subscriber } from '../Subscriber';\nimport { isDiscordWebhookUrl } from './webhookHelpers';\nimport { WebhookSender } from './WebhookSender';\n\nimport type { CoreBase } from '#interfaces/CoreBase';\nimport type { WebhookFile } from './WebhookSender';\nimport type { SubscriptionKey } from '../types/Subscriptions';\nimport type { APIMessageTopLevelComponent } from 'discord-api-types/v10';\n\n/**\n * The content a reporter returns from {@link WebhookLog.report}.\n */\nexport interface WebhookReport {\n /** Shown as the webhook message author. Defaults to the reporter's class name. */\n username?: string;\n /** Shown as the webhook message avatar. Defaults to the webhook's own. */\n avatarUrl?: string;\n components: readonly { toJSON(): APIMessageTopLevelComponent }[];\n files?: readonly WebhookFile[] | undefined;\n}\n\n/**\n * Base class for subscribers that deliver their event to a Discord webhook.\n *\n * Declare the url's environment variable with `@WebhookUrl` and implement {@link report}. Url\n * resolution, validation, sender reuse, and sending run in the base. When the variable is unset\n * the reporter is skipped at registration with a boot warning.\n *\n * @typeParam KeyOfSubscribers - The subscription key this reporter receives\n * @typeParam TCore - The transport's Core\n */\nexport abstract class WebhookLog<KeyOfSubscribers extends SubscriptionKey, TCore extends CoreBase> extends Subscriber<\n KeyOfSubscribers,\n TCore\n> {\n private static readonly senders = new Map<string, WebhookSender>();\n\n /**\n * Builds the message for one published event.\n *\n * @param suppressed - How many events were dropped since the last card\n */\n abstract report(suppressed: number): WebhookReport | Promise<WebhookReport>;\n\n /**\n * Events sharing this key collapse into one card per minute. Return `null` to send every event.\n */\n protected throttleKey(): string | null {\n return null;\n }\n\n async execute(): Promise<void> {\n const url = WebhookLog.urlOf(WebhookLog.envKeyOf(this.constructor));\n if (url === null) {\n // only an edge host reaches this branch, since it registers reporters lazily\n this.logger.warn(\n `${paint.sky.bold(this.constructor.name)} has no webhook url set, this reporter is disabled`\n );\n return;\n }\n\n const key = this.throttleKey();\n const throttle = ReportThrottle.for(this.core);\n const suppressed = key === null ? 0 : throttle.take(key);\n if (suppressed === null) {\n this.logger.debug(`counted a repeat into the next ${paint.sky.bold(this.constructor.name)} card`);\n return;\n }\n\n try {\n const { username, avatarUrl, components, files } = await this.report(suppressed);\n await WebhookLog.senderFor(url).send({\n flags: sendFlags({ ephemeral: false }),\n username: username ?? this.constructor.name,\n avatarUrl,\n components,\n files\n });\n } catch (error) {\n if (key !== null) throttle.restore(key, suppressed);\n this.logger.error('Webhook report failed', error);\n }\n }\n\n /** @internal */\n static senderFor(url: string): WebhookSender {\n let sender = WebhookLog.senders.get(url);\n if (!sender) {\n sender = new WebhookSender(url);\n WebhookLog.senders.set(url, sender);\n }\n return sender;\n }\n\n /** @internal */\n static envKeyOf(ctor: NewableFunction): string {\n const envKey = Reflect.getMetadata(WebhookUrlMetadataKey, ctor) as string | undefined;\n if (!envKey) throw new SeedcordError(SeedcordErrorCode.DecoratorWebhookUrlMissing, [ctor.name]);\n return envKey;\n }\n\n /** @internal Returns null when the env var is unset, throws when set but malformed. */\n static urlOf(envKey: string): string | null {\n if (!Envapter.has(envKey)) return null;\n const raw = Envapter.getRequired(envKey, Converters.String).trim();\n if (raw === '') return null;\n if (!isDiscordWebhookUrl(raw)) throw new SeedcordTypeError(SeedcordErrorCode.ConfigWebhookUrlInvalid, [envKey]);\n return raw;\n }\n}\n","// the default reporters are decorated at Bus module load, before any entry's own polyfill import runs\nimport 'reflect-metadata';\n\nimport { SubscribeMetadataKey } from '#src/metadataKeys';\n\nimport type { CoreBase } from '#interfaces/CoreBase';\nimport type { Subscriber } from '../Subscriber';\nimport type { SubscriptionKey } from '../types/Subscriptions';\nimport type { EventFrequency } from '@seedcord/types';\nimport type { Constructor } from 'type-fest';\n\n/**\n * Options accepted by the `@Subscribe` decorator.\n */\nexport interface SubscribeOptions {\n /**\n * Frequency: `'once'` or `'on'`.\n *\n * @defaultValue `'on'`\n */\n readonly frequency?: EventFrequency | undefined;\n}\n\n/** @internal */\nexport interface SubscribeMetadataEntry {\n readonly subscriber: SubscriptionKey;\n readonly frequency?: EventFrequency | undefined;\n}\n\n/**\n * Registers a subscriber handler class with a specific subscriber event.\n *\n * Associates the decorated class with a subscriber event type for automatic\n * registration and execution when the subscriber is emitted.\n *\n * @typeParam TSubscriber - The subscriber event name to register for\n * @param subscriber - The subscriber event name to register for\n * @param options - Options to configure the subscriber handler registration.\n * @decorator\n * @example\n * ```typescript\n * \\@Subscribe('userJoin')\n * class WelcomeHandler extends Subscriber<'userJoin'> {\n * async execute() {\n * }\n * }\n * ```\n * @example\n * ```ts\n * \\@Subscribe('userJoin', { frequency: 'once' })\n * // or\n * \\@Subscribe('userJoin')\n * ```\n */\nexport function Subscribe<TSubscriber extends SubscriptionKey>(subscriber: TSubscriber, options?: SubscribeOptions) {\n return function <HandlerCtor extends Constructor<Subscriber<TSubscriber, CoreBase>>>(\n constructor: HandlerCtor\n ): void {\n const meta: SubscribeMetadataEntry = {\n subscriber,\n frequency: options?.frequency\n };\n\n Reflect.defineMetadata(SubscribeMetadataKey, meta, constructor);\n };\n}\n","import 'reflect-metadata';\n\nimport { WebhookUrlMetadataKey } from '#src/metadataKeys';\n\nimport type { CoreBase } from '#interfaces/CoreBase';\nimport type { WebhookLog } from '../bases/WebhookLog';\nimport type { SubscriptionKey } from '../types/Subscriptions';\nimport type { Constructor } from 'type-fest';\n\n/**\n * Declares the environment variable a {@link WebhookLog} reporter reads its webhook url from.\n *\n * An unset variable disables the reporter with a boot warning. A set but malformed value throws\n * at boot.\n *\n * @param envKey - The environment variable name holding the webhook url\n * @decorator\n * @example\n * ```ts\n * \\@Subscribe('memberBanned')\n * \\@WebhookUrl('BAN_LOG_WEBHOOK_URL')\n * class BanLog extends WebhookLog<'memberBanned'> {\n * report() {\n * return { components: [new BanCard(this.data).component] };\n * }\n * }\n * ```\n */\nexport function WebhookUrl(envKey: string) {\n return function <HandlerCtor extends Constructor<WebhookLog<SubscriptionKey, CoreBase>>>(\n constructor: HandlerCtor\n ): void {\n Reflect.defineMetadata(WebhookUrlMetadataKey, envKey, constructor);\n };\n}\n","import { stripAnsi } from '@seedcord/utils';\n\nimport { BuilderComponent } from '#components/Component';\n\nimport { breakBackticks, jsonAttachment, WebhookSeparator } from '../bases/webhookHelpers';\nimport { WebhookLog } from '../bases/WebhookLog';\nimport { Subscribe } from '../decorators/Subscribe';\nimport { WebhookUrl } from '../decorators/WebhookUrl';\n\nimport type { CoreBase } from '#interfaces/CoreBase';\nimport type { Notice } from '#stops/Notice';\nimport type { WebhookReport } from '../bases/WebhookLog';\nimport type { AllSubscriptions, FaultSource } from '../types/Subscriptions';\n\n// no HANDLED_EXCEPTION_WEBHOOK_URL disables the reporter with a boot warning\n@Subscribe('handledException')\n@WebhookUrl('HANDLED_EXCEPTION_WEBHOOK_URL')\nexport class HandledException extends WebhookLog<'handledException', CoreBase> {\n protected override throttleKey(): string {\n return `handledException:${this.data.origin}:${this.data.denial.name}`;\n }\n\n report(suppressed: number): WebhookReport {\n return {\n username: 'Handled Exception',\n components: [new HandledExceptionContainer(this.data, suppressed).component],\n files: [jsonAttachment('source.json', 'The raw source the fault came from', this.data.source.raw)]\n };\n }\n}\n\nclass HandledExceptionContainer extends BuilderComponent<'container'> {\n constructor(data: AllSubscriptions['handledException'], suppressed: number) {\n super('container');\n\n const { denial, uuid, source } = data;\n\n this.instance\n .addTextDisplayComponents((text) => text.setContent(faultSummary(denial, source)))\n .addSeparatorComponents(new WebhookSeparator().component)\n .addTextDisplayComponents((text) =>\n text.setContent(`### UUID \\`${uuid}\\`\\n\\`\\`\\`${breakBackticks(causeStack(denial))}\\`\\`\\``)\n )\n .addSeparatorComponents(new WebhookSeparator().component)\n .addTextDisplayComponents((text) => text.setContent('### Source'))\n .addFileComponents((file) => file.setURL('attachment://source.json'));\n\n this.addSuppressedCount(suppressed);\n }\n\n private addSuppressedCount(suppressed: number): void {\n if (suppressed === 0) return;\n\n this.instance.addTextDisplayComponents((text) =>\n text.setContent(`-# ${suppressed} more of these since the last report`)\n );\n }\n}\n\nfunction faultSummary(denial: Notice, source: FaultSource): string {\n // name and message carry chalk codes\n const head = stripAnsi(`### A handled fault was reported: \\`${denial.name}\\`\\n**Message:** ${denial.message}\\n`);\n if (source.kind === 'event') {\n return (\n `${head}**Event:** ${source.eventName}\\n` +\n `**Handler:** ${source.handler}\\n` +\n `**User ID:** \\`${source.userId ?? 'n/a'}\\`\\n` +\n `**Guild ID:** \\`${source.guildId ?? 'n/a'}\\`\\n` +\n `**Channel ID:** \\`${source.channelId ?? 'n/a'}\\``\n );\n }\n return (\n `${head}**Kind:** ${source.interactionKind}\\n` +\n `**Command:** ${source.command ?? 'n/a'}\\n` +\n `**CustomId:** ${source.customId ?? 'n/a'}\\n` +\n `**User ID:** \\`${source.userId}\\`\\n` +\n `**Guild ID:** \\`${source.guildId ?? 'n/a'}\\`\\n` +\n `**Channel ID:** \\`${source.channelId ?? 'n/a'}\\`\\n` +\n `**Interaction ID:** \\`${source.interactionId}\\``\n );\n}\n\n// building the report must not throw on a bigint or a circular cause\nfunction causeStack(denial: Notice): string {\n const { cause } = denial;\n if (Error.isError(cause)) return stripAnsi(cause.stack ?? cause.message);\n if (typeof cause === 'string') return stripAnsi(cause);\n if (typeof cause === 'bigint' || typeof cause === 'symbol' || typeof cause === 'function') return cause.toString();\n if (cause !== undefined) {\n try {\n return JSON.stringify(cause);\n } catch {\n return '[unserializable cause]';\n }\n }\n return stripAnsi(denial.stack ?? 'No cause recorded.');\n}\n","import { DiscordAPIError } from '@discordjs/rest';\nimport { timestampFromSnowflake } from '@seedcord/utils';\n\nimport { BuilderComponent } from '#components/Component';\n\nimport { errorReport, jsonAttachment, WebhookSeparator } from '../bases/webhookHelpers';\nimport { WebhookLog } from '../bases/WebhookLog';\nimport { Subscribe } from '../decorators/Subscribe';\nimport { WebhookUrl } from '../decorators/WebhookUrl';\n\nimport type { CoreBase } from '#interfaces/CoreBase';\nimport type { WebhookReport } from '../bases/WebhookLog';\nimport type { AllSubscriptions } from '../types/Subscriptions';\n\n// no UNKNOWN_EXCEPTION_WEBHOOK_URL disables the reporter with a boot warning\n@Subscribe('unknownException')\n@WebhookUrl('UNKNOWN_EXCEPTION_WEBHOOK_URL')\nexport class UnknownException extends WebhookLog<'unknownException', CoreBase> {\n protected override throttleKey(): string {\n return `unknownException:${this.data.origin}:${nameOf(this.data.error)}`;\n }\n\n report(suppressed: number): WebhookReport {\n const { metadata } = this.data;\n return {\n username: 'Unknown Exception',\n components: [new UnhandledErrorContainer(this.data, suppressed).component],\n files: metadata\n ? [jsonAttachment('metadata.json', 'Metadata associated with the error', metadata)]\n : undefined\n };\n }\n}\n\n// discord.js puts the api code in the name, as `DiscordAPIError[10062]`\nfunction nameOf(error: Error): string {\n return error.name === 'Error' ? error.constructor.name : error.name;\n}\n\nclass UnhandledErrorContainer extends BuilderComponent<'container'> {\n constructor(data: AllSubscriptions['unknownException'], suppressed: number) {\n super('container');\n\n const { uuid, error, guild, user, metadata } = data;\n\n this.instance\n .addTextDisplayComponents((text) =>\n text.setContent(\n `### An unknown exception was thrown\\n` +\n `**Guild ID:** \\`${guild?.id ?? 'Not used in a guild'}\\`\\n` +\n `**Guild Name:** ${guild?.name ?? 'Not used in a guild'}\\n` +\n `**User ID:** \\`${user?.id ?? 'Missing user info'}\\`\\n` +\n `**Username:** ${user?.username ?? 'Missing user info'}\\n`\n )\n )\n .addSeparatorComponents(new WebhookSeparator().component)\n .addTextDisplayComponents((text) =>\n text.setContent(`### UUID \\`${uuid}\\`\\n\\`\\`\\`${errorReport(error)}\\`\\`\\``)\n );\n\n this.addTimestampsIfAvailable(error);\n this.addMetadataIfAvailable(metadata);\n this.addSuppressedCount(suppressed);\n }\n\n private addSuppressedCount(suppressed: number): void {\n if (suppressed === 0) return;\n\n this.instance.addTextDisplayComponents((text) =>\n text.setContent(`-# ${suppressed} more of these since the last report`)\n );\n }\n\n private addTimestampsIfAvailable(error: Error): void {\n if (!(error instanceof DiscordAPIError)) return;\n\n const now = Date.now();\n\n const snowflake = error.url.match(/\\/interactions\\/(\\d+)\\//)?.[1];\n if (!snowflake) return undefined;\n\n const interactionTs = timestampFromSnowflake(snowflake);\n\n const diff = now - interactionTs;\n const seconds = Math.floor(diff / 1000);\n const millis = diff % 1000;\n\n this.instance\n .addSeparatorComponents(new WebhookSeparator().component)\n .addTextDisplayComponents((text) =>\n text.setContent(\n `### Timestamps\\n` +\n `- **\\`Interaction sent\\` :** ${new Date(interactionTs).toISOString()} (${interactionTs})\\n` +\n `- **\\`Error logged \\` :** ${new Date(now).toISOString()} (${now})\\n` +\n `- **\\`Offset \\` :** ${seconds}s ${millis}ms`\n )\n );\n }\n\n private addMetadataIfAvailable(metadata: unknown): void {\n if (!metadata) return;\n\n this.instance\n .addSeparatorComponents(new WebhookSeparator().component)\n .addTextDisplayComponents((text) => text.setContent('### Metadata'))\n .addFileComponents((file) => file.setURL('attachment://metadata.json'));\n }\n}\n","// the framework's own Bus calls. a bot author reaches the Bus through publish() and a Subscriber subclass\nexport const RegisterDefaults = Symbol('seedcord:bus:register-defaults');\nexport const VerifyWebhooks = Symbol('seedcord:bus:verify-webhooks');\nexport const RegisteredCount = Symbol('seedcord:bus:registered-count');\nexport const RegisterSubscriber = Symbol('seedcord:bus:register');\nexport const UnregisterSubscriber = Symbol('seedcord:bus:unregister');\n","import { SeedcordErrorCode, paint } from '@seedcord/errors';\nimport { SeedcordError, SeedcordTypeError } from '@seedcord/errors/internal';\nimport { TypedEventEmitter } from '@seedcord/event-emitter';\nimport { Logger } from '@seedcord/logger';\n\nimport { SubscribeMetadataKey } from '#src/metadataKeys';\n\nimport { WebhookLog } from './bases/WebhookLog';\nimport { HandledException } from './default/HandledException';\nimport { UnknownException } from './default/UnknownException';\nimport { PublishDefault } from './publishDefault';\nimport { RegisterDefaults, RegisteredCount, RegisterSubscriber, UnregisterSubscriber, VerifyWebhooks } from './slots';\n\nimport type { CoreBase } from '#interfaces/CoreBase';\nimport type { SubscribeMetadataEntry } from './decorators/Subscribe';\nimport type { Subscriber } from './Subscriber';\nimport type {\n AllSubscriptions,\n DefaultSubscriptions,\n PublishableKey,\n SubscriptionKey,\n SubscriptionTuples\n} from './types/Subscriptions';\nimport type { EventFrequency, TypedConstructor } from '@seedcord/types';\n\n/**\n * Both parameters are `never` because construct-signature parameters check contravariantly, and a\n * concrete subscriber narrows each to its own payload and transport `Core`. One cast at dispatch\n * restores both.\n *\n * @internal\n */\nexport type StoredSubscriberCtor = new (data: never, core: never) => Subscriber<SubscriptionKey, CoreBase>;\n\n/**\n * `resolve` is lazy so an edge host registers from a manifest row without importing the module\n * until that key first publishes.\n *\n * @internal\n */\nexport interface SubscriberRegistration {\n readonly keys: readonly SubscriptionKey[];\n readonly frequency: EventFrequency;\n readonly resolve: () => StoredSubscriberCtor | Promise<StoredSubscriberCtor>;\n /** A server-host registration carries it from the start. A lazy one fills it on first resolve. */\n ctor?: StoredSubscriberCtor | undefined;\n}\n\nconst loggerSlot = Symbol('seedcord:bus:logger');\n\n/**\n * Registers and dispatches subscribers. Subscribers run on a programmatic or framework publish.\n * Accessed via `core.bus`. Do not construct it directly.\n */\nexport class Bus extends TypedEventEmitter<SubscriptionTuples> {\n private readonly logger = new Logger('Subscribers', { channel: 'subscribers' });\n\n /** @internal */\n readonly [loggerSlot]: Logger = this.logger;\n\n private readonly subscribersMap = new Map<SubscriptionKey, SubscriberRegistration[]>();\n private readonly executedOnce = new Set<SubscriberRegistration>();\n // url -> env keys, filled as reporters register and read once by verifyWebhooks\n private readonly webhookProbes = new Map<string, string[]>();\n\n constructor(protected core: CoreBase) {\n super();\n }\n\n /** @internal keep this inside start(). a bad webhook url thrown outside it never resets the singleton guard */\n public [RegisterDefaults](): void {\n this[RegisterSubscriber](registrationFor(UnknownException));\n this[RegisterSubscriber](registrationFor(HandledException));\n }\n\n /** @internal */\n public [RegisterSubscriber](registration: SubscriberRegistration): void {\n // a url-less reporter never registers. a publish on its key reaches nothing\n if (registration.ctor && !this.probeWebhook(registration.ctor)) return;\n\n for (const key of registration.keys) {\n let registrations = this.subscribersMap.get(key);\n if (!registrations) {\n registrations = [];\n this.subscribersMap.set(key, registrations);\n }\n registrations.push(registration);\n }\n }\n\n /** @internal */\n public [UnregisterSubscriber](ctor: StoredSubscriberCtor, keys?: readonly SubscriptionKey[]): void {\n for (const key of keys ?? [...this.subscribersMap.keys()]) {\n const registrations = this.subscribersMap.get(key);\n if (!registrations) continue;\n const index = registrations.findIndex((entry) => entry.ctor === ctor);\n if (index !== -1) registrations.splice(index, 1);\n }\n // the once state hangs off the registration identity, which is why a restored subscriber runs again\n }\n\n /** @internal */\n public get [RegisteredCount](): number {\n return [...this.subscribersMap.values()].reduce((total, entries) => total + entries.length, 0);\n }\n\n /** @internal a lazy edge registration never probes. every server-host reporter registers eagerly */\n public async [VerifyWebhooks](): Promise<void> {\n const missing: string[] = [];\n await Promise.all(\n [...this.webhookProbes].map(async ([url, envKeys]) => {\n const result = await WebhookLog.senderFor(url).verify();\n if (result === 'missing') missing.push(...envKeys);\n else if (result === 'unreachable')\n this.logger.warn(\n `could not verify webhook at ${envKeys.map((key) => paint.amber.bold(key)).join(', ')}`\n );\n })\n );\n // collected first so the first boot names every missing webhook at once\n if (missing.length > 0) throw new SeedcordError(SeedcordErrorCode.ConfigWebhookNotFound, [missing.join(', ')]);\n }\n\n // true means the class registers\n private probeWebhook(ctor: StoredSubscriberCtor): boolean {\n if (!(ctor.prototype instanceof WebhookLog)) return true;\n\n const envKey = WebhookLog.envKeyOf(ctor);\n const url = WebhookLog.urlOf(envKey);\n if (url === null) {\n this.logger.warn(`${paint.sky.bold(ctor.name)} disabled, ${paint.amber.bold(envKey)} is not set`);\n return false;\n }\n const envKeys = this.webhookProbes.get(url) ?? [];\n if (!envKeys.includes(envKey)) envKeys.push(envKey);\n this.webhookProbes.set(url, envKeys);\n return true;\n }\n\n /**\n * Publishes an event to its subscribers and native listeners.\n *\n * Fire-and-forget. Subscriber handlers run asynchronously and this returns before they finish. A\n * caller cannot assume a side effect has completed. Errors thrown by a subscriber or by an `on()`\n * listener are caught and logged, never surfaced here. One throwing listener does not stop the\n * others. Subscribers on one key run concurrently and carry no ordering guarantee. A `'once'`\n * subscriber is marked as executed when it starts, even if it throws. It never runs twice.\n *\n * The framework's own keys are excluded. Subscribe to those and listen with `on`. The framework\n * is their only publisher.\n *\n * @param event - The subscription key to publish\n * @param data - Payload passed to each subscriber\n * @returns Whether the underlying emitter had native listeners for the event\n */\n public publish<KeyOfSubscribers extends PublishableKey>(\n event: KeyOfSubscribers,\n data: AllSubscriptions[KeyOfSubscribers]\n ): boolean {\n return this.dispatchKey(event, data);\n }\n\n /**\n * @deprecated Always throws. Call {@link Bus.publish} to reach subscribers and listeners together.\n * @throws A **SeedcordTypeError** with `CoreBusEmitUnavailable`. `emit` reaches only the `on()` listeners.\n */\n public override emit<KeyOfSubscribers extends SubscriptionKey>(\n event: KeyOfSubscribers,\n ..._args: SubscriptionTuples[KeyOfSubscribers]\n ): never {\n throw new SeedcordTypeError(SeedcordErrorCode.CoreBusEmitUnavailable, [String(event)]);\n }\n\n /** @internal the framework's own publish path, keyed by a symbol no public entry exports */\n public [PublishDefault]<KeyOfSubscribers extends keyof DefaultSubscriptions>(\n event: KeyOfSubscribers,\n data: AllSubscriptions[KeyOfSubscribers]\n ): boolean {\n return this.dispatchKey(event, data);\n }\n\n private dispatchKey<KeyOfSubscribers extends SubscriptionKey>(\n event: KeyOfSubscribers,\n data: AllSubscriptions[KeyOfSubscribers]\n ): boolean {\n void this.processSubscriber(event, data);\n // justified: a generic key can't reduce SubscriptionTuples[K], but data is this event's payload by the signature\n return this.emitSafe(event, ...([data] as SubscriptionTuples[KeyOfSubscribers]));\n }\n\n protected override onListenerError(error: unknown, event: string | symbol): void {\n this.logger.error(`listener for ${String(event)} threw`, error);\n }\n\n private async processSubscriber<KeyOfSubscribers extends SubscriptionKey>(\n subscriberName: KeyOfSubscribers,\n data: AllSubscriptions[KeyOfSubscribers]\n ): Promise<void> {\n const registrations = this.subscribersMap.get(subscriberName);\n if (!registrations) return;\n\n const pending = registrations.reduce<Promise<void>[]>((acc, entry) => {\n if (entry.frequency === 'once') {\n if (this.executedOnce.has(entry)) return acc;\n // mark before awaiting, so a re-entrant publish can't run a 'once' subscriber twice mid-flight\n this.executedOnce.add(entry);\n }\n acc.push(this.runSubscriber(entry, subscriberName, data));\n return acc;\n }, []);\n\n await Promise.allSettled(pending);\n }\n\n private async runSubscriber<KeyOfSubscribers extends SubscriptionKey>(\n entry: SubscriberRegistration,\n subscriberName: KeyOfSubscribers,\n data: AllSubscriptions[KeyOfSubscribers]\n ): Promise<void> {\n // named outside the try so the catch can say which subscriber threw. several run per key\n let name = '<unresolved>';\n try {\n entry.ctor ??= await entry.resolve();\n // the registration keys each subscriber to its subscriptions. data matches this ctor's payload arm.\n const Ctor = entry.ctor as TypedConstructor<typeof Subscriber>;\n name = Ctor.name;\n await new Ctor(data, this.core).execute();\n } catch (err) {\n this.logger.error(\n `Error in subscriber ${paint.mute(String(subscriberName))} handler ${paint.sky.bold(name)}:`,\n err\n );\n }\n }\n}\n\n// every caller has already gated on the Subscribe metadata being there\n/** @internal */\nexport function registrationFor(ctor: StoredSubscriberCtor): SubscriberRegistration {\n const meta = Reflect.getMetadata(SubscribeMetadataKey, ctor) as SubscribeMetadataEntry;\n return { keys: [meta.subscriber], frequency: meta.frequency ?? 'on', resolve: () => ctor, ctor };\n}\n\n/** @internal */\nexport function busLoggerOf(bus: Bus): Logger {\n return bus[loggerSlot];\n}\n"],"mappings":"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"}
|
|
@@ -57,15 +57,16 @@ function assertNodeVersion(range, running) {
|
|
|
57
57
|
const logger = new Logger("Faults", { channel: "errors" });
|
|
58
58
|
/** @internal */
|
|
59
59
|
function registerProcessErrors(core, shutdown) {
|
|
60
|
-
function report(caught,
|
|
60
|
+
function report(caught, origin) {
|
|
61
61
|
const error = asError(caught);
|
|
62
62
|
const uuid = crypto.randomUUID();
|
|
63
63
|
if (core.config.errors?.errorStack ?? false) logger.error(uuid, error);
|
|
64
64
|
else logger.error(`${uuid} | ${error.message}`);
|
|
65
65
|
core.bus[PublishDefault]("unknownException", {
|
|
66
66
|
uuid,
|
|
67
|
+
dispatchId: null,
|
|
67
68
|
error,
|
|
68
|
-
|
|
69
|
+
origin
|
|
69
70
|
});
|
|
70
71
|
}
|
|
71
72
|
const onRejection = (reason) => {
|
|
@@ -104,6 +105,7 @@ var Pluggable = class Pluggable {
|
|
|
104
105
|
isInitialized = false;
|
|
105
106
|
startFailed = false;
|
|
106
107
|
plugins = [];
|
|
108
|
+
groups = /* @__PURE__ */ new Map();
|
|
107
109
|
pluginLogger = new Logger("Plugins", { channel: "plugins" });
|
|
108
110
|
attachments = [];
|
|
109
111
|
completedInits = /* @__PURE__ */ new Set();
|
|
@@ -173,13 +175,16 @@ var Pluggable = class Pluggable {
|
|
|
173
175
|
* Attaches a plugin under `key`. Read the instance back as `core[key]`. `seedcord codegen`
|
|
174
176
|
* writes the `Core` augmentation that types it there.
|
|
175
177
|
*
|
|
178
|
+
* Put one dot in the key to nest the plugin under a group, so `'services.users'` reads back as
|
|
179
|
+
* `core.services.users`. Each name holds one plugin or one group.
|
|
180
|
+
*
|
|
176
181
|
* Startup runs each plugin's `init()` in attach order within its phase.
|
|
177
182
|
*
|
|
178
183
|
* Attaching a plugin whose `transport` or `runtime` differs from this host fails to compile.
|
|
179
184
|
* Your constructor takes `CoreBase` as its first parameter (you don't need to pass it though).
|
|
180
185
|
* A narrower one fails to compile here.
|
|
181
186
|
*
|
|
182
|
-
* @param key - Also the channel the plugin logs on. Reserved channel names throw.
|
|
187
|
+
* @param key - Also the channel the plugin logs on, dots included. Reserved channel names throw.
|
|
183
188
|
* @param args - Whatever your constructor takes after the host.
|
|
184
189
|
* @throws A **SeedcordError** if you attach after the bot has started. A taken or reserved key throws too.
|
|
185
190
|
* @example
|
|
@@ -189,8 +194,13 @@ var Pluggable = class Pluggable {
|
|
|
189
194
|
*/
|
|
190
195
|
attach(key, Plugin, ...args) {
|
|
191
196
|
if (this.isInitialized) throw new SeedcordError(SeedcordErrorCode.CorePluginAfterInit);
|
|
192
|
-
|
|
193
|
-
|
|
197
|
+
const dot = key.indexOf(".");
|
|
198
|
+
const head = dot === -1 ? key : key.slice(0, dot);
|
|
199
|
+
if (RESERVED_KEYS.has(head)) throw new SeedcordTypeError(SeedcordErrorCode.CorePluginReservedChannel, [head]);
|
|
200
|
+
const leaf = dot === -1 ? void 0 : key.slice(dot + 1);
|
|
201
|
+
if (head === "" || leaf === "") throw new SeedcordTypeError(SeedcordErrorCode.CorePluginKeyMalformed, [key, "has an empty part."]);
|
|
202
|
+
if (leaf?.includes(".")) throw new SeedcordTypeError(SeedcordErrorCode.CorePluginKeyMalformed, [key, "has more than one dot."]);
|
|
203
|
+
this.assertFree(head, leaf, key);
|
|
194
204
|
const instance = new Plugin(this, ...args);
|
|
195
205
|
pluginLoggerOf(instance).setChannel(key);
|
|
196
206
|
this.plugins.push(instance);
|
|
@@ -198,7 +208,30 @@ var Pluggable = class Pluggable {
|
|
|
198
208
|
key,
|
|
199
209
|
instance
|
|
200
210
|
});
|
|
201
|
-
return Object.assign(this, { [key]: instance });
|
|
211
|
+
if (leaf === void 0) return Object.assign(this, { [key]: instance });
|
|
212
|
+
this.groupFor(head)[leaf] = instance;
|
|
213
|
+
return this;
|
|
214
|
+
}
|
|
215
|
+
assertFree(head, leaf, key) {
|
|
216
|
+
if (leaf === void 0) {
|
|
217
|
+
if (this.groups.has(key)) throw new SeedcordError(SeedcordErrorCode.CorePluginKeyHoldsGroup, [key]);
|
|
218
|
+
if (key in this) throw new SeedcordError(SeedcordErrorCode.CorePluginKeyExists, [key]);
|
|
219
|
+
return;
|
|
220
|
+
}
|
|
221
|
+
const group = this.groups.get(head);
|
|
222
|
+
if (!group) {
|
|
223
|
+
if (head in this) throw new SeedcordError(SeedcordErrorCode.CorePluginGroupTaken, [head, key]);
|
|
224
|
+
return;
|
|
225
|
+
}
|
|
226
|
+
if (Object.hasOwn(group, leaf)) throw new SeedcordError(SeedcordErrorCode.CorePluginKeyExists, [key]);
|
|
227
|
+
}
|
|
228
|
+
groupFor(head) {
|
|
229
|
+
const existing = this.groups.get(head);
|
|
230
|
+
if (existing) return existing;
|
|
231
|
+
const group = Object.create(null);
|
|
232
|
+
this.groups.set(head, group);
|
|
233
|
+
Object.assign(this, { [head]: group });
|
|
234
|
+
return group;
|
|
202
235
|
}
|
|
203
236
|
registerPluginTasks() {
|
|
204
237
|
if (this.pluginTasksRegistered) return;
|
|
@@ -300,4 +333,4 @@ function shutdownOf(host) {
|
|
|
300
333
|
|
|
301
334
|
//#endregion
|
|
302
335
|
export { withTimeout as a, settleWithin as i, shutdownOf as n, assertNodeVersion as r, Pluggable as t };
|
|
303
|
-
//# sourceMappingURL=Pluggable-
|
|
336
|
+
//# sourceMappingURL=Pluggable-BShEwGw9.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"Pluggable-BShEwGw9.mjs","names":[],"sources":["../src/node/Lifecycle/withTimeout.ts","../src/node/assertNodeVersion.ts","../src/node/processErrors.ts","../src/node/Pluggable.ts"],"sourcesContent":["import { SeedcordErrorCode } from '@seedcord/errors';\nimport { SeedcordError } from '@seedcord/errors/internal';\n\n// calling start() in here turns its synchronous throw into a rejection\nasync function raceTimer(\n start: () => Promise<unknown>,\n timeoutMs: number,\n onTimeout: (resolve: () => void, reject: (error: unknown) => void) => void\n): Promise<void> {\n let timer: NodeJS.Timeout | undefined;\n try {\n await Promise.race([\n start(),\n new Promise<void>((resolve, reject) => {\n // node's timer can go off before performance.now() reaches timeoutMs\n const expiresAt = performance.now() + timeoutMs;\n const tick = (): void => {\n const remaining = expiresAt - performance.now();\n if (remaining > 0) {\n timer = setTimeout(tick, remaining);\n return;\n }\n onTimeout(resolve, reject);\n };\n timer = setTimeout(tick, timeoutMs);\n })\n ]);\n } finally {\n if (timer) clearTimeout(timer);\n }\n}\n\n// rejects with a LifecycleTaskTimeout when run outlasts the bound\nexport function withTimeout(name: string, run: () => Promise<void>, timeoutMs: number): Promise<void> {\n return raceTimer(run, timeoutMs, (_, reject) => {\n reject(new SeedcordError(SeedcordErrorCode.LifecycleTaskTimeout, [name, timeoutMs]));\n });\n}\n\n// the caller does not get work's rejection\nexport function settleWithin(work: Promise<unknown>, timeoutMs: number): Promise<void> {\n return raceTimer(\n () =>\n work.then(\n () => undefined,\n () => undefined\n ),\n timeoutMs,\n (resolve) => resolve()\n );\n}\n","import { SeedcordErrorCode } from '@seedcord/errors';\nimport { SeedcordError } from '@seedcord/errors/internal';\n\n// every seedcord package declares engines.node as `>=major` or `>=major.minor`\nconst RANGE = new RegExp(String.raw`^>=(\\d+)(?:\\.(\\d+))?`);\nconst VERSION = /^v?(\\d+)\\.(\\d+)/;\nconst SPACES = /\\s/g;\n\n/** @internal */\nexport function assertNodeVersion(range: string, running: string): void {\n const required = RANGE.exec(range.replaceAll(SPACES, ''));\n const current = VERSION.exec(running);\n if (!required || !current) return;\n\n const [, requiredMajor, requiredMinor = '0'] = required;\n const [, currentMajor, currentMinor] = current;\n\n const meetsRange =\n Number(currentMajor) > Number(requiredMajor) ||\n (Number(currentMajor) === Number(requiredMajor) && Number(currentMinor) >= Number(requiredMinor));\n if (meetsRange) return;\n\n throw new SeedcordError(SeedcordErrorCode.UnsupportedNodeVersion, [range, running]);\n}\n","import crypto from 'node:crypto';\n\nimport { Logger } from '@seedcord/logger';\n\nimport { asError } from '#stops/asError';\nimport { PublishDefault } from '#subscribers/publishDefault';\n\nimport type { CoreBase } from '#interfaces/CoreBase';\nimport type { CoordinatedShutdown } from '#node/Lifecycle/CoordinatedShutdown';\n\nconst logger = new Logger('Faults', { channel: 'errors' });\n\n/** @internal */\nexport function registerProcessErrors(core: CoreBase, shutdown: CoordinatedShutdown): () => void {\n function report(caught: unknown, origin: string): void {\n const error = asError(caught);\n const uuid = crypto.randomUUID();\n\n if (core.config.errors?.errorStack ?? false) logger.error(uuid, error);\n else logger.error(`${uuid} | ${error.message}`);\n\n core.bus[PublishDefault]('unknownException', { uuid, dispatchId: null, error, origin });\n }\n\n const onRejection = (reason: unknown): void => {\n report(reason, 'process:unhandledRejection');\n };\n\n const onException = (caught: unknown): void => {\n report(caught, 'process:uncaughtException');\n // node does not exit on its own once a listener is registered\n void shutdown.run(1);\n };\n\n process.on('unhandledRejection', onRejection);\n process.on('uncaughtException', onException);\n\n return () => {\n process.off('unhandledRejection', onRejection);\n process.off('uncaughtException', onException);\n };\n}\n","import { SeedcordErrorCode, isSeedcordError } from '@seedcord/errors';\nimport { SeedcordAggregateError, SeedcordError, SeedcordTypeError } from '@seedcord/errors/internal';\nimport { FRAMEWORK_CHANNELS, Logger } from '@seedcord/logger';\nimport { HostPluginKeys, HostShutdown, HostStartup } from '@seedcord/types/internal';\n\nimport { assertNodeVersion } from '#node/assertNodeVersion';\nimport { StartupPhase } from '#src/lifecycle/phases';\nimport { pluginLoggerOf, resolvedLifecycleSpecOf } from '#src/plugin/Plugin';\n\nimport { withTimeout } from './Lifecycle/withTimeout';\nimport { registerProcessErrors } from './processErrors';\n\nimport type { CoreBase } from '#interfaces/CoreBase';\nimport type { CoordinatedShutdown } from '#node/Lifecycle/CoordinatedShutdown';\nimport type { CoordinatedStartup } from '#node/Lifecycle/CoordinatedStartup';\nimport type { ShutdownPhase } from '#src/lifecycle/phases';\nimport type { Runtime, RuntimeAssert, Transport, TransportAssert } from '#src/plugin/options';\nimport type {\n Attached,\n AttachKeyAssert,\n CoreParamAssert,\n PluginArgs,\n PluginCtor,\n PluginLike\n} from '#src/plugin/Plugin';\nimport type { Bus } from '#subscribers/Bus';\nimport type { REST } from '@discordjs/rest';\nimport type { Config, IRateLimiter } from '@seedcord/types';\n\ninterface Attachment {\n readonly key: string;\n readonly instance: PluginLike;\n}\n\nconst RESERVED_KEYS: ReadonlySet<string> = new Set(FRAMEWORK_CHANNELS);\n\n/**\n * Base class for a plugin host, a transport `Seedcord` class.\n *\n * You attach plugins while configuring the bot. Within one startup phase, their `init()` calls run\n * one after another in attach order.\n */\n// BotRt has no default because RuntimeAssert rejects every plugin once 'edge' is in the union\nexport abstract class Pluggable<BotT extends Transport, BotRt extends Runtime> implements CoreBase {\n public abstract readonly config: Config;\n public abstract readonly rest: REST;\n public abstract readonly applicationId: string;\n public abstract readonly rateLimiter: IRateLimiter;\n public abstract readonly bus: Bus;\n\n /** @internal */\n readonly [HostShutdown]: CoordinatedShutdown;\n /** @internal */\n readonly [HostStartup]: CoordinatedStartup;\n\n /** Add a task that runs while the bot shuts down. */\n public readonly shutdown: Pick<CoordinatedShutdown, 'addTask'>;\n\n /** Add a task that runs while the bot starts. */\n public readonly startup: Pick<CoordinatedStartup, 'addTask'>;\n\n protected isInitialized = false;\n protected startFailed = false;\n protected readonly plugins: PluginLike[] = [];\n private readonly groups = new Map<string, Record<string, PluginLike>>();\n\n private readonly pluginLogger = new Logger('Plugins', { channel: 'plugins' });\n\n private readonly attachments: Attachment[] = [];\n private readonly completedInits = new Set<Attachment>();\n private readonly disposePhases = new Set<ShutdownPhase>();\n private pluginTasksRegistered = false;\n private initPromise?: Promise<this> | undefined;\n\n private static isInstantiated = false;\n private static liveHost?: object | undefined;\n private static liveShutdown?: CoordinatedShutdown | undefined;\n private static liveProcessErrors?: (() => void) | undefined;\n\n constructor(shutdown: CoordinatedShutdown, startup: CoordinatedStartup) {\n // a `sideEffects: false` build would drop the same call in the node entry\n assertNodeVersion(process.env.PACKAGE_NODE_RANGE ?? '', process.version);\n\n if (Pluggable.isInstantiated) {\n // signal handlers from the caller's shutdown stay on the process unless dropped here\n shutdown.removeSignalHandlers();\n throw new SeedcordError(SeedcordErrorCode.CoreSingletonViolation);\n }\n\n Pluggable.isInstantiated = true;\n Pluggable.liveHost = this;\n Pluggable.liveShutdown = shutdown;\n this[HostShutdown] = shutdown;\n this[HostStartup] = startup;\n // a getter returning the slot would expose run() and the signal handlers too\n this.shutdown = { addTask: shutdown.addTask.bind(shutdown) };\n this.startup = { addTask: startup.addTask.bind(startup) };\n }\n\n /** @internal */\n protected init(): Promise<this> {\n // clearing the slot on a rejection means a later retry throws its own error\n this.initPromise ??= this.runInit().catch((caught: unknown) => {\n this.initPromise = undefined;\n throw caught;\n });\n return this.initPromise;\n }\n\n private async runInit(): Promise<this> {\n if (this.isInitialized) return this;\n // a rerun after a failed startup would re-init the rolled-back plugins\n if (this.startFailed) throw new SeedcordError(SeedcordErrorCode.LifecycleRestartAfterFailure);\n\n this.registerPluginTasks();\n\n if (this.config.errors?.catchProcessErrors ?? true) {\n Pluggable.liveProcessErrors = registerProcessErrors(this, this[HostShutdown]);\n }\n\n const startupSettled: PromiseWithResolvers<void> = Promise.withResolvers();\n this[HostShutdown].gateOnStartup(startupSettled.promise);\n\n try {\n await this[HostStartup].run();\n } catch (caught) {\n await this.rollback();\n throw caught;\n } finally {\n startupSettled.resolve();\n }\n\n this.isInitialized = true;\n return this;\n }\n\n /** @internal codegen reads this to emit the `Core` augmentation */\n public get [HostPluginKeys](): readonly string[] {\n return this.attachments.map((attachment) => attachment.key);\n }\n\n /** @internal */\n protected static reset(host?: object): boolean {\n if (host !== undefined && Pluggable.liveHost !== host) return false;\n\n Pluggable.liveShutdown?.removeSignalHandlers();\n Pluggable.liveShutdown = undefined;\n Pluggable.liveProcessErrors?.();\n Pluggable.liveProcessErrors = undefined;\n Pluggable.liveHost = undefined;\n Pluggable.isInstantiated = false;\n return true;\n }\n\n /**\n * Attaches a plugin under `key`. Read the instance back as `core[key]`. `seedcord codegen`\n * writes the `Core` augmentation that types it there.\n *\n * Put one dot in the key to nest the plugin under a group, so `'services.users'` reads back as\n * `core.services.users`. Each name holds one plugin or one group.\n *\n * Startup runs each plugin's `init()` in attach order within its phase.\n *\n * Attaching a plugin whose `transport` or `runtime` differs from this host fails to compile.\n * Your constructor takes `CoreBase` as its first parameter (you don't need to pass it though).\n * A narrower one fails to compile here.\n *\n * @param key - Also the channel the plugin logs on, dots included. Reserved channel names throw.\n * @param args - Whatever your constructor takes after the host.\n * @throws A **SeedcordError** if you attach after the bot has started. A taken or reserved key throws too.\n * @example\n * ```ts\n * seedcord.attach('db', Mongoose, { uri: 'mongodb://...', name: 'seedcord', dir: ... });\n * ```\n */\n public attach<Key extends string, Ctor extends PluginCtor>(\n this: this,\n key: AttachKeyAssert<Key, this>,\n Plugin: Ctor &\n TransportAssert<InstanceType<Ctor>, BotT> &\n RuntimeAssert<InstanceType<Ctor>, BotRt> &\n CoreParamAssert<Ctor>,\n ...args: PluginArgs<Ctor>\n ): this & Attached<Key, InstanceType<Ctor>> {\n if (this.isInitialized) {\n throw new SeedcordError(SeedcordErrorCode.CorePluginAfterInit);\n }\n\n const dot = key.indexOf('.');\n const head = dot === -1 ? key : key.slice(0, dot);\n\n // several reserved channels are also members on a host, which the next check would report first\n if (RESERVED_KEYS.has(head)) {\n throw new SeedcordTypeError(SeedcordErrorCode.CorePluginReservedChannel, [head]);\n }\n\n const leaf = dot === -1 ? undefined : key.slice(dot + 1);\n if (head === '' || leaf === '') {\n throw new SeedcordTypeError(SeedcordErrorCode.CorePluginKeyMalformed, [key, 'has an empty part.']);\n }\n if (leaf?.includes('.')) {\n throw new SeedcordTypeError(SeedcordErrorCode.CorePluginKeyMalformed, [key, 'has more than one dot.']);\n }\n this.assertFree(head, leaf, key);\n\n const instance = new Plugin(this, ...args);\n pluginLoggerOf(instance).setChannel(key);\n this.plugins.push(instance);\n this.attachments.push({ key, instance });\n\n if (leaf === undefined) {\n return Object.assign(this, { [key]: instance }) as this & Attached<Key, InstanceType<Ctor>>;\n }\n\n this.groupFor(head)[leaf] = instance;\n return this as this & Attached<Key, InstanceType<Ctor>>;\n }\n\n private assertFree(head: string, leaf: string | undefined, key: string): void {\n if (leaf === undefined) {\n if (this.groups.has(key)) throw new SeedcordError(SeedcordErrorCode.CorePluginKeyHoldsGroup, [key]);\n if (key in this) throw new SeedcordError(SeedcordErrorCode.CorePluginKeyExists, [key]);\n return;\n }\n\n const group = this.groups.get(head);\n if (!group) {\n if (head in this) throw new SeedcordError(SeedcordErrorCode.CorePluginGroupTaken, [head, key]);\n return;\n }\n if (Object.hasOwn(group, leaf)) throw new SeedcordError(SeedcordErrorCode.CorePluginKeyExists, [key]);\n }\n\n private groupFor(head: string): Record<string, PluginLike> {\n const existing = this.groups.get(head);\n if (existing) return existing;\n\n // a null prototype makes a leaf called __proto__ or valueOf an ordinary key\n const group = Object.create(null) as Record<string, PluginLike>;\n this.groups.set(head, group);\n Object.assign(this, { [head]: group });\n return group;\n }\n\n // one combined task per phase keeps plugin inits sequential while the phase's other tasks run concurrently\n private registerPluginTasks(): void {\n if (this.pluginTasksRegistered) return;\n this.pluginTasksRegistered = true;\n\n const groups = new Map<StartupPhase, Attachment[]>();\n for (const attachment of this.attachments) {\n const phase = resolvedLifecycleSpecOf(attachment.instance).init.phase;\n const group = groups.get(phase) ?? [];\n group.push(attachment);\n groups.set(phase, group);\n }\n\n for (const [phase, group] of groups) {\n // Ready inits run in the combined Ready task, before the ready hooks\n if (phase === StartupPhase.Ready) continue;\n const budget = group.reduce((sum, a) => sum + resolvedLifecycleSpecOf(a.instance).init.timeout, 0);\n this.startup.addTask(phase, 'plugins-init', () => this.runInits(group), budget);\n }\n\n this.registerReadyTask(groups.get(StartupPhase.Ready) ?? []);\n }\n\n private async runInits(group: readonly Attachment[]): Promise<void> {\n for (const attachment of group) {\n const { key, instance } = attachment;\n const spec = resolvedLifecycleSpecOf(instance);\n\n pluginLoggerOf(instance).utils.initialization(key, 'start');\n const running = instance.init();\n try {\n await withTimeout(`Plugin (${key})`, () => running, spec.init.timeout);\n } catch (caught) {\n if (isSeedcordError(caught, undefined, SeedcordErrorCode.LifecycleTaskTimeout)) {\n this.disposeWhenInitResolves(attachment, running);\n }\n throw caught;\n }\n pluginLoggerOf(instance).utils.initialization(key, 'end');\n\n this.completedInits.add(attachment);\n if (instance.dispose) this.registerDisposeTask(spec.dispose.phase);\n }\n }\n\n // disposeCompleted skips a plugin missing from completedInits\n private disposeWhenInitResolves(attachment: Attachment, running: Promise<void>): void {\n const dispose = attachment.instance.dispose?.bind(attachment.instance);\n const spec = resolvedLifecycleSpecOf(attachment.instance);\n\n void running.then(\n async () => {\n if (!dispose) return;\n await withTimeout(`Plugin:${attachment.key}:dispose`, dispose, spec.dispose.timeout).catch(\n (caught: unknown) =>\n this.pluginLogger.warn(`${attachment.key} dispose failed after a timed-out init`, caught)\n );\n },\n (caught: unknown) => this.pluginLogger.warn(`${attachment.key} init failed after its timeout`, caught)\n );\n }\n\n private registerReadyTask(readyInits: readonly Attachment[]): void {\n const steps = this.attachments.reduce<{ key: string; run: () => Promise<void>; timeout: number }[]>(\n (acc, a) => {\n const ready = a.instance.ready?.bind(a.instance);\n if (ready) {\n acc.push({\n key: a.key,\n run: ready,\n timeout: resolvedLifecycleSpecOf(a.instance).ready.timeout\n });\n }\n return acc;\n },\n []\n );\n if (readyInits.length === 0 && steps.length === 0) return;\n\n const budget =\n readyInits.reduce((sum, a) => sum + resolvedLifecycleSpecOf(a.instance).init.timeout, 0) +\n steps.reduce((sum, step) => sum + step.timeout, 0);\n this.startup.addTask(\n StartupPhase.Ready,\n 'plugins-ready',\n async () => {\n await this.runInits(readyInits);\n for (const step of steps) {\n await withTimeout(`Plugin:${step.key}:ready`, step.run, step.timeout);\n }\n },\n budget\n );\n }\n\n private registerDisposeTask(phase: ShutdownPhase): void {\n if (this.disposePhases.has(phase)) return;\n this.disposePhases.add(phase);\n\n // the budget covers every dispose in this phase because they all run in this one task\n const budget = this.attachments.reduce((sum, a) => {\n const spec = resolvedLifecycleSpecOf(a.instance);\n return a.instance.dispose && spec.dispose.phase === phase ? sum + spec.dispose.timeout : sum;\n }, 0);\n this.shutdown.addTask(phase, 'plugins-dispose', () => this.runDisposals(phase), budget);\n }\n\n private async runDisposals(phase: ShutdownPhase): Promise<void> {\n const failures: unknown[] = [];\n await this.disposeCompleted(phase, (caught) => failures.push(caught));\n if (failures.length === 1) throw failures[0];\n if (failures.length > 1) {\n throw new SeedcordAggregateError(SeedcordErrorCode.PluginDisposeFailures, failures, [failures.length]);\n }\n }\n\n private async disposeCompleted(\n phase: ShutdownPhase | undefined,\n onError: (caught: unknown) => void\n ): Promise<void> {\n for (const attachment of [...this.attachments].reverse()) {\n if (!this.completedInits.has(attachment)) continue;\n const dispose = attachment.instance.dispose?.bind(attachment.instance);\n if (!dispose) continue;\n const spec = resolvedLifecycleSpecOf(attachment.instance);\n if (phase !== undefined && spec.dispose.phase !== phase) continue;\n\n try {\n await withTimeout(`Plugin:${attachment.key}:dispose`, dispose, spec.dispose.timeout);\n } catch (caught) {\n onError(caught);\n }\n }\n }\n\n private async rollback(): Promise<void> {\n this.startFailed = true;\n // log rollback dispose failures so they do not hide the startup error\n await this.disposeCompleted(undefined, (caught) => this.pluginLogger.warn('rollback dispose failed', caught));\n // prevents re-dispose when shutdown runs after rollback\n this.completedInits.clear();\n }\n}\n\n// the public `shutdown` field carries addTask alone\nexport function shutdownOf(host: Pick<Pluggable<Transport, Runtime>, typeof HostShutdown>): CoordinatedShutdown {\n return host[HostShutdown];\n}\n"],"mappings":"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"}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { i as Bus, n as StartupPhase, r as CoreBase, t as ShutdownPhase } from "./phases-
|
|
2
|
-
import {
|
|
1
|
+
import { i as Bus, n as StartupPhase, r as CoreBase, t as ShutdownPhase } from "./phases-DJf12l5T.mjs";
|
|
2
|
+
import { c as PluginLike, d as RuntimeAssert, f as Transport, n as Attached, o as PluginArgs, p as TransportAssert, r as CoreParamAssert, s as PluginCtor, t as AttachKeyAssert, u as Runtime } from "./Plugin-D3wIj0xX.mjs";
|
|
3
3
|
import { Logger } from "@seedcord/logger";
|
|
4
4
|
import { REST } from "@discordjs/rest";
|
|
5
5
|
import { HostPluginKeys, HostShutdown, HostStartup } from "@seedcord/types/internal";
|
|
@@ -103,6 +103,7 @@ declare abstract class Pluggable<BotT extends Transport, BotRt extends Runtime>
|
|
|
103
103
|
protected isInitialized: boolean;
|
|
104
104
|
protected startFailed: boolean;
|
|
105
105
|
protected readonly plugins: PluginLike[];
|
|
106
|
+
private readonly groups;
|
|
106
107
|
private readonly pluginLogger;
|
|
107
108
|
private readonly attachments;
|
|
108
109
|
private readonly completedInits;
|
|
@@ -125,13 +126,16 @@ declare abstract class Pluggable<BotT extends Transport, BotRt extends Runtime>
|
|
|
125
126
|
* Attaches a plugin under `key`. Read the instance back as `core[key]`. `seedcord codegen`
|
|
126
127
|
* writes the `Core` augmentation that types it there.
|
|
127
128
|
*
|
|
129
|
+
* Put one dot in the key to nest the plugin under a group, so `'services.users'` reads back as
|
|
130
|
+
* `core.services.users`. Each name holds one plugin or one group.
|
|
131
|
+
*
|
|
128
132
|
* Startup runs each plugin's `init()` in attach order within its phase.
|
|
129
133
|
*
|
|
130
134
|
* Attaching a plugin whose `transport` or `runtime` differs from this host fails to compile.
|
|
131
135
|
* Your constructor takes `CoreBase` as its first parameter (you don't need to pass it though).
|
|
132
136
|
* A narrower one fails to compile here.
|
|
133
137
|
*
|
|
134
|
-
* @param key - Also the channel the plugin logs on. Reserved channel names throw.
|
|
138
|
+
* @param key - Also the channel the plugin logs on, dots included. Reserved channel names throw.
|
|
135
139
|
* @param args - Whatever your constructor takes after the host.
|
|
136
140
|
* @throws A **SeedcordError** if you attach after the bot has started. A taken or reserved key throws too.
|
|
137
141
|
* @example
|
|
@@ -139,7 +143,9 @@ declare abstract class Pluggable<BotT extends Transport, BotRt extends Runtime>
|
|
|
139
143
|
* seedcord.attach('db', Mongoose, { uri: 'mongodb://...', name: 'seedcord', dir: ... });
|
|
140
144
|
* ```
|
|
141
145
|
*/
|
|
142
|
-
attach<Key extends string, Ctor extends PluginCtor>(this: this, key:
|
|
146
|
+
attach<Key extends string, Ctor extends PluginCtor>(this: this, key: AttachKeyAssert<Key, this>, Plugin: Ctor & TransportAssert<InstanceType<Ctor>, BotT> & RuntimeAssert<InstanceType<Ctor>, BotRt> & CoreParamAssert<Ctor>, ...args: PluginArgs<Ctor>): this & Attached<Key, InstanceType<Ctor>>;
|
|
147
|
+
private assertFree;
|
|
148
|
+
private groupFor;
|
|
143
149
|
private registerPluginTasks;
|
|
144
150
|
private runInits;
|
|
145
151
|
private disposeWhenInitResolves;
|
|
@@ -152,4 +158,4 @@ declare abstract class Pluggable<BotT extends Transport, BotRt extends Runtime>
|
|
|
152
158
|
declare function shutdownOf(host: Pick<Pluggable<Transport, Runtime>, typeof HostShutdown>): CoordinatedShutdown;
|
|
153
159
|
//#endregion
|
|
154
160
|
export { LifecycleTask as a, CoordinatedShutdown as i, shutdownOf as n, CoordinatedStartup as r, Pluggable as t };
|
|
155
|
-
//# sourceMappingURL=Pluggable-
|
|
161
|
+
//# sourceMappingURL=Pluggable-CAteNmfy.d.mts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"Plugin-BJfRk0s_.mjs","names":[],"sources":["../src/plugin/lifecycle.ts","../src/plugin/Plugin.ts"],"sourcesContent":["import { SeedcordErrorCode } from '@seedcord/errors';\nimport { SeedcordRangeError } from '@seedcord/errors/internal';\n\nimport { ShutdownPhase, StartupPhase } from '#src/lifecycle/phases';\n\n/**\n * Sets when a plugin's lifecycle methods run. Pass it as the second argument to `super()`. Omit a\n * field to keep its default.\n *\n * @see {@link StartupPhase}\n * @see {@link ShutdownPhase}\n * @example\n * ```ts\n * super(host, { init: { phase: StartupPhase.Login }, dispose: { timeout: 30_000 } });\n * ```\n */\nexport interface PluginLifecycleSpec {\n /**\n * Defaults to `StartupPhase.Configuration` and 15000ms. Set the phase to `Ready` and `init()`\n * runs ahead of every plugin's `ready()`.\n */\n init?: { phase?: StartupPhase; timeout?: number };\n /** `ready()` always runs in `StartupPhase.Ready`. Defaults to a 15000ms timeout. */\n ready?: { timeout?: number };\n /** Defaults to `ShutdownPhase.Disconnect` and 10000ms. */\n dispose?: { phase?: ShutdownPhase; timeout?: number };\n}\n\n/** @internal */\nexport interface ResolvedPluginLifecycleSpec {\n readonly init: { readonly phase: StartupPhase; readonly timeout: number };\n readonly ready: { readonly timeout: number };\n readonly dispose: { readonly phase: ShutdownPhase; readonly timeout: number };\n}\n\nconst DEFAULT_INIT_TIMEOUT_MS = 15_000;\nconst DEFAULT_READY_TIMEOUT_MS = 15_000;\nconst DEFAULT_DISPOSE_TIMEOUT_MS = 10_000;\n\nfunction timeoutOf(\n field: 'init' | 'ready' | 'dispose',\n declared: number | undefined,\n fallback: number,\n pluginName: string\n): number {\n if (declared === undefined) return fallback;\n if (!Number.isFinite(declared) || declared <= 0) {\n throw new SeedcordRangeError(SeedcordErrorCode.PluginInvalidLifecycleTimeout, [pluginName, field, declared]);\n }\n return declared;\n}\n\n/** @internal */\nexport function resolveLifecycleSpec(\n spec: PluginLifecycleSpec | undefined,\n pluginName: string\n): ResolvedPluginLifecycleSpec {\n return {\n init: {\n phase: spec?.init?.phase ?? StartupPhase.Configuration,\n timeout: timeoutOf('init', spec?.init?.timeout, DEFAULT_INIT_TIMEOUT_MS, pluginName)\n },\n ready: {\n timeout: timeoutOf('ready', spec?.ready?.timeout, DEFAULT_READY_TIMEOUT_MS, pluginName)\n },\n dispose: {\n phase: spec?.dispose?.phase ?? ShutdownPhase.Disconnect,\n timeout: timeoutOf('dispose', spec?.dispose?.timeout, DEFAULT_DISPOSE_TIMEOUT_MS, pluginName)\n }\n };\n}\n","import { SeedcordErrorCode } from '@seedcord/errors';\nimport { SeedcordError } from '@seedcord/errors/internal';\nimport { Logger } from '@seedcord/logger';\n\nimport { getDevChannel } from '#hmr/devChannel';\n\nimport { RuntimeBrand, TransportBrand } from './brands';\nimport { resolveLifecycleSpec } from './lifecycle';\n\nimport type { CoreBase } from '#interfaces/CoreBase';\nimport type { ResolvedPluginLifecycleSpec, PluginLifecycleSpec } from './lifecycle';\nimport type { TransportOf, PluginOptions, RuntimeOf } from './options';\nimport type { Tail, HmrAware, HmrUpdateEvent } from '@seedcord/types';\n\nexport interface Initializeable {\n init(): Promise<void>;\n}\n\n/** @internal */\nconst resolvedSpecSlot = Symbol('seedcord:plugin:spec');\n/** @internal */\nconst loggerSlot = Symbol('seedcord:plugin:logger');\n\n/**\n * Base class for a seedcord plugin. Extend it and implement `init()`. `this.core` is the running\n * bot.\n *\n * Your constructor takes the host first and passes it to `super()`. Add your own parameters after\n * it. `attach()` types them at the call site.\n *\n * @typeParam Opts - Where this plugin may attach, checked only at compile time.\n * @typeParam TCore - The `Core` each transport binds `this.core` to.\n *\n * @example\n * ```ts\n * class Analytics extends Plugin<{ transport: 'gateway' }> {\n * constructor(\n * host: CoreBase,\n * private readonly apiKey: string\n * ) {\n * super(host, { init: { phase: StartupPhase.Login } });\n * }\n *\n * public async init(): Promise<void> {\n * await this.connect(this.apiKey);\n * }\n * }\n *\n * seedcord.attach('analytics', Analytics, apiKey);\n * ```\n */\nexport abstract class Plugin<Opts extends PluginOptions = {}, TCore extends CoreBase = CoreBase>\n implements Initializeable, HmrAware\n{\n // phantom, never set at runtime.\n /** @internal */\n declare readonly [TransportBrand]?: TransportOf<Opts>;\n /** @internal */\n declare readonly [RuntimeBrand]?: RuntimeOf<Opts>;\n\n /** @internal */\n readonly [resolvedSpecSlot]: ResolvedPluginLifecycleSpec;\n /** @internal */\n readonly [loggerSlot]: Logger;\n\n /** Logs under the plugin's class name, on the channel its attach key sets. */\n protected readonly logger: Logger;\n\n // CoreBase here keeps the augmented Core out of ConstructorParameters, which attach reads\n constructor(\n private readonly host: CoreBase,\n spec?: PluginLifecycleSpec\n ) {\n this.logger = new Logger(this.constructor.name);\n this[loggerSlot] = this.logger;\n this[resolvedSpecSlot] = resolveLifecycleSpec(spec, this.constructor.name);\n }\n\n /** The host, typed to the transport whose `Plugin` base this class extends. */\n protected get core(): TCore {\n // justified: each transport binds TCore to the Core its own host satisfies\n return this.host as TCore;\n }\n\n /**\n * Runs in `StartupPhase.Configuration` by default.\n * Move it to a different phase with the {@link PluginLifecycleSpec}.\n */\n abstract init(): Promise<void>;\n\n /**\n * Runs in the Ready phase, after every attached plugin's `init()` has resolved. The other Ready\n * tasks run alongside it, including the http server binding its port.\n *\n * Override it when your work needs a logged-in client or a resolved application id.\n */\n ready?(): Promise<void>;\n\n /**\n * Runs during teardown, in `ShutdownPhase.Disconnect` by default. seedcord skips it when `init()`\n * has thrown.\n *\n * When `init()` outlasts its timeout, seedcord calls this once that `init()` resolves, outside any\n * shutdown phase. The process must still be alive for that call to happen.\n */\n dispose?(): Promise<void>;\n\n /**\n * Throws a `SeedcordError` that contains the plugin class name and the reason.\n *\n * @param reason - Why the options are invalid.\n */\n protected rejectOptions(reason: string): never {\n throw new SeedcordError(SeedcordErrorCode.PluginOptionsRejected, [this.constructor.name, reason]);\n }\n\n /** Override to reload plugin state on an HMR update. */\n public onHmr(_event: HmrUpdateEvent): Promise<void> {\n return Promise.resolve();\n }\n\n /**\n * Registers critical file patterns that should trigger a full restart when changed in Dev HMR.\n * @param patterns - Glob patterns relative to the project root\n */\n protected registerCriticalFiles(patterns: string[]): void {\n getDevChannel()?.send('seedcord:register-critical-files', { patterns });\n }\n}\n\n/** @internal */\nexport function resolvedLifecycleSpecOf(plugin: PluginLike): ResolvedPluginLifecycleSpec {\n return plugin[resolvedSpecSlot];\n}\n\n/** @internal */\nexport function pluginLoggerOf(plugin: PluginLike): Logger {\n return plugin[loggerSlot];\n}\n\nexport type { PluginLifecycleSpec } from './lifecycle';\nexport type { PluginOptions } from './options';\n\n// a bound of Plugin<{}> rejects every plugin that declares an option, since Plugin<A> and Plugin<B>\n// are mutually unassignable. every member here must stay Opts-independent.\n/** @internal */\nexport type PluginLike = Pick<\n Plugin,\n 'init' | 'ready' | 'dispose' | 'onHmr' | typeof resolvedSpecSlot | typeof loggerSlot\n>;\n\n// a `CoreBase` first parameter rejects a narrowing ctor on its own, and it also collapses\n// `InstanceType<Ctor>` to `PluginLike` at every attach call. CoreParamAssert checks it instead.\n/** @internal */\nexport type PluginCtor<TPlugin extends PluginLike = PluginLike> = new (...args: any[]) => TPlugin;\n\n/** @internal */\nexport type PluginArgs<Ctor extends PluginCtor> = Tail<ConstructorParameters<Ctor>>;\n\ntype CoreParamTooNarrow = Record<\n 'this plugin constructor must take CoreBase as its first parameter and read the transport Core off this.core',\n never\n>;\n\n// a narrowed first parameter fails `CoreBase extends Param`\n/** @internal */\nexport type CoreParamAssert<Ctor extends PluginCtor> = CoreBase extends ConstructorParameters<Ctor>[0]\n ? unknown\n : CoreParamTooNarrow;\n"],"mappings":";;;;;;;AAmCA,MAAM,0BAA0B;AAChC,MAAM,2BAA2B;AACjC,MAAM,6BAA6B;AAEnC,SAAS,UACL,OACA,UACA,UACA,YACM;CACN,IAAI,aAAa,QAAW,OAAO;CACnC,IAAI,CAAC,OAAO,SAAS,QAAQ,KAAK,YAAY,GAC1C,MAAM,IAAI,mBAAmB,kBAAkB,+BAA+B;EAAC;EAAY;EAAO;CAAQ,CAAC;CAE/G,OAAO;AACX;;AAGA,SAAgB,qBACZ,MACA,YAC2B;CAC3B,OAAO;EACH,MAAM;GACF,OAAO,MAAM,MAAM;GACnB,SAAS,UAAU,QAAQ,MAAM,MAAM,SAAS,yBAAyB,UAAU;EACvF;EACA,OAAO,EACH,SAAS,UAAU,SAAS,MAAM,OAAO,SAAS,0BAA0B,UAAU,EAC1F;EACA,SAAS;GACL,OAAO,MAAM,SAAS;GACtB,SAAS,UAAU,WAAW,MAAM,SAAS,SAAS,4BAA4B,UAAU;EAChG;CACJ;AACJ;;;;;ACnDA,MAAM,mBAAmB,OAAO,sBAAsB;;AAEtD,MAAM,aAAa,OAAO,wBAAwB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AA8BlD,IAAsB,SAAtB,MAEA;CAiByB;;CATrB,CAAU;;CAEV,CAAU;;CAGV,AAAmB;CAGnB,YACI,AAAiB,MACjB,MACF;EAFmB;EAGjB,KAAK,SAAS,IAAI,OAAO,KAAK,YAAY,IAAI;EAC9C,KAAK,cAAc,KAAK;EACxB,KAAK,oBAAoB,qBAAqB,MAAM,KAAK,YAAY,IAAI;CAC7E;;CAGA,IAAc,OAAc;EAExB,OAAO,KAAK;CAChB;;;;;;CA8BA,AAAU,cAAc,QAAuB;EAC3C,MAAM,IAAI,cAAc,kBAAkB,uBAAuB,CAAC,KAAK,YAAY,MAAM,MAAM,CAAC;CACpG;;CAGA,AAAO,MAAM,QAAuC;EAChD,OAAO,QAAQ,QAAQ;CAC3B;;;;;CAMA,AAAU,sBAAsB,UAA0B;EACtD,cAAc,CAAC,EAAE,KAAK,oCAAoC,EAAE,SAAS,CAAC;CAC1E;AACJ;;AAGA,SAAgB,wBAAwB,QAAiD;CACrF,OAAO,OAAO;AAClB;;AAGA,SAAgB,eAAe,QAA4B;CACvD,OAAO,OAAO;AAClB"}
|
|
1
|
+
{"version":3,"file":"Plugin-BJfRk0s_.mjs","names":[],"sources":["../src/plugin/lifecycle.ts","../src/plugin/Plugin.ts"],"sourcesContent":["import { SeedcordErrorCode } from '@seedcord/errors';\nimport { SeedcordRangeError } from '@seedcord/errors/internal';\n\nimport { ShutdownPhase, StartupPhase } from '#src/lifecycle/phases';\n\n/**\n * Sets when a plugin's lifecycle methods run. Pass it as the second argument to `super()`. Omit a\n * field to keep its default.\n *\n * @see {@link StartupPhase}\n * @see {@link ShutdownPhase}\n * @example\n * ```ts\n * super(host, { init: { phase: StartupPhase.Login }, dispose: { timeout: 30_000 } });\n * ```\n */\nexport interface PluginLifecycleSpec {\n /**\n * Defaults to `StartupPhase.Configuration` and 15000ms. Set the phase to `Ready` and `init()`\n * runs ahead of every plugin's `ready()`.\n */\n init?: { phase?: StartupPhase; timeout?: number };\n /** `ready()` always runs in `StartupPhase.Ready`. Defaults to a 15000ms timeout. */\n ready?: { timeout?: number };\n /** Defaults to `ShutdownPhase.Disconnect` and 10000ms. */\n dispose?: { phase?: ShutdownPhase; timeout?: number };\n}\n\n/** @internal */\nexport interface ResolvedPluginLifecycleSpec {\n readonly init: { readonly phase: StartupPhase; readonly timeout: number };\n readonly ready: { readonly timeout: number };\n readonly dispose: { readonly phase: ShutdownPhase; readonly timeout: number };\n}\n\nconst DEFAULT_INIT_TIMEOUT_MS = 15_000;\nconst DEFAULT_READY_TIMEOUT_MS = 15_000;\nconst DEFAULT_DISPOSE_TIMEOUT_MS = 10_000;\n\nfunction timeoutOf(\n field: 'init' | 'ready' | 'dispose',\n declared: number | undefined,\n fallback: number,\n pluginName: string\n): number {\n if (declared === undefined) return fallback;\n if (!Number.isFinite(declared) || declared <= 0) {\n throw new SeedcordRangeError(SeedcordErrorCode.PluginInvalidLifecycleTimeout, [pluginName, field, declared]);\n }\n return declared;\n}\n\n/** @internal */\nexport function resolveLifecycleSpec(\n spec: PluginLifecycleSpec | undefined,\n pluginName: string\n): ResolvedPluginLifecycleSpec {\n return {\n init: {\n phase: spec?.init?.phase ?? StartupPhase.Configuration,\n timeout: timeoutOf('init', spec?.init?.timeout, DEFAULT_INIT_TIMEOUT_MS, pluginName)\n },\n ready: {\n timeout: timeoutOf('ready', spec?.ready?.timeout, DEFAULT_READY_TIMEOUT_MS, pluginName)\n },\n dispose: {\n phase: spec?.dispose?.phase ?? ShutdownPhase.Disconnect,\n timeout: timeoutOf('dispose', spec?.dispose?.timeout, DEFAULT_DISPOSE_TIMEOUT_MS, pluginName)\n }\n };\n}\n","import { SeedcordErrorCode } from '@seedcord/errors';\nimport { SeedcordError } from '@seedcord/errors/internal';\nimport { Logger } from '@seedcord/logger';\n\nimport { getDevChannel } from '#hmr/devChannel';\n\nimport { RuntimeBrand, TransportBrand } from './brands';\nimport { resolveLifecycleSpec } from './lifecycle';\n\nimport type { CoreBase } from '#interfaces/CoreBase';\nimport type { ResolvedPluginLifecycleSpec, PluginLifecycleSpec } from './lifecycle';\nimport type { TransportOf, PluginOptions, RuntimeOf } from './options';\nimport type { Tail, FrameworkChannel, HmrAware, HmrUpdateEvent } from '@seedcord/types';\n\nexport interface Initializeable {\n init(): Promise<void>;\n}\n\n/** @internal */\nconst resolvedSpecSlot = Symbol('seedcord:plugin:spec');\n/** @internal */\nconst loggerSlot = Symbol('seedcord:plugin:logger');\n\n/**\n * Base class for a seedcord plugin. Extend it and implement `init()`. `this.core` is the running\n * bot.\n *\n * Your constructor takes the host first and passes it to `super()`. Add your own parameters after\n * it. `attach()` types them at the call site.\n *\n * @typeParam Opts - Where this plugin may attach, checked only at compile time.\n * @typeParam TCore - The `Core` each transport binds `this.core` to.\n *\n * @example\n * ```ts\n * class Analytics extends Plugin<{ transport: 'gateway' }> {\n * constructor(\n * host: CoreBase,\n * private readonly apiKey: string\n * ) {\n * super(host, { init: { phase: StartupPhase.Login } });\n * }\n *\n * public async init(): Promise<void> {\n * await this.connect(this.apiKey);\n * }\n * }\n *\n * seedcord.attach('analytics', Analytics, apiKey);\n * ```\n */\nexport abstract class Plugin<Opts extends PluginOptions = {}, TCore extends CoreBase = CoreBase>\n implements Initializeable, HmrAware\n{\n // phantom, never set at runtime.\n /** @internal */\n declare readonly [TransportBrand]?: TransportOf<Opts>;\n /** @internal */\n declare readonly [RuntimeBrand]?: RuntimeOf<Opts>;\n\n /** @internal */\n readonly [resolvedSpecSlot]: ResolvedPluginLifecycleSpec;\n /** @internal */\n readonly [loggerSlot]: Logger;\n\n /** Logs under the plugin's class name, on the channel its attach key sets. */\n protected readonly logger: Logger;\n\n // CoreBase here keeps the augmented Core out of ConstructorParameters, which attach reads\n constructor(\n private readonly host: CoreBase,\n spec?: PluginLifecycleSpec\n ) {\n this.logger = new Logger(this.constructor.name);\n this[loggerSlot] = this.logger;\n this[resolvedSpecSlot] = resolveLifecycleSpec(spec, this.constructor.name);\n }\n\n /** The host, typed to the transport whose `Plugin` base this class extends. */\n protected get core(): TCore {\n // justified: each transport binds TCore to the Core its own host satisfies\n return this.host as TCore;\n }\n\n /**\n * Runs in `StartupPhase.Configuration` by default.\n * Move it to a different phase with the {@link PluginLifecycleSpec}.\n */\n abstract init(): Promise<void>;\n\n /**\n * Runs in the Ready phase, after every attached plugin's `init()` has resolved. The other Ready\n * tasks run alongside it, including the http server binding its port.\n *\n * Override it when your work needs a logged-in client or a resolved application id.\n */\n ready?(): Promise<void>;\n\n /**\n * Runs during teardown, in `ShutdownPhase.Disconnect` by default. seedcord skips it when `init()`\n * has thrown.\n *\n * When `init()` outlasts its timeout, seedcord calls this once that `init()` resolves, outside any\n * shutdown phase. The process must still be alive for that call to happen.\n */\n dispose?(): Promise<void>;\n\n /**\n * Throws a `SeedcordError` that contains the plugin class name and the reason.\n *\n * @param reason - Why the options are invalid.\n */\n protected rejectOptions(reason: string): never {\n throw new SeedcordError(SeedcordErrorCode.PluginOptionsRejected, [this.constructor.name, reason]);\n }\n\n /** Override to reload plugin state on an HMR update. */\n public onHmr(_event: HmrUpdateEvent): Promise<void> {\n return Promise.resolve();\n }\n\n /**\n * Registers critical file patterns that should trigger a full restart when changed in Dev HMR.\n * @param patterns - Glob patterns relative to the project root\n */\n protected registerCriticalFiles(patterns: string[]): void {\n getDevChannel()?.send('seedcord:register-critical-files', { patterns });\n }\n}\n\n/** @internal */\nexport function resolvedLifecycleSpecOf(plugin: PluginLike): ResolvedPluginLifecycleSpec {\n return plugin[resolvedSpecSlot];\n}\n\n/** @internal */\nexport function pluginLoggerOf(plugin: PluginLike): Logger {\n return plugin[loggerSlot];\n}\n\nexport type { PluginLifecycleSpec } from './lifecycle';\nexport type { PluginOptions } from './options';\n\n// a bound of Plugin<{}> rejects every plugin that declares an option, since Plugin<A> and Plugin<B>\n// are mutually unassignable. every member here must stay Opts-independent.\n/** @internal */\nexport type PluginLike = Pick<\n Plugin,\n 'init' | 'ready' | 'dispose' | 'onHmr' | typeof resolvedSpecSlot | typeof loggerSlot\n>;\n\n// a `CoreBase` first parameter rejects a narrowing ctor on its own, and it also collapses\n// `InstanceType<Ctor>` to `PluginLike` at every attach call. CoreParamAssert checks it instead.\n/** @internal */\nexport type PluginCtor<TPlugin extends PluginLike = PluginLike> = new (...args: any[]) => TPlugin;\n\n/** @internal */\nexport type PluginArgs<Ctor extends PluginCtor> = Tail<ConstructorParameters<Ctor>>;\n\n/** @internal */\nexport type Attached<Key extends string, Instance> = Key extends `${infer Group}.${infer Leaf}`\n ? Record<Group, Record<Leaf, Instance>>\n : Record<Key, Instance>;\n\n// attach writes the key onto the host, so a key like toString would shadow the one on Object.prototype\n// eslint-disable-next-line @typescript-eslint/no-wrapper-object-types -- lint:fix swaps this for `keyof object`, which is never\ntype HostMember<Host> = keyof Host | keyof Object;\n\n// a string argument against an object type renders only as `string is not assignable`\n/** @internal */\nexport type AttachKeyAssert<Key extends string, Host> = Key extends ''\n ? 'A plugin key needs a name.'\n : Key extends `${infer Group}.${infer Leaf}`\n ? Leaf extends `${string}.${string}`\n ? `'${Key}' has more than one dot. A plugin key takes one dot at most.`\n : '' extends Group | Leaf\n ? `'${Key}' has an empty part. Write a group and a plugin name around the dot, like 'services.users'.`\n : Group extends FrameworkChannel\n ? `'${Group}' is a channel the framework logs on. Pick another group name.`\n : Group extends keyof Host\n ? Host[Group] extends PluginLike\n ? `'${Group}' already holds a plugin, so '${Leaf}' cannot nest inside it.`\n : Host[Group] extends Record<string, PluginLike>\n ? Leaf extends keyof Host[Group]\n ? `'${Key}' is already attached.`\n : Key\n : `'${Group}' is already a member on the bot. Pick another group name.`\n : Group extends HostMember<Host>\n ? `'${Group}' is already a member on the bot. Pick another group name.`\n : Key\n : Key extends FrameworkChannel\n ? `'${Key}' is a channel the framework logs on. Pick another plugin key.`\n : Key extends keyof Host\n ? Host[Key] extends PluginLike\n ? `'${Key}' is already attached.`\n : Host[Key] extends Record<string, PluginLike>\n ? `'${Key}' already holds a group of plugins. Attach this one under a name of its own.`\n : `'${Key}' is already a member on the bot. Pick another plugin key.`\n : Key extends HostMember<Host>\n ? `'${Key}' is already a member on the bot. Pick another plugin key.`\n : Key;\n\ntype CoreParamTooNarrow = Record<\n 'this plugin constructor must take CoreBase as its first parameter and read the transport Core off this.core',\n never\n>;\n\n// a narrowed first parameter fails `CoreBase extends Param`\n/** @internal */\nexport type CoreParamAssert<Ctor extends PluginCtor> = CoreBase extends ConstructorParameters<Ctor>[0]\n ? unknown\n : CoreParamTooNarrow;\n"],"mappings":";;;;;;;AAmCA,MAAM,0BAA0B;AAChC,MAAM,2BAA2B;AACjC,MAAM,6BAA6B;AAEnC,SAAS,UACL,OACA,UACA,UACA,YACM;CACN,IAAI,aAAa,QAAW,OAAO;CACnC,IAAI,CAAC,OAAO,SAAS,QAAQ,KAAK,YAAY,GAC1C,MAAM,IAAI,mBAAmB,kBAAkB,+BAA+B;EAAC;EAAY;EAAO;CAAQ,CAAC;CAE/G,OAAO;AACX;;AAGA,SAAgB,qBACZ,MACA,YAC2B;CAC3B,OAAO;EACH,MAAM;GACF,OAAO,MAAM,MAAM;GACnB,SAAS,UAAU,QAAQ,MAAM,MAAM,SAAS,yBAAyB,UAAU;EACvF;EACA,OAAO,EACH,SAAS,UAAU,SAAS,MAAM,OAAO,SAAS,0BAA0B,UAAU,EAC1F;EACA,SAAS;GACL,OAAO,MAAM,SAAS;GACtB,SAAS,UAAU,WAAW,MAAM,SAAS,SAAS,4BAA4B,UAAU;EAChG;CACJ;AACJ;;;;;ACnDA,MAAM,mBAAmB,OAAO,sBAAsB;;AAEtD,MAAM,aAAa,OAAO,wBAAwB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AA8BlD,IAAsB,SAAtB,MAEA;CAiByB;;CATrB,CAAU;;CAEV,CAAU;;CAGV,AAAmB;CAGnB,YACI,AAAiB,MACjB,MACF;EAFmB;EAGjB,KAAK,SAAS,IAAI,OAAO,KAAK,YAAY,IAAI;EAC9C,KAAK,cAAc,KAAK;EACxB,KAAK,oBAAoB,qBAAqB,MAAM,KAAK,YAAY,IAAI;CAC7E;;CAGA,IAAc,OAAc;EAExB,OAAO,KAAK;CAChB;;;;;;CA8BA,AAAU,cAAc,QAAuB;EAC3C,MAAM,IAAI,cAAc,kBAAkB,uBAAuB,CAAC,KAAK,YAAY,MAAM,MAAM,CAAC;CACpG;;CAGA,AAAO,MAAM,QAAuC;EAChD,OAAO,QAAQ,QAAQ;CAC3B;;;;;CAMA,AAAU,sBAAsB,UAA0B;EACtD,cAAc,CAAC,EAAE,KAAK,oCAAoC,EAAE,SAAS,CAAC;CAC1E;AACJ;;AAGA,SAAgB,wBAAwB,QAAiD;CACrF,OAAO,OAAO;AAClB;;AAGA,SAAgB,eAAe,QAA4B;CACvD,OAAO,OAAO;AAClB"}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { n as StartupPhase, r as CoreBase, t as ShutdownPhase } from "./phases-
|
|
1
|
+
import { n as StartupPhase, r as CoreBase, t as ShutdownPhase } from "./phases-DJf12l5T.mjs";
|
|
2
2
|
import { Logger } from "@seedcord/logger";
|
|
3
3
|
import { FrameworkChannel, HmrAware, HmrUpdateEvent, Tail, TypedExclude } from "@seedcord/types";
|
|
4
4
|
//#region src/plugin/brands.d.ts
|
|
@@ -87,8 +87,6 @@ type Runtime = TypedExclude<NonNullable<PluginOptions['runtime']>, 'any'>;
|
|
|
87
87
|
type TransportMismatch<PluginT extends string, BotT extends string> = Record<`this plugin declares transport '${PluginT}' but this bot runs '${BotT}'`, never>;
|
|
88
88
|
type RuntimeMismatch<PluginRt extends string, BotRt extends string> = Record<`this plugin declares runtime '${PluginRt}' but this bot runs '${BotRt}'`, never>;
|
|
89
89
|
type EdgePluginsUnsupported = Record<'an edge bot takes no plugins until edge support ships after v1', never>;
|
|
90
|
-
/** @internal */
|
|
91
|
-
type ChannelKeyAssert<Key extends string> = Key extends FrameworkChannel ? `'${Key}' is a channel the framework logs on. Pick another plugin key.` : Key;
|
|
92
90
|
type BrandTransport<Plug> = Plug extends {
|
|
93
91
|
readonly [TransportBrand]?: infer T extends string;
|
|
94
92
|
} ? T : 'any';
|
|
@@ -191,9 +189,14 @@ type PluginLike = Pick<Plugin, 'init' | 'ready' | 'dispose' | 'onHmr' | typeof r
|
|
|
191
189
|
type PluginCtor<TPlugin extends PluginLike = PluginLike> = new (...args: any[]) => TPlugin;
|
|
192
190
|
/** @internal */
|
|
193
191
|
type PluginArgs<Ctor extends PluginCtor> = Tail<ConstructorParameters<Ctor>>;
|
|
192
|
+
/** @internal */
|
|
193
|
+
type Attached<Key extends string, Instance> = Key extends `${infer Group}.${infer Leaf}` ? Record<Group, Record<Leaf, Instance>> : Record<Key, Instance>;
|
|
194
|
+
type HostMember<Host> = keyof Host | keyof Object;
|
|
195
|
+
/** @internal */
|
|
196
|
+
type AttachKeyAssert<Key extends string, Host> = Key extends '' ? 'A plugin key needs a name.' : Key extends `${infer Group}.${infer Leaf}` ? Leaf extends `${string}.${string}` ? `'${Key}' has more than one dot. A plugin key takes one dot at most.` : '' extends Group | Leaf ? `'${Key}' has an empty part. Write a group and a plugin name around the dot, like 'services.users'.` : Group extends FrameworkChannel ? `'${Group}' is a channel the framework logs on. Pick another group name.` : Group extends keyof Host ? Host[Group] extends PluginLike ? `'${Group}' already holds a plugin, so '${Leaf}' cannot nest inside it.` : Host[Group] extends Record<string, PluginLike> ? Leaf extends keyof Host[Group] ? `'${Key}' is already attached.` : Key : `'${Group}' is already a member on the bot. Pick another group name.` : Group extends HostMember<Host> ? `'${Group}' is already a member on the bot. Pick another group name.` : Key : Key extends FrameworkChannel ? `'${Key}' is a channel the framework logs on. Pick another plugin key.` : Key extends keyof Host ? Host[Key] extends PluginLike ? `'${Key}' is already attached.` : Host[Key] extends Record<string, PluginLike> ? `'${Key}' already holds a group of plugins. Attach this one under a name of its own.` : `'${Key}' is already a member on the bot. Pick another plugin key.` : Key extends HostMember<Host> ? `'${Key}' is already a member on the bot. Pick another plugin key.` : Key;
|
|
194
197
|
type CoreParamTooNarrow = Record<'this plugin constructor must take CoreBase as its first parameter and read the transport Core off this.core', never>;
|
|
195
198
|
/** @internal */
|
|
196
199
|
type CoreParamAssert<Ctor extends PluginCtor> = CoreBase extends ConstructorParameters<Ctor>[0] ? unknown : CoreParamTooNarrow;
|
|
197
200
|
//#endregion
|
|
198
|
-
export {
|
|
199
|
-
//# sourceMappingURL=Plugin-
|
|
201
|
+
export { Plugin as a, PluginLike as c, RuntimeAssert as d, Transport as f, Initializeable as i, PluginOptions as l, PluginLifecycleSpec as m, Attached as n, PluginArgs as o, TransportAssert as p, CoreParamAssert as r, PluginCtor as s, AttachKeyAssert as t, Runtime as u };
|
|
202
|
+
//# sourceMappingURL=Plugin-D3wIj0xX.d.mts.map
|