Rutas de fallo
Cuando un comando con prefijo falla en cualquier etapa, el framework produce una excepción específica que puedes interceptar y formatear. Entender la taxonomía de fallos te ayuda a diseñar respuestas de error amigables para usuarios en lugar de dejar que errores crudos se filtren al chat. Cada tipo de fallo corresponde a una clase de excepción distinta con su propio hook de layout.
Layouts de excepciones
Los layouts de excepciones te permiten definir respuestas personalizadas para tipos de excepción concretos. En lugar de escribir bloques try-catch en cada handler, registras un layout una sola vez y el framework lo aplica automáticamente siempre que ese tipo de excepción se lance durante la ejecución de comandos con prefijo. Esto mantiene limpios tus handlers y hace consistente el formato de errores en todo el bot.
Un layout recibe el Message original y la excepción lanzada, para que puedas construir embeds de error ricos con contexto sobre qué salió mal. El helper defineExceptionLayout crea una fábrica de layouts que el framework descubre mediante el registro de tu módulo.
import { defineExceptionLayout } from '@spraxium/core';
import { ArgumentException } from '@spraxium/common';
export const ArgumentExceptionLayout = defineExceptionLayout(
ArgumentException,
async (message, exception) => {
await message.reply({
content: `Invalid argument: **${exception.argName}** - ${exception.message}`,
});
},
);Registra layouts en tu módulo junto a comandos y handlers. El framework los detecta durante la inicialización del módulo y los aplica automáticamente a los tipos de excepción correctos.
import { Module } from '@spraxium/common';
import { RollCommand } from './commands/roll.command';
import { RollHandler } from './handlers/roll.handler';
import { ArgumentExceptionLayout } from './layouts/argument-exception.layout';
import { CommandNotFoundLayout } from './layouts/not-found.layout';
@Module({
commands: [RollCommand],
handlers: [RollHandler],
layouts: [ArgumentExceptionLayout, CommandNotFoundLayout],
})
export class PrefixModule {}Introspección del dispatcher
El PrefixDispatcher expone métodos para inspeccionar en runtime el árbol de comandos registrados. El método getCommandInfo devuelve un arreglo de objetos PrefixCommandInfo con nombres de comandos, descripciones, listas de subcomandos y esquemas de argumentos. Esta es la base para construir comandos de ayuda dinámicos.
El dispatcher también expone getResolved para los bindings comando-handler completamente resueltos, size para el número total de entradas registradas, commandCount para comandos de nivel superior distintos y guildPrefixes para el manager de prefijos por guild. Estos métodos de introspección son útiles para paneles administrativos, health checks y herramientas de depuración.
import { Ctx, PrefixCommandHandler } from '@spraxium/common';
import { PrefixDispatcher } from '@spraxium/core';
import type { Message } from 'discord.js';
import { HelpCommand } from '../commands/help.command';
@PrefixCommandHandler(HelpCommand)
export class HelpHandler {
constructor(private readonly dispatcher: PrefixDispatcher) {}
async handle(@Ctx() message: Message): Promise<void> {
const commands = this.dispatcher.getCommandInfo();
const lines = commands.map((cmd) => {
const subs = cmd.subcommands?.map((s) => s.name).join(', ');
return subs
? `**${cmd.name}** - subcommands: ${subs}`
: `**${cmd.name}**`;
});
await message.reply(lines.join('\n') || 'No commands registered.');
}
}Guards con comandos con prefijo
Los comandos con prefijo soportan el mismo sistema de guards usado por slash commands y listeners. Decora una clase handler con @UseGuards para adjuntar uno o más guards que se ejecutan antes de que el handler se ejecute. Si cualquier guard devuelve false o lanza una excepción, el handler se omite. Los guards son útiles para comprobaciones de permisos, restricciones por rol y filtrado por tipo de canal.
Los guards reciben el contexto de ejecución, que para comandos con prefijo incluye el objeto Message original. Puedes usar las mismas clases guard en comandos con prefijo, slash commands y listeners sin modificaciones. Este es uno de los beneficios de la arquitectura unificada de guards.
import { CanActivate, ExecutionContext } from '@spraxium/common';
export class ModeratorGuard implements CanActivate {
async canActivate(context: ExecutionContext): Promise<boolean> {
const message = context.getMessage();
const member = message.member;
if (!member) return false;
return member.permissions.has('ModerateMembers');
}
}