Spraxium logoSpraxium

Decoradores y jobs

Cinco decoradores definen jobs de schedule: cron, interval, timeout, retrasos post-conexión y fechas absolutas de un solo disparo. Aprende las opciones de cada decorador, los helpers tipo enum y cómo combinar múltiples jobs en un mismo servicio.

@Cron y @Interval

Los dos tipos de jobs repetitivos cubren la gran mayoría de necesidades de programación. @Cron se ejecuta en calendarios basados en expresiones cron estándar de 5 o 6 campos. @Interval se repite con un offset fijo en milisegundos respecto de la ejecución anterior. Ambos decoradores aceptan un objeto de opciones para nombrar, deshabilitar y controlar si el job se ejecuta inmediatamente al iniciar.

La opción name es importante porque es la clave que usas para pausar, reanudar o destruir el job en runtime a través de ScheduleService. Si omites el nombre, el framework genera uno automáticamente a partir del nombre de la clase y del método, pero los nombres explícitos son más fáciles de referenciar en comandos de administración y dashboards. La opción disabled te permite definir un job en código pero impedir que arranque, lo cual es útil para feature flags.

El decorador @Cron también acepta una opción timezone que sobrescribe la zona horaria global de la configuración de schedule. Esto es útil cuando un mismo bot atiende comunidades en distintas zonas horarias y necesitas que un resumen diario se envíe a las 9 AM hora local en cada región.

import { Injectable } from '@spraxium/common';
import { Cron, CronExpression } from '@spraxium/schedule';

@Injectable()
export class CleanupService {
  @Cron(CronExpression.EVERY_DAY_AT_MIDNIGHT, {
    name: 'cleanup-expired-warnings',
    timezone: 'UTC',
  })
  async cleanExpiredWarnings(): Promise<void> {
    // Remove warnings older than 30 days
  }
}

@Timeout y @AfterOnline

Los dos tipos de jobs de un solo disparo cubren patrones de inicialización diferida. @Timeout se dispara una vez después de un retraso fijo desde el arranque de la aplicación. @AfterOnline se dispara una vez después de que el cliente de Discord emite su evento ready más un retraso adicional opcional. Ninguno de los dos jobs se repite; se ejecutan exactamente una vez y luego el framework los elimina del registro.

La diferencia clave entre @Timeout y @AfterOnline es el punto inicial del retraso. Un timeout empieza a contar desde el momento en que arranca la aplicación, lo que significa que el cliente de Discord puede no estar conectado todavía. Un job after-online empieza a contar desde el evento ready, garantizando que el cliente, los guilds y los canales estén disponibles cuando se ejecute el método. Usa @AfterOnline siempre que tu job necesite llamar métodos de la API de Discord.

Ambos decoradores aceptan las mismas opciones name y disabled. El decorador @Timeout no soporta runOnInit porque, por naturaleza, es un timer de un solo disparo que siempre se ejecuta una vez. El valor del retraso está en milisegundos para ambos decoradores.

import { Injectable } from '@spraxium/common';
import { Timeout, TimeoutExpression } from '@spraxium/schedule';

@Injectable()
export class InitService {
  @Timeout(TimeoutExpression.AFTER_30_SECONDS, {
    name: 'deferred-cache-warmup',
  })
  async warmCache(): Promise<void> {
    // Populate caches 30 seconds after boot
  }
}

@RunOnce

@RunOnce programa un método para ejecutarse exactamente una vez en un momento absoluto del futuro. A diferencia de los decoradores repetitivos y de retraso relativo, aquí pasas una instancia de Date en lugar de una duración o una expresión cron. Después de que el job se dispara, el framework lo desregistra automáticamente y no aparecerá en ScheduleService.getAll() después.

Si la fecha proporcionada ya está en el pasado cuando inicia el bot, el job se registra pero nunca se ejecuta. El framework emite una advertencia en logs para que la mala configuración sea visible, pero no lanza una excepción ni impide el arranque.

import { Injectable } from '@spraxium/common';
import { RunOnce } from '@spraxium/schedule';

const LAUNCH_DATE = new Date('2026-06-01T20:00:00Z');

@Injectable()
export class LaunchService {
  @RunOnce(LAUNCH_DATE, { name: 'product-launch-ping' })
  async pingLaunch(): Promise<void> {
    // Fires once at 20:00 UTC on 2026-06-01
    await this.notifier.ping();
  }
}

@RunOnce requiere una instancia Date válida. Pasar un string de fecha o una fecha inválida lanza una ConfigurationException al arrancar, antes de que el bot siquiera se conecte a Discord.

Referencia de opciones de decoradores

Los cinco decoradores comparten una interfaz base de opciones comunes, con campos adicionales para tipos de job específicos. La siguiente tabla lista cada opción disponible y qué decoradores la soportan.

OpciónTipoDecoradoresDescripción
namestringTodosIdentificador único para la gestión en runtime
disabledbooleanTodosOmite el registro (útil para feature flags)
runOnInitboolean@Cron, @IntervalEjecuta inmediatamente durante la fase de arranque
timezonestring@CronSobrescribe la zona horaria global para este job
dateDate@RunOnceFecha absoluta en la que se dispara el job. Se pasa como primer argumento posicional, no dentro del objeto de opciones.

Helpers tipo enum

El módulo schedule ofrece tres objetos tipo enum con constantes nombradas para valores de tiempo comunes. Usar estas constantes hace que tu código sea más legible que usar números crudos y strings cron. Cada helper cubre los valores más frecuentes que usarías en el contexto de un bot de Discord.

CronExpression ofrece más de 45 presets como EVERY_DAY_AT_MIDNIGHT, EVERY_HOUR, EVERY_MONDAY_AT_MIDNIGHT y EVERY_5_MINUTES. IntervalExpression ofrece más de 25 presets en milisegundos como EVERY_MINUTE, EVERY_5_MINUTES y EVERY_HOUR. TimeoutExpression ofrece más de 20 presets como AFTER_10_SECONDS, AFTER_5_MINUTES y AFTER_1_HOUR.

Siempre puedes pasar valores crudos en lugar de constantes de enum. Los helpers tipo enum son solo reexports de conveniencia que mejoran la legibilidad y previenen errores tipográficos en expresiones cron.

src/modules/tasks/services/example.service.ts
import { Injectable } from '@spraxium/common';
import {
  Cron,
  CronExpression,
  Interval,
  IntervalExpression,
} from '@spraxium/schedule';

@Injectable()
export class ExampleService {
  @Cron(CronExpression.EVERY_WEEKDAY)
  async weekdayReport(): Promise<void> {}

  @Cron('0 */6 * * *')
  async customCron(): Promise<void> {}

  @Interval(IntervalExpression.EVERY_30_SECONDS)
  async frequentPoll(): Promise<void> {}
}