@seedcord/core 0.11.0 → 0.12.0-next.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +3 -3
- package/dist/Bus-VJw-QpoY.mjs +986 -0
- package/dist/Bus-VJw-QpoY.mjs.map +1 -0
- package/dist/{CoordinatedShutdown-H5YjZn8A.d.mts → CoordinatedShutdown-fPKLWjc5.d.mts} +13 -4
- package/dist/{CoordinatedStartup-CGnJrqcl.mjs → CoordinatedStartup-Zw_Lk7Zk.mjs} +22 -20
- package/dist/CoordinatedStartup-Zw_Lk7Zk.mjs.map +1 -0
- package/dist/Generated-ByC53_A2.d.mts +26 -0
- package/dist/HmrModuleHandler-Btgu8r96.mjs +165 -0
- package/dist/HmrModuleHandler-Btgu8r96.mjs.map +1 -0
- package/dist/{PluginHost-DB4j8unu.d.mts → PluginHost-DFnkIldj.d.mts} +43 -15
- package/dist/PluginHost-DTtxWRiL.mjs +552 -0
- package/dist/PluginHost-DTtxWRiL.mjs.map +1 -0
- package/dist/{PluginLifecycle-AhQ46NIr.mjs → PluginLifecycle-hx5zUc-P.mjs} +4 -4
- package/dist/PluginLifecycle-hx5zUc-P.mjs.map +1 -0
- package/dist/{ServerHost-DYeh0KTk.d.mts → ServerHost-B5o3ScCF.d.mts} +7 -6
- package/dist/{ServerHost-CtmAhEXn.mjs → ServerHost-CDaEDR4C.mjs} +17 -6
- package/dist/{ServerHost-CtmAhEXn.mjs.map → ServerHost-CDaEDR4C.mjs.map} +1 -1
- package/dist/bindViteEnv-NFzvdqlT.mjs +32 -0
- package/dist/{bindViteEnv-DWq5rftJ.mjs.map → bindViteEnv-NFzvdqlT.mjs.map} +1 -1
- package/dist/env-internal.index.mjs +1 -1
- package/dist/hmr.index.d.mts +20 -20
- package/dist/hmr.index.mjs +1 -1
- package/dist/{index-Du71Xx69.d.mts → index-DGHng1IN.d.mts} +115 -201
- package/dist/index.d.mts +321 -74
- package/dist/index.mjs +323 -102
- package/dist/index.mjs.map +1 -1
- package/dist/internal.index.d.mts +223 -83
- package/dist/internal.index.mjs +573 -74
- package/dist/internal.index.mjs.map +1 -1
- package/dist/node-internal.index.d.mts +8 -4
- package/dist/node-internal.index.mjs +19 -6
- package/dist/node-internal.index.mjs.map +1 -1
- package/dist/node.index.d.mts +3 -3
- package/dist/node.index.mjs +22 -28
- package/dist/node.index.mjs.map +1 -1
- package/dist/{phases-FTM69J3a.d.mts → phases-C2QCSqRu.d.mts} +164 -55
- package/dist/plugin.index.d.mts +3 -2
- package/dist/plugin.index.mjs +1 -1
- package/dist/subscribers-CGuVjbrg.mjs +982 -0
- package/dist/subscribers-CGuVjbrg.mjs.map +1 -0
- package/dist/{bindViteEnv-DWq5rftJ.mjs → usingCtx-Cf2cZi96.mjs} +1 -31
- package/package.json +14 -11
- package/dist/CoordinatedStartup-CGnJrqcl.mjs.map +0 -1
- package/dist/HmrModuleHandler-8ZC-UuZh.mjs +0 -162
- package/dist/HmrModuleHandler-8ZC-UuZh.mjs.map +0 -1
- package/dist/PluginHost-B8kxLOeb.mjs +0 -245
- package/dist/PluginHost-B8kxLOeb.mjs.map +0 -1
- package/dist/PluginLifecycle-AhQ46NIr.mjs.map +0 -1
- package/dist/phases-BauKJOTT.mjs +0 -31
- package/dist/phases-BauKJOTT.mjs.map +0 -1
- package/dist/publishDefault-D3dAyzZ9.mjs +0 -7
- package/dist/publishDefault-D3dAyzZ9.mjs.map +0 -1
- package/dist/subscribers-Ce3SWy23.mjs +0 -1913
- package/dist/subscribers-Ce3SWy23.mjs.map +0 -1
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"Bus-VJw-QpoY.mjs","names":[],"sources":["../src/components/botColorHolder.ts","../src/components/builderTypes.ts","../src/components/colors.ts","../src/components/resolveColor.ts","../src/components/Component.ts","../src/metadataKeys.ts","../src/decorators/assertUniqueKeys.ts","../src/subscribers/publishDefault.ts","../src/lifecycle/phases.ts","../src/kv/MemoryDriver.ts","../src/dispatch/OnceTracker.ts","../src/reply/flags.ts","../src/subscribers/ReportThrottle.ts","../src/handlers/runCase.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 readBotColor: () => BotColor | undefined = () => undefined;\n\nexport function bindBotColor(read: () => BotColor | undefined): void {\n readBotColor = read;\n}\n\nexport function getBotColor(): BotColor {\n return readBotColor() ?? DEFAULT_COLOR;\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, [resolved]);\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 // this is so the bot color doesn't override an color set in this instance of the component\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 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 { SeedcordErrorCode } from '@seedcord/errors';\nimport { SeedcordTypeError } from '@seedcord/errors/internal';\n\nexport function assertUniqueKeys(\n decorator: string,\n ctor: { name: string },\n keys: readonly string[],\n fix = 'Remove one of the two tuples.'\n): void {\n const seen = new Set<string>();\n for (const key of keys) {\n if (seen.has(key)) {\n throw new SeedcordTypeError(SeedcordErrorCode.DecoratorDuplicateKey, [decorator, ctor.name, key, fix]);\n }\n seen.add(key);\n }\n}\n","/** @internal PublishableKey excludes this key so publish() never emits it externally. */\nexport const PublishDefault = Symbol('seedcord:bus:publish-default');\n","/**\n * Startup phases, in run order.\n */\nexport enum StartupPhase {\n /** Nothing connected. Plugin init runs here by default. */\n Configuration = 1,\n /** The gateway session opens, and http runs its REST-only startup work. */\n Login,\n /** Handlers are live and interactions dispatch. */\n Ready\n}\n\n/**\n * Shutdown phases, in run order.\n */\nexport enum ShutdownPhase {\n /** Stop taking new interactions and requests. */\n Unbind = 1,\n /** Waits for in-flight work to settle and stops internal services. */\n Drain,\n /** External resources close. Plugin dispose runs here by default. */\n Disconnect,\n /** The client disconnects last so in-flight handlers can still reference it during drain. */\n Logout,\n /**\n * Resources seedcord holds for the bot, like the `config.kv` driver, close after every plugin\n * has stopped. Only seedcord runs work here.\n */\n Release\n}\n\n// the phases a bot's shutdown task or a plugin's dispose() can run in\nexport type TaskShutdownPhase = Exclude<ShutdownPhase, ShutdownPhase.Release>;\n","import type { KvDriver, KvEntry } from '@seedcord/types';\n\nconst SWEEP_GAP_MS = 60_000;\n\ninterface Entry {\n value: string;\n expiresAt: number | undefined;\n}\n\n/**\n * The default `config.kv`. It keeps every key in a `Map` and loses them on restart. On serverless,\n * each running copy keeps its own.\n */\nexport class MemoryDriver implements KvDriver, Disposable {\n readonly #entries = new Map<string, Entry>();\n #lastSweep = Date.now();\n\n public get(key: string): string | undefined {\n this.#maybeSweep();\n return this.#live(key)?.value;\n }\n\n public set(key: string, value: string, ttlMs?: number): void {\n this.#maybeSweep();\n this.#entries.set(key, { value, expiresAt: ttlMs === undefined ? undefined : Date.now() + ttlMs });\n }\n\n public delete(key: string): void {\n this.#entries.delete(key);\n }\n\n public *entries(prefix: string): Iterable<KvEntry> {\n this.#maybeSweep();\n for (const key of this.#entries.keys()) {\n const entry = key.startsWith(prefix) ? this.#live(key) : undefined;\n if (entry) yield [key, entry.value];\n }\n }\n\n public [Symbol.dispose](): void {\n this.#entries.clear();\n }\n\n #live(key: string): Entry | undefined {\n const entry = this.#entries.get(key);\n if (entry?.expiresAt !== undefined && entry.expiresAt <= Date.now()) {\n this.#entries.delete(key);\n return undefined;\n }\n return entry;\n }\n\n // runs on access because a timer in global scope throws on workers\n #maybeSweep(): void {\n const now = Date.now();\n if (now - this.#lastSweep < SWEEP_GAP_MS) return;\n this.#lastSweep = now;\n for (const key of this.#entries.keys()) this.#live(key);\n }\n}\n","import type { EventFrequency } from '@seedcord/types';\n\ninterface Registered {\n readonly ctor: object;\n readonly frequency: EventFrequency;\n}\n\n// keyed by class so an hmr rollback stays spent and a save runs again\nexport class OnceTracker {\n // weak so an unloaded class can be collected\n private readonly spent = new WeakMap<object, Set<string>>();\n\n public isSpent(entry: Registered, key: string): boolean {\n return entry.frequency === 'once' && (this.spent.get(entry.ctor)?.has(key) ?? false);\n }\n\n public claim(entry: Registered, key: string): boolean {\n if (entry.frequency !== 'once') return true;\n const keys = this.spent.get(entry.ctor) ?? new Set<string>();\n if (keys.has(key)) return false;\n this.spent.set(entry.ctor, keys.add(key));\n return true;\n }\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 { SeedcordErrorCode } from '@seedcord/errors';\nimport { SeedcordTypeError } from '@seedcord/errors/internal';\n\nimport type { Promisable } from 'type-fest';\n\ntype ResolvedReturn<Case> = Case extends (...args: never[]) => infer Result ? Awaited<Result> : never;\n\n// Ret is never when the caller doesn't write one\nexport type CaseReturn<Ret> = [Ret] extends [never] ? unknown : Promisable<Ret>;\n\nexport type MatchResult<Ret, Cases> = [Ret] extends [never] ? ResolvedReturn<Cases[keyof Cases]> : Ret;\n\n// keyof reads discord.js's Events.MessageCreate key and the 'messageCreate' that an author writes as different types\ntype ShapeKey<Shape> = keyof Shape | `${Extract<keyof Shape, string>}`;\n\nexport type OnlyCases<Cases, Shape> = Cases & Record<Exclude<keyof Cases, ShapeKey<Shape>>, never>;\n\nexport async function runCase<Result>(\n handler: object,\n cases: object,\n kind: string,\n value: string,\n ...args: unknown[]\n): Promise<Result> {\n // hasOwn, since a plain lookup for a key named `constructor` returns Object.prototype's\n const callback = Object.hasOwn(cases, value) ? (cases as Record<string, unknown>)[value] : undefined;\n if (typeof callback !== 'function') {\n throw new SeedcordTypeError(SeedcordErrorCode.MatchCaseMissing, [handler.constructor.name, kind, value]);\n }\n // justified: a case's parameters and result come from the handler's generics, which the runtime key can't check\n return (await (callback as (...params: unknown[]) => unknown)(...args)) as Result;\n}\n","import { Logger } from '@seedcord/logger';\n\nimport { runCase } from '#src/handlers/runCase';\n\nimport type { CoreBase } from '#interfaces/CoreBase';\nimport type { CaseReturn, MatchResult, OnlyCases } from '#src/handlers/runCase';\nimport type { SubscriptionKey, AllSubscriptions } from './types/Subscriptions';\nimport type { IsUnion } from 'type-fest';\n\ntype SubscriberMatchCases<Keys extends SubscriptionKey, Ret> = {\n [Key in Keys]: (data: AllSubscriptions[Key]) => CaseReturn<Ret>;\n};\n\ntype SingleKeyData<Keys extends SubscriptionKey> = IsUnion<Keys> extends true ? never : AllSubscriptions[Keys];\n\n/**\n * Abstract base class for handling application subscribers.\n *\n * Pass the subscription key(s) as the generic, the same one(s) as `@Subscribe`. On a single-key subscriber, read\n * `this.data` directly. On a subscriber for several keys, branch with `this.match`, keyed by subscription key.\n *\n * @typeParam Keys - One or more subscription keys, like `'userJoin'` or `'userJoin' | 'userLeave'`.\n * @typeParam TCore - The transport's Core\n */\nexport abstract class Subscriber<in out Keys extends SubscriptionKey, TCore extends CoreBase> {\n protected readonly logger: Logger;\n readonly #payload: AllSubscriptions[Keys];\n\n constructor(\n payload: AllSubscriptions[Keys],\n protected readonly core: TCore,\n protected readonly key: Keys\n ) {\n this.#payload = payload;\n this.logger = new Logger(this.constructor.name, { channel: 'subscribers' });\n }\n\n /** The published payload. It's `never` on a subscriber for several keys, where `this.match` reads it. */\n protected get data(): SingleKeyData<Keys> {\n return this.#payload;\n }\n\n /**\n * Executes the subscriber handler logic.\n */\n abstract execute(): Promise<void>;\n\n /**\n * Run the case for the published key. Use this only when the subscriber is registered for several\n * keys. On a single-key subscriber, read `this.data` directly.\n *\n * Provide one case per registered key. Each case receives that key's own payload. A missing key or an unknown\n * key is a compile error.\n *\n * @typeParam Ret - The type that every case must return. Leave it out to get the union of what the cases return.\n * @throws A **SeedcordTypeError** with `MatchCaseMissing` when the published key doesn't have a case.\n *\n * @example\n * ```ts\n * import { Subscribe, Subscriber } from '@seedcord/gateway';\n *\n * \\@Subscribe(['interactionDispatched'], ['eventDispatched'])\n * class Metrics extends Subscriber<'interactionDispatched' | 'eventDispatched'> {\n * async execute() {\n * await this.match({\n * interactionDispatched: (data) => this.logger.info(data.routeId),\n * eventDispatched: (data) => this.logger.info(data.name)\n * });\n * }\n * }\n * ```\n */\n protected async match<Ret = never, Cases extends SubscriberMatchCases<Keys, Ret> = SubscriberMatchCases<Keys, Ret>>(\n cases: OnlyCases<Cases, SubscriberMatchCases<Keys, Ret>>\n ): Promise<MatchResult<Ret, Cases>> {\n return await runCase<MatchResult<Ret, Cases>>(this, cases, 'subscription key', this.key, this.#payload);\n }\n}\n","import { filterCirculars, stripAnsi } from '@seedcord/utils';\nimport { pluralize } from '@seedcord/utils/internal';\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 ${pluralize(count, 'failure')}`;\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.CoreWebhookUrlInvalid, [new.target.name]);\n }\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 { Envapter } from 'envapt';\n\nimport { sendFlags } from '#reply/flags';\nimport { WebhookUrlMetadataKey } from '#src/metadataKeys';\nimport { ReportThrottle } from '#subscribers/ReportThrottle';\nimport { Subscriber } from '#subscribers/Subscriber';\n\nimport { isDiscordWebhookUrl } from './webhookHelpers';\nimport { WebhookSender } from './WebhookSender';\n\nimport type { CoreBase } from '#interfaces/CoreBase';\nimport type { SubscriptionKey } from '#subscribers/types/Subscriptions';\nimport type { WebhookFile } from './WebhookSender';\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}. The base\n * resolves the url, validates it, reuses one sender per url, and sends. Registration skips a reporter\n * whose variable is unset and warns at boot.\n *\n * @typeParam KeyOfSubscribers - The subscription key or keys that this reporter receives, the same ones as `@Subscribe`\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 envKey = WebhookLog.envKeyOf(this.constructor);\n const url = WebhookLog.urlOf(envKey);\n // Bus.probeWebhook skipped every url-less reporter at registration. reaching this means the env changed since.\n if (url === null) throw new SeedcordError(SeedcordErrorCode.ConfigMissingEnv, [envKey]);\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.trace(`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 SeedcordTypeError(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 const raw = Envapter.get(envKey)?.trim();\n if (!raw) return null;\n if (!isDiscordWebhookUrl(raw)) {\n throw new SeedcordError(SeedcordErrorCode.ConfigInvalidEnv, [\n envKey,\n \"the webhook URL from the channel's Integrations settings\"\n ]);\n }\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 { assertUniqueKeys } from '#src/decorators/assertUniqueKeys';\nimport { SubscribeMetadataKey } from '#src/metadataKeys';\n\nimport type { CoreBase } from '#interfaces/CoreBase';\nimport type { Subscriber } from '#subscribers/Subscriber';\nimport type { SubscriptionKey } from '#subscribers/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\ntype SubscribeSpec<Key extends SubscriptionKey> = readonly [key: Key, options?: SubscribeOptions];\n\n/** @internal */\nexport interface SubscribeMetadataEntry {\n readonly key: SubscriptionKey;\n readonly frequency: EventFrequency;\n}\n\n/**\n * Registers a subscriber class with one or more subscription keys.\n *\n * Supply one `[key, options?]` tuple per key. The subscriber's generic must list the same keys.\n *\n * @decorator\n * @example\n * ```ts\n * \\@Subscribe(['userJoin'])\n * class WelcomeHandler extends Subscriber<'userJoin'> {\n * async execute() {\n * this.logger.info(`${this.data.userId} joined`);\n * }\n * }\n * ```\n * @example\n * ```ts\n * \\@Subscribe(['userJoin', { frequency: 'once' }], ['userLeave'])\n * class Presence extends Subscriber<'userJoin' | 'userLeave'> {\n * async execute() {\n * await this.match({\n * userJoin: (data) => this.logger.info(`${data.userId} joined`),\n * userLeave: (data) => this.logger.info(`${data.userId} left`)\n * });\n * }\n * }\n * ```\n */\nexport function Subscribe<const Defs extends readonly SubscribeSpec<SubscriptionKey>[]>(...defs: Defs) {\n type Key = Defs[number][0];\n\n // an invariant key generic on Subscriber makes this reject a mismatch in either direction\n return function <HandlerCtor extends Constructor<Subscriber<Key, CoreBase>>>(constructor: HandlerCtor): void {\n const entries = defs.map<SubscribeMetadataEntry>(([key, options]) => ({\n key,\n frequency: options?.frequency ?? 'on'\n }));\n assertUniqueKeys(\n '@Subscribe',\n constructor,\n entries.map((entry) => entry.key)\n );\n Reflect.defineMetadata(SubscribeMetadataKey, entries, constructor);\n };\n}\n","import 'reflect-metadata';\n\nimport { WebhookUrlMetadataKey } from '#src/metadataKeys';\n\nimport type { CoreBase } from '#interfaces/CoreBase';\nimport type { WebhookLog } from '#subscribers/bases/WebhookLog';\nimport type { SubscriptionKey } from '#subscribers/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 <Keys extends SubscriptionKey>(constructor: Constructor<WebhookLog<Keys, CoreBase>>): void {\n Reflect.defineMetadata(WebhookUrlMetadataKey, envKey, constructor);\n };\n}\n","import { stripAnsi } from '@seedcord/utils';\n\nimport { BuilderComponent } from '#components/Component';\nimport { breakBackticks, jsonAttachment, WebhookSeparator } from '#subscribers/bases/webhookHelpers';\nimport { WebhookLog } from '#subscribers/bases/WebhookLog';\nimport { Subscribe } from '#subscribers/decorators/Subscribe';\nimport { WebhookUrl } from '#subscribers/decorators/WebhookUrl';\n\nimport type { CoreBase } from '#interfaces/CoreBase';\nimport type { Notice } from '#stops/Notice';\nimport type { WebhookReport } from '#subscribers/bases/WebhookLog';\nimport type { AllSubscriptions, FaultSource } from '#subscribers/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';\nimport { errorReport, jsonAttachment, WebhookSeparator } from '#subscribers/bases/webhookHelpers';\nimport { WebhookLog } from '#subscribers/bases/WebhookLog';\nimport { Subscribe } from '#subscribers/decorators/Subscribe';\nimport { WebhookUrl } from '#subscribers/decorators/WebhookUrl';\n\nimport type { CoreBase } from '#interfaces/CoreBase';\nimport type { WebhookReport } from '#subscribers/bases/WebhookLog';\nimport type { AllSubscriptions } from '#subscribers/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 { OnceTracker } from '#src/dispatch/OnceTracker';\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';\nimport type { Constructor } from 'type-fest';\n\n// structural because Subscriber is invariant in its keys\n/** @internal */\nexport type StoredSubscriberCtor = Constructor<{ execute(): Promise<void> }, never[]>;\n\ninterface SubscriberRegistration {\n readonly key: SubscriptionKey;\n readonly frequency: EventFrequency;\n readonly ctor: StoredSubscriberCtor;\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 onceRuns = new OnceTracker();\n // url -> env keys\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 for (const ctor of [UnknownException, HandledException]) this[RegisterSubscriber](ctor);\n }\n\n /** @internal */\n public [RegisterSubscriber](ctor: StoredSubscriberCtor): void {\n if (!this.probeWebhook(ctor)) return;\n\n for (const registration of registrationsFor(ctor)) {\n let registrations = this.subscribersMap.get(registration.key);\n if (!registrations) {\n registrations = [];\n this.subscribersMap.set(registration.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 }\n\n /** @internal */\n public get [RegisteredCount](): number {\n return new Set([...this.subscribersMap.values()].flat().map((entry) => entry.ctor)).size;\n }\n\n /** @internal registration already skipped every reporter with no url set */\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 one throw lists every missing webhook\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)} is off because ${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.\n * Errors thrown by a subscriber or by an `on()` listener are caught and logged, never surfaced\n * here. One throwing listener does not stop the others. Subscribers on one key run concurrently\n * and carry no ordering guarantee. A `'once'` subscriber is marked as executed when it starts,\n * 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 Call {@link Bus.publish} to reach subscribers and listeners together.\n * @throws A **SeedcordTypeError** every time, 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 // claimed before awaiting so a re-entrant publish can't run it twice mid-flight\n if (!this.onceRuns.claim(entry, subscriberName)) return acc;\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\n let name = '<unresolved>';\n try {\n // data matches this ctor's payload type because the registration keyed it to its subscriptions\n const Ctor = entry.ctor as TypedConstructor<typeof Subscriber>;\n name = Ctor.name;\n await new Ctor(data, this.core, subscriberName).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 already checked that the Subscribe metadata is there\nexport function registrationsFor(ctor: StoredSubscriberCtor): SubscriberRegistration[] {\n const entries = Reflect.getMetadata(SubscribeMetadataKey, ctor) as SubscribeMetadataEntry[];\n return entries.map(({ key, frequency }) => ({ key, frequency, ctor }));\n}\n\n/** @internal */\nexport function busLoggerOf(bus: Bus): Logger {\n return bus[loggerSlot];\n}\n"],"mappings":"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"}
|
|
@@ -1,5 +1,6 @@
|
|
|
1
|
-
import { n as StartupPhase, t as ShutdownPhase } from "./phases-
|
|
1
|
+
import { n as StartupPhase, r as TaskShutdownPhase, t as ShutdownPhase } from "./phases-C2QCSqRu.mjs";
|
|
2
2
|
import { Logger } from "@seedcord/logger";
|
|
3
|
+
import { Tail } from "@seedcord/types";
|
|
3
4
|
import { Promisable } from "type-fest";
|
|
4
5
|
//#region src/lifecycle/LifecycleTypes.d.ts
|
|
5
6
|
interface LifecycleTask {
|
|
@@ -30,6 +31,8 @@ declare abstract class CoordinatedLifecycle<TPhase extends number> {
|
|
|
30
31
|
}
|
|
31
32
|
//#endregion
|
|
32
33
|
//#region src/lifecycle/CoordinatedStartup.d.ts
|
|
34
|
+
/** Adds work to a bot's startup. */
|
|
35
|
+
type StartupTasks = Pick<CoordinatedStartup, 'addTask'>;
|
|
33
36
|
declare class CoordinatedStartup extends CoordinatedLifecycle<StartupPhase> {
|
|
34
37
|
#private;
|
|
35
38
|
constructor();
|
|
@@ -49,9 +52,15 @@ declare class CoordinatedStartup extends CoordinatedLifecycle<StartupPhase> {
|
|
|
49
52
|
abort(): void;
|
|
50
53
|
/** @internal */
|
|
51
54
|
get isReady(): boolean;
|
|
55
|
+
/** @internal */
|
|
56
|
+
get hasBegun(): boolean;
|
|
52
57
|
}
|
|
53
58
|
//#endregion
|
|
54
59
|
//#region src/node/Lifecycle/CoordinatedShutdown.d.ts
|
|
60
|
+
/** Adds work to a node bot's shutdown, in any phase before `ShutdownPhase.Release`. */
|
|
61
|
+
interface ShutdownTasks {
|
|
62
|
+
addTask(phase: TaskShutdownPhase, ...rest: Tail<Parameters<CoordinatedShutdown['addTask']>>): void;
|
|
63
|
+
}
|
|
55
64
|
declare class CoordinatedShutdown extends CoordinatedLifecycle<ShutdownPhase> {
|
|
56
65
|
#private;
|
|
57
66
|
constructor(deadlineMs?: number);
|
|
@@ -65,7 +74,7 @@ declare class CoordinatedShutdown extends CoordinatedLifecycle<ShutdownPhase> {
|
|
|
65
74
|
registerSignalHandlers(): void;
|
|
66
75
|
/** @internal */
|
|
67
76
|
removeSignalHandlers(): void;
|
|
68
|
-
/** @internal
|
|
77
|
+
/** @internal */
|
|
69
78
|
gateOnStartup(settled: Promise<void>): void;
|
|
70
79
|
addTask(phase: ShutdownPhase, taskName: string, task: LifecycleTask['task'], timeoutMs?: number): void;
|
|
71
80
|
/** @internal */
|
|
@@ -74,5 +83,5 @@ declare class CoordinatedShutdown extends CoordinatedLifecycle<ShutdownPhase> {
|
|
|
74
83
|
run(exitCode?: number, exitProcess?: boolean): Promise<void>;
|
|
75
84
|
}
|
|
76
85
|
//#endregion
|
|
77
|
-
export {
|
|
78
|
-
//# sourceMappingURL=CoordinatedShutdown-
|
|
86
|
+
export { LifecycleTask as a, StartupTasks as i, ShutdownTasks as n, CoordinatedStartup as r, CoordinatedShutdown as t };
|
|
87
|
+
//# sourceMappingURL=CoordinatedShutdown-fPKLWjc5.d.mts.map
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import { r as withTimeout } from "./PluginLifecycle-
|
|
1
|
+
import { b as StartupPhase } from "./Bus-VJw-QpoY.mjs";
|
|
2
|
+
import { r as withTimeout } from "./PluginLifecycle-hx5zUc-P.mjs";
|
|
3
3
|
import { SeedcordErrorCode, paint } from "@seedcord/errors";
|
|
4
4
|
import { SeedcordAggregateError, SeedcordError, SeedcordTypeError } from "@seedcord/errors/internal";
|
|
5
5
|
import { Logger } from "@seedcord/logger";
|
|
@@ -83,8 +83,7 @@ const PHASE_ORDER = [
|
|
|
83
83
|
3
|
|
84
84
|
];
|
|
85
85
|
var CoordinatedStartup = class extends CoordinatedLifecycle {
|
|
86
|
-
#
|
|
87
|
-
#hasStarted = false;
|
|
86
|
+
#state = "idle";
|
|
88
87
|
constructor() {
|
|
89
88
|
super("Startup", PHASE_ORDER, StartupPhase);
|
|
90
89
|
}
|
|
@@ -96,12 +95,12 @@ var CoordinatedStartup = class extends CoordinatedLifecycle {
|
|
|
96
95
|
super.addTask(phase, taskName, task, timeoutMs);
|
|
97
96
|
}
|
|
98
97
|
canAddTask() {
|
|
99
|
-
if (this.#
|
|
100
|
-
if (this.#
|
|
98
|
+
if (this.#state === "finished") throw new SeedcordError(SeedcordErrorCode.LifecycleTaskChangeRejected, ["add", "finished"]);
|
|
99
|
+
if (this.#state === "running") throw new SeedcordError(SeedcordErrorCode.LifecycleTaskChangeRejected, ["add", "running"]);
|
|
101
100
|
return true;
|
|
102
101
|
}
|
|
103
102
|
canRemoveTask() {
|
|
104
|
-
if (this.#
|
|
103
|
+
if (this.#state === "running") throw new SeedcordError(SeedcordErrorCode.LifecycleTaskChangeRejected, ["remove", "running"]);
|
|
105
104
|
return true;
|
|
106
105
|
}
|
|
107
106
|
getTaskType() {
|
|
@@ -113,15 +112,15 @@ var CoordinatedStartup = class extends CoordinatedLifecycle {
|
|
|
113
112
|
}
|
|
114
113
|
/** @internal */
|
|
115
114
|
async run(afterReady) {
|
|
116
|
-
if (this.#
|
|
115
|
+
if (this.#state === "finished") {
|
|
117
116
|
this.logger.warn("Startup sequence has already completed");
|
|
118
117
|
return;
|
|
119
118
|
}
|
|
120
|
-
if (this.#
|
|
119
|
+
if (this.#state === "running") {
|
|
121
120
|
this.logger.warn("Startup sequence already in progress");
|
|
122
121
|
return;
|
|
123
122
|
}
|
|
124
|
-
this.#
|
|
123
|
+
this.#state = "running";
|
|
125
124
|
this.beginRun();
|
|
126
125
|
this.logger.info(`${paint.mint.bold("Starting")} coordinated startup sequence`);
|
|
127
126
|
const steps = PHASE_ORDER.map((phase) => () => this.runPhase(phase));
|
|
@@ -129,40 +128,43 @@ var CoordinatedStartup = class extends CoordinatedLifecycle {
|
|
|
129
128
|
try {
|
|
130
129
|
for (const step of steps) {
|
|
131
130
|
await step();
|
|
132
|
-
if (
|
|
131
|
+
if (this.isAborted()) {
|
|
133
132
|
this.logger.warn("Startup sequence aborted");
|
|
134
133
|
return;
|
|
135
134
|
}
|
|
136
135
|
}
|
|
137
|
-
this.#
|
|
136
|
+
this.#state = "finished";
|
|
138
137
|
this.logger.info(`${paint.mint.bold("Coordinated startup completed")} successfully`);
|
|
139
138
|
} catch (error) {
|
|
140
|
-
if (
|
|
139
|
+
if (this.isAborted()) {
|
|
141
140
|
this.logger.warn("Startup sequence aborted during error handling");
|
|
142
141
|
return;
|
|
143
142
|
}
|
|
143
|
+
this.#state = "stopped";
|
|
144
144
|
this.logger.error(`${paint.coral.bold("Coordinated startup failed")}`);
|
|
145
145
|
throw error;
|
|
146
|
-
} finally {
|
|
147
|
-
this.#isStartingUp = false;
|
|
148
146
|
}
|
|
149
147
|
}
|
|
150
148
|
isAborted() {
|
|
151
|
-
return
|
|
149
|
+
return this.#state !== "running";
|
|
152
150
|
}
|
|
153
151
|
/** @internal */
|
|
154
152
|
abort() {
|
|
155
|
-
if (
|
|
156
|
-
this.#
|
|
153
|
+
if (this.#state !== "running") return;
|
|
154
|
+
this.#state = "stopped";
|
|
157
155
|
this.abortRun();
|
|
158
156
|
this.logger.warn("Aborting coordinated startup sequence");
|
|
159
157
|
}
|
|
160
158
|
/** @internal */
|
|
161
159
|
get isReady() {
|
|
162
|
-
return this.#
|
|
160
|
+
return this.#state === "finished";
|
|
161
|
+
}
|
|
162
|
+
/** @internal */
|
|
163
|
+
get hasBegun() {
|
|
164
|
+
return this.#state !== "idle";
|
|
163
165
|
}
|
|
164
166
|
};
|
|
165
167
|
|
|
166
168
|
//#endregion
|
|
167
169
|
export { CoordinatedLifecycle as n, CoordinatedStartup as t };
|
|
168
|
-
//# sourceMappingURL=CoordinatedStartup-
|
|
170
|
+
//# sourceMappingURL=CoordinatedStartup-Zw_Lk7Zk.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"CoordinatedStartup-Zw_Lk7Zk.mjs","names":[],"sources":["../src/lifecycle/CoordinatedLifecycle.ts","../src/lifecycle/CoordinatedStartup.ts"],"sourcesContent":["/*\n * Inspired by Akka Coordinated Shutdown: https://doc.akka.io/libraries/akka-core/current/coordinated-shutdown.html\n * and Lewis's implementation in a private repo elsewhere (https://github.com/Yomanz)\n */\n\nimport { SeedcordErrorCode, paint } from '@seedcord/errors';\nimport { SeedcordAggregateError, SeedcordTypeError } from '@seedcord/errors/internal';\nimport { Logger } from '@seedcord/logger';\n\nimport { withTimeout } from './withTimeout';\n\nimport type { LifecycleTask } from './LifecycleTypes';\n\n// base for the startup and shutdown coordinators, runs phase-ordered tasks off a per-phase map\nexport abstract class CoordinatedLifecycle<TPhase extends number> {\n protected readonly logger: Logger;\n protected readonly tasksMap = new Map<TPhase, LifecycleTask[]>();\n #run = new AbortController();\n\n protected constructor(\n loggerName: string,\n protected readonly phaseOrder: TPhase[],\n protected readonly phaseEnum: Record<number, string>\n ) {\n this.logger = new Logger(loggerName, { channel: 'lifecycle' });\n this.phaseOrder.forEach((phase) => this.tasksMap.set(phase, []));\n }\n\n public addTask(phase: TPhase, taskName: string, task: LifecycleTask['task'], timeoutMs: number): void {\n if (!this.canAddTask()) return;\n\n const tasks = this.tasksMap.get(phase);\n if (!tasks) {\n throw new SeedcordTypeError(SeedcordErrorCode.LifecycleUnknownPhase, [phase]);\n }\n\n tasks.push({ name: taskName, task, timeout: timeoutMs });\n }\n\n public removeTask(phase: TPhase, taskName: string): boolean {\n if (!this.canRemoveTask()) return false;\n\n const tasks = this.tasksMap.get(phase);\n if (!tasks) return false;\n\n const initialLength = tasks.length;\n const filteredTasks = tasks.filter((task) => task.name !== taskName);\n this.tasksMap.set(phase, filteredTasks);\n\n return initialLength !== filteredTasks.length;\n }\n\n protected async runPhase(phase: TPhase): Promise<void> {\n const tasks = this.tasksMap.get(phase) ?? [];\n if (tasks.length === 0) {\n this.logger.trace(`No tasks to run in phase ${paint.iris.bold(this.phaseEnum[phase])}`);\n return;\n }\n\n this.logger.debug(\n `${paint.amber.bold('Running')} ${this.getTaskType()} phase ${paint.iris.bold(this.phaseEnum[phase])} with ${paint.sky.bold(String(tasks.length))} tasks`\n );\n\n const results: PromiseSettledResult<void>[] = await this.executeTasksInPhase(phase, tasks);\n\n const reasons = results.reduce<unknown[]>((rejected, result) => {\n if (result.status === 'rejected') rejected.push(result.reason as unknown);\n return rejected;\n }, []);\n if (reasons.length > 0) {\n // paint here would leak ANSI codes into the serialized error message (the unknown-exception webhook)\n throw new SeedcordAggregateError(SeedcordErrorCode.LifecyclePhaseFailures, reasons, [\n this.phaseEnum[phase],\n reasons.length\n ]);\n }\n\n this.logger.debug(\n `Phase ${paint.iris.bold(this.phaseEnum[phase])} ${paint.mint.bold('completed successfully')}`\n );\n }\n\n protected async runTaskWithTimeout(phase: TPhase, task: LifecycleTask): Promise<void> {\n this.logger.trace(\n `${paint.italic('Starting')} task ${paint.sky.bold(task.name)} in phase ${paint.iris.bold(this.phaseEnum[phase])}`\n );\n\n const stopTask = new AbortController();\n const signal = AbortSignal.any([stopTask.signal, this.#run.signal]);\n try {\n await withTimeout(task.name, () => task.task(signal), task.timeout);\n\n this.logger.trace(\n `${paint.italic('Completed')} task ${paint.sky.bold(task.name)} in phase ${paint.iris.bold(this.phaseEnum[phase])}`\n );\n } catch (error) {\n stopTask.abort(error);\n if (this.isAborted()) return;\n this.logger.error(\n `${paint.italic('Failed')} task ${paint.sky.bold(task.name)} in phase ${paint.iris.bold(this.phaseEnum[phase])}:`,\n error\n );\n throw error;\n }\n }\n\n protected isAborted(): boolean {\n return false;\n }\n\n protected beginRun(): void {\n this.#run = new AbortController();\n }\n\n protected abortRun(): void {\n this.#run.abort();\n }\n\n protected abstract canAddTask(): boolean;\n protected abstract canRemoveTask(): boolean;\n protected abstract getTaskType(): string;\n protected abstract executeTasksInPhase(\n phase: TPhase,\n tasks: LifecycleTask[]\n ): Promise<PromiseSettledResult<void>[]>;\n}\n","import { SeedcordErrorCode, paint } from '@seedcord/errors';\nimport { SeedcordError } from '@seedcord/errors/internal';\n\nimport { StartupPhase } from '#src/lifecycle/phases';\n\nimport { CoordinatedLifecycle } from './CoordinatedLifecycle';\n\nimport type { LifecycleTask } from './LifecycleTypes';\nimport type { Promisable } from 'type-fest';\n\nconst PHASE_ORDER: StartupPhase[] = [StartupPhase.Configuration, StartupPhase.Login, StartupPhase.Ready];\n\n/** Adds work to a bot's startup. */\nexport type StartupTasks = Pick<CoordinatedStartup, 'addTask'>;\n\nexport class CoordinatedStartup extends CoordinatedLifecycle<StartupPhase> {\n #state: 'idle' | 'running' | 'stopped' | 'finished' = 'idle';\n\n public constructor() {\n super('Startup', PHASE_ORDER, StartupPhase);\n }\n\n /**\n * Adds a task to a startup phase. The task gets a signal that aborts when it runs past `timeoutMs`\n * or when startup is aborted. Pass it to any request the task makes.\n */\n public override addTask(\n phase: StartupPhase,\n taskName: string,\n task: LifecycleTask['task'],\n timeoutMs = 10_000\n ): void {\n super.addTask(phase, taskName, task, timeoutMs);\n }\n\n protected canAddTask(): boolean {\n if (this.#state === 'finished') {\n throw new SeedcordError(SeedcordErrorCode.LifecycleTaskChangeRejected, ['add', 'finished']);\n }\n\n if (this.#state === 'running') {\n throw new SeedcordError(SeedcordErrorCode.LifecycleTaskChangeRejected, ['add', 'running']);\n }\n\n return true;\n }\n\n protected canRemoveTask(): boolean {\n if (this.#state === 'running') {\n throw new SeedcordError(SeedcordErrorCode.LifecycleTaskChangeRejected, ['remove', 'running']);\n }\n\n return true;\n }\n\n protected getTaskType(): string {\n return 'startup';\n }\n\n protected async executeTasksInPhase(\n phase: StartupPhase,\n tasks: LifecycleTask[]\n ): Promise<PromiseSettledResult<void>[]> {\n const promises = tasks.map((task) => this.runTaskWithTimeout(phase, task));\n return Promise.allSettled(promises);\n }\n\n // each phase completes fully before the next begins\n /** @internal */\n public async run(afterReady?: () => Promisable<void>): Promise<void> {\n if (this.#state === 'finished') {\n this.logger.warn('Startup sequence has already completed');\n return;\n }\n\n if (this.#state === 'running') {\n this.logger.warn('Startup sequence already in progress');\n return;\n }\n\n this.#state = 'running';\n this.beginRun();\n this.logger.info(`${paint.mint.bold('Starting')} coordinated startup sequence`);\n\n const steps: (() => Promisable<void>)[] = PHASE_ORDER.map((phase) => () => this.runPhase(phase));\n if (afterReady) steps.push(afterReady);\n\n try {\n for (const step of steps) {\n await step();\n if (this.isAborted()) {\n this.logger.warn('Startup sequence aborted');\n return;\n }\n }\n\n this.#state = 'finished';\n this.logger.info(`${paint.mint.bold('Coordinated startup completed')} successfully`);\n } catch (error) {\n if (this.isAborted()) {\n this.logger.warn('Startup sequence aborted during error handling');\n return;\n }\n this.#state = 'stopped';\n this.logger.error(`${paint.coral.bold('Coordinated startup failed')}`);\n throw error;\n }\n }\n\n protected override isAborted(): boolean {\n return this.#state !== 'running';\n }\n\n /** @internal */\n public abort(): void {\n if (this.#state !== 'running') return;\n\n this.#state = 'stopped';\n this.abortRun();\n this.logger.warn('Aborting coordinated startup sequence');\n }\n\n /** @internal */\n public get isReady(): boolean {\n return this.#state === 'finished';\n }\n\n /** @internal */\n public get hasBegun(): boolean {\n return this.#state !== 'idle';\n }\n}\n"],"mappings":"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"}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import { IsStringLiteral } from "type-fest";
|
|
2
|
+
//#region src/plugin/Generated.d.ts
|
|
3
|
+
/**
|
|
4
|
+
* The type of what each plugin's `codegen()` returned, keyed by the plugin class's `static id`.
|
|
5
|
+
* `seedcord codegen` writes the rows into `seedcord-gen.ts`. Read a plugin's row with {@link Generated}.
|
|
6
|
+
*/
|
|
7
|
+
interface PluginTypes {}
|
|
8
|
+
interface CodegenPluginClass {
|
|
9
|
+
readonly id: string;
|
|
10
|
+
readonly prototype: object;
|
|
11
|
+
}
|
|
12
|
+
type CodegenResultOf<PluginClass extends CodegenPluginClass> = PluginClass['prototype'] extends {
|
|
13
|
+
codegen(): infer Result;
|
|
14
|
+
} ? Awaited<Result> : never;
|
|
15
|
+
type IdNotLiteral = "This plugin's id is a plain string. Declare it as static readonly id = '<your package name>'.";
|
|
16
|
+
type FieldsSetTo<PluginClass extends CodegenPluginClass, Message> = { [Field in keyof CodegenResultOf<PluginClass>]: Message; };
|
|
17
|
+
/**
|
|
18
|
+
* The data that `codegen()` returned for a plugin class, merged across every attachment of it.
|
|
19
|
+
* Before codegen has run, it is the declared return type of `codegen()`.
|
|
20
|
+
*
|
|
21
|
+
* When the class's `id` is typed as a plain `string`, each field becomes a message that says how to fix it.
|
|
22
|
+
*/
|
|
23
|
+
type Generated<PluginClass extends CodegenPluginClass> = IsStringLiteral<PluginClass['id']> extends false ? FieldsSetTo<PluginClass, IdNotLiteral> : PluginClass['id'] extends keyof PluginTypes ? PluginTypes[PluginClass['id']] : CodegenResultOf<PluginClass>;
|
|
24
|
+
//#endregion
|
|
25
|
+
export { PluginTypes as n, Generated as t };
|
|
26
|
+
//# sourceMappingURL=Generated-ByC53_A2.d.mts.map
|
|
@@ -0,0 +1,165 @@
|
|
|
1
|
+
import { SeedcordErrorCode, isSeedcordError, paint } from "@seedcord/errors";
|
|
2
|
+
import { botFolder, vitePluginThrew } from "@seedcord/utils/internal";
|
|
3
|
+
import { formatFilePath } from "@seedcord/utils";
|
|
4
|
+
import { pathToFileURL } from "node:url";
|
|
5
|
+
|
|
6
|
+
//#region src/hmr/HmrModuleHandler.ts
|
|
7
|
+
const displayName = (unit, fallback) => typeof unit === "function" ? unit.name : fallback;
|
|
8
|
+
function unitNames({ handlers, middlewares }) {
|
|
9
|
+
return [...handlers.map((handler) => displayName(handler, "Handler")), ...middlewares.map((middleware) => displayName(middleware, "Middleware"))];
|
|
10
|
+
}
|
|
11
|
+
function hasUnits({ handlers, middlewares }) {
|
|
12
|
+
return handlers.length > 0 || middlewares.length > 0;
|
|
13
|
+
}
|
|
14
|
+
function addToFile(fileToUnits, file, unit) {
|
|
15
|
+
const units = fileToUnits.get(file);
|
|
16
|
+
if (units) units.add(unit);
|
|
17
|
+
else fileToUnits.set(file, /* @__PURE__ */ new Set([unit]));
|
|
18
|
+
}
|
|
19
|
+
/**
|
|
20
|
+
* Reloads THandler and optional TMiddleware modules on HMR updates.
|
|
21
|
+
*
|
|
22
|
+
* Call `handle()` from your `HmrAware.onHmr()` for standard reload behavior, or write a fully custom `onHmr()` and skip this class.
|
|
23
|
+
*
|
|
24
|
+
* This is only useful during development.
|
|
25
|
+
*/
|
|
26
|
+
var HmrModuleHandler = class {
|
|
27
|
+
#options;
|
|
28
|
+
#fileToHandlers = /* @__PURE__ */ new Map();
|
|
29
|
+
#fileToMiddlewares = /* @__PURE__ */ new Map();
|
|
30
|
+
#handlerArtifacts = /* @__PURE__ */ new Map();
|
|
31
|
+
#blockedFiles = /* @__PURE__ */ new Set();
|
|
32
|
+
constructor(options) {
|
|
33
|
+
this.#options = {
|
|
34
|
+
...options,
|
|
35
|
+
logger: options.logger.withChannel("hmr")
|
|
36
|
+
};
|
|
37
|
+
}
|
|
38
|
+
/**
|
|
39
|
+
* Unloads or reloads the modules affected by an {@link HmrUpdateEvent}, scoped to the handler and middleware directories.
|
|
40
|
+
* When that unloads something, it also retries every file that failed earlier on a duplicate name.
|
|
41
|
+
*/
|
|
42
|
+
async handle(event) {
|
|
43
|
+
const { file, affectedModules, type } = event;
|
|
44
|
+
if (type === "createDir") return;
|
|
45
|
+
const affected = affectedModules?.length ? affectedModules : [file];
|
|
46
|
+
const retried = affected.some((candidate) => this.#holdsUnits(candidate)) ? [...this.#blockedFiles].filter((blocked) => blocked !== file) : [];
|
|
47
|
+
if (type === "delete" || type === "deleteDir") {
|
|
48
|
+
this.#report("Unloaded", unitNames(this.#unload(file)), [file]);
|
|
49
|
+
await this.#reload(retried, event);
|
|
50
|
+
} else await this.#reload([...affected, ...retried], event);
|
|
51
|
+
}
|
|
52
|
+
/** Links the handler to its source file so a later update can unload it. */
|
|
53
|
+
trackHandler(file, handler) {
|
|
54
|
+
addToFile(this.#fileToHandlers, file, handler);
|
|
55
|
+
const { getArtifacts } = this.#options.handlers;
|
|
56
|
+
if (getArtifacts) this.#handlerArtifacts.set(handler, getArtifacts(handler));
|
|
57
|
+
}
|
|
58
|
+
/** Links the middleware to its source file so a later update can unload it. */
|
|
59
|
+
trackMiddleware(file, middleware) {
|
|
60
|
+
addToFile(this.#fileToMiddlewares, file, middleware);
|
|
61
|
+
}
|
|
62
|
+
#holdsUnits(file) {
|
|
63
|
+
return this.#fileToHandlers.has(file) || this.#fileToMiddlewares.has(file);
|
|
64
|
+
}
|
|
65
|
+
#inScope(file) {
|
|
66
|
+
const { handlers, middlewares } = this.#options;
|
|
67
|
+
if (botFolder(handlers.dir).holdsModule(file)) return true;
|
|
68
|
+
return middlewares ? botFolder(middlewares.dir).holdsModule(file) : false;
|
|
69
|
+
}
|
|
70
|
+
async #reload(candidates, { type, rollback = true }) {
|
|
71
|
+
const files = [...new Set(candidates)].filter((file) => this.#inScope(file));
|
|
72
|
+
const lastGood = new Map(files.map((file) => [file, this.#unload(file)]));
|
|
73
|
+
const startedAt = Date.now();
|
|
74
|
+
const names = [];
|
|
75
|
+
const touched = [];
|
|
76
|
+
for (const [file, units] of lastGood) {
|
|
77
|
+
const loaded = await this.#load(file, units, rollback);
|
|
78
|
+
if (loaded.length === 0) continue;
|
|
79
|
+
names.push(...loaded);
|
|
80
|
+
touched.push(file);
|
|
81
|
+
}
|
|
82
|
+
this.#report(type === "create" ? "Registered" : "Reloaded", names, touched, Date.now() - startedAt);
|
|
83
|
+
}
|
|
84
|
+
#report(verb, names, files, elapsedMs) {
|
|
85
|
+
if (names.length === 0) return;
|
|
86
|
+
const [only, ...others] = files;
|
|
87
|
+
const from = only && others.length === 0 ? formatFilePath(only) : `${String(files.length)} files`;
|
|
88
|
+
const timing = elapsedMs === void 0 ? "" : ` ${paint.mute("in")} ${String(elapsedMs)}ms`;
|
|
89
|
+
this.#options.logger.debug(`${verb} ${paint.sky.bold(names.join(", "))} from ${paint.mute(from)}${timing}`);
|
|
90
|
+
}
|
|
91
|
+
#unload(file) {
|
|
92
|
+
const handlers = [...this.#fileToHandlers.get(file) ?? []];
|
|
93
|
+
const middlewares = [...this.#fileToMiddlewares.get(file) ?? []];
|
|
94
|
+
for (const handler of handlers) {
|
|
95
|
+
this.#options.handlers.unregister(handler, this.#handlerArtifacts.get(handler));
|
|
96
|
+
this.#handlerArtifacts.delete(handler);
|
|
97
|
+
}
|
|
98
|
+
for (const middleware of middlewares) this.#options.middlewares?.unregister(middleware);
|
|
99
|
+
this.#fileToHandlers.delete(file);
|
|
100
|
+
this.#fileToMiddlewares.delete(file);
|
|
101
|
+
this.#blockedFiles.delete(file);
|
|
102
|
+
return {
|
|
103
|
+
handlers,
|
|
104
|
+
middlewares
|
|
105
|
+
};
|
|
106
|
+
}
|
|
107
|
+
async #load(file, lastGood, rollback) {
|
|
108
|
+
try {
|
|
109
|
+
const units = await this.#importUnits(file);
|
|
110
|
+
this.#register(file, units);
|
|
111
|
+
return unitNames(units);
|
|
112
|
+
} catch (error) {
|
|
113
|
+
const path = formatFilePath(file);
|
|
114
|
+
this.#options.logger.error(`Failed to reload file: ${path}`, vitePluginThrew(error) ?? error);
|
|
115
|
+
this.#abandon(file, error);
|
|
116
|
+
}
|
|
117
|
+
if (rollback && hasUnits(lastGood)) this.#rollBack(file, lastGood);
|
|
118
|
+
return [];
|
|
119
|
+
}
|
|
120
|
+
async #importUnits(file) {
|
|
121
|
+
const { handlers, middlewares } = this.#options;
|
|
122
|
+
let fileUrl = pathToFileURL(file).href;
|
|
123
|
+
if (process.env.VITEST === "true") fileUrl += `?update=${Date.now()}`;
|
|
124
|
+
const imported = await import(
|
|
125
|
+
/* @vite-ignore */
|
|
126
|
+
fileUrl
|
|
127
|
+
);
|
|
128
|
+
const units = {
|
|
129
|
+
handlers: [],
|
|
130
|
+
middlewares: []
|
|
131
|
+
};
|
|
132
|
+
for (const val of Object.values(imported)) if (handlers.is(val)) units.handlers.push(val);
|
|
133
|
+
else if (middlewares?.is(val)) units.middlewares.push(val);
|
|
134
|
+
return units;
|
|
135
|
+
}
|
|
136
|
+
#register(file, { handlers, middlewares }) {
|
|
137
|
+
for (const handler of handlers) {
|
|
138
|
+
this.#options.handlers.register(handler, file);
|
|
139
|
+
this.trackHandler(file, handler);
|
|
140
|
+
}
|
|
141
|
+
for (const middleware of middlewares) {
|
|
142
|
+
this.#options.middlewares?.register(middleware, file);
|
|
143
|
+
this.trackMiddleware(file, middleware);
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
#abandon(file, error) {
|
|
147
|
+
this.#unload(file);
|
|
148
|
+
if (isSeedcordError(error, "SeedcordTypeError", SeedcordErrorCode.CoreDuplicateName)) this.#blockedFiles.add(file);
|
|
149
|
+
}
|
|
150
|
+
#rollBack(file, lastGood) {
|
|
151
|
+
const { logger } = this.#options;
|
|
152
|
+
const path = paint.mute(formatFilePath(file));
|
|
153
|
+
try {
|
|
154
|
+
this.#register(file, lastGood);
|
|
155
|
+
logger.warn(`${paint.amber.bold("Rolled back")} ${path} to the last-good version`);
|
|
156
|
+
} catch (error) {
|
|
157
|
+
logger.error(`Failed to roll back ${path}`, error);
|
|
158
|
+
this.#abandon(file, error);
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
};
|
|
162
|
+
|
|
163
|
+
//#endregion
|
|
164
|
+
export { HmrModuleHandler as t };
|
|
165
|
+
//# sourceMappingURL=HmrModuleHandler-Btgu8r96.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"HmrModuleHandler-Btgu8r96.mjs","names":[],"sources":["../src/hmr/HmrModuleHandler.ts"],"sourcesContent":["import { pathToFileURL } from 'node:url';\n\nimport { isSeedcordError, paint, SeedcordErrorCode } from '@seedcord/errors';\nimport { formatFilePath } from '@seedcord/utils';\nimport { botFolder, vitePluginThrew } from '@seedcord/utils/internal';\n\nimport type { Logger } from '@seedcord/logger';\nimport type { HmrUpdateEvent } from '@seedcord/types';\n\ninterface HmrFolderOptions<TUnit> {\n dir: string;\n is: (val: unknown) => val is TUnit;\n register: (unit: TUnit, file: string) => void;\n}\n\ninterface HmrHandlerOptions<THandler, TArtifacts> extends HmrFolderOptions<THandler> {\n unregister: (handler: THandler, artifacts?: TArtifacts) => void;\n /** Called each time a handler is tracked. `unregister` receives the result as its second argument. */\n getArtifacts?: (handler: THandler) => TArtifacts;\n}\n\ninterface HmrMiddlewareOptions<TMiddleware> extends HmrFolderOptions<TMiddleware> {\n unregister: (middleware: TMiddleware) => void;\n}\n\ninterface HmrModuleHandlerOptions<THandler, TMiddleware = void, TArtifacts = unknown> {\n handlers: HmrHandlerOptions<THandler, TArtifacts>;\n middlewares?: HmrMiddlewareOptions<TMiddleware>;\n logger: Logger;\n}\n\ninterface FileUnits<THandler, TMiddleware> {\n handlers: THandler[];\n middlewares: TMiddleware[];\n}\n\nconst displayName = (unit: unknown, fallback: string): string => (typeof unit === 'function' ? unit.name : fallback);\n\nfunction unitNames({ handlers, middlewares }: FileUnits<unknown, unknown>): string[] {\n return [\n ...handlers.map((handler) => displayName(handler, 'Handler')),\n ...middlewares.map((middleware) => displayName(middleware, 'Middleware'))\n ];\n}\n\nfunction hasUnits({ handlers, middlewares }: FileUnits<unknown, unknown>): boolean {\n return handlers.length > 0 || middlewares.length > 0;\n}\n\nfunction addToFile<TUnit>(fileToUnits: Map<string, Set<TUnit>>, file: string, unit: TUnit): void {\n const units = fileToUnits.get(file);\n if (units) units.add(unit);\n else fileToUnits.set(file, new Set([unit]));\n}\n\n/**\n * Reloads THandler and optional TMiddleware modules on HMR updates.\n *\n * Call `handle()` from your `HmrAware.onHmr()` for standard reload behavior, or write a fully custom `onHmr()` and skip this class.\n *\n * This is only useful during development.\n */\nexport class HmrModuleHandler<THandler, TMiddleware = void, TArtifacts = unknown> {\n readonly #options: HmrModuleHandlerOptions<THandler, TMiddleware, TArtifacts>;\n // no import.meta.hot.data stash needed because a leaf reload never reconstructs the singleton holding these Maps\n readonly #fileToHandlers = new Map<string, Set<THandler>>();\n readonly #fileToMiddlewares = new Map<string, Set<TMiddleware>>();\n readonly #handlerArtifacts = new Map<THandler, TArtifacts>();\n // a moved handler's new file can load before its old file unloads\n readonly #blockedFiles = new Set<string>();\n\n constructor(options: HmrModuleHandlerOptions<THandler, TMiddleware, TArtifacts>) {\n this.#options = { ...options, logger: options.logger.withChannel('hmr') };\n }\n\n /**\n * Unloads or reloads the modules affected by an {@link HmrUpdateEvent}, scoped to the handler and middleware directories.\n * When that unloads something, it also retries every file that failed earlier on a duplicate name.\n */\n public async handle(event: HmrUpdateEvent): Promise<void> {\n const { file, affectedModules, type } = event;\n\n // a directory is never importable. its files arrive as their own create events\n if (type === 'createDir') return;\n\n const affected = affectedModules?.length ? affectedModules : [file];\n const freesNames = affected.some((candidate) => this.#holdsUnits(candidate));\n const retried = freesNames ? [...this.#blockedFiles].filter((blocked) => blocked !== file) : [];\n\n if (type === 'delete' || type === 'deleteDir') {\n this.#report('Unloaded', unitNames(this.#unload(file)), [file]);\n await this.#reload(retried, event);\n } else {\n await this.#reload([...affected, ...retried], event);\n }\n }\n\n /** Links the handler to its source file so a later update can unload it. */\n public trackHandler(file: string, handler: THandler): void {\n addToFile(this.#fileToHandlers, file, handler);\n\n const { getArtifacts } = this.#options.handlers;\n if (getArtifacts) this.#handlerArtifacts.set(handler, getArtifacts(handler));\n }\n\n /** Links the middleware to its source file so a later update can unload it. */\n public trackMiddleware(file: string, middleware: TMiddleware): void {\n addToFile(this.#fileToMiddlewares, file, middleware);\n }\n\n #holdsUnits(file: string): boolean {\n return this.#fileToHandlers.has(file) || this.#fileToMiddlewares.has(file);\n }\n\n #inScope(file: string): boolean {\n const { handlers, middlewares } = this.#options;\n\n if (botFolder(handlers.dir).holdsModule(file)) return true;\n return middlewares ? botFolder(middlewares.dir).holdsModule(file) : false;\n }\n\n async #reload(candidates: Iterable<string>, { type, rollback = true }: HmrUpdateEvent): Promise<void> {\n const files = [...new Set(candidates)].filter((file) => this.#inScope(file));\n // every file unloads before any file loads, so no file in this save can block another\n const lastGood = new Map(files.map((file) => [file, this.#unload(file)]));\n\n const startedAt = Date.now();\n const names: string[] = [];\n const touched: string[] = [];\n\n for (const [file, units] of lastGood) {\n const loaded = await this.#load(file, units, rollback);\n if (loaded.length === 0) continue;\n\n names.push(...loaded);\n touched.push(file);\n }\n\n this.#report(type === 'create' ? 'Registered' : 'Reloaded', names, touched, Date.now() - startedAt);\n }\n\n // one line per save, since one edit can reload every module that imports the file\n #report(verb: string, names: string[], files: string[], elapsedMs?: number): void {\n if (names.length === 0) return;\n\n const [only, ...others] = files;\n const from = only && others.length === 0 ? formatFilePath(only) : `${String(files.length)} files`;\n const timing = elapsedMs === undefined ? '' : ` ${paint.mute('in')} ${String(elapsedMs)}ms`;\n this.#options.logger.debug(`${verb} ${paint.sky.bold(names.join(', '))} from ${paint.mute(from)}${timing}`);\n }\n\n // these are the same class objects, which is how a live db connection survives a rollback\n #unload(file: string): FileUnits<THandler, TMiddleware> {\n const handlers = [...(this.#fileToHandlers.get(file) ?? [])];\n const middlewares = [...(this.#fileToMiddlewares.get(file) ?? [])];\n\n for (const handler of handlers) {\n this.#options.handlers.unregister(handler, this.#handlerArtifacts.get(handler));\n this.#handlerArtifacts.delete(handler);\n }\n for (const middleware of middlewares) {\n this.#options.middlewares?.unregister(middleware);\n }\n\n this.#fileToHandlers.delete(file);\n this.#fileToMiddlewares.delete(file);\n this.#blockedFiles.delete(file);\n return { handlers, middlewares };\n }\n\n async #load(file: string, lastGood: FileUnits<THandler, TMiddleware>, rollback: boolean): Promise<string[]> {\n try {\n const units = await this.#importUnits(file);\n this.#register(file, units);\n return unitNames(units);\n } catch (error) {\n const path = formatFilePath(file);\n this.#options.logger.error(`Failed to reload file: ${path}`, vitePluginThrew(error) ?? error);\n this.#abandon(file, error);\n }\n\n if (rollback && hasUnits(lastGood)) this.#rollBack(file, lastGood);\n return [];\n }\n\n async #importUnits(file: string): Promise<FileUnits<THandler, TMiddleware>> {\n const { handlers, middlewares } = this.#options;\n\n let fileUrl = pathToFileURL(file).href;\n // vitest has no real vite server managing the module graph\n if (process.env.VITEST === 'true') fileUrl += `?update=${Date.now()}`;\n\n // justified: a dynamic import resolves to an untyped export map\n const imported = (await import(/* @vite-ignore */ fileUrl)) as Record<string, unknown>;\n\n const units: FileUnits<THandler, TMiddleware> = { handlers: [], middlewares: [] };\n for (const val of Object.values(imported)) {\n if (handlers.is(val)) units.handlers.push(val);\n else if (middlewares?.is(val)) units.middlewares.push(val);\n }\n return units;\n }\n\n #register(file: string, { handlers, middlewares }: FileUnits<THandler, TMiddleware>): void {\n for (const handler of handlers) {\n this.#options.handlers.register(handler, file);\n this.trackHandler(file, handler);\n }\n for (const middleware of middlewares) {\n this.#options.middlewares?.register(middleware, file);\n this.trackMiddleware(file, middleware);\n }\n }\n\n // a failed registration can leave part of the file registered\n #abandon(file: string, error: unknown): void {\n this.#unload(file);\n if (isSeedcordError(error, 'SeedcordTypeError', SeedcordErrorCode.CoreDuplicateName)) {\n this.#blockedFiles.add(file);\n }\n }\n\n // a typo keeps the last-good units live until the next good save\n #rollBack(file: string, lastGood: FileUnits<THandler, TMiddleware>): void {\n const { logger } = this.#options;\n const path = paint.mute(formatFilePath(file));\n\n try {\n this.#register(file, lastGood);\n logger.warn(`${paint.amber.bold('Rolled back')} ${path} to the last-good version`);\n } catch (error) {\n logger.error(`Failed to roll back ${path}`, error);\n this.#abandon(file, error);\n }\n }\n}\n"],"mappings":";;;;;;AAoCA,MAAM,eAAe,MAAe,aAA8B,OAAO,SAAS,aAAa,KAAK,OAAO;AAE3G,SAAS,UAAU,EAAE,UAAU,eAAsD;CACjF,OAAO,CACH,GAAG,SAAS,KAAK,YAAY,YAAY,SAAS,SAAS,CAAC,GAC5D,GAAG,YAAY,KAAK,eAAe,YAAY,YAAY,YAAY,CAAC,CAC5E;AACJ;AAEA,SAAS,SAAS,EAAE,UAAU,eAAqD;CAC/E,OAAO,SAAS,SAAS,KAAK,YAAY,SAAS;AACvD;AAEA,SAAS,UAAiB,aAAsC,MAAc,MAAmB;CAC7F,MAAM,QAAQ,YAAY,IAAI,IAAI;CAClC,IAAI,OAAO,MAAM,IAAI,IAAI;MACpB,YAAY,IAAI,sBAAM,IAAI,IAAI,CAAC,IAAI,CAAC,CAAC;AAC9C;;;;;;;;AASA,IAAa,mBAAb,MAAkF;CAC9E,AAAS;CAET,AAAS,kCAAkB,IAAI,IAA2B;CAC1D,AAAS,qCAAqB,IAAI,IAA8B;CAChE,AAAS,oCAAoB,IAAI,IAA0B;CAE3D,AAAS,gCAAgB,IAAI,IAAY;CAEzC,YAAY,SAAqE;EAC7E,KAAK,WAAW;GAAE,GAAG;GAAS,QAAQ,QAAQ,OAAO,YAAY,KAAK;EAAE;CAC5E;;;;;CAMA,MAAa,OAAO,OAAsC;EACtD,MAAM,EAAE,MAAM,iBAAiB,SAAS;EAGxC,IAAI,SAAS,aAAa;EAE1B,MAAM,WAAW,iBAAiB,SAAS,kBAAkB,CAAC,IAAI;EAElE,MAAM,UADa,SAAS,MAAM,cAAc,KAAK,YAAY,SAAS,CACjD,IAAI,CAAC,GAAG,KAAK,aAAa,CAAC,CAAC,QAAQ,YAAY,YAAY,IAAI,IAAI,CAAC;EAE9F,IAAI,SAAS,YAAY,SAAS,aAAa;GAC3C,KAAK,QAAQ,YAAY,UAAU,KAAK,QAAQ,IAAI,CAAC,GAAG,CAAC,IAAI,CAAC;GAC9D,MAAM,KAAK,QAAQ,SAAS,KAAK;EACrC,OACI,MAAM,KAAK,QAAQ,CAAC,GAAG,UAAU,GAAG,OAAO,GAAG,KAAK;CAE3D;;CAGA,AAAO,aAAa,MAAc,SAAyB;EACvD,UAAU,KAAK,iBAAiB,MAAM,OAAO;EAE7C,MAAM,EAAE,iBAAiB,KAAK,SAAS;EACvC,IAAI,cAAc,KAAK,kBAAkB,IAAI,SAAS,aAAa,OAAO,CAAC;CAC/E;;CAGA,AAAO,gBAAgB,MAAc,YAA+B;EAChE,UAAU,KAAK,oBAAoB,MAAM,UAAU;CACvD;CAEA,YAAY,MAAuB;EAC/B,OAAO,KAAK,gBAAgB,IAAI,IAAI,KAAK,KAAK,mBAAmB,IAAI,IAAI;CAC7E;CAEA,SAAS,MAAuB;EAC5B,MAAM,EAAE,UAAU,gBAAgB,KAAK;EAEvC,IAAI,UAAU,SAAS,GAAG,CAAC,CAAC,YAAY,IAAI,GAAG,OAAO;EACtD,OAAO,cAAc,UAAU,YAAY,GAAG,CAAC,CAAC,YAAY,IAAI,IAAI;CACxE;CAEA,MAAM,QAAQ,YAA8B,EAAE,MAAM,WAAW,QAAuC;EAClG,MAAM,QAAQ,CAAC,GAAG,IAAI,IAAI,UAAU,CAAC,CAAC,CAAC,QAAQ,SAAS,KAAK,SAAS,IAAI,CAAC;EAE3E,MAAM,WAAW,IAAI,IAAI,MAAM,KAAK,SAAS,CAAC,MAAM,KAAK,QAAQ,IAAI,CAAC,CAAC,CAAC;EAExE,MAAM,YAAY,KAAK,IAAI;EAC3B,MAAM,QAAkB,CAAC;EACzB,MAAM,UAAoB,CAAC;EAE3B,KAAK,MAAM,CAAC,MAAM,UAAU,UAAU;GAClC,MAAM,SAAS,MAAM,KAAK,MAAM,MAAM,OAAO,QAAQ;GACrD,IAAI,OAAO,WAAW,GAAG;GAEzB,MAAM,KAAK,GAAG,MAAM;GACpB,QAAQ,KAAK,IAAI;EACrB;EAEA,KAAK,QAAQ,SAAS,WAAW,eAAe,YAAY,OAAO,SAAS,KAAK,IAAI,IAAI,SAAS;CACtG;CAGA,QAAQ,MAAc,OAAiB,OAAiB,WAA0B;EAC9E,IAAI,MAAM,WAAW,GAAG;EAExB,MAAM,CAAC,MAAM,GAAG,UAAU;EAC1B,MAAM,OAAO,QAAQ,OAAO,WAAW,IAAI,eAAe,IAAI,IAAI,GAAG,OAAO,MAAM,MAAM,EAAE;EAC1F,MAAM,SAAS,cAAc,SAAY,KAAK,IAAI,MAAM,KAAK,IAAI,EAAE,GAAG,OAAO,SAAS,EAAE;EACxF,KAAK,SAAS,OAAO,MAAM,GAAG,KAAK,GAAG,MAAM,IAAI,KAAK,MAAM,KAAK,IAAI,CAAC,EAAE,QAAQ,MAAM,KAAK,IAAI,IAAI,QAAQ;CAC9G;CAGA,QAAQ,MAAgD;EACpD,MAAM,WAAW,CAAC,GAAI,KAAK,gBAAgB,IAAI,IAAI,KAAK,CAAC,CAAE;EAC3D,MAAM,cAAc,CAAC,GAAI,KAAK,mBAAmB,IAAI,IAAI,KAAK,CAAC,CAAE;EAEjE,KAAK,MAAM,WAAW,UAAU;GAC5B,KAAK,SAAS,SAAS,WAAW,SAAS,KAAK,kBAAkB,IAAI,OAAO,CAAC;GAC9E,KAAK,kBAAkB,OAAO,OAAO;EACzC;EACA,KAAK,MAAM,cAAc,aACrB,KAAK,SAAS,aAAa,WAAW,UAAU;EAGpD,KAAK,gBAAgB,OAAO,IAAI;EAChC,KAAK,mBAAmB,OAAO,IAAI;EACnC,KAAK,cAAc,OAAO,IAAI;EAC9B,OAAO;GAAE;GAAU;EAAY;CACnC;CAEA,MAAM,MAAM,MAAc,UAA4C,UAAsC;EACxG,IAAI;GACA,MAAM,QAAQ,MAAM,KAAK,aAAa,IAAI;GAC1C,KAAK,UAAU,MAAM,KAAK;GAC1B,OAAO,UAAU,KAAK;EAC1B,SAAS,OAAO;GACZ,MAAM,OAAO,eAAe,IAAI;GAChC,KAAK,SAAS,OAAO,MAAM,0BAA0B,QAAQ,gBAAgB,KAAK,KAAK,KAAK;GAC5F,KAAK,SAAS,MAAM,KAAK;EAC7B;EAEA,IAAI,YAAY,SAAS,QAAQ,GAAG,KAAK,UAAU,MAAM,QAAQ;EACjE,OAAO,CAAC;CACZ;CAEA,MAAM,aAAa,MAAyD;EACxE,MAAM,EAAE,UAAU,gBAAgB,KAAK;EAEvC,IAAI,UAAU,cAAc,IAAI,CAAC,CAAC;EAElC,IAAI,QAAQ,IAAI,WAAW,QAAQ,WAAW,WAAW,KAAK,IAAI;EAGlE,MAAM,WAAY,MAAM;;GAA0B;;EAElD,MAAM,QAA0C;GAAE,UAAU,CAAC;GAAG,aAAa,CAAC;EAAE;EAChF,KAAK,MAAM,OAAO,OAAO,OAAO,QAAQ,GACpC,IAAI,SAAS,GAAG,GAAG,GAAG,MAAM,SAAS,KAAK,GAAG;OACxC,IAAI,aAAa,GAAG,GAAG,GAAG,MAAM,YAAY,KAAK,GAAG;EAE7D,OAAO;CACX;CAEA,UAAU,MAAc,EAAE,UAAU,eAAuD;EACvF,KAAK,MAAM,WAAW,UAAU;GAC5B,KAAK,SAAS,SAAS,SAAS,SAAS,IAAI;GAC7C,KAAK,aAAa,MAAM,OAAO;EACnC;EACA,KAAK,MAAM,cAAc,aAAa;GAClC,KAAK,SAAS,aAAa,SAAS,YAAY,IAAI;GACpD,KAAK,gBAAgB,MAAM,UAAU;EACzC;CACJ;CAGA,SAAS,MAAc,OAAsB;EACzC,KAAK,QAAQ,IAAI;EACjB,IAAI,gBAAgB,OAAO,qBAAqB,kBAAkB,iBAAiB,GAC/E,KAAK,cAAc,IAAI,IAAI;CAEnC;CAGA,UAAU,MAAc,UAAkD;EACtE,MAAM,EAAE,WAAW,KAAK;EACxB,MAAM,OAAO,MAAM,KAAK,eAAe,IAAI,CAAC;EAE5C,IAAI;GACA,KAAK,UAAU,MAAM,QAAQ;GAC7B,OAAO,KAAK,GAAG,MAAM,MAAM,KAAK,aAAa,EAAE,GAAG,KAAK,0BAA0B;EACrF,SAAS,OAAO;GACZ,OAAO,MAAM,uBAAuB,QAAQ,KAAK;GACjD,KAAK,SAAS,MAAM,KAAK;EAC7B;CACJ;AACJ"}
|