@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ção | Tipo | Decorators | Descrição |
|---|---|---|---|
| name | string | Todos | Identificador único para gerenciamento em runtime |
| disabled | boolean | Todos | Pula o registro, útil para feature flags |
| runOnInit | boolean | @Cron, @Interval | Executa imediatamente durante a fase de boot |
| timezone | string | @Cron | Sobrescreve o fuso horário global para este job |
| date | Date | @RunOnce | Data 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.
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> {}
}