/** * Redis-backed distributed lock provider using atomic `SET NX` operations. * * Implements the `LockProvider` interface with owner-based release semantics: * a lock is only released if the requesting instance owns it, preventing * accidental release of another process's lock after TTL expiry. * * @module redis-lock-provider */ import type { RedisClientAdapter } from "../store/adapter.types.js"; import type { LockProvider } from "../types/idempotency.types.js"; /** Configuration for `RedisLockProvider`. */ export interface RedisLockProviderConfig { /** Redis client adapter. */ readonly client: RedisClientAdapter; /** Key prefix for lock keys. Default: `'lock:'`. */ readonly prefix?: string; /** Unique identifier for this lock owner (e.g. worker ID). */ readonly ownerId?: string; } /** * Redis-backed distributed lock using `SET NX` with TTL. * * Owner-verified release ensures that only the process that acquired the * lock can release it — safe for use in distributed multi-process deployments. */ export class RedisLockProvider implements LockProvider { protected readonly client: RedisClientAdapter; protected readonly prefix: string; protected readonly ownerId: string; constructor(config: RedisLockProviderConfig) { this.client = config.client; this.prefix = config.prefix ?? "lock:"; this.ownerId = config.ownerId ?? `owner-${Date.now()}-${Math.random().toString(36).slice(2)}`; } /** * Attempt to acquire the lock for `resource` with the given TTL. * Returns `true` if the lock was acquired, `false` if already held. */ async acquire(resource: string, ttlMs: number): Promise { const key = `${this.prefix}${resource}`; return this.client.setNx(key, this.ownerId, ttlMs); } /** * Release the lock for `resource` if this instance owns it. * No-ops if the lock is not held or was taken by another owner. */ async release(resource: string): Promise { const key = `${this.prefix}${resource}`; const value = await this.client.get(key); if (value === this.ownerId) { await this.client.del(key); } } }