Skip to content

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}`)
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: 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.