Spraxium logoSpraxium

Drivers y operaciones

El módulo schedule soporta drivers conectables para entornos de proceso único y distribuidos. Aprende a usar MemoryDriver, RedisScheduleDriver y la API en runtime de ScheduleService.

Por qué importan los drivers

En un bot de proceso único, cada job programado se ejecuta exactamente una vez porque solo existe una instancia del scheduler. Cuando escalas a múltiples shards o procesos, el mismo job cron puede dispararse simultáneamente en cada instancia y causar ejecuciones duplicadas. El sistema de drivers resuelve esto introduciendo un mecanismo de lock que garantiza que solo una instancia adquiera el derecho de ejecutar un job dado en cada disparo.

Los drivers son conectables. Configuras el driver una sola vez en tu configuración de schedule y el framework se encarga del resto de forma transparente. Tus métodos de job no necesitan saber qué driver está activo. Esto significa que puedes desarrollar localmente con MemoryDriver y cambiar a RedisScheduleDriver en producción sin modificar código de servicios.

El modelo de locking es optimista. Cuando un job se dispara, el driver intenta adquirir un lock usando el nombre del job como clave. Si adquiere el lock, el job se ejecuta. Si otro proceso ya mantiene el lock, el job se omite silenciosamente en esa instancia. El lock tiene un TTL configurable que se libera automáticamente después de la ventana de ejecución esperada.

Drivers disponibles

DriverPaqueteMecanismo de lockMejor para
MemoryDriver@spraxium/scheduleSiempre adquiere (no-op)Bots de proceso único, desarrollo local
RedisScheduleDriver@spraxium/scheduleRedis SET NX + liberación con LuaProducción con múltiples shards y procesos

Configurar drivers

El MemoryDriver por defecto no requiere configuración. Siempre adquiere el lock, así que cada job se ejecuta cada vez que se dispara. Este es el comportamiento correcto para bots de proceso único.

Para entornos distribuidos, cambia a RedisScheduleDriver pasando opciones de conexión de Redis y, opcionalmente, un lock TTL. El driver usa SET key NX EX ttl para adquirir locks de forma atómica y un script Lua para liberarlos de forma segura sin condiciones de carrera.

import { defineSchedule } from '@spraxium/schedule';

export default defineSchedule({
  timezone: 'UTC',
});

API de ScheduleService

ScheduleService es un provider inyectable que te da control total en runtime sobre los jobs registrados. Inyéctalo en cualquier servicio, handler de comando o controlador para pausar, reanudar, destruir o disparar jobs manualmente por nombre. El servicio se exporta desde ScheduleModule global, así que está disponible en todos los módulos sin imports explícitos.

Cada método recibe el name del job, tal como se define en las opciones del decorador. Si no estableciste un nombre explícito, el framework usa un nombre generado a partir de la clase y el método. Se recomiendan fuertemente los nombres explícitos para cualquier job que planees gestionar en runtime.

MétodoRetornoDescripción
pause(name)Sin valor de retornoDetiene un job en ejecución para reanudarlo después
resume(name)Sin valor de retornoReanuda un job previamente pausado
destroy(name)Sin valor de retornoElimina permanentemente un job y su timer
get(name)JobStatus o nullRecupera un job registrado por nombre
getAll()Lista de entradas JobStatusLista todos los jobs registrados con su estado
runNow(name)Promise sin valorEjecuta un job inmediatamente fuera de su programación normal

Usar ScheduleService en un comando

Un caso práctico para ScheduleService es crear comandos de administración que permitan a owners de guilds o administradores del bot controlar tareas en segundo plano sin reiniciar el bot. El siguiente ejemplo muestra un handler de prefix command que pausa o reanuda un job por nombre. El mismo patrón funciona con slash commands o endpoints de dashboard.

src/modules/admin/handlers/schedule-admin.handler.ts
import { Ctx, PrefixArg, PrefixCommandHandler } from '@spraxium/common';
import { ScheduleService } from '@spraxium/schedule';
import type { Message } from 'discord.js';
import { ScheduleAdminCommand } from '../commands/schedule-admin.command';

@PrefixCommandHandler(ScheduleAdminCommand)
export class ScheduleAdminHandler {
  constructor(private readonly scheduleService: ScheduleService) {}

  async handle(
    @Ctx() message: Message,
    @PrefixArg('action') action: string,
    @PrefixArg('jobName') jobName: string,
  ): Promise<void> {
    const job = this.scheduleService.get(jobName);
    if (!job) {
      await message.reply(`Job \`${jobName}\` not found.`);
      return;
    }

    if (action === 'pause') {
      this.scheduleService.pause(jobName);
      await message.reply(`Job \`${jobName}\` paused.`);
    } else if (action === 'resume') {
      this.scheduleService.resume(jobName);
      await message.reply(`Job \`${jobName}\` resumed.`);
    } else if (action === 'run') {
      await this.scheduleService.runNow(jobName);
      await message.reply(`Job \`${jobName}\` triggered manually.`);
    }
  }
}