Spraxium logoSpraxium

Manejo de errores y aspectos avanzados

Maneja rate limits, errores HTTP y apagados limpios en bots con sharding. Aprende sobre hooks del proceso padre, procesos hijo en TypeScript, broadcastEval e integración con presencia y logging.

Manejo de errores durante el spawn

El SpraxiumShardManager captura errores durante la fase de spawn y los traduce en mensajes claros y accionables. Los dos fallos más comunes son los rate limits y los tokens inválidos. En lugar de un stack trace crudo, el framework imprime un error estructurado con una descripción y una solución sugerida, y luego termina el proceso.

Una respuesta 429 (Too Many Requests) ocurre cuando reinicias el bot con demasiada frecuencia y Discord aplica rate limit al endpoint /gateway/bot. Esto es especialmente común con totalShards: 'auto' porque el framework debe consultar Discord para obtener el número recomendado de shards en cada arranque. El mensaje de error incluye el valor del header Retry-After cuando está disponible y sugiere usar una cantidad fija de shards durante desarrollo.

Cualquier otro error HTTP (401, 403, etc.) se captura y se imprime con el código de estado y una sugerencia para revisar el token del bot. Los errores que no son HTTP muestran el mensaje crudo. Todos los errores de spawn son fatales; el proceso termina después de imprimirlos porque un bot sin shards no puede funcionar.

src/main.ts
import { SpraxiumFactory } from '@spraxium/core';
import { AppModule } from './app.module';

async function main() {
  const app = await SpraxiumFactory.create({
    token: process.env.DISCORD_TOKEN,
  });
  app.useModule(AppModule);
  app.useSharding({ totalShards: 2, spawnDelay: 6000 });
  await app.listen();
}

main();

Apagado limpio

El proceso padre registra handlers de apagado para SIGINT, SIGTERM y SIGHUP (en plataformas que no son Windows). Cuando se recibe cualquiera de estas señales, el padre recorre todos los shards gestionados, termina cada proceso hijo, registra el apagado y sale de forma limpia. Esto garantiza que los procesos de shard no queden huérfanos cuando detienes el bot con Ctrl+C o cuando un gestor de procesos envía una señal de terminación.

La secuencia de apagado es intencionalmente simple. Llama a shard.kill() en cada shard sin esperar desconexiones elegantes, porque las conexiones al gateway de Discord se cierran de inmediato cuando el proceso muere. Si necesitas lógica de limpieza personalizada antes del apagado, registra tu propio handler antes del del framework.

src/main.ts
import { SpraxiumFactory } from '@spraxium/core';

const app = await SpraxiumFactory.create();
app.useSharding({ totalShards: 'auto' });

process.once('SIGINT', async () => {
  console.log('Custom cleanup before shutdown');
  process.exit(0);
});

await app.listen();

Hooks del proceso padre

El ParentHookRegistry te permite registrar funciones que se ejecutan en el proceso padre después de que todos los shards se hayan creado y reportado como listos. Esto es útil para tareas que deben ocurrir exactamente una vez en todo el bot, como registrar slash commands globales, ejecutar una migración de base de datos o iniciar un servidor HTTP de health-check.

Cada hook recibe la instancia de ShardingManager de discord.js, que puedes usar para llamar a broadcastEval o inspeccionar el estado de los shards. Los hooks se ejecutan de forma secuencial en el orden de registro.

src/main.ts
import { ParentHookRegistry, SpraxiumFactory } from '@spraxium/core';

ParentHookRegistry.register(async (manager) => {
  const guilds = await manager.broadcastEval((c) => c.guilds.cache.size);
  const total = (guilds as number[]).reduce((sum, n) => sum + n, 0);
  console.log(`All shards ready. Total guilds: ${total}`);
});

const app = await SpraxiumFactory.create();
app.useSharding({ totalShards: 'auto' });
await app.listen();

BroadcastEval

El método broadcastEval del ShardingManager ejecuta una función en cada proceso hijo de shard y devuelve los resultados como un arreglo. Así es como el proceso padre recopila datos como cantidad de guilds, tags de usuario o métricas personalizadas de todos los shards. La función se ejecuta en el contexto de cada hijo, con acceso a la instancia de Client de discord.js.

Mantén los callbacks de broadcastEval simples y serializables. No pueden referenciar variables del alcance del padre porque se serializan, se envían a cada hijo y se evalúan allí. Si necesitas pasar datos al callback, usa la opción context.

src/services/stats.service.ts
import { ParentHookRegistry } from '@spraxium/core';

ParentHookRegistry.register(async (manager) => {
  const memberCounts = await manager.broadcastEval((c) =>
    c.guilds.cache.reduce((sum, g) => sum + g.memberCount, 0),
  );
  const totalMembers = (memberCounts as number[]).reduce((a, b) => a + b, 0);
  console.log(`Total members across all shards: ${totalMembers}`);
});

Procesos hijo en TypeScript

Cuando el SpraxiumShardManager detecta que el archivo de entrada tiene extensión .ts, .mts o .cts, agrega automáticamente --import tsx a los argumentos de ejecución de los procesos hijo. Esto significa que los hijos de shard pueden ejecutar TypeScript directamente sin un paso de build separado. Si tus argumentos de ejecución ya incluyen tsx o ts-node, el framework omite agregarlo para evitar duplicados.

Esta detección automática funciona tanto para proyectos CommonJS como ESM. El archivo de entrada se resuelve desde require.main.filename (CJS), process.argv[1] (ESM), o usa dist/main.js en el directorio de trabajo actual como fallback.

Para despliegues en producción donde precompilas a JavaScript, no se necesita configuración especial. Los procesos hijo recibirán archivos .js y los argumentos de ejecución permanecerán intactos.

Integración con presencia

El sistema de presencia funciona sin fricción con sharding. Cada proceso hijo de shard ejecuta su propia instancia de PresenceManager, y placeholders integrados como {{shardId}}, {{shardNumber}} y {{totalServers}} se resuelven con los valores específicos de ese shard. Esto significa que la misma plantilla de actividad produce una salida diferente en cada shard.

Las sobrescrituras de presencia por shard (tanto en arranque como en runtime) te permiten asignar actividades distintas a shards específicos. La página de visión general de la categoría Presence cubre la API completa. Al combinar sharding con rotación de presencia, cada shard rota de forma independiente con su propio temporizador.

src/main.ts
import { SpraxiumFactory } from '@spraxium/core';
import type { PresenceOptions } from '@spraxium/core';
import { AppModule } from './app.module';

const presenceConfig: PresenceOptions = {
  status: 'online',
  activities: [
    {
      name: 'Shard {{shardNumber}}/{{totalShards}} · {{totalServers}} guilds',
      type: 'Watching',
    },
  ],
};

async function main() {
  const app = await SpraxiumFactory.create({
    token: process.env.DISCORD_TOKEN,
  });
  app.useModule(AppModule);
  app.useSharding({ totalShards: 'auto' });
  app.initPresence(presenceConfig);
  await app.listen();
}

main();

Integración con logging

El ConsoleTransport del sistema de logger incluye un indicador de shard en las líneas de log cuando el proceso es un hijo de shard. Esto facilita rastrear qué shard produjo una entrada de log específica al revisar la salida de múltiples procesos. El DiscordTransport también funciona por shard, enviando logs desde cada hijo al canal o webhook configurado de forma independiente.

El proceso padre usa el logger para reportar eventos del ciclo de vida de los shards: progreso de spawn, desconexiones, reconexiones, muertes y señales de apagado. Estos logs a nivel de padre no incluyen datos de guilds por shard porque el padre no se conecta al gateway.

src/listeners/ready.listener.ts
import { Logger, Listener, On } from '@spraxium/core';

const logger = new Logger('ReadyListener');

@Listener()
export class ReadyListener {
  @On('clientReady')
  onReady() {
    logger.info('Shard is ready and accepting events');
  }
}