Skip to content

Referencia: Locker y Transacciones

PgLocker / InMemoryLocker

Servicio para adquirir locks distribuidos. Inyecta siempre la clase Locker: el runner del proyecto la conecta a PgLocker (PostgreSQL advisory locks) cuando DATABASE_URL está configurada, o a InMemoryLocker (un solo proceso — desarrollo/testing) cuando no.

import { Locker, injectable } from '@wabot-dev/framework'
@injectable()
export class MiServicio {
constructor(private locker: Locker) {}
}

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}`)
ClaveTipo
stringSe convierte a un lock numérico estable vía hash.
numberSe 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: Locker) {}
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.