import { OnModuleInit } from '@nestjs/common'; import type Redis from 'ioredis'; /** * Lock acquisition strategy */ export declare enum LockStrategy { /** * Skip execution if lock cannot be acquired immediately * This is the default behavior for backward compatibility */ SKIP = "SKIP", /** * Throw an error if lock cannot be acquired * Useful for critical operations that must have exclusive access */ THROW = "THROW", /** * Wait/block until the lock can be acquired * Will retry indefinitely with the specified retry delay */ WAIT = "WAIT" } export interface LockOptions { /** * Lock expiration time in milliseconds * @default 300000 (5 minutes) */ ttl?: number; /** * Maximum number of retry attempts to acquire the lock * Only applies when strategy is SKIP or THROW * For WAIT strategy, this is ignored (infinite retries) * @default 0 (no retries) */ retryCount?: number; /** * Delay between retry attempts in milliseconds * @default 500 */ retryDelay?: number; /** * Custom lock key prefix * @default 'lock' */ keyPrefix?: string; /** * Whether to throw an error if lock acquisition fails * @deprecated Use strategy: LockStrategy.THROW instead * @default false */ throwOnFailure?: boolean; /** * Lock acquisition strategy * - SKIP: Skip execution if lock cannot be acquired (default) * - THROW: Throw error if lock cannot be acquired * - WAIT: Wait/block until lock can be acquired (indefinite retries) * @default LockStrategy.SKIP */ strategy?: LockStrategy; /** * Maximum wait time in milliseconds when using WAIT strategy * If set, will timeout after this duration * @default undefined (no timeout, wait indefinitely) */ waitTimeout?: number; /** * Automatic lock extension interval in milliseconds * If set, the lock will be automatically extended at this interval * @default 0 (no automatic extension) */ autoExtend?: number; /** * Whether to use exponential backoff for retry attempts * If true, retry delay will increase exponentially: retryDelay * 2^(attempt-1) * @default false */ useExponentialBackoff?: boolean; /** * Maximum retry delay in milliseconds when using exponential backoff * Ensures retry delay doesn't grow indefinitely * @default 60000 (60 seconds) */ maxRetryDelay?: number; /** * Random jitter to add to retry delay in milliseconds * Helps prevent synchronized retries across multiple instances * @default 0 (no jitter) */ retryJitter?: number; } export interface LockResult { /** * Whether the lock was successfully acquired */ acquired: boolean; /** * Lock value (identifier) if acquired */ lockValue?: string; /** * Error message if acquisition failed */ error?: string; /** * Timer ID for auto-extension if enabled */ autoExtendTimer?: NodeJS.Timeout; } /** * Redis-based distributed lock service * * Provides thread-safe, distributed locking mechanism using Redis. * Supports automatic lock expiration, retries, proper lock release, * and automatic lock extension. * * @example * ```typescript * constructor(private lockService: RedisLockService) {} * * async processTask() { * const result = await this.lockService.acquireLock('task:process', { ttl: 60000 }); * * if (result.acquired) { * try { * // Do work * } finally { * await this.lockService.releaseLock('task:process', result.lockValue); * } * } * } * ``` */ export declare class RedisLockService implements OnModuleInit { private static globalInstance; private readonly logger; private redis; private instanceId; private readonly defaultOptions; constructor(); /** * Get the global singleton instance * This allows decorators to access the service without dependency injection */ static getGlobalInstance(): RedisLockService | null; /** * Get or create the global singleton instance * If no instance exists, it will create one */ static getOrCreateGlobalInstance(): RedisLockService; /** * Set the global singleton instance * Useful for testing or manual setup */ static setGlobalInstance(instance: RedisLockService): void; onModuleInit(): Promise; onModuleDestroy(): Promise; /** * Set Redis client (required for dependency injection) * This service requires an external Redis client to function */ setRedisClient(redis: Redis): void; /** * Whether this service has a Redis client configured. */ hasRedisClient(): boolean; /** * Set default TTL for locks */ setDefaultTtl(ttl: number): void; /** * Set default key prefix for locks */ setDefaultKeyPrefix(prefix: string): void; /** * Get default key prefix for lock cleanup and diagnostics. */ getDefaultKeyPrefix(): string; /** * Set default retry count for lock acquisition */ setDefaultRetryCount(count: number): void; /** * Set default retry delay for lock acquisition */ setDefaultRetryDelay(delay: number): void; /** * Acquire a distributed lock * * @param key - Lock key (will be prefixed with 'lock:') * @param options - Lock options * @returns Lock acquisition result */ acquireLock(key: string, options?: LockOptions): Promise; /** * Release a distributed lock * * Uses Lua script to ensure atomic check-and-delete operation * Only releases the lock if the value matches (prevents releasing someone else's lock) * * @param key - Lock key * @param lockValue - Lock value obtained during acquisition * @param keyPrefix - Key prefix (must match acquisition) * @returns True if lock was released, false otherwise */ releaseLock(key: string, lockValue: string, keyPrefix?: string): Promise; /** * Extend the expiration time of an existing lock * * @param key - Lock key * @param lockValue - Lock value obtained during acquisition * @param ttl - New TTL in milliseconds * @param keyPrefix - Key prefix (must match acquisition) * @returns True if lock was extended, false otherwise */ extendLock(key: string, lockValue: string, ttl: number, keyPrefix?: string): Promise; /** * Check if a lock exists * * @param key - Lock key * @param keyPrefix - Key prefix * @returns True if lock exists, false otherwise */ isLocked(key: string, keyPrefix?: string): Promise; /** * Execute a function with automatic lock acquisition and release * * @param key - Lock key * @param fn - Function to execute while holding the lock * @param options - Lock options * @returns Result of the function execution, or null if lock couldn't be acquired */ withLock(key: string, fn: () => Promise, options?: LockOptions): Promise; /** * Get lock information from Redis * * @param key - Lock key * @param keyPrefix - Key prefix * @returns Lock information including TTL and value */ getLockInfo(key: string, keyPrefix?: string): Promise<{ value: string | null; ttl: number | null; }>; /** * Force release a lock (use with caution) * * @param key - Lock key * @param keyPrefix - Key prefix * @returns True if lock was released, false otherwise */ forceRelease(key: string, keyPrefix?: string): Promise; /** * Release a lock only if its current value still matches the observed value. * * This is intended for cleanup flows that first scan lock keys and then delete * stale entries. It avoids deleting a fresh lock that was acquired after the * scan result was read. */ releaseObservedLock(key: string, observedLockValue: string, keyPrefix?: string): Promise; /** * Clean up locks by pattern * Useful for cleaning up locks after process restart or for specific services * * WARNING: Use with caution in production! This will forcefully delete locks. * * **Note**: Uses SCAN command to avoid blocking Redis in production. * * @param pattern - Lock pattern (e.g., 'MyService:*' or '*:migration:*') * @param keyPrefix - Key prefix (default: 'lock') * @returns Number of locks deleted * * @example * ```typescript * // Clean up all locks for MyService * await lockService.cleanupLocksByPattern('MyService:*'); * * // Clean up all migration locks * await lockService.cleanupLocksByPattern('*:migration:*'); * ``` */ cleanupLocksByPattern(pattern: string, keyPrefix?: string): Promise; /** * Clean up locks on startup * This method is useful for cleaning up stale locks from previous process instances * * WARNING: Only use this if you're sure the locks are from previous process instances! * * @param patterns - Array of lock patterns to clean up * @param keyPrefix - Key prefix (default: 'lock') * @returns Total number of locks deleted * * @example * ```typescript * // In your module's onModuleInit * async onModuleInit() { * // Clean up locks from this service that might be left from previous restart * await this.lockService.cleanupOnStartup([ * 'MyService:*', * 'AnotherService:*' * ]); * } * ``` */ cleanupOnStartup(patterns: string[], keyPrefix?: string): Promise; /** * Health check for Redis connection * Returns information about Redis connectivity and lock service status * * @returns Health check result */ healthCheck(): Promise<{ healthy: boolean; redis: boolean; message: string; }>; /** * Get all active locks with optional pattern filtering * Useful for monitoring and debugging * * **Note**: Uses SCAN command to avoid blocking Redis in production. * * @param pattern - Optional pattern to filter locks (e.g., 'MyService:*') * @param keyPrefix - Key prefix (default: 'lock') * @returns Array of active lock information */ getActiveLocks(pattern?: string, keyPrefix?: string): Promise>; /** * Get Redis connection * Requires external Redis client to be set */ private getRedis; /** * Generate a unique lock value * Format: instanceId:timestamp:random:pid * * @returns Unique lock identifier */ private generateLockValue; /** * Get current instance identifier. */ getInstanceId(): string; /** * Create instance identifier * Used to distinguish different process instances in distributed environments * * @returns Instance identifier */ private createInstanceId; /** * Build the full Redis key for a lock * * @param key - Lock key * @param prefix - Key prefix * @returns Full Redis key */ private buildLockKey; /** * Sleep for a specified duration * * @param ms - Milliseconds to sleep */ private sleep; }