all repos — kitten @ db843bde0743c7d7e04c0d8aae914104ce2ccc65

router: more logging yay
vi did:web:vt3e.cat
Fri, 03 Jul 2026 18:37:10 +0100
commit

db843bde0743c7d7e04c0d8aae914104ce2ccc65

parent

3a1fb0e60b9a5d928ff52958941d627edb43c872

1 files changed, 103 insertions(+), 12 deletions(-)

jump to
M pkgs/router/src/kitten.tspkgs/router/src/kitten.ts

@@ -45,6 +45,7 @@ options: KittenOptions = {},

) { this.client.on("interactionCreate", this.handleInteraction.bind(this)); this.logger = options.logger ?? baseLogger; + this.logger.debug("kitten instance initialized."); } builder() {

@@ -137,16 +138,28 @@ components?: KittenComponent<any, any>[];

}) { registry.commands?.forEach((cmd) => { if (cmd instanceof ContextMenuCommand) { - if (cmd.type === "user") this.userContextMenus.set(cmd.name, cmd); - else if (cmd.type === "message") this.messageContextMenus.set(cmd.name, cmd); + if (cmd.type === "user") { + this.userContextMenus.set(cmd.name, cmd); + this.logger.debug(`registered user context menu: "${cmd.name}"`); + } else if (cmd.type === "message") { + this.messageContextMenus.set(cmd.name, cmd); + this.logger.debug(`registered message context menu: "${cmd.name}"`); + } } else { this.commands.set(cmd.name, cmd); + const subInfo = cmd instanceof SubcommandGroupBuilder ? " (subcommand group)" : ""; + this.logger.debug(`registered command: "${cmd.name}"${subInfo}`); } }); - registry.components?.forEach((comp) => this.components.set(comp.name, comp)); + registry.components?.forEach((comp) => { + this.components.set(comp.name, comp); + this.logger.debug(`registered component prefix: "${comp.name}"`); + }); } private async handleInteraction(interaction: BaseInteraction) { + this.logger.debug(`received interaction ID: ${interaction.id}, Type: ${interaction.type}`); + switch (true) { // commands case interaction.isChatInputCommand():

@@ -165,22 +178,41 @@ case interaction.isAnySelectMenu():

return await this.routeComponent(interaction); case interaction.isModalSubmit(): return await this.routeComponent(interaction); + default: + this.logger.debug(`interaction ID: ${interaction.id} could not be classified or handled.`); } } private async routeCommand(interaction: ChatInputCommandInteraction) { const entry = this.commands.get(interaction.commandName); - if (!entry) return; + if (!entry) { + this.logger.debug(`command routing aborted: "${interaction.commandName}" is not registered.`); + return; + } if (entry instanceof SubcommandGroupBuilder) { const groupName = interaction.options.getSubcommandGroup(false); const subName = interaction.options.getSubcommand(false); + this.logger.debug( + `routing group-based command: "${interaction.commandName}", group: ${groupName ?? "none"}, subcommand: ${subName ?? "none"}`, + ); + if (groupName) { const group = entry.groups[groupName]; - if (!group) return; + if (!group) { + this.logger.debug( + `subcommand group "${groupName}" not found under "${interaction.commandName}".`, + ); + return; + } const subcommand = group.subcommands[subName ?? ""]; - if (!subcommand) return; + if (!subcommand) { + this.logger.debug( + `subcommand "${subName}" not found in group "${groupName}" under "${interaction.commandName}".`, + ); + return; + } await this.executeWithMiddlewares( interaction,

@@ -190,7 +222,12 @@ subcommand.config.options,

); } else if (subName) { const subcommand = entry.subcommands[subName]; - if (!subcommand) return; + if (!subcommand) { + this.logger.debug( + `subcommand "${subName}" not found under "${interaction.commandName}".`, + ); + return; + } await this.executeWithMiddlewares( interaction,

@@ -200,6 +237,7 @@ subcommand.config.options,

); } } else { + this.logger.debug(`routing top-level chat command: "${interaction.commandName}"`); await this.executeWithMiddlewares( interaction, entry.middlewares,

@@ -218,7 +256,14 @@ type === "user"

? this.userContextMenus.get(interaction.commandName) : this.messageContextMenus.get(interaction.commandName); - if (!entry) return; + if (!entry) { + this.logger.debug( + `context menu routing aborted: "${interaction.commandName}" (${type}) is not registered.`, + ); + return; + } + + this.logger.debug(`routing context menu command: "${interaction.commandName}" (${type})`); await this.executeWithMiddlewares(interaction, entry.middlewares, entry.run); }

@@ -231,25 +276,40 @@ componentArgs?: Record<string, any>,

) { try { let context = {}; - for (const mw of middlewares) { + + this.logger.debug( + `Running ${middlewares.length} middleware(s) for interaction ${interaction.id}`, + ); + + for (let i = 0; i < middlewares.length; i++) { + const mw = middlewares[i]; + if (!mw) continue; + this.logger.debug(`executing middleware [${i + 1}/${middlewares.length}]`); const result = await mw(interaction, context); if (result) context = { ...context, ...result }; } if (interaction.isChatInputCommand()) { const args = this.parseSlashOptions(interaction, optionsSchema); + this.logger.debug(`executing command handler for "${interaction.commandName}"`, { args }); await run(interaction, args, context); } else if ( interaction.isButton() || interaction.isAnySelectMenu() || interaction.isModalSubmit() ) { + const customId = (interaction as any).customId; + this.logger.debug(`executing component handler for custom ID: "${customId}"`, { + componentArgs, + }); await run(interaction, componentArgs ?? {}, context); } else { + this.logger.debug(`executing generic fallback handler for interaction ${interaction.id}`); await run(interaction, context); } } catch (err) { if (err instanceof HaltExecution) { + this.logger.debug(`execution halted intentionally for interaction ${interaction.id}.`); if ( err.replyPayload && interaction.isRepliable() &&

@@ -292,7 +352,12 @@ }

private async routeAutocomplete(interaction: AutocompleteInteraction) { const entry = this.commands.get(interaction.commandName); - if (!entry) return; + if (!entry) { + this.logger.debug( + `autocomplete aborted: command "${interaction.commandName}" not registered.`, + ); + return; + } const focused = interaction.options.getFocused(true); let optionsSchema: Record<string, Option<any, any>> | undefined;

@@ -310,18 +375,38 @@ }

const opt = optionsSchema?.[focused.name]; if (opt?.autocomplete) { + this.logger.debug( + `handling autocomplete for command "${interaction.commandName}" option "${focused.name}" with value "${focused.value}"`, + ); const choices = await opt.autocomplete(interaction, focused.value); await interaction.respond(choices).catch(() => null); + } else { + this.logger.debug( + `no autocomplete handler configured for option "${focused.name}" in command "${interaction.commandName}"`, + ); } } private async routeComponent(interaction: KittenComponentInteraction) { const [prefix] = interaction.customId.split(":"); - if (!prefix) return; + if (!prefix) { + this.logger.debug( + `component routing skipped: unable to extract prefix from customId "${interaction.customId}".`, + ); + return; + } const component = this.components.get(prefix); - if (!component) return; + if (!component) { + this.logger.debug( + `component routing skipped: no component handler registered for prefix "${prefix}".`, + ); + return; + } + this.logger.debug( + `routing component for prefix "${prefix}" (customId:${interaction.customId})`, + ); const args = component.parseArgs(interaction.customId); await this.executeWithMiddlewares( interaction,

@@ -338,6 +423,8 @@ optionsSchema?: Record<string, Option<any, any>>,

): Record<string, any> { if (!optionsSchema) return {}; const parsed: Record<string, any> = {}; + + this.logger.debug(`parsing options for command "${interaction.commandName}"`); for (const [name, opt] of Object.entries(optionsSchema)) { switch (opt.type) {

@@ -368,6 +455,8 @@ break;

case "attachment": parsed[name] = interaction.options.getAttachment(name); break; + default: + this.logger.debug(`skipping option "${name}" due to unmapped option type: "${opt.type}"`); } } return parsed;

@@ -456,6 +545,8 @@

if (!this.client.application) { throw new Error("client application not ready to sync. ensure you await client.login()"); } + + this.logger.debug(`preparing to sync ${payloads.length} command(s) to Discord API...`); if (guildId) { await this.client.application.commands.set(payloads, guildId);