Skip to content

Referencia: Async — Comandos, Cron y Transacciones

Comandos asíncronos

@command(name)

Decorador de clase. Registra una clase de datos como comando ejecutable. Cada campo lleva decoradores de validación — el comando se valida antes de encolarse.

import { command, isString, isNotEmpty } from '@wabot-dev/framework'
@command('enviar-email')
export class EnviarEmailCommand {
@isString()
@isNotEmpty()
destinatario: string = ''
@isString()
@isNotEmpty()
asunto: string = ''
@isString()
cuerpo: string = ''
}

name es el identificador del comando — debe ser único en la aplicación.


@commandHandler(CommandClass)

Decorador de clase. Registra un handler para procesar un tipo de comando.

import { commandHandler, ICommandHandler } from '@wabot-dev/framework'
@commandHandler(EnviarEmailCommand)
export class EnviarEmailHandler implements ICommandHandler<EnviarEmailCommand> {
async handle(cmd: EnviarEmailCommand) {
await this.emailService.send({
to: cmd.destinatario,
subject: cmd.asunto,
body: cmd.cuerpo,
})
}
}

También acepta un objeto de configuración para ajustar reintentos y deduplicación: @commandHandler({ command: EnviarEmailCommand, reintentsDelaysInSeconds: [30, 120] }).

ICommandHandler<C>:

interface ICommandHandler<C> {
handle(command: C): void | Promise<void>
}

Async

Servicio singleton para despachar comandos.

import { Async, injectable } from '@wabot-dev/framework'
@injectable()
export class NotificacionService {
constructor(private async: Async) {}
async notificar(email: string) {
// Ejecutar inmediatamente (en background)
await this.async.runCommand(EnviarEmailCommand, {
destinatario: email,
asunto: 'Bienvenido',
cuerpo: 'Gracias por registrarte.',
})
}
async notificarLuego(email: string) {
// Programar para más tarde
await this.async.scheduleCommand(
EnviarEmailCommand,
{ destinatario: email, asunto: 'Recordatorio', cuerpo: '...' },
{ hours: 24 }, // en 24 horas
)
}
}
MétodoDescripción
runCommand(Clase, data)Crea el job y lo intenta ejecutar de inmediato.
scheduleCommand(Clase, data, cuando)Crea el job para ejecutarse en un momento futuro.

IScheduleAt — opciones de tiempo

type IScheduleAt =
| Date // fecha exacta
| { seconds: number }
| { minutes: number }
| { hours: number }
| { days: number }

runCommandHandlers(handlers)

Inicia el procesador de jobs para los handlers indicados. Solo es necesario fuera del runner del proyectorun(config) descubre los @commandHandler y activa los workers automáticamente. Para detenerlos: stopCommandHandlers(handlers).

import { runCommandHandlers, stopCommandHandlers } from '@wabot-dev/framework'
runCommandHandlers([EnviarEmailHandler, ProcesarPagoHandler])

Tareas cron

@cronHandler(config)

Decorador de clase. Registra una tarea que se ejecuta según una expresión cron.

import { cronHandler, ICronHandler } from '@wabot-dev/framework'
@cronHandler({ name: 'limpieza-diaria', cron: '0 2 * * *' })
export class LimpiezaDiariaHandler implements ICronHandler {
async handle() {
await this.servicio.limpiarRegistrosExpirados()
}
async handleError(error: any) {
// Opcional — manejo de errores del cron
}
}
ConfigTipoDescripción
namestringIdentificador único de la tarea.
cronstringExpresión cron (5 campos: min hora día mes día-semana).
disabledbooleanSi true, la tarea no se ejecuta (default: false).
maxRunningJobsnumberMáximo de ejecuciones simultáneas.
misfirePolicy'RUN_ONCE' | 'RUN_ALL' | 'SKIP'Qué hacer con disparos perdidos.
reintentsDelaysInSecondsnumber[]Delays de reintento ante fallos.

ICronHandler:

interface ICronHandler {
handle(): void | Promise<void>
handleError?(e: any): void | Promise<void>
}

Referencia rápida de expresiones cron

ExpresiónCuándo
* * * * *Cada minuto
*/5 * * * *Cada 5 minutos
0 * * * *Cada hora
0 9 * * *Todos los días a las 9:00 AM
0 2 * * *Todos los días a las 2:00 AM
0 9 * * 1Cada lunes a las 9:00 AM
0 9 * * 1-5Lunes a viernes a las 9:00 AM
0 0 1 * *El primer día de cada mes

runCronHandlers(handlers)

Inicia el scheduler para las tareas cron indicadas. Solo es necesario fuera del runner del proyectorun(config) descubre los @cronHandler automáticamente. Para detenerlas: stopCronHandlers(handlers).

import { runCronHandlers, stopCronHandlers } from '@wabot-dev/framework'
runCronHandlers([LimpiezaDiariaHandler, ReporteSemanalHandler])

@transaction()

Decorador de método. Envuelve el método en una transacción de base de datos. Si el método lanza un error, se hace ROLLBACK de todas las operaciones.

import { transaction, injectable } from '@wabot-dev/framework'
@injectable()
export class VentasService {
@transaction()
async cerrarVenta(prospectId: string, offerId: string) {
// Si cualquier operación falla → ROLLBACK automático
await this.prospectRepo.marcarGanado(prospectId)
await this.offerRepo.marcarAceptada(offerId)
await this.facturaRepo.crear({ prospectId, offerId })
}
}

Transacciones anidadas

Cuando un método @transaction() llama a otro método @transaction(), la interior usa un savepoint PostgreSQL en lugar de una nueva transacción:

@transaction()
async operacionExterna() {
await this.operacionInterior() // usa SAVEPOINT automáticamente
await this.otraOperacion()
}
@transaction()
async operacionInterior() { }

Configuración manual (sin ProjectRunner)

Si no usas el flujo estándar, registra el adaptador manualmente:

import { container, TransactionMetadataStore, PgTransactionAdapter } from '@wabot-dev/framework'
import { Pool } from 'pg'
const store = container.resolve(TransactionMetadataStore)
store.registerAdapter('default', new PgTransactionAdapter(new Pool({ connectionString: '...' })))

Con el flujo estándar (run(config) en src/_run_.ts + PostgreSQL configurado), el runner registra el adaptador automáticamente bajo el nombre 'default'.