Spraxium logoSpraxium

Decorators e jobs

Cinco decorators definem jobs de schedule: cron, interval, timeout, atrasos após a conexão e datas absolutas de execução única. Conheça as opções de cada decorator, os helpers em enum e como combinar vários jobs em um único serviço.

@Cron e @Interval

Os dois tipos de job recorrente cobrem a grande maioria das necessidades de agendamento. @Cron roda em agendas baseadas em calendário usando expressões cron padrão de 5 ou 6 campos. @Interval se repete em um deslocamento fixo em milissegundos a partir da execução anterior. Ambos os decorators aceitam um objeto de opções para nomear, desabilitar e controlar se o job deve rodar imediatamente na inicialização.

A opção name é importante porque ela é a chave usada para pausar, retomar ou destruir o job em runtime por meio do ScheduleService. Se você omitir o nome, o framework gera um automaticamente a partir da classe e do método, mas nomes explícitos são mais fáceis de referenciar em comandos administrativos e dashboards. A opção disabled permite definir o job no código sem iniciá-lo, o que é útil com feature flags.

O decorator @Cron também aceita a opção timezone, que sobrescreve o fuso horário global definido na config de schedule. Isso é útil quando um único bot atende comunidades em fusos diferentes e você precisa que um resumo diário seja enviado às 9h no horário local de cada região.

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 e @AfterOnline

Os dois tipos de job de execução única cobrem padrões de inicialização adiada. @Timeout dispara uma vez após um atraso fixo contado desde o boot da aplicação. @AfterOnline dispara uma vez depois que o cliente do Discord emite o evento ready, com a possibilidade de um atraso adicional opcional. Nenhum dos dois se repete: eles executam exatamente uma vez e depois o framework os remove do registro.

A diferença principal entre @Timeout e @AfterOnline está no ponto inicial da contagem do atraso. Um timeout começa a contar no momento em que a aplicação dá boot, o que significa que o cliente do Discord talvez ainda não esteja conectado. Um job after-online começa a contar a partir do evento ready, garantindo que o client, as guilds e os canais estejam disponíveis quando o método rodar. Use @AfterOnline sempre que o job precisar chamar métodos da API do Discord.

Os dois decorators aceitam as mesmas opções name e disabled. O decorator @Timeout não oferece suporte a runOnInit porque, por definição, é um timer de execução única que sempre roda apenas uma vez. Em ambos os casos, o valor do atraso é dado em milissegundos.

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 agenda um método para rodar exatamente uma vez em um ponto absoluto no futuro. Ao contrário dos decorators recorrentes e de atraso relativo, aqui você passa uma instância de Date em vez de uma duração ou de uma expressão cron. Depois que o job dispara, o framework o remove automaticamente do registro, e ele deixa de aparecer em ScheduleService.getAll().

Se a data informada já estiver no passado quando o bot iniciar, o job será registrado, mas nunca vai disparar. O framework emite um aviso no log para deixar a configuração incorreta visível, mas não lança erro nem impede a inicialização.

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 exige uma instância válida de Date. Passar uma string de data ou uma data inválida lança uma ConfigurationException na inicialização, antes mesmo de o bot se conectar ao Discord.

Referência das opções dos decorators

Os cinco decorators compartilham uma interface base de opções, com campos adicionais para tipos específicos de job. A tabela abaixo lista todas as opções disponíveis e quais decorators dão suporte a cada uma.

OpçãoTipoDecoratorsDescrição
namestringTodosIdentificador único para gerenciamento em runtime
disabledbooleanTodosPula o registro, útil para feature flags
runOnInitboolean@Cron, @IntervalExecuta imediatamente durante a fase de boot
timezonestring@CronSobrescreve o fuso horário global para este job
dateDate@RunOnceData absoluta em que o job dispara. Ela é passada como o primeiro argumento posicional, e não dentro do objeto de opções.

Helpers em enum

O módulo de schedule fornece três objetos no estilo enum com constantes nomeadas para valores de tempo comuns. Usar essas constantes deixa o código mais legível do que trabalhar com números crus e strings cron. Cada helper cobre os valores mais frequentes no contexto de bots de Discord.

CronExpression oferece mais de 45 presets, como EVERY_DAY_AT_MIDNIGHT, EVERY_HOUR, EVERY_MONDAY_AT_MIDNIGHT e EVERY_5_MINUTES. IntervalExpression oferece mais de 25 presets em milissegundos, como EVERY_MINUTE, EVERY_5_MINUTES e EVERY_HOUR. TimeoutExpression oferece mais de 20 presets, como AFTER_10_SECONDS, AFTER_5_MINUTES e AFTER_1_HOUR.

Você sempre pode passar valores brutos em vez das constantes do enum. Esses helpers são apenas reexports de conveniência para melhorar a legibilidade e evitar erros de digitação em expressões 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> {}
}