@seedcord/core 0.7.0 → 0.9.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/{Pluggable-keys1cVr.mjs → Pluggable-BShEwGw9.mjs} +37 -5
- package/dist/Pluggable-BShEwGw9.mjs.map +1 -0
- package/dist/{Pluggable-CqKvMXm5.d.mts → Pluggable-CAteNmfy.d.mts} +10 -4
- package/dist/Plugin-BJfRk0s_.mjs.map +1 -1
- package/dist/{Plugin-BdqfRjU0.d.mts → Plugin-D3wIj0xX.d.mts} +7 -4
- package/dist/{cursor-BY5dmiz6.d.mts → cursor-BPaPFTic.d.mts} +67 -9
- package/dist/index.d.mts +2 -2
- package/dist/index.mjs +3 -23
- package/dist/index.mjs.map +1 -1
- package/dist/internal.index.d.mts +2 -2
- package/dist/node-internal.index.d.mts +4 -2
- package/dist/node-internal.index.mjs +36 -12
- package/dist/node-internal.index.mjs.map +1 -1
- package/dist/node.index.d.mts +1 -1
- package/dist/node.index.mjs +1 -1
- package/dist/plugin.index.d.mts +1 -1
- package/dist/{subscribers-Bf4cBTvK.mjs → subscribers-kAQQt_YP.mjs} +32 -4
- package/dist/subscribers-kAQQt_YP.mjs.map +1 -0
- package/package.json +13 -13
- package/dist/Pluggable-keys1cVr.mjs.map +0 -1
- package/dist/subscribers-Bf4cBTvK.mjs.map +0 -1
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
import { a as RegisterSubscriber, c as VerifyWebhooks, f as Subscriber, g as CommandMetadataKey, i as RegisterDefaults, k as contextMenuLeaves, n as busLoggerOf, o as RegisteredCount, r as registrationFor, s as UnregisterSubscriber, w as SubscribeMetadataKey } from "./Bus-Djm6688b.mjs";
|
|
2
|
-
import {
|
|
2
|
+
import { r as CommandInjector, t as ConfigGuilds } from "./subscribers-kAQQt_YP.mjs";
|
|
3
3
|
import { t as PublishDefault } from "./publishDefault-BuSZPbjT.mjs";
|
|
4
4
|
import { n as StartupPhase, t as ShutdownPhase } from "./phases-BauKJOTT.mjs";
|
|
5
5
|
import { t as getDevChannel } from "./devChannel-CQU7hq7Q.mjs";
|
|
6
6
|
import { n as isCommandClass, t as slashRouteLeaves } from "./slashRouteLeaves-DTFHzCKN.mjs";
|
|
7
7
|
import { t as HmrModuleHandler } from "./HmrModuleHandler-9AUfKXRl.mjs";
|
|
8
|
-
import { i as settleWithin, n as shutdownOf, r as assertNodeVersion } from "./Pluggable-
|
|
8
|
+
import { i as settleWithin, n as shutdownOf, r as assertNodeVersion } from "./Pluggable-BShEwGw9.mjs";
|
|
9
9
|
import { SlashCommandBuilder } from "@discordjs/builders";
|
|
10
10
|
import { Routes } from "discord-api-types/v10";
|
|
11
11
|
import { SeedcordErrorCode, paint } from "@seedcord/errors";
|
|
@@ -180,6 +180,21 @@ var SubscriberLoader = class {
|
|
|
180
180
|
}
|
|
181
181
|
};
|
|
182
182
|
|
|
183
|
+
//#endregion
|
|
184
|
+
//#region src/commands/deployTarget.ts
|
|
185
|
+
function resolveDeployTarget(meta, configured, commandName) {
|
|
186
|
+
if (meta.scope === "global") return { scope: "global" };
|
|
187
|
+
if (meta.scope === "config") return configured.length > 0 ? guildTarget(configured) : { scope: "global" };
|
|
188
|
+
if (configured.length === 0 && meta.guilds.includes("config")) throw new SeedcordError(SeedcordErrorCode.CoreCommandGuildsEmpty, [commandName]);
|
|
189
|
+
return guildTarget(meta.guilds.flatMap((target) => target === "config" ? configured : target));
|
|
190
|
+
}
|
|
191
|
+
function guildTarget(guilds) {
|
|
192
|
+
return {
|
|
193
|
+
scope: "guild",
|
|
194
|
+
guilds: [...new Set(guilds)]
|
|
195
|
+
};
|
|
196
|
+
}
|
|
197
|
+
|
|
183
198
|
//#endregion
|
|
184
199
|
//#region src/node/commands/CommandRegistry.ts
|
|
185
200
|
function indexById(commands) {
|
|
@@ -203,14 +218,16 @@ var CommandRegistry = class {
|
|
|
203
218
|
pendingEvents = /* @__PURE__ */ new Map();
|
|
204
219
|
injector = new CommandInjector();
|
|
205
220
|
dir;
|
|
221
|
+
configuredGuilds;
|
|
206
222
|
constructor(core) {
|
|
207
223
|
this.core = core;
|
|
208
|
-
const
|
|
209
|
-
if (!
|
|
210
|
-
this.dir =
|
|
224
|
+
const { commands } = core.config.bot;
|
|
225
|
+
if (!commands.path) throw new SeedcordError(SeedcordErrorCode.CoreControllerPathMissing, ["CommandRegistry", "commands"]);
|
|
226
|
+
this.dir = commands.path;
|
|
227
|
+
this.configuredGuilds = commands.guilds ?? [];
|
|
211
228
|
if (!Envapter.isDevelopment && !Envapter.isTest) return;
|
|
212
229
|
this.hmrHandler = new HmrModuleHandler({
|
|
213
|
-
handlersDir: dir,
|
|
230
|
+
handlersDir: this.dir,
|
|
214
231
|
isHandler: isCommandClass,
|
|
215
232
|
registerHandler: this.registerCommand.bind(this),
|
|
216
233
|
unregisterHandler: this.unregisterCommand.bind(this),
|
|
@@ -285,16 +302,16 @@ var CommandRegistry = class {
|
|
|
285
302
|
if (!meta) return;
|
|
286
303
|
const comp = new Ctor().component;
|
|
287
304
|
const kind = comp instanceof SlashCommandBuilder ? "slash command" : "context menu";
|
|
288
|
-
|
|
289
|
-
|
|
305
|
+
const target = resolveDeployTarget(meta, this.configuredGuilds, Ctor.name);
|
|
306
|
+
if (target.scope === "global") this.globalCommands.push(comp);
|
|
307
|
+
else for (const g of target.guilds) {
|
|
290
308
|
const arr = this.guildCommands.get(g) ?? [];
|
|
291
309
|
arr.push(comp);
|
|
292
310
|
this.guildCommands.set(g, arr);
|
|
293
311
|
}
|
|
294
312
|
this.ctorToCommand.set(Ctor, {
|
|
295
313
|
name: comp.name,
|
|
296
|
-
|
|
297
|
-
...meta.scope === "guild" && { guilds: meta.guilds }
|
|
314
|
+
...target
|
|
298
315
|
});
|
|
299
316
|
if (this.loading) this.loadedCommands.push({
|
|
300
317
|
name: comp.name,
|
|
@@ -308,7 +325,7 @@ var CommandRegistry = class {
|
|
|
308
325
|
if (info.scope === "global") {
|
|
309
326
|
const idx = this.globalCommands.findIndex((c) => c.name === info.name);
|
|
310
327
|
if (idx !== -1) this.globalCommands.splice(idx, 1);
|
|
311
|
-
} else for (const g of info.guilds
|
|
328
|
+
} else for (const g of info.guilds) {
|
|
312
329
|
const arr = this.guildCommands.get(g);
|
|
313
330
|
if (arr) {
|
|
314
331
|
const idx = arr.findIndex((c) => c.name === info.name);
|
|
@@ -330,7 +347,7 @@ var CommandRegistry = class {
|
|
|
330
347
|
this.logger.utils.block(`Deployed ${this.globalCommands.length} global ${tag}`, this.logger.utils.wrap(this.globalCommands.map((command) => command.name)), "info");
|
|
331
348
|
}
|
|
332
349
|
for (const [guildId, commands] of this.guildCommands.entries()) {
|
|
333
|
-
const deployed = await this.
|
|
350
|
+
const deployed = await this.putGuild(appId, guildId, commands);
|
|
334
351
|
result.guilds.set(guildId, indexById(deployed));
|
|
335
352
|
const tag = commands.length === 1 ? "command" : "commands";
|
|
336
353
|
this.logger.utils.block(`Deployed ${paint.iris.bold(commands.length)} ${tag} to ${paint.sky.bold(guildId)}`, this.logger.utils.wrap(commands.map((command) => command.name)), "info");
|
|
@@ -339,6 +356,13 @@ var CommandRegistry = class {
|
|
|
339
356
|
this.core.bus[PublishDefault]("commandsDeployed", deployedPayload(result));
|
|
340
357
|
return result;
|
|
341
358
|
}
|
|
359
|
+
async putGuild(appId, guildId, commands) {
|
|
360
|
+
try {
|
|
361
|
+
return await this.put(Routes.applicationGuildCommands(appId, guildId), commands);
|
|
362
|
+
} catch (error) {
|
|
363
|
+
throw new SeedcordError(SeedcordErrorCode.CoreCommandGuildDeployFailed, [guildId], { cause: error });
|
|
364
|
+
}
|
|
365
|
+
}
|
|
342
366
|
async put(route, commands) {
|
|
343
367
|
const body = commands.map((command) => command.toJSON());
|
|
344
368
|
const { rest } = this.core;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"node-internal.index.mjs","names":[],"sources":["../src/node/Lifecycle/drainWindow.ts","../src/node/HealthResponder.ts","../src/node/HealthCheck.ts","../src/node/subscribers/SubscriberLoader.ts","../src/node/commands/CommandRegistry.ts","../src/node-internal.index.ts"],"sourcesContent":["import { settleWithin } from './withTimeout';\n\nimport type { Logger } from '@seedcord/logger';\n\n// gateway already used 5s. discord's 3s first-ack deadline is the floor.\nexport const DRAIN_WINDOW_MS = 5000;\n\nconst DRAIN_HEADROOM_MS = 1000;\nexport const DRAIN_TASK_TIMEOUT_MS = DRAIN_WINDOW_MS + DRAIN_HEADROOM_MS;\n\nexport async function drainInFlight(\n inFlight: ReadonlySet<Promise<unknown>>,\n timeoutMs: number,\n logger: Logger,\n label: string\n): Promise<void> {\n await settleWithin(Promise.allSettled(inFlight), timeoutMs);\n // nothing cancels these before the process exits\n if (inFlight.size > 0) logger.warn(`${label}: ${String(inFlight.size)} still running when the drain window closed`);\n}\n","import type { IncomingMessage, ServerResponse } from 'node:http';\n\nconst HTTP_OK = 200;\nconst DEFAULT_HEALTH_CHECK_PATH = '/health';\n\nexport class HealthResponder {\n public readonly path: string;\n\n constructor(path?: string) {\n this.path = path ?? DEFAULT_HEALTH_CHECK_PATH;\n }\n\n public tryRespond(req: IncomingMessage, res: ServerResponse): boolean {\n if (req.method !== 'GET' || req.url !== this.path) return false;\n\n res.writeHead(HTTP_OK, { 'Content-Type': 'application/json' });\n res.end(JSON.stringify({ status: 'ok', timestamp: Date.now() }));\n return true;\n }\n}\n","import { createServer } from 'node:http';\n\nimport { paint } from '@seedcord/errors';\nimport { Logger } from '@seedcord/logger';\n\nimport { ShutdownPhase } from '#src/lifecycle/phases';\n\nimport { HealthResponder } from './HealthResponder';\n\nimport type { CoordinatedShutdown } from './Lifecycle/CoordinatedShutdown';\nimport type { HealthCheckConfig, HealthCheckOption } from '@seedcord/types';\nimport type { IncomingMessage, Server, ServerResponse } from 'node:http';\n\nconst HTTP_NOT_FOUND = 404;\n\nconst DEFAULT_HEALTH_CHECK_PORT = 6967;\n\nexport class HealthCheck {\n public readonly logger = new Logger('HealthCheck', { channel: 'health' });\n\n private readonly _port: number;\n private readonly _host: string | undefined;\n\n private readonly responder: HealthResponder;\n private server?: Server;\n\n constructor(shutdown: CoordinatedShutdown, options?: HealthCheckConfig) {\n this._port = options?.port ?? DEFAULT_HEALTH_CHECK_PORT;\n this._host = options?.host;\n this.responder = new HealthResponder(options?.path);\n\n shutdown.addTask(ShutdownPhase.Drain, 'stop-healthcheck-server', () => this.stop());\n }\n\n public get port(): number {\n return this._port;\n }\n\n public get host(): string | undefined {\n return this._host;\n }\n\n public get path(): string {\n return this.responder.path;\n }\n\n public static fromOption(shutdown: CoordinatedShutdown, option?: HealthCheckOption): HealthCheck | undefined {\n if (option === false) return undefined;\n return new HealthCheck(shutdown, typeof option === 'object' ? option : undefined);\n }\n\n public async init(): Promise<void> {\n return new Promise<void>((resolve, reject) => {\n const server = createServer((req: IncomingMessage, res: ServerResponse) => {\n if (this.responder.tryRespond(req, res)) return;\n\n res.writeHead(HTTP_NOT_FOUND, { 'Content-Type': 'application/json' });\n res.end(JSON.stringify({ status: 'not found' }));\n });\n this.server = server;\n\n const onListenError = (err: Error): void => reject(err);\n server.on('error', onListenError);\n\n server.once('listening', () => {\n // a late error would reject an already-settled promise, and node throws process-wide without a listener here\n server.removeListener('error', onListenError);\n server.on('error', (err) => this.logger.error('Health check server error', err));\n\n // binds all interfaces, so log an address a browser can open\n const address = this._host ?? 'localhost';\n this.logger.info(\n `${paint.mint.bold('✔︎')} Health check server listening on ${paint.sky.bold(`http://${address}:${this._port}${this.path}`)}`\n );\n resolve();\n });\n\n if (this._host) {\n this.logger.debug(`Binding health check server to ${this._host}`);\n server.listen(this._port, this._host);\n } else {\n this.logger.debug('Binding health check server to all interfaces');\n server.listen(this._port);\n }\n });\n }\n\n public stop(): Promise<void> {\n const server = this.server;\n // close() on a non-listening server invokes its callback with ERR_SERVER_NOT_RUNNING and\n // never fires 'close', so without this guard the promise would hang the shutdown phase.\n if (!server?.listening) return Promise.resolve();\n\n return new Promise((resolve, reject) => {\n // settle only in the callback, a 'close'-event resolve would swallow the callback's error\n server.close((err) => {\n if (err) {\n reject(err);\n return;\n }\n this.logger.info(paint.coral.bold('Health check server stopped'));\n resolve();\n });\n });\n }\n}\n","import { paint } from '@seedcord/errors';\nimport { traverseDirectory } from '@seedcord/utils/node';\nimport { Envapter } from 'envapt';\n\nimport { HmrModuleHandler } from '#hmr/HmrModuleHandler';\nimport { SubscribeMetadataKey } from '#src/metadataKeys';\nimport { busLoggerOf } from '#subscribers/Bus';\nimport { registrationFor, Subscriber } from '#subscribers/index';\nimport {\n RegisterDefaults,\n RegisteredCount,\n RegisterSubscriber,\n UnregisterSubscriber,\n VerifyWebhooks\n} from '#subscribers/slots';\n\nimport type { Initializeable } from '#src/plugin/Plugin';\nimport type { SubscribeMetadataEntry } from '#subscribers/decorators/Subscribe';\nimport type { StoredSubscriberCtor, Bus } from '#subscribers/index';\nimport type { SubscriptionKey } from '#subscribers/types/Subscriptions';\nimport type { HmrAware, HmrUpdateEvent } from '@seedcord/types';\n\ntype SubscriberArtifact = SubscriptionKey[];\n\n// loads subscribers off the filesystem into a Bus and stores their hot-reload wiring. node only, a bundled isolate registers from the manifest instead\nexport class SubscriberLoader implements Initializeable, HmrAware {\n private isInitialized = false;\n private readonly hmrHandler?: HmrModuleHandler<StoredSubscriberCtor, void, SubscriberArtifact>;\n\n constructor(\n private readonly bus: Bus,\n private readonly directory: string | null\n ) {\n if (!this.directory) return;\n if (!Envapter.isDevelopment && !Envapter.isTest) return;\n\n this.hmrHandler = new HmrModuleHandler({\n handlersDir: this.directory,\n isHandler: this.isSubscriber.bind(this),\n registerHandler: this.registerSubscriber.bind(this),\n unregisterHandler: this.unregisterSubscriber.bind(this),\n getArtifacts: this.getArtifacts.bind(this),\n logger: busLoggerOf(this.bus)\n });\n }\n\n /** @internal */\n public async init(): Promise<void> {\n if (this.isInitialized) return;\n this.isInitialized = true;\n\n this.bus[RegisterDefaults]();\n\n const { directory } = this;\n if (directory) {\n busLoggerOf(this.bus).debug(paint.mute(directory));\n await this.load(directory);\n busLoggerOf(this.bus).utils.list(\n [`${paint.iris.bold(this.bus[RegisteredCount])} subscribers`],\n paint.mint.bold('Loaded'),\n 'debug'\n );\n }\n\n if (!Envapter.isTest) await this.bus[VerifyWebhooks]();\n }\n\n /** @internal */\n public async onHmr(event: HmrUpdateEvent): Promise<void> {\n if (this.hmrHandler) await this.hmrHandler.handle(event);\n }\n\n private async load(dir: string): Promise<void> {\n await traverseDirectory(dir, (fullPath, relativePath, imported) => {\n for (const exportName of Object.keys(imported)) {\n const value = imported[exportName];\n if (!this.isSubscriber(value)) continue;\n\n this.registerSubscriber(value);\n this.hmrHandler?.trackHandler(fullPath, value);\n busLoggerOf(this.bus).utils.registration(value.name, relativePath, undefined, 'trace');\n }\n });\n }\n\n private registerSubscriber(ctor: StoredSubscriberCtor): void {\n this.bus[RegisterSubscriber](registrationFor(ctor));\n }\n\n private unregisterSubscriber(ctor: StoredSubscriberCtor, artifacts?: SubscriberArtifact): void {\n this.bus[UnregisterSubscriber](ctor, artifacts);\n }\n\n private getArtifacts(ctor: StoredSubscriberCtor): SubscriberArtifact {\n const meta = Reflect.getMetadata(SubscribeMetadataKey, ctor) as SubscribeMetadataEntry;\n return [meta.subscriber];\n }\n\n private isSubscriber(value: unknown): value is StoredSubscriberCtor {\n if (typeof value !== 'function') return false;\n return value.prototype instanceof Subscriber && Reflect.hasMetadata(SubscribeMetadataKey, value);\n }\n}\n","import { resolve } from 'node:path';\n\nimport { SlashCommandBuilder } from '@discordjs/builders';\nimport { SeedcordErrorCode, paint } from '@seedcord/errors';\nimport { SeedcordError } from '@seedcord/errors/internal';\nimport { Logger } from '@seedcord/logger';\nimport { formatFilePath } from '@seedcord/utils';\nimport { traverseDirectory } from '@seedcord/utils/node';\nimport { Routes } from 'discord-api-types/v10';\nimport { Envapter } from 'envapt';\n\nimport { getDevChannel } from '#hmr/devChannel';\nimport { HmrModuleHandler } from '#hmr/HmrModuleHandler';\nimport { CommandInjector } from '#src/commands/CommandInjector';\nimport { contextMenuLeaves } from '#src/commands/contextMenuLeaves';\nimport { isCommandClass } from '#src/commands/isCommandClass';\nimport { slashRouteLeaves } from '#src/commands/slashRouteLeaves';\nimport { CommandMetadataKey } from '#src/metadataKeys';\nimport { PublishDefault } from '#subscribers/publishDefault';\n\nimport type { CommandMeta } from '#decorators/Command';\nimport type { CoreBase } from '#interfaces/CoreBase';\nimport type { ContextMenuLeaves } from '#src/commands/contextMenuLeaves';\nimport type { CommandCtor } from '#src/commands/isCommandClass';\nimport type { CommandBuilder, DeployResult } from '#src/commands/types';\nimport type { Initializeable } from '#src/plugin/Plugin';\nimport type { AllSubscriptions } from '#subscribers/types/Subscriptions';\nimport type { HmrAware, HmrUpdateEvent } from '@seedcord/types';\nimport type { APIApplicationCommand } from 'discord-api-types/v10';\n\ninterface CommandArtifact {\n name: string;\n scope: 'global' | 'guild';\n guilds?: string[];\n}\n\nfunction indexById(commands: readonly APIApplicationCommand[]): Map<string, APIApplicationCommand> {\n return new Map(commands.map((command) => [command.id, command]));\n}\n\n/**\n * Scans the command directory, builds each command, and PUTs the global and guild scopes to Discord.\n *\n * @internal\n */\nexport class CommandRegistry implements Initializeable, HmrAware {\n private readonly logger = new Logger('Commands', { channel: 'commands' });\n private isInitialised = false;\n\n public readonly globalCommands: CommandBuilder[] = [];\n public readonly guildCommands = new Map<string, CommandBuilder[]>();\n\n private readonly ctorToCommand = new Map<CommandCtor, CommandArtifact>();\n\n // batched during bulk load. A reload reports on the hmr channel instead\n private loading = false;\n private readonly loadedCommands: { name: string; from: string; kind: 'slash command' | 'context menu' }[] = [];\n\n private readonly hmrHandler?: HmrModuleHandler<CommandCtor, void, CommandArtifact | undefined>;\n private readonly pendingEvents = new Map<string, HmrUpdateEvent>();\n private readonly injector = new CommandInjector();\n\n private readonly dir: string;\n\n public constructor(private readonly core: CoreBase) {\n const dir = core.config.bot.commands.path;\n if (!dir) {\n throw new SeedcordError(SeedcordErrorCode.CoreControllerPathMissing, ['CommandRegistry', 'commands']);\n }\n this.dir = dir;\n\n if (!Envapter.isDevelopment && !Envapter.isTest) return;\n this.hmrHandler = new HmrModuleHandler<CommandCtor, void, CommandArtifact | undefined>({\n handlersDir: dir,\n isHandler: isCommandClass,\n registerHandler: this.registerCommand.bind(this),\n unregisterHandler: this.unregisterCommand.bind(this),\n getArtifacts: this.getArtifacts.bind(this),\n logger: this.logger\n });\n }\n\n private getArtifacts(ctor: CommandCtor): CommandArtifact | undefined {\n return this.ctorToCommand.get(ctor);\n }\n\n /** @internal */\n public async init(): Promise<void> {\n if (this.isInitialised) return;\n this.isInitialised = true;\n\n this.loading = true;\n this.loadedCommands.length = 0;\n try {\n await this.loadCommands(this.dir);\n } finally {\n this.loading = false;\n }\n\n this.reportLoad();\n\n getDevChannel()?.on('seedcord:refresh-commands', (data) => {\n void this.refresh(data.shouldRefresh);\n });\n }\n\n private reportLoad(): void {\n const { utils } = this.logger;\n\n utils.summary(\n 'Loaded commands',\n { global: this.globalCommands.length, 'guild groups': this.guildCommands.size },\n 'debug'\n );\n\n let slash = 0;\n let menu = 0;\n for (const command of this.loadedCommands) {\n if (command.kind === 'slash command') slash++;\n else menu++;\n }\n\n utils.block(\n 'Loaded commands',\n [\n ...utils.entries(this.loadedCommands),\n ...utils.counts({ 'slash commands': slash, 'context menus': menu })\n ],\n 'debug'\n );\n }\n\n /** @internal */\n public async refresh(shouldRefresh = true): Promise<void> {\n if (!shouldRefresh) {\n this.logger.debug(paint.italic('Command refresh cancelled.'));\n this.pendingEvents.clear();\n return;\n }\n\n this.logger.debug(paint.italic('Refreshing commands...'));\n for (const event of this.pendingEvents.values()) {\n await this.hmrHandler?.handle(event);\n }\n this.pendingEvents.clear();\n await this.setCommands();\n }\n\n /** @internal */\n public async onHmr(event: HmrUpdateEvent): Promise<void> {\n if (event.file.startsWith(resolve(process.cwd(), this.dir))) {\n this.pendingEvents.delete(event.file);\n this.pendingEvents.set(event.file, event);\n getDevChannel()?.send('seedcord:commands-update-prompt', {\n files: [...this.pendingEvents.keys()].map((f) => formatFilePath(f))\n });\n } else {\n await this.hmrHandler?.handle(event);\n }\n }\n\n private async loadCommands(dir: string): Promise<void> {\n await traverseDirectory(dir, (fullPath, rel, mod) => {\n for (const exported of Object.values(mod))\n if (isCommandClass(exported)) {\n this.registerCommand(exported, rel);\n this.hmrHandler?.trackHandler(fullPath, exported);\n }\n });\n }\n\n private registerCommand(Ctor: CommandCtor, rel: string): void {\n const meta = Reflect.getMetadata(CommandMetadataKey, Ctor) as CommandMeta | undefined;\n\n if (!meta) return;\n\n const instance = new Ctor();\n const comp = instance.component;\n const kind = comp instanceof SlashCommandBuilder ? 'slash command' : 'context menu';\n\n if (meta.scope === 'global') {\n this.globalCommands.push(comp);\n } else {\n for (const g of meta.guilds) {\n const arr = this.guildCommands.get(g) ?? [];\n arr.push(comp);\n this.guildCommands.set(g, arr);\n }\n }\n\n this.ctorToCommand.set(Ctor, {\n name: comp.name,\n scope: meta.scope,\n ...(meta.scope === 'guild' && { guilds: meta.guilds })\n });\n\n if (this.loading) {\n this.loadedCommands.push({ name: comp.name, from: formatFilePath(rel), kind });\n }\n }\n\n private unregisterCommand(ctor: CommandCtor, artifacts?: CommandArtifact): void {\n const info = artifacts ?? this.ctorToCommand.get(ctor);\n if (!info) return;\n\n if (info.scope === 'global') {\n const idx = this.globalCommands.findIndex((c) => c.name === info.name);\n if (idx !== -1) this.globalCommands.splice(idx, 1);\n } else {\n for (const g of info.guilds ?? []) {\n const arr = this.guildCommands.get(g);\n if (arr) {\n const idx = arr.findIndex((c) => c.name === info.name);\n if (idx !== -1) arr.splice(idx, 1);\n }\n }\n }\n this.ctorToCommand.delete(ctor);\n }\n\n /** @internal */\n public async setCommands(): Promise<DeployResult> {\n const result: DeployResult = { global: new Map(), guilds: new Map() };\n const appId = this.core.applicationId;\n\n if (this.globalCommands.length > 0) {\n const deployed = await this.put(Routes.applicationCommands(appId), this.globalCommands);\n result.global = indexById(deployed);\n const tag = this.globalCommands.length === 1 ? 'command' : 'commands';\n this.logger.utils.block(\n `Deployed ${this.globalCommands.length} global ${tag}`,\n this.logger.utils.wrap(this.globalCommands.map((command) => command.name)),\n 'info'\n );\n }\n\n for (const [guildId, commands] of this.guildCommands.entries()) {\n const deployed = await this.put(Routes.applicationGuildCommands(appId, guildId), commands);\n result.guilds.set(guildId, indexById(deployed));\n const tag = commands.length === 1 ? 'command' : 'commands';\n this.logger.utils.block(\n `Deployed ${paint.iris.bold(commands.length)} ${tag} to ${paint.sky.bold(guildId)}`,\n this.logger.utils.wrap(commands.map((command) => command.name)),\n 'info'\n );\n }\n\n this.injector.inject(result, this.allCommands());\n this.core.bus[PublishDefault]('commandsDeployed', deployedPayload(result));\n return result;\n }\n\n private async put(route: `/${string}`, commands: readonly CommandBuilder[]): Promise<APIApplicationCommand[]> {\n const body = commands.map((command) => command.toJSON());\n // read here because gateway assigns core.rest after this registry is constructed\n const { rest } = this.core;\n // justified: the bulk-overwrite routes return the deployed command array\n return (await rest.put(route, { body })) as APIApplicationCommand[];\n }\n\n /** Every registered builder, global first. A guild command in N guilds appears once per guild. @internal */\n public allCommands(): CommandBuilder[] {\n return [...this.globalCommands, ...this.guildCommands.values()].flat();\n }\n\n /** @internal */\n public routeLeaves(): Set<string> {\n return slashRouteLeaves(this.allCommands());\n }\n\n /** @internal */\n public contextMenuLeaves(): ContextMenuLeaves {\n return contextMenuLeaves(this.allCommands());\n }\n}\n\nfunction deployedPayload(result: DeployResult): AllSubscriptions['commandsDeployed'] {\n const guilds: Record<string, APIApplicationCommand[]> = {};\n for (const [guildId, deployed] of result.guilds) guilds[guildId] = [...deployed.values()];\n\n return { global: [...result.global.values()], guilds };\n}\n","import { assertNodeVersion } from '#node/assertNodeVersion';\n\n// the edge bundle never reaches this entry\nassertNodeVersion(process.env.PACKAGE_NODE_RANGE ?? '', process.version);\n\nexport * from '#node/Lifecycle';\nexport * from '#node/HealthCheck';\nexport { HealthResponder } from '#node/HealthResponder';\nexport { settleWithin } from '#node/Lifecycle/withTimeout';\nexport { SubscriberLoader } from '#node/subscribers/SubscriberLoader';\nexport { CommandRegistry } from '#node/commands/CommandRegistry';\nexport { shutdownOf } from '#node/Pluggable';\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"node-internal.index.mjs","names":[],"sources":["../src/node/Lifecycle/drainWindow.ts","../src/node/HealthResponder.ts","../src/node/HealthCheck.ts","../src/node/subscribers/SubscriberLoader.ts","../src/commands/deployTarget.ts","../src/node/commands/CommandRegistry.ts","../src/node-internal.index.ts"],"sourcesContent":["import { settleWithin } from './withTimeout';\n\nimport type { Logger } from '@seedcord/logger';\n\n// gateway already used 5s. discord's 3s first-ack deadline is the floor.\nexport const DRAIN_WINDOW_MS = 5000;\n\nconst DRAIN_HEADROOM_MS = 1000;\nexport const DRAIN_TASK_TIMEOUT_MS = DRAIN_WINDOW_MS + DRAIN_HEADROOM_MS;\n\nexport async function drainInFlight(\n inFlight: ReadonlySet<Promise<unknown>>,\n timeoutMs: number,\n logger: Logger,\n label: string\n): Promise<void> {\n await settleWithin(Promise.allSettled(inFlight), timeoutMs);\n // nothing cancels these before the process exits\n if (inFlight.size > 0) logger.warn(`${label}: ${String(inFlight.size)} still running when the drain window closed`);\n}\n","import type { IncomingMessage, ServerResponse } from 'node:http';\n\nconst HTTP_OK = 200;\nconst DEFAULT_HEALTH_CHECK_PATH = '/health';\n\nexport class HealthResponder {\n public readonly path: string;\n\n constructor(path?: string) {\n this.path = path ?? DEFAULT_HEALTH_CHECK_PATH;\n }\n\n public tryRespond(req: IncomingMessage, res: ServerResponse): boolean {\n if (req.method !== 'GET' || req.url !== this.path) return false;\n\n res.writeHead(HTTP_OK, { 'Content-Type': 'application/json' });\n res.end(JSON.stringify({ status: 'ok', timestamp: Date.now() }));\n return true;\n }\n}\n","import { createServer } from 'node:http';\n\nimport { paint } from '@seedcord/errors';\nimport { Logger } from '@seedcord/logger';\n\nimport { ShutdownPhase } from '#src/lifecycle/phases';\n\nimport { HealthResponder } from './HealthResponder';\n\nimport type { CoordinatedShutdown } from './Lifecycle/CoordinatedShutdown';\nimport type { HealthCheckConfig, HealthCheckOption } from '@seedcord/types';\nimport type { IncomingMessage, Server, ServerResponse } from 'node:http';\n\nconst HTTP_NOT_FOUND = 404;\n\nconst DEFAULT_HEALTH_CHECK_PORT = 6967;\n\nexport class HealthCheck {\n public readonly logger = new Logger('HealthCheck', { channel: 'health' });\n\n private readonly _port: number;\n private readonly _host: string | undefined;\n\n private readonly responder: HealthResponder;\n private server?: Server;\n\n constructor(shutdown: CoordinatedShutdown, options?: HealthCheckConfig) {\n this._port = options?.port ?? DEFAULT_HEALTH_CHECK_PORT;\n this._host = options?.host;\n this.responder = new HealthResponder(options?.path);\n\n shutdown.addTask(ShutdownPhase.Drain, 'stop-healthcheck-server', () => this.stop());\n }\n\n public get port(): number {\n return this._port;\n }\n\n public get host(): string | undefined {\n return this._host;\n }\n\n public get path(): string {\n return this.responder.path;\n }\n\n public static fromOption(shutdown: CoordinatedShutdown, option?: HealthCheckOption): HealthCheck | undefined {\n if (option === false) return undefined;\n return new HealthCheck(shutdown, typeof option === 'object' ? option : undefined);\n }\n\n public async init(): Promise<void> {\n return new Promise<void>((resolve, reject) => {\n const server = createServer((req: IncomingMessage, res: ServerResponse) => {\n if (this.responder.tryRespond(req, res)) return;\n\n res.writeHead(HTTP_NOT_FOUND, { 'Content-Type': 'application/json' });\n res.end(JSON.stringify({ status: 'not found' }));\n });\n this.server = server;\n\n const onListenError = (err: Error): void => reject(err);\n server.on('error', onListenError);\n\n server.once('listening', () => {\n // a late error would reject an already-settled promise, and node throws process-wide without a listener here\n server.removeListener('error', onListenError);\n server.on('error', (err) => this.logger.error('Health check server error', err));\n\n // binds all interfaces, so log an address a browser can open\n const address = this._host ?? 'localhost';\n this.logger.info(\n `${paint.mint.bold('✔︎')} Health check server listening on ${paint.sky.bold(`http://${address}:${this._port}${this.path}`)}`\n );\n resolve();\n });\n\n if (this._host) {\n this.logger.debug(`Binding health check server to ${this._host}`);\n server.listen(this._port, this._host);\n } else {\n this.logger.debug('Binding health check server to all interfaces');\n server.listen(this._port);\n }\n });\n }\n\n public stop(): Promise<void> {\n const server = this.server;\n // close() on a non-listening server invokes its callback with ERR_SERVER_NOT_RUNNING and\n // never fires 'close', so without this guard the promise would hang the shutdown phase.\n if (!server?.listening) return Promise.resolve();\n\n return new Promise((resolve, reject) => {\n // settle only in the callback, a 'close'-event resolve would swallow the callback's error\n server.close((err) => {\n if (err) {\n reject(err);\n return;\n }\n this.logger.info(paint.coral.bold('Health check server stopped'));\n resolve();\n });\n });\n }\n}\n","import { paint } from '@seedcord/errors';\nimport { traverseDirectory } from '@seedcord/utils/node';\nimport { Envapter } from 'envapt';\n\nimport { HmrModuleHandler } from '#hmr/HmrModuleHandler';\nimport { SubscribeMetadataKey } from '#src/metadataKeys';\nimport { busLoggerOf } from '#subscribers/Bus';\nimport { registrationFor, Subscriber } from '#subscribers/index';\nimport {\n RegisterDefaults,\n RegisteredCount,\n RegisterSubscriber,\n UnregisterSubscriber,\n VerifyWebhooks\n} from '#subscribers/slots';\n\nimport type { Initializeable } from '#src/plugin/Plugin';\nimport type { SubscribeMetadataEntry } from '#subscribers/decorators/Subscribe';\nimport type { StoredSubscriberCtor, Bus } from '#subscribers/index';\nimport type { SubscriptionKey } from '#subscribers/types/Subscriptions';\nimport type { HmrAware, HmrUpdateEvent } from '@seedcord/types';\n\ntype SubscriberArtifact = SubscriptionKey[];\n\n// loads subscribers off the filesystem into a Bus and stores their hot-reload wiring. node only, a bundled isolate registers from the manifest instead\nexport class SubscriberLoader implements Initializeable, HmrAware {\n private isInitialized = false;\n private readonly hmrHandler?: HmrModuleHandler<StoredSubscriberCtor, void, SubscriberArtifact>;\n\n constructor(\n private readonly bus: Bus,\n private readonly directory: string | null\n ) {\n if (!this.directory) return;\n if (!Envapter.isDevelopment && !Envapter.isTest) return;\n\n this.hmrHandler = new HmrModuleHandler({\n handlersDir: this.directory,\n isHandler: this.isSubscriber.bind(this),\n registerHandler: this.registerSubscriber.bind(this),\n unregisterHandler: this.unregisterSubscriber.bind(this),\n getArtifacts: this.getArtifacts.bind(this),\n logger: busLoggerOf(this.bus)\n });\n }\n\n /** @internal */\n public async init(): Promise<void> {\n if (this.isInitialized) return;\n this.isInitialized = true;\n\n this.bus[RegisterDefaults]();\n\n const { directory } = this;\n if (directory) {\n busLoggerOf(this.bus).debug(paint.mute(directory));\n await this.load(directory);\n busLoggerOf(this.bus).utils.list(\n [`${paint.iris.bold(this.bus[RegisteredCount])} subscribers`],\n paint.mint.bold('Loaded'),\n 'debug'\n );\n }\n\n if (!Envapter.isTest) await this.bus[VerifyWebhooks]();\n }\n\n /** @internal */\n public async onHmr(event: HmrUpdateEvent): Promise<void> {\n if (this.hmrHandler) await this.hmrHandler.handle(event);\n }\n\n private async load(dir: string): Promise<void> {\n await traverseDirectory(dir, (fullPath, relativePath, imported) => {\n for (const exportName of Object.keys(imported)) {\n const value = imported[exportName];\n if (!this.isSubscriber(value)) continue;\n\n this.registerSubscriber(value);\n this.hmrHandler?.trackHandler(fullPath, value);\n busLoggerOf(this.bus).utils.registration(value.name, relativePath, undefined, 'trace');\n }\n });\n }\n\n private registerSubscriber(ctor: StoredSubscriberCtor): void {\n this.bus[RegisterSubscriber](registrationFor(ctor));\n }\n\n private unregisterSubscriber(ctor: StoredSubscriberCtor, artifacts?: SubscriberArtifact): void {\n this.bus[UnregisterSubscriber](ctor, artifacts);\n }\n\n private getArtifacts(ctor: StoredSubscriberCtor): SubscriberArtifact {\n const meta = Reflect.getMetadata(SubscribeMetadataKey, ctor) as SubscribeMetadataEntry;\n return [meta.subscriber];\n }\n\n private isSubscriber(value: unknown): value is StoredSubscriberCtor {\n if (typeof value !== 'function') return false;\n return value.prototype instanceof Subscriber && Reflect.hasMetadata(SubscribeMetadataKey, value);\n }\n}\n","import { SeedcordErrorCode } from '@seedcord/errors';\nimport { SeedcordError } from '@seedcord/errors/internal';\n\nimport { ConfigGuilds } from '#decorators/Command';\n\nimport type { CommandMeta } from '#decorators/Command';\n\nexport type DeployTarget = Exclude<CommandMeta, { scope: 'config' }>;\n\nexport function resolveDeployTarget(\n meta: CommandMeta,\n configured: readonly string[],\n commandName: string\n): DeployTarget {\n if (meta.scope === 'global') return { scope: 'global' };\n\n if (meta.scope === 'config') {\n return configured.length > 0 ? guildTarget(configured) : { scope: 'global' };\n }\n\n if (configured.length === 0 && meta.guilds.includes(ConfigGuilds)) {\n throw new SeedcordError(SeedcordErrorCode.CoreCommandGuildsEmpty, [commandName]);\n }\n\n return guildTarget(meta.guilds.flatMap((target) => (target === ConfigGuilds ? configured : target)));\n}\n\n// a repeated id would push the same builder into one guild twice, and discord rejects a duplicate name in the put\nfunction guildTarget(guilds: readonly string[]): DeployTarget {\n return { scope: 'guild', guilds: [...new Set(guilds)] };\n}\n","import { resolve } from 'node:path';\n\nimport { SlashCommandBuilder } from '@discordjs/builders';\nimport { SeedcordErrorCode, paint } from '@seedcord/errors';\nimport { SeedcordError } from '@seedcord/errors/internal';\nimport { Logger } from '@seedcord/logger';\nimport { formatFilePath } from '@seedcord/utils';\nimport { traverseDirectory } from '@seedcord/utils/node';\nimport { Routes } from 'discord-api-types/v10';\nimport { Envapter } from 'envapt';\n\nimport { getDevChannel } from '#hmr/devChannel';\nimport { HmrModuleHandler } from '#hmr/HmrModuleHandler';\nimport { CommandInjector } from '#src/commands/CommandInjector';\nimport { contextMenuLeaves } from '#src/commands/contextMenuLeaves';\nimport { resolveDeployTarget } from '#src/commands/deployTarget';\nimport { isCommandClass } from '#src/commands/isCommandClass';\nimport { slashRouteLeaves } from '#src/commands/slashRouteLeaves';\nimport { CommandMetadataKey } from '#src/metadataKeys';\nimport { PublishDefault } from '#subscribers/publishDefault';\n\nimport type { CommandMeta } from '#decorators/Command';\nimport type { CoreBase } from '#interfaces/CoreBase';\nimport type { ContextMenuLeaves } from '#src/commands/contextMenuLeaves';\nimport type { DeployTarget } from '#src/commands/deployTarget';\nimport type { CommandCtor } from '#src/commands/isCommandClass';\nimport type { CommandBuilder, DeployResult } from '#src/commands/types';\nimport type { Initializeable } from '#src/plugin/Plugin';\nimport type { AllSubscriptions } from '#subscribers/types/Subscriptions';\nimport type { HmrAware, HmrUpdateEvent } from '@seedcord/types';\nimport type { APIApplicationCommand } from 'discord-api-types/v10';\n\ntype CommandArtifact = DeployTarget & { name: string };\n\nfunction indexById(commands: readonly APIApplicationCommand[]): Map<string, APIApplicationCommand> {\n return new Map(commands.map((command) => [command.id, command]));\n}\n\n/**\n * Scans the command directory, builds each command, and PUTs the global and guild scopes to Discord.\n *\n * @internal\n */\nexport class CommandRegistry implements Initializeable, HmrAware {\n private readonly logger = new Logger('Commands', { channel: 'commands' });\n private isInitialised = false;\n\n public readonly globalCommands: CommandBuilder[] = [];\n public readonly guildCommands = new Map<string, CommandBuilder[]>();\n\n private readonly ctorToCommand = new Map<CommandCtor, CommandArtifact>();\n\n // batched during bulk load. A reload reports on the hmr channel instead\n private loading = false;\n private readonly loadedCommands: { name: string; from: string; kind: 'slash command' | 'context menu' }[] = [];\n\n private readonly hmrHandler?: HmrModuleHandler<CommandCtor, void, CommandArtifact | undefined>;\n private readonly pendingEvents = new Map<string, HmrUpdateEvent>();\n private readonly injector = new CommandInjector();\n\n private readonly dir: string;\n private readonly configuredGuilds: readonly string[];\n\n public constructor(private readonly core: CoreBase) {\n const { commands } = core.config.bot;\n if (!commands.path) {\n throw new SeedcordError(SeedcordErrorCode.CoreControllerPathMissing, ['CommandRegistry', 'commands']);\n }\n this.dir = commands.path;\n this.configuredGuilds = commands.guilds ?? [];\n\n if (!Envapter.isDevelopment && !Envapter.isTest) return;\n this.hmrHandler = new HmrModuleHandler<CommandCtor, void, CommandArtifact | undefined>({\n handlersDir: this.dir,\n isHandler: isCommandClass,\n registerHandler: this.registerCommand.bind(this),\n unregisterHandler: this.unregisterCommand.bind(this),\n getArtifacts: this.getArtifacts.bind(this),\n logger: this.logger\n });\n }\n\n private getArtifacts(ctor: CommandCtor): CommandArtifact | undefined {\n return this.ctorToCommand.get(ctor);\n }\n\n /** @internal */\n public async init(): Promise<void> {\n if (this.isInitialised) return;\n this.isInitialised = true;\n\n this.loading = true;\n this.loadedCommands.length = 0;\n try {\n await this.loadCommands(this.dir);\n } finally {\n this.loading = false;\n }\n\n this.reportLoad();\n\n getDevChannel()?.on('seedcord:refresh-commands', (data) => {\n void this.refresh(data.shouldRefresh);\n });\n }\n\n private reportLoad(): void {\n const { utils } = this.logger;\n\n utils.summary(\n 'Loaded commands',\n { global: this.globalCommands.length, 'guild groups': this.guildCommands.size },\n 'debug'\n );\n\n let slash = 0;\n let menu = 0;\n for (const command of this.loadedCommands) {\n if (command.kind === 'slash command') slash++;\n else menu++;\n }\n\n utils.block(\n 'Loaded commands',\n [\n ...utils.entries(this.loadedCommands),\n ...utils.counts({ 'slash commands': slash, 'context menus': menu })\n ],\n 'debug'\n );\n }\n\n /** @internal */\n public async refresh(shouldRefresh = true): Promise<void> {\n if (!shouldRefresh) {\n this.logger.debug(paint.italic('Command refresh cancelled.'));\n this.pendingEvents.clear();\n return;\n }\n\n this.logger.debug(paint.italic('Refreshing commands...'));\n for (const event of this.pendingEvents.values()) {\n await this.hmrHandler?.handle(event);\n }\n this.pendingEvents.clear();\n await this.setCommands();\n }\n\n /** @internal */\n public async onHmr(event: HmrUpdateEvent): Promise<void> {\n if (event.file.startsWith(resolve(process.cwd(), this.dir))) {\n this.pendingEvents.delete(event.file);\n this.pendingEvents.set(event.file, event);\n getDevChannel()?.send('seedcord:commands-update-prompt', {\n files: [...this.pendingEvents.keys()].map((f) => formatFilePath(f))\n });\n } else {\n await this.hmrHandler?.handle(event);\n }\n }\n\n private async loadCommands(dir: string): Promise<void> {\n await traverseDirectory(dir, (fullPath, rel, mod) => {\n for (const exported of Object.values(mod))\n if (isCommandClass(exported)) {\n this.registerCommand(exported, rel);\n this.hmrHandler?.trackHandler(fullPath, exported);\n }\n });\n }\n\n private registerCommand(Ctor: CommandCtor, rel: string): void {\n const meta = Reflect.getMetadata(CommandMetadataKey, Ctor) as CommandMeta | undefined;\n\n if (!meta) return;\n\n const instance = new Ctor();\n const comp = instance.component;\n const kind = comp instanceof SlashCommandBuilder ? 'slash command' : 'context menu';\n\n const target = resolveDeployTarget(meta, this.configuredGuilds, Ctor.name);\n\n if (target.scope === 'global') {\n this.globalCommands.push(comp);\n } else {\n for (const g of target.guilds) {\n const arr = this.guildCommands.get(g) ?? [];\n arr.push(comp);\n this.guildCommands.set(g, arr);\n }\n }\n\n this.ctorToCommand.set(Ctor, { name: comp.name, ...target });\n\n if (this.loading) {\n this.loadedCommands.push({ name: comp.name, from: formatFilePath(rel), kind });\n }\n }\n\n private unregisterCommand(ctor: CommandCtor, artifacts?: CommandArtifact): void {\n const info = artifacts ?? this.ctorToCommand.get(ctor);\n if (!info) return;\n\n if (info.scope === 'global') {\n const idx = this.globalCommands.findIndex((c) => c.name === info.name);\n if (idx !== -1) this.globalCommands.splice(idx, 1);\n } else {\n for (const g of info.guilds) {\n const arr = this.guildCommands.get(g);\n if (arr) {\n const idx = arr.findIndex((c) => c.name === info.name);\n if (idx !== -1) arr.splice(idx, 1);\n }\n }\n }\n this.ctorToCommand.delete(ctor);\n }\n\n /** @internal */\n public async setCommands(): Promise<DeployResult> {\n const result: DeployResult = { global: new Map(), guilds: new Map() };\n const appId = this.core.applicationId;\n\n if (this.globalCommands.length > 0) {\n const deployed = await this.put(Routes.applicationCommands(appId), this.globalCommands);\n result.global = indexById(deployed);\n const tag = this.globalCommands.length === 1 ? 'command' : 'commands';\n this.logger.utils.block(\n `Deployed ${this.globalCommands.length} global ${tag}`,\n this.logger.utils.wrap(this.globalCommands.map((command) => command.name)),\n 'info'\n );\n }\n\n for (const [guildId, commands] of this.guildCommands.entries()) {\n const deployed = await this.putGuild(appId, guildId, commands);\n result.guilds.set(guildId, indexById(deployed));\n const tag = commands.length === 1 ? 'command' : 'commands';\n this.logger.utils.block(\n `Deployed ${paint.iris.bold(commands.length)} ${tag} to ${paint.sky.bold(guildId)}`,\n this.logger.utils.wrap(commands.map((command) => command.name)),\n 'info'\n );\n }\n\n this.injector.inject(result, this.allCommands());\n this.core.bus[PublishDefault]('commandsDeployed', deployedPayload(result));\n return result;\n }\n\n private async putGuild(\n appId: string,\n guildId: string,\n commands: readonly CommandBuilder[]\n ): Promise<APIApplicationCommand[]> {\n try {\n return await this.put(Routes.applicationGuildCommands(appId, guildId), commands);\n } catch (error) {\n throw new SeedcordError(SeedcordErrorCode.CoreCommandGuildDeployFailed, [guildId], { cause: error });\n }\n }\n\n private async put(route: `/${string}`, commands: readonly CommandBuilder[]): Promise<APIApplicationCommand[]> {\n const body = commands.map((command) => command.toJSON());\n // read here because gateway assigns core.rest after this registry is constructed\n const { rest } = this.core;\n // justified: the bulk-overwrite routes return the deployed command array\n return (await rest.put(route, { body })) as APIApplicationCommand[];\n }\n\n /** Every registered builder, global first. A guild command in N guilds appears once per guild. @internal */\n public allCommands(): CommandBuilder[] {\n return [...this.globalCommands, ...this.guildCommands.values()].flat();\n }\n\n /** @internal */\n public routeLeaves(): Set<string> {\n return slashRouteLeaves(this.allCommands());\n }\n\n /** @internal */\n public contextMenuLeaves(): ContextMenuLeaves {\n return contextMenuLeaves(this.allCommands());\n }\n}\n\nfunction deployedPayload(result: DeployResult): AllSubscriptions['commandsDeployed'] {\n const guilds: Record<string, APIApplicationCommand[]> = {};\n for (const [guildId, deployed] of result.guilds) guilds[guildId] = [...deployed.values()];\n\n return { global: [...result.global.values()], guilds };\n}\n","import { assertNodeVersion } from '#node/assertNodeVersion';\n\n// the edge bundle never reaches this entry\nassertNodeVersion(process.env.PACKAGE_NODE_RANGE ?? '', process.version);\n\nexport * from '#node/Lifecycle';\nexport * from '#node/HealthCheck';\nexport { HealthResponder } from '#node/HealthResponder';\nexport { settleWithin } from '#node/Lifecycle/withTimeout';\nexport { SubscriberLoader } from '#node/subscribers/SubscriberLoader';\nexport { CommandRegistry } from '#node/commands/CommandRegistry';\nexport { shutdownOf } from '#node/Pluggable';\n"],"mappings":"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"}
|
package/dist/node.index.d.mts
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import { i as CoordinatedShutdown, r as CoordinatedStartup, t as Pluggable } from "./Pluggable-
|
|
1
|
+
import { i as CoordinatedShutdown, r as CoordinatedStartup, t as Pluggable } from "./Pluggable-CAteNmfy.mjs";
|
|
2
2
|
export { CoordinatedShutdown, CoordinatedStartup, Pluggable };
|
package/dist/node.index.mjs
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { n as StartupPhase, t as ShutdownPhase } from "./phases-BauKJOTT.mjs";
|
|
2
|
-
import { a as withTimeout, i as settleWithin, t as Pluggable } from "./Pluggable-
|
|
2
|
+
import { a as withTimeout, i as settleWithin, t as Pluggable } from "./Pluggable-BShEwGw9.mjs";
|
|
3
3
|
import { SeedcordErrorCode, paint } from "@seedcord/errors";
|
|
4
4
|
import { SeedcordAggregateError, SeedcordError, SeedcordRangeError, SeedcordTypeError } from "@seedcord/errors/internal";
|
|
5
5
|
import { Logger } from "@seedcord/logger";
|
package/dist/plugin.index.d.mts
CHANGED
|
@@ -1,3 +1,3 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { a as Plugin, l as PluginOptions, m as PluginLifecycleSpec } from "./Plugin-D3wIj0xX.mjs";
|
|
2
2
|
import { HmrAware, HmrUpdateEvent } from "@seedcord/types";
|
|
3
3
|
export { type HmrAware, type HmrUpdateEvent, Plugin, type PluginLifecycleSpec, type PluginOptions };
|
|
@@ -1,7 +1,9 @@
|
|
|
1
|
-
import { D as clearStore, E as accessorStore, O as guardedAccessor, k as contextMenuLeaves } from "./Bus-Djm6688b.mjs";
|
|
1
|
+
import { D as clearStore, E as accessorStore, O as guardedAccessor, g as CommandMetadataKey, k as contextMenuLeaves } from "./Bus-Djm6688b.mjs";
|
|
2
|
+
import "reflect-metadata";
|
|
2
3
|
import { SlashCommandBuilder } from "@discordjs/builders";
|
|
3
4
|
import { ApplicationCommandType } from "discord-api-types/v10";
|
|
4
|
-
import { paint } from "@seedcord/errors";
|
|
5
|
+
import { SeedcordErrorCode, paint } from "@seedcord/errors";
|
|
6
|
+
import { SeedcordError, SeedcordTypeError } from "@seedcord/errors/internal";
|
|
5
7
|
import { Logger } from "@seedcord/logger";
|
|
6
8
|
import { routeLeavesOf } from "@seedcord/utils/internal";
|
|
7
9
|
|
|
@@ -135,5 +137,31 @@ var CommandInjector = class {
|
|
|
135
137
|
};
|
|
136
138
|
|
|
137
139
|
//#endregion
|
|
138
|
-
|
|
139
|
-
|
|
140
|
+
//#region src/decorators/Command.ts
|
|
141
|
+
const ConfigGuilds = "config";
|
|
142
|
+
function RegisterCommand(scope = "config", guilds = []) {
|
|
143
|
+
return (ctor) => {
|
|
144
|
+
const existingMeta = Reflect.getOwnMetadata(CommandMetadataKey, ctor);
|
|
145
|
+
if (existingMeta) throw new SeedcordError(SeedcordErrorCode.DecoratorCommandAlreadyRegistered, [
|
|
146
|
+
ctor.name,
|
|
147
|
+
decoratorCall(existingMeta.scope),
|
|
148
|
+
decoratorCall(scope)
|
|
149
|
+
]);
|
|
150
|
+
if (scope === "global" && guilds.length > 0) throw new SeedcordTypeError(SeedcordErrorCode.DecoratorCommandGlobalWithGuilds);
|
|
151
|
+
if (scope === "guild" && (!Array.isArray(guilds) || guilds.length === 0)) throw new SeedcordTypeError(SeedcordErrorCode.DecoratorCommandGuildWithoutGuilds);
|
|
152
|
+
const meta = scope === "guild" ? {
|
|
153
|
+
scope,
|
|
154
|
+
guilds
|
|
155
|
+
} : { scope };
|
|
156
|
+
Reflect.defineMetadata(CommandMetadataKey, meta, ctor);
|
|
157
|
+
};
|
|
158
|
+
}
|
|
159
|
+
function decoratorCall(scope) {
|
|
160
|
+
if (scope === "global") return "@RegisterCommand('global')";
|
|
161
|
+
if (scope === "guild") return "@RegisterCommand('guild', [...])";
|
|
162
|
+
return "@RegisterCommand()";
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
//#endregion
|
|
166
|
+
export { ContextMenus as a, Commands as i, RegisterCommand as n, CommandInjector as r, ConfigGuilds as t };
|
|
167
|
+
//# sourceMappingURL=subscribers-kAQQt_YP.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"subscribers-kAQQt_YP.mjs","names":[],"sources":["../src/commands/CommandInjector.ts","../src/decorators/Command.ts"],"sourcesContent":["import { SlashCommandBuilder } from '@discordjs/builders';\nimport { paint } from '@seedcord/errors';\nimport { Logger } from '@seedcord/logger';\nimport { routeLeavesOf } from '@seedcord/utils/internal';\nimport { ApplicationCommandType } from 'discord-api-types/v10';\n\nimport { contextMenuLeaves } from '#src/commands/contextMenuLeaves';\nimport { accessorStore, clearStore, guardedAccessor } from '#src/miscellaneous/guarded';\n\nimport type {\n ContextMenuKind,\n MessageContextMenuRegistry,\n UserContextMenuRegistry\n} from '#registries/ContextMenuRegistry';\nimport type { SlashRegistry } from '#registries/SlashRegistry';\nimport type { CommandBuilder, DeployResult } from '#src/commands/types';\nimport type { APIApplicationCommand, Snowflake } from 'discord-api-types/v10';\n\nconst commandStorage = accessorStore<CommandInfo>();\nconst userMenuStorage = accessorStore<ContextMenuInfo>();\nconst messageMenuStorage = accessorStore<ContextMenuInfo>();\n\n/** What {@link Commands} resolves to for one slash route. */\nexport interface CommandInfo {\n /** The clickable `</config set:id>` mention. A route whose id did not resolve falls back to `/config set`. */\n readonly mention: string;\n /** The deployed command id. Absent when the command deployed to two or more guilds. */\n readonly id?: string;\n /** The full route, e.g. `config/set`. */\n readonly route: string;\n /** The route's own description, taken from the subcommand for a nested route. */\n readonly description: string;\n}\n\n/** Each registered slash route mapped to its deployed command. */\nexport type InjectedCommandMap = {\n [K in keyof SlashRegistry]: CommandInfo;\n};\n\n/**\n * The bot's deployed slash commands, keyed by {@link SlashRegistry}. Filled by\n * {@link CommandInjector} after each deploy, and a read before that throws.\n *\n * @example\n * ```ts\n * import { Commands } from '@seedcord/gateway';\n *\n * await this.reply(`Try ${Commands['config/set'].mention}`);\n *\n * // a help listing\n * const lines = Object.values(Commands).map((c) => `${c.mention} ${c.description}`);\n * ```\n */\nexport const Commands = guardedAccessor('Commands', commandStorage) as InjectedCommandMap;\n\n/** What {@link ContextMenus} resolves to for one context-menu command. */\nexport interface ContextMenuInfo {\n /** The deployed command id. Absent when the command deployed to two or more guilds. */\n readonly id?: string;\n /** The name shown in Discord's right-click menu. */\n readonly name: string;\n readonly kind: ContextMenuKind;\n}\n\n/** Deployed context-menu commands, split by kind because Discord scopes name uniqueness per command type. */\nexport interface InjectedContextMenuMap {\n readonly user: { [K in keyof UserContextMenuRegistry]: ContextMenuInfo };\n readonly message: { [K in keyof MessageContextMenuRegistry]: ContextMenuInfo };\n}\n\n/**\n * The bot's deployed context-menu commands. Filled by {@link CommandInjector} after each deploy, and a read\n * before that throws.\n *\n * @example\n * ```ts\n * import { ContextMenus } from '@seedcord/gateway';\n *\n * const id = ContextMenus.user['View Profile'].id;\n * ```\n */\nexport const ContextMenus = {\n user: guardedAccessor('ContextMenus.user', userMenuStorage),\n message: guardedAccessor('ContextMenus.message', messageMenuStorage)\n} as InjectedContextMenuMap;\n\nconst MULTI_GUILD = Symbol('seedcord:commands:multi-guild');\n\n// Discord scopes name uniqueness per type, and a context-menu name may be all lowercase like a slash name\nfunction commandKey(type: ApplicationCommandType, name: string): string {\n return `${type}:${name}`;\n}\n\nfunction slashBuildersOf(all: readonly CommandBuilder[]): SlashCommandBuilder[] {\n const slash = all.filter((command): command is SlashCommandBuilder => command instanceof SlashCommandBuilder);\n // a guild command in N guilds is the same builder pushed once per guild\n return [...new Set(slash)];\n}\n\ninterface DeployedIds {\n readonly global: Map<string, Snowflake>;\n readonly guild: Map<string, Snowflake | typeof MULTI_GUILD>;\n readonly warned: Set<string>;\n}\n\n/** @internal */\nexport class CommandInjector {\n private readonly logger = new Logger('Commands', { channel: 'bot' });\n\n public inject(deploy: DeployResult, all: readonly CommandBuilder[]): void {\n this.clear();\n\n const ids: DeployedIds = {\n global: this.indexByKey(deploy.global),\n guild: this.indexGuilds(deploy.guilds),\n warned: new Set<string>()\n };\n let clickable = 0;\n\n for (const command of slashBuildersOf(all)) {\n const id = this.resolveId(\n ApplicationCommandType.ChatInput,\n command.name,\n ids,\n 'falling back to plain text (not clickable)'\n );\n for (const leaf of routeLeavesOf(command.toJSON())) {\n commandStorage[leaf.route] = {\n mention: id ? this.toMention(leaf.route, id) : this.toPlain(leaf.route),\n ...(id && { id }),\n route: leaf.route,\n description: leaf.description\n };\n if (id) clickable++;\n }\n }\n\n const menus = contextMenuLeaves(all);\n this.injectMenus(menus.user, ApplicationCommandType.User, userMenuStorage, ids);\n this.injectMenus(menus.message, ApplicationCommandType.Message, messageMenuStorage, ids);\n\n this.logger.utils.summary('Linked commands', {\n clickable,\n plain: Object.keys(commandStorage).length - clickable,\n 'context menus': menus.user.size + menus.message.size\n });\n }\n\n private injectMenus(\n names: ReadonlySet<string>,\n kind: ContextMenuKind,\n storage: Record<string, ContextMenuInfo>,\n ids: DeployedIds\n ): void {\n for (const name of names) {\n const id = this.resolveId(kind, name, ids, 'leaving its id unset');\n storage[name] = { ...(id && { id }), name, kind };\n }\n }\n\n private indexByKey(collection: Map<Snowflake, APIApplicationCommand>): Map<string, Snowflake> {\n const map = new Map<string, Snowflake>();\n for (const command of collection.values()) map.set(commandKey(command.type, command.name), command.id);\n return map;\n }\n\n private indexGuilds(\n guilds: Map<Snowflake, Map<Snowflake, APIApplicationCommand>>\n ): Map<string, Snowflake | typeof MULTI_GUILD> {\n const map = new Map<string, Snowflake | typeof MULTI_GUILD>();\n for (const collection of guilds.values()) {\n for (const command of collection.values()) {\n const key = commandKey(command.type, command.name);\n const existing = map.get(key);\n // Discord assigns a separate id per guild, so two ids for one name means no single mention\n if (existing === undefined) map.set(key, command.id);\n else if (existing !== command.id) map.set(key, MULTI_GUILD);\n }\n }\n return map;\n }\n\n private resolveId(\n type: ApplicationCommandType,\n name: string,\n ids: DeployedIds,\n consequence: string\n ): Snowflake | undefined {\n const key = commandKey(type, name);\n const global = ids.global.get(key);\n if (global) return global;\n\n const guild = ids.guild.get(key);\n if (guild === undefined) return undefined;\n if (guild === MULTI_GUILD) {\n if (!ids.warned.has(key)) {\n this.logger.warn(`${paint.sky.bold(name)} is deployed to multiple guilds, ${consequence}.`);\n ids.warned.add(key);\n }\n return undefined;\n }\n return guild;\n }\n\n private toMention(route: string, id: Snowflake): string {\n return `</${route.replaceAll('/', ' ')}:${id}>`;\n }\n\n private toPlain(route: string): string {\n return `/${route.replaceAll('/', ' ')}`;\n }\n\n private clear(): void {\n for (const store of [commandStorage, userMenuStorage, messageMenuStorage]) clearStore(store);\n }\n}\n","import 'reflect-metadata';\n\nimport { SeedcordErrorCode } from '@seedcord/errors';\nimport { SeedcordError, SeedcordTypeError } from '@seedcord/errors/internal';\n\nimport { CommandMetadataKey } from '#src/metadataKeys';\n\nimport type { BuilderComponent } from '#components/Component';\nimport type { Constructor, LiteralUnion } from 'type-fest';\n\ntype CommandCtor = Constructor<BuilderComponent<'command' | 'context_menu'>>;\n\nexport const ConfigGuilds = 'config';\n\n/** A guild id, or `'config'` for every id in `commands.guilds`. */\nexport type GuildTarget = LiteralUnion<typeof ConfigGuilds, string>;\n\ninterface GlobalMeta {\n scope: 'global';\n}\n\ninterface GuildMeta {\n scope: 'guild';\n guilds: GuildTarget[];\n}\n\ninterface ConfigMeta {\n scope: 'config';\n}\n\nexport type CommandMeta = GlobalMeta | GuildMeta | ConfigMeta;\n\ntype CommandScope = CommandMeta['scope'];\n\n/**\n * Registers a command in the guilds listed under `commands.guilds`. With no ids listed there, the\n * command deploys globally.\n *\n * @decorator\n * @example\n * ```typescript\n * \\@RegisterCommand()\n * class PingCommand extends BuilderComponent<'command'> {} // or BuilderComponent<'context_menu'>\n * ```\n */\nexport function RegisterCommand(): (ctor: CommandCtor) => void;\n\n/**\n * Registers a command globally, ignoring `commands.guilds`.\n *\n * @decorator\n * @example\n * ```typescript\n * \\@RegisterCommand('global')\n * class PingCommand extends BuilderComponent<'command'> {} // or BuilderComponent<'context_menu'>\n * ```\n */\n// eslint-disable-next-line @typescript-eslint/unified-signatures -- merging these puts two different deploy targets under one doc block\nexport function RegisterCommand(scope: 'global'): (ctor: CommandCtor) => void;\n\n/**\n * Registers a command in the guilds you pass. These ids replace `commands.guilds`. Include\n * `'config'` among them to keep that list too.\n *\n * @decorator\n * @example\n * ```typescript\n * \\@RegisterCommand('guild', ['config', '123456789'])\n * class AdminCommand extends BuilderComponent<'command'> {} // or BuilderComponent<'context_menu'>\n * ```\n */\nexport function RegisterCommand(scope: 'guild', guilds: GuildTarget[]): (ctor: CommandCtor) => void;\n\nexport function RegisterCommand(scope: CommandScope = 'config', guilds: GuildTarget[] = []) {\n return (ctor: CommandCtor): void => {\n const existingMeta = Reflect.getOwnMetadata(CommandMetadataKey, ctor) as CommandMeta | undefined;\n if (existingMeta) {\n throw new SeedcordError(SeedcordErrorCode.DecoratorCommandAlreadyRegistered, [\n ctor.name,\n decoratorCall(existingMeta.scope),\n decoratorCall(scope)\n ]);\n }\n\n if (scope === 'global' && guilds.length > 0) {\n throw new SeedcordTypeError(SeedcordErrorCode.DecoratorCommandGlobalWithGuilds);\n }\n\n if (scope === 'guild' && (!Array.isArray(guilds) || guilds.length === 0)) {\n throw new SeedcordTypeError(SeedcordErrorCode.DecoratorCommandGuildWithoutGuilds);\n }\n\n const meta: CommandMeta = scope === 'guild' ? { scope, guilds } : { scope };\n Reflect.defineMetadata(CommandMetadataKey, meta, ctor);\n };\n}\n\nfunction decoratorCall(scope: CommandScope): string {\n if (scope === 'global') return \"@RegisterCommand('global')\";\n if (scope === 'guild') return \"@RegisterCommand('guild', [...])\";\n\n return '@RegisterCommand()';\n}\n"],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@seedcord/core",
|
|
3
3
|
"type": "module",
|
|
4
|
-
"version": "0.
|
|
4
|
+
"version": "0.9.0",
|
|
5
5
|
"engines": {
|
|
6
6
|
"node": ">=24.11"
|
|
7
7
|
},
|
|
@@ -69,28 +69,28 @@
|
|
|
69
69
|
"LICENSE"
|
|
70
70
|
],
|
|
71
71
|
"peerDependencies": {
|
|
72
|
-
"envapt": "^8.
|
|
72
|
+
"envapt": "^8.2.2",
|
|
73
73
|
"typescript": "^6.0.3"
|
|
74
74
|
},
|
|
75
75
|
"dependencies": {
|
|
76
76
|
"@discordjs/builders": "^1.14.1",
|
|
77
77
|
"@discordjs/rest": "^2.6.3",
|
|
78
|
-
"@seedcord/custom-id": "^0.2.
|
|
79
|
-
"@seedcord/errors": "^0.
|
|
78
|
+
"@seedcord/custom-id": "^0.2.3",
|
|
79
|
+
"@seedcord/errors": "^0.10.0",
|
|
80
80
|
"@seedcord/event-emitter": "^0.1.5",
|
|
81
|
-
"@seedcord/logger": "^0.
|
|
82
|
-
"@seedcord/types": "^0.
|
|
83
|
-
"@seedcord/utils": "^0.8.
|
|
81
|
+
"@seedcord/logger": "^0.4.0",
|
|
82
|
+
"@seedcord/types": "^0.14.0",
|
|
83
|
+
"@seedcord/utils": "^0.8.13",
|
|
84
84
|
"discord-api-types": "^0.38.52",
|
|
85
85
|
"reflect-metadata": "^0.2.2"
|
|
86
86
|
},
|
|
87
87
|
"devDependencies": {
|
|
88
88
|
"@seedcord/eslint-config": "^2.2.1",
|
|
89
|
-
"@seedcord/rate-limiter": "^0.1.
|
|
89
|
+
"@seedcord/rate-limiter": "^0.1.9",
|
|
90
90
|
"@seedcord/tsconfig": "^2.0.3",
|
|
91
91
|
"@seedcord/tsdown-config": "^2.0.1",
|
|
92
92
|
"@seedcord/vitest-config": "^0.0.1",
|
|
93
|
-
"envapt": "^8.
|
|
93
|
+
"envapt": "^8.2.2",
|
|
94
94
|
"type-fest": "^5.8.0",
|
|
95
95
|
"vite": "^8.2.0"
|
|
96
96
|
},
|
|
@@ -101,10 +101,10 @@
|
|
|
101
101
|
"scripts": {
|
|
102
102
|
"build": "tsdown",
|
|
103
103
|
"clean": "rm -rf dist",
|
|
104
|
-
"lint": "eslint '
|
|
105
|
-
"lint:fix": "eslint '
|
|
106
|
-
"fmt": "prettier --write '
|
|
107
|
-
"fmt:check": "prettier --check '
|
|
104
|
+
"lint": "eslint '**/*.{ts,tsx}' --cache --no-error-on-unmatched-pattern",
|
|
105
|
+
"lint:fix": "eslint '**/*.{ts,tsx}' --fix --cache --no-error-on-unmatched-pattern",
|
|
106
|
+
"fmt": "prettier --write '**/*.{ts,tsx,json,md}' --cache --no-error-on-unmatched-pattern",
|
|
107
|
+
"fmt:check": "prettier --check '**/*.{ts,tsx,json,md}' --cache --no-error-on-unmatched-pattern",
|
|
108
108
|
"tc": "tsgo --noEmit",
|
|
109
109
|
"test": "vitest run",
|
|
110
110
|
"test:watch": "vitest dev",
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"Pluggable-keys1cVr.mjs","names":[],"sources":["../src/node/Lifecycle/withTimeout.ts","../src/node/assertNodeVersion.ts","../src/node/processErrors.ts","../src/node/Pluggable.ts"],"sourcesContent":["import { SeedcordErrorCode } from '@seedcord/errors';\nimport { SeedcordError } from '@seedcord/errors/internal';\n\n// calling start() in here turns its synchronous throw into a rejection\nasync function raceTimer(\n start: () => Promise<unknown>,\n timeoutMs: number,\n onTimeout: (resolve: () => void, reject: (error: unknown) => void) => void\n): Promise<void> {\n let timer: NodeJS.Timeout | undefined;\n try {\n await Promise.race([\n start(),\n new Promise<void>((resolve, reject) => {\n // node's timer can go off before performance.now() reaches timeoutMs\n const expiresAt = performance.now() + timeoutMs;\n const tick = (): void => {\n const remaining = expiresAt - performance.now();\n if (remaining > 0) {\n timer = setTimeout(tick, remaining);\n return;\n }\n onTimeout(resolve, reject);\n };\n timer = setTimeout(tick, timeoutMs);\n })\n ]);\n } finally {\n if (timer) clearTimeout(timer);\n }\n}\n\n// rejects with a LifecycleTaskTimeout when run outlasts the bound\nexport function withTimeout(name: string, run: () => Promise<void>, timeoutMs: number): Promise<void> {\n return raceTimer(run, timeoutMs, (_, reject) => {\n reject(new SeedcordError(SeedcordErrorCode.LifecycleTaskTimeout, [name, timeoutMs]));\n });\n}\n\n// the caller does not get work's rejection\nexport function settleWithin(work: Promise<unknown>, timeoutMs: number): Promise<void> {\n return raceTimer(\n () =>\n work.then(\n () => undefined,\n () => undefined\n ),\n timeoutMs,\n (resolve) => resolve()\n );\n}\n","import { SeedcordErrorCode } from '@seedcord/errors';\nimport { SeedcordError } from '@seedcord/errors/internal';\n\n// every seedcord package declares engines.node as `>=major` or `>=major.minor`\nconst RANGE = new RegExp(String.raw`^>=(\\d+)(?:\\.(\\d+))?`);\nconst VERSION = /^v?(\\d+)\\.(\\d+)/;\nconst SPACES = /\\s/g;\n\n/** @internal */\nexport function assertNodeVersion(range: string, running: string): void {\n const required = RANGE.exec(range.replaceAll(SPACES, ''));\n const current = VERSION.exec(running);\n if (!required || !current) return;\n\n const [, requiredMajor, requiredMinor = '0'] = required;\n const [, currentMajor, currentMinor] = current;\n\n const meetsRange =\n Number(currentMajor) > Number(requiredMajor) ||\n (Number(currentMajor) === Number(requiredMajor) && Number(currentMinor) >= Number(requiredMinor));\n if (meetsRange) return;\n\n throw new SeedcordError(SeedcordErrorCode.UnsupportedNodeVersion, [range, running]);\n}\n","import crypto from 'node:crypto';\n\nimport { Logger } from '@seedcord/logger';\n\nimport { asError } from '#stops/asError';\nimport { PublishDefault } from '#subscribers/publishDefault';\n\nimport type { CoreBase } from '#interfaces/CoreBase';\nimport type { CoordinatedShutdown } from '#node/Lifecycle/CoordinatedShutdown';\n\nconst logger = new Logger('Faults', { channel: 'errors' });\n\n/** @internal */\nexport function registerProcessErrors(core: CoreBase, shutdown: CoordinatedShutdown): () => void {\n function report(caught: unknown, origin: string): void {\n const error = asError(caught);\n const uuid = crypto.randomUUID();\n\n if (core.config.errors?.errorStack ?? false) logger.error(uuid, error);\n else logger.error(`${uuid} | ${error.message}`);\n\n core.bus[PublishDefault]('unknownException', { uuid, dispatchId: null, error, origin });\n }\n\n const onRejection = (reason: unknown): void => {\n report(reason, 'process:unhandledRejection');\n };\n\n const onException = (caught: unknown): void => {\n report(caught, 'process:uncaughtException');\n // node does not exit on its own once a listener is registered\n void shutdown.run(1);\n };\n\n process.on('unhandledRejection', onRejection);\n process.on('uncaughtException', onException);\n\n return () => {\n process.off('unhandledRejection', onRejection);\n process.off('uncaughtException', onException);\n };\n}\n","import { SeedcordErrorCode, isSeedcordError } from '@seedcord/errors';\nimport { SeedcordAggregateError, SeedcordError, SeedcordTypeError } from '@seedcord/errors/internal';\nimport { FRAMEWORK_CHANNELS, Logger } from '@seedcord/logger';\nimport { HostPluginKeys, HostShutdown, HostStartup } from '@seedcord/types/internal';\n\nimport { assertNodeVersion } from '#node/assertNodeVersion';\nimport { StartupPhase } from '#src/lifecycle/phases';\nimport { pluginLoggerOf, resolvedLifecycleSpecOf } from '#src/plugin/Plugin';\n\nimport { withTimeout } from './Lifecycle/withTimeout';\nimport { registerProcessErrors } from './processErrors';\n\nimport type { CoreBase } from '#interfaces/CoreBase';\nimport type { CoordinatedShutdown } from '#node/Lifecycle/CoordinatedShutdown';\nimport type { CoordinatedStartup } from '#node/Lifecycle/CoordinatedStartup';\nimport type { ShutdownPhase } from '#src/lifecycle/phases';\nimport type { ChannelKeyAssert, Runtime, RuntimeAssert, Transport, TransportAssert } from '#src/plugin/options';\nimport type { CoreParamAssert, PluginArgs, PluginCtor, PluginLike } from '#src/plugin/Plugin';\nimport type { Bus } from '#subscribers/Bus';\nimport type { REST } from '@discordjs/rest';\nimport type { Config, IRateLimiter } from '@seedcord/types';\n\ninterface Attachment {\n readonly key: string;\n readonly instance: PluginLike;\n}\n\nconst RESERVED_KEYS: ReadonlySet<string> = new Set(FRAMEWORK_CHANNELS);\n\n/**\n * Base class for a plugin host, a transport `Seedcord` class.\n *\n * You attach plugins while configuring the bot. Within one startup phase, their `init()` calls run\n * one after another in attach order.\n */\n// no defaults, a default runtime of the full union contains 'edge' and RuntimeAssert rejects every plugin on that\nexport abstract class Pluggable<BotT extends Transport, BotRt extends Runtime> implements CoreBase {\n public abstract readonly config: Config;\n public abstract readonly rest: REST;\n public abstract readonly applicationId: string;\n public abstract readonly rateLimiter: IRateLimiter;\n public abstract readonly bus: Bus;\n\n /** @internal */\n readonly [HostShutdown]: CoordinatedShutdown;\n /** @internal */\n readonly [HostStartup]: CoordinatedStartup;\n\n /** Add a task that runs while the bot shuts down. */\n public readonly shutdown: Pick<CoordinatedShutdown, 'addTask'>;\n\n /** Add a task that runs while the bot starts. */\n public readonly startup: Pick<CoordinatedStartup, 'addTask'>;\n\n protected isInitialized = false;\n protected startFailed = false;\n protected readonly plugins: PluginLike[] = [];\n\n private readonly pluginLogger = new Logger('Plugins', { channel: 'plugins' });\n\n private readonly attachments: Attachment[] = [];\n private readonly completedInits = new Set<Attachment>();\n private readonly disposePhases = new Set<ShutdownPhase>();\n private pluginTasksRegistered = false;\n private initPromise?: Promise<this> | undefined;\n\n private static isInstantiated = false;\n private static liveHost?: object | undefined;\n private static liveShutdown?: CoordinatedShutdown | undefined;\n private static liveProcessErrors?: (() => void) | undefined;\n\n constructor(shutdown: CoordinatedShutdown, startup: CoordinatedStartup) {\n // a `sideEffects: false` build would drop the same call in the node entry\n assertNodeVersion(process.env.PACKAGE_NODE_RANGE ?? '', process.version);\n\n if (Pluggable.isInstantiated) {\n // signal handlers from the caller's shutdown stay on the process unless dropped here\n shutdown.removeSignalHandlers();\n throw new SeedcordError(SeedcordErrorCode.CoreSingletonViolation);\n }\n\n Pluggable.isInstantiated = true;\n Pluggable.liveHost = this;\n Pluggable.liveShutdown = shutdown;\n this[HostShutdown] = shutdown;\n this[HostStartup] = startup;\n // a getter returning the slot would hand back run() and the signal handlers with it\n this.shutdown = { addTask: shutdown.addTask.bind(shutdown) };\n this.startup = { addTask: startup.addTask.bind(startup) };\n }\n\n /** @internal */\n protected init(): Promise<this> {\n // clearing the slot on a rejection lets a later retry throw its own error\n this.initPromise ??= this.runInit().catch((caught: unknown) => {\n this.initPromise = undefined;\n throw caught;\n });\n return this.initPromise;\n }\n\n private async runInit(): Promise<this> {\n if (this.isInitialized) return this;\n // a rerun after a failed startup would re-init the rolled-back plugins\n if (this.startFailed) throw new SeedcordError(SeedcordErrorCode.LifecycleRestartAfterFailure);\n\n this.registerPluginTasks();\n\n if (this.config.errors?.catchProcessErrors ?? true) {\n Pluggable.liveProcessErrors = registerProcessErrors(this, this[HostShutdown]);\n }\n\n const startupSettled: PromiseWithResolvers<void> = Promise.withResolvers();\n this[HostShutdown].gateOnStartup(startupSettled.promise);\n\n try {\n await this[HostStartup].run();\n } catch (caught) {\n await this.rollback();\n throw caught;\n } finally {\n startupSettled.resolve();\n }\n\n this.isInitialized = true;\n return this;\n }\n\n /** @internal codegen reads this to emit the `Core` augmentation */\n public get [HostPluginKeys](): readonly string[] {\n return this.attachments.map((attachment) => attachment.key);\n }\n\n /** @internal */\n protected static reset(host?: object): boolean {\n if (host !== undefined && Pluggable.liveHost !== host) return false;\n\n Pluggable.liveShutdown?.removeSignalHandlers();\n Pluggable.liveShutdown = undefined;\n Pluggable.liveProcessErrors?.();\n Pluggable.liveProcessErrors = undefined;\n Pluggable.liveHost = undefined;\n Pluggable.isInstantiated = false;\n return true;\n }\n\n /**\n * Attaches a plugin under `key`. Read the instance back as `core[key]`. `seedcord codegen`\n * writes the `Core` augmentation that types it there.\n *\n * Startup runs each plugin's `init()` in attach order within its phase.\n *\n * Attaching a plugin whose `transport` or `runtime` differs from this host fails to compile.\n * Your constructor takes `CoreBase` as its first parameter (you don't need to pass it though).\n * A narrower one fails to compile here.\n *\n * @param key - Also the channel the plugin logs on. Reserved channel names throw.\n * @param args - Whatever your constructor takes after the host.\n * @throws A **SeedcordError** if you attach after the bot has started. A taken or reserved key throws too.\n * @example\n * ```ts\n * seedcord.attach('db', Mongoose, { uri: 'mongodb://...', name: 'seedcord', dir: ... });\n * ```\n */\n public attach<Key extends string, Ctor extends PluginCtor>(\n this: this,\n key: ChannelKeyAssert<Key>,\n Plugin: Ctor &\n TransportAssert<InstanceType<Ctor>, BotT> &\n RuntimeAssert<InstanceType<Ctor>, BotRt> &\n CoreParamAssert<Ctor>,\n ...args: PluginArgs<Ctor>\n ): this & Record<Key, InstanceType<Ctor>> {\n if (this.isInitialized) {\n throw new SeedcordError(SeedcordErrorCode.CorePluginAfterInit);\n }\n // several reserved channels are also members on a host, which the next check would report first\n if (RESERVED_KEYS.has(key)) {\n throw new SeedcordTypeError(SeedcordErrorCode.CorePluginReservedChannel, [key]);\n }\n if (key in this) {\n throw new SeedcordError(SeedcordErrorCode.CorePluginKeyExists, [key]);\n }\n\n const instance = new Plugin(this, ...args);\n pluginLoggerOf(instance).setChannel(key);\n this.plugins.push(instance);\n this.attachments.push({ key, instance });\n\n return Object.assign(this, { [key]: instance } as Record<Key, InstanceType<Ctor>>);\n }\n\n // one combined task per phase keeps plugin inits sequential while the phase's other tasks run concurrently\n private registerPluginTasks(): void {\n if (this.pluginTasksRegistered) return;\n this.pluginTasksRegistered = true;\n\n const groups = new Map<StartupPhase, Attachment[]>();\n for (const attachment of this.attachments) {\n const phase = resolvedLifecycleSpecOf(attachment.instance).init.phase;\n const group = groups.get(phase) ?? [];\n group.push(attachment);\n groups.set(phase, group);\n }\n\n for (const [phase, group] of groups) {\n // Ready inits run in the combined Ready task, before the ready hooks\n if (phase === StartupPhase.Ready) continue;\n const budget = group.reduce((sum, a) => sum + resolvedLifecycleSpecOf(a.instance).init.timeout, 0);\n this.startup.addTask(phase, 'plugins-init', () => this.runInits(group), budget);\n }\n\n this.registerReadyTask(groups.get(StartupPhase.Ready) ?? []);\n }\n\n private async runInits(group: readonly Attachment[]): Promise<void> {\n for (const attachment of group) {\n const { key, instance } = attachment;\n const spec = resolvedLifecycleSpecOf(instance);\n\n pluginLoggerOf(instance).utils.initialization(key, 'start');\n const running = instance.init();\n try {\n await withTimeout(`Plugin (${key})`, () => running, spec.init.timeout);\n } catch (caught) {\n if (isSeedcordError(caught, undefined, SeedcordErrorCode.LifecycleTaskTimeout)) {\n this.disposeWhenInitResolves(attachment, running);\n }\n throw caught;\n }\n pluginLoggerOf(instance).utils.initialization(key, 'end');\n\n this.completedInits.add(attachment);\n if (instance.dispose) this.registerDisposeTask(spec.dispose.phase);\n }\n }\n\n // disposeCompleted skips a plugin missing from completedInits\n private disposeWhenInitResolves(attachment: Attachment, running: Promise<void>): void {\n const dispose = attachment.instance.dispose?.bind(attachment.instance);\n const spec = resolvedLifecycleSpecOf(attachment.instance);\n\n void running.then(\n async () => {\n if (!dispose) return;\n await withTimeout(`Plugin:${attachment.key}:dispose`, dispose, spec.dispose.timeout).catch(\n (caught: unknown) =>\n this.pluginLogger.warn(`${attachment.key} dispose failed after a timed-out init`, caught)\n );\n },\n (caught: unknown) => this.pluginLogger.warn(`${attachment.key} init failed after its timeout`, caught)\n );\n }\n\n private registerReadyTask(readyInits: readonly Attachment[]): void {\n const steps = this.attachments.reduce<{ key: string; run: () => Promise<void>; timeout: number }[]>(\n (acc, a) => {\n const ready = a.instance.ready?.bind(a.instance);\n if (ready) {\n acc.push({\n key: a.key,\n run: ready,\n timeout: resolvedLifecycleSpecOf(a.instance).ready.timeout\n });\n }\n return acc;\n },\n []\n );\n if (readyInits.length === 0 && steps.length === 0) return;\n\n const budget =\n readyInits.reduce((sum, a) => sum + resolvedLifecycleSpecOf(a.instance).init.timeout, 0) +\n steps.reduce((sum, step) => sum + step.timeout, 0);\n this.startup.addTask(\n StartupPhase.Ready,\n 'plugins-ready',\n async () => {\n await this.runInits(readyInits);\n for (const step of steps) {\n await withTimeout(`Plugin:${step.key}:ready`, step.run, step.timeout);\n }\n },\n budget\n );\n }\n\n private registerDisposeTask(phase: ShutdownPhase): void {\n if (this.disposePhases.has(phase)) return;\n this.disposePhases.add(phase);\n\n // the budget covers every dispose in this phase because they all run in this one task\n const budget = this.attachments.reduce((sum, a) => {\n const spec = resolvedLifecycleSpecOf(a.instance);\n return a.instance.dispose && spec.dispose.phase === phase ? sum + spec.dispose.timeout : sum;\n }, 0);\n this.shutdown.addTask(phase, 'plugins-dispose', () => this.runDisposals(phase), budget);\n }\n\n private async runDisposals(phase: ShutdownPhase): Promise<void> {\n const failures: unknown[] = [];\n // keep disposing the rest even when one fails\n await this.disposeCompleted(phase, (caught) => failures.push(caught));\n if (failures.length === 1) throw failures[0];\n if (failures.length > 1) {\n throw new SeedcordAggregateError(SeedcordErrorCode.PluginDisposeFailures, failures, [failures.length]);\n }\n }\n\n private async disposeCompleted(\n phase: ShutdownPhase | undefined,\n onError: (caught: unknown) => void\n ): Promise<void> {\n for (const attachment of [...this.attachments].reverse()) {\n if (!this.completedInits.has(attachment)) continue;\n const dispose = attachment.instance.dispose?.bind(attachment.instance);\n if (!dispose) continue;\n const spec = resolvedLifecycleSpecOf(attachment.instance);\n if (phase !== undefined && spec.dispose.phase !== phase) continue;\n\n try {\n await withTimeout(`Plugin:${attachment.key}:dispose`, dispose, spec.dispose.timeout);\n } catch (caught) {\n onError(caught);\n }\n }\n }\n\n private async rollback(): Promise<void> {\n this.startFailed = true;\n // log rollback dispose failures so they do not hide the startup error\n await this.disposeCompleted(undefined, (caught) => this.pluginLogger.warn('rollback dispose failed', caught));\n // prevents re-dispose when shutdown runs after rollback\n this.completedInits.clear();\n }\n}\n\n// the public `shutdown` field carries addTask alone\nexport function shutdownOf(host: Pick<Pluggable<Transport, Runtime>, typeof HostShutdown>): CoordinatedShutdown {\n return host[HostShutdown];\n}\n"],"mappings":"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"}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"subscribers-Bf4cBTvK.mjs","names":[],"sources":["../src/commands/CommandInjector.ts"],"sourcesContent":["import { SlashCommandBuilder } from '@discordjs/builders';\nimport { paint } from '@seedcord/errors';\nimport { Logger } from '@seedcord/logger';\nimport { routeLeavesOf } from '@seedcord/utils/internal';\nimport { ApplicationCommandType } from 'discord-api-types/v10';\n\nimport { contextMenuLeaves } from '#src/commands/contextMenuLeaves';\nimport { accessorStore, clearStore, guardedAccessor } from '#src/miscellaneous/guarded';\n\nimport type {\n ContextMenuKind,\n MessageContextMenuRegistry,\n UserContextMenuRegistry\n} from '#registries/ContextMenuRegistry';\nimport type { SlashRegistry } from '#registries/SlashRegistry';\nimport type { CommandBuilder, DeployResult } from '#src/commands/types';\nimport type { APIApplicationCommand, Snowflake } from 'discord-api-types/v10';\n\nconst commandStorage = accessorStore<CommandInfo>();\nconst userMenuStorage = accessorStore<ContextMenuInfo>();\nconst messageMenuStorage = accessorStore<ContextMenuInfo>();\n\n/** What {@link Commands} resolves to for one slash route. */\nexport interface CommandInfo {\n /** The clickable `</config set:id>` mention. A route whose id did not resolve falls back to `/config set`. */\n readonly mention: string;\n /** The deployed command id. Absent when the command deployed to two or more guilds. */\n readonly id?: string;\n /** The full route, e.g. `config/set`. */\n readonly route: string;\n /** The route's own description, taken from the subcommand for a nested route. */\n readonly description: string;\n}\n\n/** Each registered slash route mapped to its deployed command. */\nexport type InjectedCommandMap = {\n [K in keyof SlashRegistry]: CommandInfo;\n};\n\n/**\n * The bot's deployed slash commands, keyed by {@link SlashRegistry}. Filled by\n * {@link CommandInjector} after each deploy, and a read before that throws.\n *\n * @example\n * ```ts\n * import { Commands } from '@seedcord/gateway';\n *\n * await this.reply(`Try ${Commands['config/set'].mention}`);\n *\n * // a help listing\n * const lines = Object.values(Commands).map((c) => `${c.mention} ${c.description}`);\n * ```\n */\nexport const Commands = guardedAccessor('Commands', commandStorage) as InjectedCommandMap;\n\n/** What {@link ContextMenus} resolves to for one context-menu command. */\nexport interface ContextMenuInfo {\n /** The deployed command id. Absent when the command deployed to two or more guilds. */\n readonly id?: string;\n /** The name shown in Discord's right-click menu. */\n readonly name: string;\n readonly kind: ContextMenuKind;\n}\n\n/** Deployed context-menu commands, split by kind because Discord scopes name uniqueness per command type. */\nexport interface InjectedContextMenuMap {\n readonly user: { [K in keyof UserContextMenuRegistry]: ContextMenuInfo };\n readonly message: { [K in keyof MessageContextMenuRegistry]: ContextMenuInfo };\n}\n\n/**\n * The bot's deployed context-menu commands. Filled by {@link CommandInjector} after each deploy, and a read\n * before that throws.\n *\n * @example\n * ```ts\n * import { ContextMenus } from '@seedcord/gateway';\n *\n * const id = ContextMenus.user['View Profile'].id;\n * ```\n */\nexport const ContextMenus = {\n user: guardedAccessor('ContextMenus.user', userMenuStorage),\n message: guardedAccessor('ContextMenus.message', messageMenuStorage)\n} as InjectedContextMenuMap;\n\nconst MULTI_GUILD = Symbol('seedcord:commands:multi-guild');\n\n// Discord scopes name uniqueness per type, and a context-menu name may be all lowercase like a slash name\nfunction commandKey(type: ApplicationCommandType, name: string): string {\n return `${type}:${name}`;\n}\n\nfunction slashBuildersOf(all: readonly CommandBuilder[]): SlashCommandBuilder[] {\n const slash = all.filter((command): command is SlashCommandBuilder => command instanceof SlashCommandBuilder);\n // a guild command in N guilds is the same builder pushed once per guild\n return [...new Set(slash)];\n}\n\ninterface DeployedIds {\n readonly global: Map<string, Snowflake>;\n readonly guild: Map<string, Snowflake | typeof MULTI_GUILD>;\n readonly warned: Set<string>;\n}\n\n/** @internal */\nexport class CommandInjector {\n private readonly logger = new Logger('Commands', { channel: 'bot' });\n\n public inject(deploy: DeployResult, all: readonly CommandBuilder[]): void {\n this.clear();\n\n const ids: DeployedIds = {\n global: this.indexByKey(deploy.global),\n guild: this.indexGuilds(deploy.guilds),\n warned: new Set<string>()\n };\n let clickable = 0;\n\n for (const command of slashBuildersOf(all)) {\n const id = this.resolveId(\n ApplicationCommandType.ChatInput,\n command.name,\n ids,\n 'falling back to plain text (not clickable)'\n );\n for (const leaf of routeLeavesOf(command.toJSON())) {\n commandStorage[leaf.route] = {\n mention: id ? this.toMention(leaf.route, id) : this.toPlain(leaf.route),\n ...(id && { id }),\n route: leaf.route,\n description: leaf.description\n };\n if (id) clickable++;\n }\n }\n\n const menus = contextMenuLeaves(all);\n this.injectMenus(menus.user, ApplicationCommandType.User, userMenuStorage, ids);\n this.injectMenus(menus.message, ApplicationCommandType.Message, messageMenuStorage, ids);\n\n this.logger.utils.summary('Linked commands', {\n clickable,\n plain: Object.keys(commandStorage).length - clickable,\n 'context menus': menus.user.size + menus.message.size\n });\n }\n\n private injectMenus(\n names: ReadonlySet<string>,\n kind: ContextMenuKind,\n storage: Record<string, ContextMenuInfo>,\n ids: DeployedIds\n ): void {\n for (const name of names) {\n const id = this.resolveId(kind, name, ids, 'leaving its id unset');\n storage[name] = { ...(id && { id }), name, kind };\n }\n }\n\n private indexByKey(collection: Map<Snowflake, APIApplicationCommand>): Map<string, Snowflake> {\n const map = new Map<string, Snowflake>();\n for (const command of collection.values()) map.set(commandKey(command.type, command.name), command.id);\n return map;\n }\n\n private indexGuilds(\n guilds: Map<Snowflake, Map<Snowflake, APIApplicationCommand>>\n ): Map<string, Snowflake | typeof MULTI_GUILD> {\n const map = new Map<string, Snowflake | typeof MULTI_GUILD>();\n for (const collection of guilds.values()) {\n for (const command of collection.values()) {\n const key = commandKey(command.type, command.name);\n const existing = map.get(key);\n // Discord assigns a separate id per guild, so two ids for one name means no single mention\n if (existing === undefined) map.set(key, command.id);\n else if (existing !== command.id) map.set(key, MULTI_GUILD);\n }\n }\n return map;\n }\n\n private resolveId(\n type: ApplicationCommandType,\n name: string,\n ids: DeployedIds,\n consequence: string\n ): Snowflake | undefined {\n const key = commandKey(type, name);\n const global = ids.global.get(key);\n if (global) return global;\n\n const guild = ids.guild.get(key);\n if (guild === undefined) return undefined;\n if (guild === MULTI_GUILD) {\n if (!ids.warned.has(key)) {\n this.logger.warn(`${paint.sky.bold(name)} is deployed to multiple guilds, ${consequence}.`);\n ids.warned.add(key);\n }\n return undefined;\n }\n return guild;\n }\n\n private toMention(route: string, id: Snowflake): string {\n return `</${route.replaceAll('/', ' ')}:${id}>`;\n }\n\n private toPlain(route: string): string {\n return `/${route.replaceAll('/', ' ')}`;\n }\n\n private clear(): void {\n for (const store of [commandStorage, userMenuStorage, messageMenuStorage]) clearStore(store);\n }\n}\n"],"mappings":"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"}
|