UNPKG

@shagital/atomic-lock

Version:

Universal atomic locking with pluggable drivers (Redis, SQLite, File, Memory)

169 lines (134 loc) 4.01 kB
import { LockDriver, LockInfo } from '../types' /** * In-memory driver implementation (for testing and single-process scenarios) */ export class MemoryLockDriver implements LockDriver { private locks = new Map<string, { value: string, expiresAt: number, createdAt: number }>() private cleanupInterval?: NodeJS.Timeout constructor(config: { cleanupInterval?: number } = {}) { if (config.cleanupInterval) { this.cleanupInterval = setInterval(() => { this.cleanup().catch(console.error) }, config.cleanupInterval) } } async tryAcquire(key: string, lockValue: string, expiryInSeconds: number): Promise<boolean> { const now = Date.now() const existing = this.locks.get(key) // Check if existing lock is expired if (existing && existing.expiresAt < now) { this.locks.delete(key) } // Check if lock exists and is not expired if (this.locks.has(key)) { return false } // Acquire lock this.locks.set(key, { value: lockValue, expiresAt: now + (expiryInSeconds * 1000), createdAt: now }) return true } async tryAcquireMultiple(keys: string[], lockValue: string, expiryInSeconds: number): Promise<boolean> { const now = Date.now() // Clean up expired locks for all keys for (const key of keys) { const existing = this.locks.get(key) if (existing && existing.expiresAt < now) { this.locks.delete(key) } } // Check if any locks exist for (const key of keys) { if (this.locks.has(key)) { return false } } // Acquire all locks const lockData = { value: lockValue, expiresAt: now + (expiryInSeconds * 1000), createdAt: now } for (const key of keys) { this.locks.set(key, lockData) } return true } async release(key: string, lockValue: string): Promise<boolean> { const existing = this.locks.get(key) if (existing && existing.value === lockValue) { this.locks.delete(key) return true } return false } async releaseMultiple(keys: string[], lockValue: string): Promise<number> { let released = 0 for (const key of keys) { const success = await this.release(key, lockValue) if (success) released++ } return released } async exists(key: string): Promise<boolean> { const existing = this.locks.get(key) if (!existing) return false // Check if expired if (existing.expiresAt < Date.now()) { this.locks.delete(key) return false } return true } async getLockInfo(key: string): Promise<LockInfo | null> { const existing = this.locks.get(key) if (!existing) return null // Check if expired if (existing.expiresAt < Date.now()) { this.locks.delete(key) return null } return { key, value: existing.value, expiresAt: existing.expiresAt, createdAt: existing.createdAt } } async cleanup(): Promise<void> { const now = Date.now() for (const [key, lock] of this.locks.entries()) { if (lock.expiresAt < now) { this.locks.delete(key) } } } async close(): Promise<void> { if (this.cleanupInterval) { clearInterval(this.cleanupInterval) } this.locks.clear() } // For testing purposes getLockCount(): number { return this.locks.size } getAllLocks(): Map<string, LockInfo> { const result = new Map<string, LockInfo>() const now = Date.now() for (const [key, lock] of this.locks.entries()) { if (lock.expiresAt >= now) { result.set(key, { key, value: lock.value, expiresAt: lock.expiresAt, createdAt: lock.createdAt }) } } return result } }