Referencia: Locker y Transacciones
PgLocker / InMemoryLocker
Servicio singleton para adquirir locks distribuidos. Usa PostgreSQL advisory locks (PgLocker) o una implementación en memoria (InMemoryLocker — solo para desarrollo/testing).
import { Locker, injectable } from '@wabot-dev/framework'
@injectable()export class MiServicio { constructor(private locker: Locker) {}}PgLocker e InMemoryLocker implementan la interfaz Locker — puedes tiparlo como Locker si quieres abstracción.
locker.withKey(clave)
Selecciona el lock por clave. Retorna un objeto con los métodos run y tryRun.
const lock = this.locker.withKey(`orden-${orderId}`)| Clave | Tipo |
|---|---|
string | Se convierte a un lock numérico estable vía hash. |
number | Se usa directamente como advisory lock ID. |
Claves distintas son locks independientes. Dos instancias de la aplicación compiten por el mismo lock si usan la misma clave.
lock.run(fn) — bloqueante
Espera hasta adquirir el lock, ejecuta fn, y lo libera. Si otra instancia ya tiene el lock, espera indefinidamente hasta que se libere.
const resultado = await this.locker.withKey(`proceso-${id}`).run(async () => { // Solo una instancia ejecuta esto a la vez para este id const registro = await this.repo.find(id) registro.marcarProcesado() await this.repo.update(registro) return registro})Retorna el valor que retorna fn.
lock.tryRun(fn) — no bloqueante
Intenta adquirir el lock sin esperar. Si está ocupado, retorna undefined inmediatamente.
const resultado = await this.locker.withKey('tarea-global').tryRun(async () => { await this.procesarCola() return 'completado'})
if (resultado === undefined) { // Otra instancia ya está ejecutando la tarea}Útil para tareas de cron o workers donde no importa saltar una ejecución si ya hay otra en curso.
Casos de uso
Prevenir duplicados en cron
@cronHandler({ name: 'sync-inventario', cron: '*/5 * * * *' })export class SyncInventarioHandler implements ICronHandler { constructor(private locker: PgLocker) {}
async handle() { const resultado = await this.locker.withKey('sync-inventario').tryRun(async () => { await this.sincronizar() }) // Si resultado === undefined, ya había otra instancia corriendo → se omite sin error }}Proteger operaciones críticas
@transaction()async procesarPago(pagoId: string) { await this.locker.withKey(`pago-${pagoId}`).run(async () => { const pago = await this.repo.find(pagoId) if (pago.status === 'procesado') return await this.gateway.charge(pago.monto) pago.marcarProcesado() await this.repo.update(pago) })}@transaction() — decorador de transacción
Ver referencia completa en Async — @transaction.