UNPKG

redlock-universal

Version:

Production-ready distributed Redis locks for Node.js with support for both node-redis and ioredis clients

1,222 lines (1,197 loc) 41.2 kB
import { Redis } from 'ioredis'; /** * Structured logger for redlock-universal */ declare enum LogLevel { DEBUG = 0, INFO = 1, WARN = 2, ERROR = 3 } interface LogEntry { readonly level: LogLevel; readonly message: string; readonly timestamp: number; readonly context?: Record<string, unknown>; readonly error?: Error; } interface LoggerConfig { readonly level: LogLevel; readonly prefix?: string; readonly enableConsole?: boolean; readonly enableCollection?: boolean; readonly maxEntries?: number; } declare class Logger { private readonly config; private readonly entries; private _reusableEntry; constructor(config?: Partial<LoggerConfig>); /** * Log debug message */ debug(message: string, context?: Record<string, unknown>): void; /** * Log info message */ info(message: string, context?: Record<string, unknown>): void; /** * Log warning message */ warn(message: string, context?: Record<string, unknown>): void; /** * Log error message */ error(message: string, error?: Error, context?: Record<string, unknown>): void; /** * Create a child logger with additional context */ child(context: Record<string, unknown>): Logger; /** * Get collected log entries */ getEntries(level?: LogLevel): LogEntry[]; /** * Clear collected log entries */ clear(): void; /** * Set log level */ setLevel(level: LogLevel): void; private log; private writeToConsole; } declare const logger: Logger; /** * Redis adapter types for universal client support */ /** * Result of atomic lock extension operation */ interface AtomicExtensionResult { /** Result code: 1=success, 0=too_late, -1=value_mismatch/key_missing */ readonly resultCode: 1 | 0 | -1; /** Actual TTL at time of check (ms, -2 if key doesn't exist) */ readonly actualTTL: number; /** Human-readable result message */ readonly message: string; } /** * Result of successful batch lock acquisition */ interface BatchAcquireSuccess { /** Acquisition succeeded */ readonly success: true; /** Number of locks acquired (equals keys.length) */ readonly acquiredCount: number; } /** * Result of failed batch lock acquisition */ interface BatchAcquireFailure { /** Acquisition failed */ readonly success: false; /** Number of locks acquired (always 0 due to all-or-nothing semantics) */ readonly acquiredCount: 0; /** Key that was already locked */ readonly failedKey: string; /** 1-based index of the failed key */ readonly failedIndex: number; } /** * Result of batch lock acquisition operation (discriminated union) */ type BatchAcquireResult = BatchAcquireSuccess | BatchAcquireFailure; /** * Configuration options for Redis adapters */ interface RedisAdapterOptions { /** Prefix to add to all keys */ readonly keyPrefix?: string; /** Maximum number of retries for failed operations */ readonly maxRetries?: number; /** Delay between retries in milliseconds */ readonly retryDelay?: number; /** Timeout for Redis operations in milliseconds */ readonly timeout?: number; /** Optional logger for structured logging (default: none) */ readonly logger?: Logger; } /** * Universal Redis adapter interface * Abstracts differences between node-redis and ioredis */ interface RedisAdapter { /** * Set key with value if not exists, with TTL in milliseconds * @param key - Redis key * @param value - Value to set * @param ttl - Time to live in milliseconds * @returns Promise resolving to 'OK' on success, null if key exists */ setNX(key: string, value: string, ttl: number): Promise<string | null>; /** * Get value by key * @param key - Redis key * @returns Promise resolving to value or null if not found */ get(key: string): Promise<string | null>; /** * Delete key * @param key - Key to delete * @returns Promise resolving to number of deleted keys */ del(key: string): Promise<number>; /** * Delete key only if value matches (atomic operation) * @param key - Redis key * @param value - Expected value * @returns Promise resolving to true if deleted, false otherwise */ delIfMatch(key: string, value: string): Promise<boolean>; /** * Extend TTL of a key only if value matches (atomic operation) * @param key - Redis key * @param value - Expected value * @param ttl - New TTL in milliseconds * @returns Promise resolving to true if extended, false otherwise */ extendIfMatch(key: string, value: string, ttl: number): Promise<boolean>; /** * Atomic extension with TTL feedback and race condition protection * @param key - Redis key * @param value - Expected value * @param minTTL - Minimum TTL required for extension (race condition protection) * @param newTTL - New TTL to set in milliseconds * @returns Promise resolving to atomic extension result with TTL feedback */ atomicExtend(key: string, value: string, minTTL: number, newTTL: number): Promise<AtomicExtensionResult>; /** * Atomically acquire multiple locks in a single operation * All-or-nothing semantics: either all locks are acquired or none * * @param keys - Array of Redis keys to lock * @param values - Array of values (one per key, same length as keys) * @param ttl - Time to live in milliseconds for all locks * @returns Promise resolving to batch acquisition result * @throws Error if keys and values arrays have different lengths */ batchSetNX(keys: string[], values: string[], ttl: number): Promise<BatchAcquireResult>; /** * Ping Redis server * @returns Promise resolving to 'PONG' */ ping(): Promise<string>; /** * Check if adapter is connected * @returns Connection status */ isConnected(): boolean; /** * Disconnect from Redis * @returns Promise that resolves when disconnected */ disconnect(): Promise<void>; } /** * Auto-extension utility for lock management * Provides clean separation between lock implementations and auto-extension logic */ /** * Configuration for auto-extension */ interface AutoExtensionConfig<T> { /** Lock instances to manage */ readonly locks: readonly Lock[]; /** Lock handles to extend */ readonly handles: readonly LockHandle[]; /** TTL for extensions */ readonly ttl: number; /** Function to execute while holding locks */ readonly routine: (signal: AbortSignal) => Promise<T>; /** Extension threshold ratio (default: 0.2 = extend at 80% TTL consumed) */ readonly extensionThresholdRatio?: number; /** Minimum interval between extension attempts */ readonly minExtensionInterval?: number; /** Optional logger for error reporting */ readonly logger?: Logger; } /** * Enhanced AbortSignal with error information */ interface ExtendedAbortSignal extends AbortSignal { /** Error that caused the abort (if any) */ readonly error?: Error; } /** * Execute a routine with automatic lock extension * * This utility function provides auto-extension for any lock implementation, * avoiding code duplication between SimpleLock and RedLock. * * @param config - Configuration for auto-extension * @returns Promise resolving to the routine result */ declare function executeWithAutoExtension<T>(config: AutoExtensionConfig<T>): Promise<T>; /** * Convenience function for single lock auto-extension */ declare function executeWithSingleLockExtension<T>(lock: Lock, handle: LockHandle, ttl: number, routine: (signal: AbortSignal) => Promise<T>, logger?: Logger): Promise<T>; /** * Lock-related types and interfaces */ /** * Lock handle returned when a lock is successfully acquired * Contains information needed to release the lock */ interface LockHandle { /** Unique identifier for this lock instance */ readonly id: string; /** Lock key */ readonly key: string; /** Lock value (for safe release) */ readonly value: string; /** Timestamp when lock was acquired */ readonly acquiredAt: number; /** Lock TTL in milliseconds */ readonly ttl: number; /** Extended metadata for debugging */ readonly metadata?: LockMetadata; } /** * Additional metadata for lock debugging and monitoring */ interface LockMetadata { /** How many attempts it took to acquire */ readonly attempts: number; /** Time spent acquiring the lock (ms) */ readonly acquisitionTime: number; /** Which Redis instances participated (for distributed locks) */ readonly nodes?: string[]; /** Acquisition strategy used */ readonly strategy: 'simple' | 'redlock'; } /** * Configuration for simple (single-instance) locks */ interface SimpleLockConfig { /** Redis adapter instance */ readonly adapter: RedisAdapter; /** Lock key */ readonly key: string; /** Time-to-live in milliseconds (default: 30000) */ readonly ttl?: number; /** Maximum retry attempts (default: 3) */ readonly retryAttempts?: number; /** Delay between retries in milliseconds (default: 100) */ readonly retryDelay?: number; /** Optional logger for structured logging (default: none) */ readonly logger?: Logger; } /** * Configuration for distributed (Redlock) locks */ interface RedLockConfig { /** Array of Redis adapter instances */ readonly adapters: readonly RedisAdapter[]; /** Lock key */ readonly key: string; /** Time-to-live in milliseconds (default: 30000) */ readonly ttl?: number; /** Minimum number of nodes for quorum (default: majority) */ readonly quorum?: number; /** Maximum retry attempts (default: 3) */ readonly retryAttempts?: number; /** Delay between retries in milliseconds (default: 200) */ readonly retryDelay?: number; /** Clock drift factor (default: 0.01) */ readonly clockDriftFactor?: number; /** Optional logger for structured logging (default: none) */ readonly logger?: Logger; } /** * Abstract base class for all lock implementations */ interface Lock { /** * Attempt to acquire the lock * @returns Promise resolving to lock handle on success * @throws LockAcquisitionError on failure */ acquire(): Promise<LockHandle>; /** * Release a previously acquired lock * @param handle - Lock handle from acquire() * @returns Promise resolving to true if released, false if already expired */ release(handle: LockHandle): Promise<boolean>; /** * Extend the TTL of an existing lock * @param handle - Lock handle from acquire() * @param ttl - New TTL in milliseconds * @returns Promise resolving to true if extended, false if lock expired */ extend(handle: LockHandle, ttl: number): Promise<boolean>; /** * Check if a lock is currently held * @param key - Lock key to check * @returns Promise resolving to true if locked, false otherwise */ isLocked(key: string): Promise<boolean>; /** * Get the underlying Redis adapter for atomic operations * @returns Redis adapter instance or null if not applicable (e.g., for RedLock with multiple adapters) */ getAdapter(): RedisAdapter | null; /** * Execute a routine with automatic lock management and extension * Auto-extends when remaining TTL < 20% (extends at ~80% consumed) * Provides ExtendedAbortSignal when extension fails * * @param routine - Function to execute while holding the lock * @returns Promise resolving to the routine result */ using<T>(routine: (signal: ExtendedAbortSignal) => Promise<T>): Promise<T>; } /** * Configuration for creating a simple lock */ interface CreateLockConfig { /** Redis adapter instance */ readonly adapter: RedisAdapter; /** Lock key */ readonly key: string; /** Time-to-live in milliseconds (default: 30000) */ readonly ttl?: number; /** Maximum retry attempts (default: 3) */ readonly retryAttempts?: number; /** Delay between retries in milliseconds (default: 100) */ readonly retryDelay?: number; /** Performance mode: 'standard' (default) | 'lean' | 'enterprise' */ readonly performance?: 'standard' | 'lean' | 'enterprise'; } /** * Create a simple lock instance * * @param config - Lock configuration * @returns Lock instance */ declare function createLock(config: CreateLockConfig): Lock; /** * Create multiple locks with shared configuration * * @param adapter - Redis adapter to use for all locks * @param keys - Array of lock keys * @param options - Shared configuration options * @returns Array of lock instances */ declare function createLocks(adapter: RedisAdapter, keys: readonly string[], options?: Omit<CreateLockConfig, 'adapter' | 'key'>): Lock[]; /** * Create a lock with automatic key prefixing * * @param adapter - Redis adapter instance * @param prefix - Key prefix to add * @param key - Base key name * @param options - Additional lock options * @returns Lock instance with prefixed key */ declare function createPrefixedLock(adapter: RedisAdapter, prefix: string, key: string, options?: Omit<CreateLockConfig, 'adapter' | 'key'>): Lock; /** * Configuration for creating a distributed RedLock */ interface CreateRedlockConfig { /** Array of Redis adapter instances */ readonly adapters: readonly RedisAdapter[]; /** Lock key */ readonly key: string; /** Time-to-live in milliseconds (default: 30000) */ readonly ttl?: number; /** Minimum number of nodes for quorum (default: majority) */ readonly quorum?: number; /** Maximum retry attempts (default: 3) */ readonly retryAttempts?: number; /** Delay between retries in milliseconds (default: 200) */ readonly retryDelay?: number; /** Clock drift factor (default: 0.01) */ readonly clockDriftFactor?: number; } /** * Create a distributed RedLock instance * * @param config - RedLock configuration * @returns RedLock instance */ declare function createRedlock(config: CreateRedlockConfig): Lock; /** * Create multiple RedLocks with shared configuration * * @param adapters - Array of Redis adapters to use for all locks * @param keys - Array of lock keys * @param options - Shared configuration options * @returns Array of RedLock instances */ declare function createRedlocks(adapters: readonly RedisAdapter[], keys: readonly string[], options?: Omit<CreateRedlockConfig, 'adapters' | 'key'>): Lock[]; /** * Simple lock implementation for single Redis instance * Provides reliable locking with retry logic and proper error handling * Memory-optimized for production 24/7 systems */ declare class SimpleLock implements Lock { private readonly adapter; private readonly key; private readonly ttl; private readonly retryAttempts; private readonly retryDelay; private readonly logger; private readonly correlationId?; private readonly onAcquire?; private readonly onRelease?; private _configCache?; private _lastHealthCheck; private _healthCheckInterval; private _isHealthy; private _circuitBreakerFailures; private _circuitBreakerThreshold; private _circuitBreakerTimeout; private _circuitBreakerOpenedAt; private _circuitBreakerState; private readonly _metadataTemplate; constructor(config: SimpleLockConfig); /** * Validate configuration parameters */ private validateConfig; /** * Circuit breaker pattern implementation */ private updateCircuitBreaker; /** * Check Redis connection health periodically */ private checkConnectionHealth; /** * Attempt to acquire the lock */ acquire(): Promise<LockHandle>; /** * Release a previously acquired lock */ release(handle: LockHandle): Promise<boolean>; /** * Extend the TTL of an existing lock */ extend(handle: LockHandle, ttl: number): Promise<boolean>; /** * Check if a lock is currently held */ isLocked(key: string): Promise<boolean>; /** * Validate lock handle */ private validateHandle; /** * Sleep for specified milliseconds */ private sleep; /** * Get lock configuration (for debugging) */ getConfig(): Readonly<SimpleLockConfig>; /** * Get the underlying Redis adapter (for advanced usage) */ getAdapter(): RedisAdapter | null; /** * Get connection health status */ getHealth(): { healthy: boolean; lastCheck: number; connected: boolean; circuitBreaker: { state: 'closed' | 'open' | 'half-open'; failures: number; openedAt: number; }; }; /** * Execute a routine with automatic lock management and extension * Auto-extends when remaining TTL < 20% (extends at ~80% consumed) * Provides AbortSignal when extension fails * * @param routine - Function to execute while holding the lock * @returns Result of the routine */ using<T>(routine: (signal: ExtendedAbortSignal) => Promise<T>): Promise<T>; } /** * Memory-optimized SimpleLock implementation */ /** * Lean SimpleLock - Optimized for memory efficiency * * Memory optimizations: * - No circuit breaker by default (saves ~100KB) * - No health checks by default (saves ~50KB) * - Pre-allocated error objects (saves stack trace overhead) * - Minimal property storage with short names * - No closures or callbacks * - Inline value generation */ declare class LeanSimpleLock implements Lock { private readonly a; private readonly k; private readonly t; private readonly r; private readonly d; constructor(config: SimpleLockConfig); acquire(): Promise<LockHandle>; release(handle: LockHandle): Promise<boolean>; extend(handle: LockHandle, newTtl: number): Promise<boolean>; isLocked(key: string): Promise<boolean>; /** * Get the underlying Redis adapter for atomic operations * @returns Redis adapter instance */ getAdapter(): RedisAdapter; /** * Execute a routine with automatic lock management and extension * Auto-extends when remaining TTL < 20% (extends at ~80% consumed) * Provides AbortSignal when extension fails * * @param routine - Function to execute while holding the lock * @returns Result of the routine */ using<T>(routine: (signal: ExtendedAbortSignal) => Promise<T>): Promise<T>; } /** * RedLock implementation for distributed Redis locking * Implements the Redlock algorithm as specified by Redis documentation */ /** * RedLock implementation for distributed Redis instances * Provides reliability across multiple Redis nodes using quorum consensus */ declare class RedLock implements Lock { private readonly adapters; private readonly config; constructor(config: RedLockConfig); /** * Validate RedLock configuration */ private validateConfig; /** * Attempt to acquire the distributed lock using Redlock algorithm */ acquire(): Promise<LockHandle>; /** * Attempt to acquire lock on all Redis nodes */ private attemptLockAcquisition; /** * Attempt to acquire lock on a single Redis node */ private acquireOnSingleNode; /** * Release any partially acquired locks to prevent deadlocks */ private releasePartialLocks; /** * Release a previously acquired distributed lock */ release(handle: LockHandle): Promise<boolean>; /** * Extend the TTL of an existing distributed lock */ extend(handle: LockHandle, ttl: number): Promise<boolean>; /** * Check if the distributed lock is currently held */ isLocked(key: string): Promise<boolean>; /** * Validate lock handle */ private validateHandle; /** * Sleep for specified milliseconds */ private sleep; /** * Get RedLock configuration (for debugging) */ getConfig(): Readonly<RedLockConfig>; /** * Get all underlying Redis adapters (for advanced usage) */ getAdapters(): readonly RedisAdapter[]; /** * Get the underlying Redis adapter for atomic operations * RedLock manages multiple adapters, so returns null */ getAdapter(): RedisAdapter | null; /** * Get quorum requirement */ getQuorum(): number; /** * Execute a routine with automatic lock management and extension * Auto-extends when remaining TTL < 20% (extends at ~80% consumed) * Uses quorum-based extension strategy (continues if majority of nodes succeed) * Provides AbortSignal when extension fails */ using<T>(routine: (signal: ExtendedAbortSignal) => Promise<T>): Promise<T>; } /** * Base adapter class providing common functionality for all Redis clients. * Implements validation and error handling that's shared across adapters. */ declare abstract class BaseAdapter implements RedisAdapter { protected readonly options: Required<Omit<RedisAdapterOptions, 'logger'>> & { logger?: Logger; }; protected readonly scriptSHAs: Map<string, string>; constructor(options?: RedisAdapterOptions); /** * Validates lock key format and requirements */ protected validateKey(key: string): void; /** * Validates lock value format and requirements */ protected validateValue(value: string): void; /** * Validates TTL (time-to-live) value */ protected validateTTL(ttl: number): void; /** * Adds prefix to key if configured */ protected prefixKey(key: string): string; protected stripPrefix(prefixedKey: string): string; /** * Handles timeout for Redis operations * Properly cleans up timeout handles to prevent memory leaks */ protected withTimeout<T>(operation: Promise<T>, timeoutMs?: number): Promise<T>; /** * Interpret atomic extension script result into structured response */ protected interpretAtomicExtensionResult(key: string, minTTL: number, scriptResult: [number, number]): AtomicExtensionResult; abstract setNX(key: string, value: string, ttl: number): Promise<string | null>; abstract get(key: string): Promise<string | null>; abstract del(key: string): Promise<number>; abstract delIfMatch(key: string, value: string): Promise<boolean>; abstract extendIfMatch(key: string, value: string, ttl: number): Promise<boolean>; abstract atomicExtend(key: string, value: string, minTTL: number, newTTL: number): Promise<AtomicExtensionResult>; abstract batchSetNX(keys: string[], values: string[], ttl: number): Promise<BatchAcquireResult>; abstract ping(): Promise<string>; abstract isConnected(): boolean; abstract disconnect(): Promise<void>; } /** * Flexible type for node-redis clients. * * Using 'any' here is a justified compromise because: * 1. Redis client types are extremely complex and vary based on installed modules * 2. Type safety is maintained at our adapter's public API boundary * 3. We validate all inputs and type all outputs * 4. Redis command behavior is stable and well-documented * 5. The 'any' is private and doesn't leak to library users */ type FlexibleRedisClient = any; /** * Redis adapter for node-redis v4+ clients. * Provides unified interface for node-redis specific operations. */ declare class NodeRedisAdapter extends BaseAdapter { private readonly client; constructor(client: FlexibleRedisClient, options?: RedisAdapterOptions); /** * Factory method to create adapter from client */ static from(client: FlexibleRedisClient, options?: RedisAdapterOptions): NodeRedisAdapter; /** * Execute a Lua script with automatic loading, caching, and NOSCRIPT retry handling * @private */ private _executeScript; setNX(key: string, value: string, ttl: number): Promise<string | null>; get(key: string): Promise<string | null>; del(key: string): Promise<number>; delIfMatch(key: string, value: string): Promise<boolean>; extendIfMatch(key: string, value: string, ttl: number): Promise<boolean>; atomicExtend(key: string, value: string, minTTL: number, newTTL: number): Promise<AtomicExtensionResult>; batchSetNX(keys: string[], values: string[], ttl: number): Promise<BatchAcquireResult>; ping(): Promise<string>; isConnected(): boolean; disconnect(): Promise<void>; /** * Get the underlying node-redis client (for advanced usage) */ getClient(): FlexibleRedisClient; } /** * Redis adapter for ioredis v5+ clients. * Provides unified interface for ioredis specific operations. */ declare class IoredisAdapter extends BaseAdapter { private readonly client; constructor(client: Redis, options?: RedisAdapterOptions); /** * Factory method to create adapter from client */ static from(client: Redis, options?: RedisAdapterOptions): IoredisAdapter; /** * Execute a Lua script with automatic loading, caching, and NOSCRIPT retry handling * @private */ private _executeScript; setNX(key: string, value: string, ttl: number): Promise<string | null>; get(key: string): Promise<string | null>; del(key: string): Promise<number>; delIfMatch(key: string, value: string): Promise<boolean>; extendIfMatch(key: string, value: string, ttl: number): Promise<boolean>; atomicExtend(key: string, value: string, minTTL: number, newTTL: number): Promise<AtomicExtensionResult>; batchSetNX(keys: string[], values: string[], ttl: number): Promise<BatchAcquireResult>; ping(): Promise<string>; isConnected(): boolean; disconnect(): Promise<void>; /** * Get the underlying ioredis client (for advanced usage) */ getClient(): Redis; } /** * Configuration for LockManager */ interface LockManagerConfig { /** Redis adapters/clients to use */ readonly nodes: RedisAdapter[]; /** Default TTL for locks in milliseconds */ readonly defaultTTL?: number; /** Default retry attempts */ readonly defaultRetryAttempts?: number; /** Default retry delay in milliseconds */ readonly defaultRetryDelay?: number; /** Optional logger for operational visibility */ readonly logger?: Logger; /** Monitoring configuration */ readonly monitoring?: { readonly enabled?: boolean; readonly metricsPort?: number; readonly healthCheckInterval?: number; }; } /** * Lock statistics for monitoring */ interface LockStats { readonly totalLocks: number; readonly activeLocks: number; readonly acquiredLocks: number; readonly failedLocks: number; readonly averageAcquisitionTime: number; readonly averageHoldTime: number; } /** * Production-ready lock manager for Redis distributed locking * Provides centralized management of locks with monitoring and health checks */ declare class LockManager { private readonly config; private readonly activeLocks; private readonly stats; constructor(config: LockManagerConfig); /** * Validate configuration parameters */ private validateConfig; /** * Create a simple lock for single Redis instance */ createSimpleLock(key: string, options?: { readonly ttl?: number; readonly retryAttempts?: number; readonly retryDelay?: number; readonly nodeIndex?: number; }): SimpleLock; /** * Create a distributed RedLock for multiple Redis instances */ createRedLock(key: string, options?: { readonly ttl?: number; readonly retryAttempts?: number; readonly retryDelay?: number; readonly quorum?: number; readonly clockDriftFactor?: number; }): RedLock; /** * Acquire a lock with automatic tracking */ acquireLock(key: string, options?: { readonly ttl?: number; readonly retryAttempts?: number; readonly retryDelay?: number; readonly useRedLock?: boolean; }): Promise<LockHandle>; /** * Release a tracked lock */ releaseLock(handle: LockHandle): Promise<boolean>; /** * Acquire multiple locks atomically in a single Redis operation * * **IMPORTANT - Deadlock Prevention:** * Keys are automatically sorted alphabetically before acquisition to prevent deadlocks. * The returned lock handles will be in SORTED key order, NOT the original input order. * * **Atomicity Guarantee:** * All locks are acquired atomically using a Lua script - either all succeed or none do. * Redis guarantees that Lua scripts execute atomically without interruption. * * **Example:** * ```typescript * // Input: ['user:3', 'user:1', 'user:2'] * const handles = await manager.acquireBatch(['user:3', 'user:1', 'user:2']); * // Returns handles in sorted order: ['user:1', 'user:2', 'user:3'] * ``` * * @param keys - Array of lock keys to acquire (will be sorted internally) * @param options - Acquisition options * @param options.ttl - Lock time-to-live in milliseconds (defaults to manager's defaultTTL) * @param options.nodeIndex - Redis node index to use (defaults to 0) * @returns Promise resolving to array of lock handles in SORTED key order * @throws {Error} If keys array is empty or contains duplicates * @throws {LockAcquisitionError} If any key is already locked */ acquireBatch(keys: string[], options?: { readonly ttl?: number; readonly nodeIndex?: number; }): Promise<LockHandle[]>; /** * Release multiple locks * * @param handles - Array of lock handles to release * @returns Promise resolving to array of results (true if released, false if already expired) */ releaseBatch(handles: LockHandle[]): Promise<boolean[]>; /** * Acquire and manage multiple locks with automatic extension * Combines batch acquisition with auto-extension for long-running operations * * @param keys - Array of lock keys to acquire * @param routine - Function to execute while holding all locks * @param options - Lock configuration options * @returns Promise resolving to the routine result */ usingBatch<T>(keys: string[], routine: (signal: ExtendedAbortSignal) => Promise<T>, options?: { readonly ttl?: number; readonly nodeIndex?: number; }): Promise<T>; /** * Get current lock statistics */ getStats(): LockStats; /** * Get list of currently active locks */ getActiveLocks(): LockHandle[]; /** * Check health of all Redis nodes */ checkHealth(): Promise<{ healthy: boolean; nodes: Array<{ index: number; healthy: boolean; error?: string; }>; }>; /** * Clean up expired locks from tracking */ cleanupExpiredLocks(): Promise<number>; /** * Get metrics in Prometheus format (if monitoring enabled) */ getMetrics(): string; } /** * Metrics collector for redlock-universal operations */ interface LockMetrics { readonly acquisitionTime: number; readonly attempts: number; readonly success: boolean; readonly key: string; readonly timestamp: number; } interface RedLockMetrics extends LockMetrics { readonly nodesTotal: number; readonly nodesSuccessful: number; readonly quorum: number; } interface MetricsSummary { readonly totalOperations: number; readonly successfulOperations: number; readonly failedOperations: number; readonly averageAcquisitionTime: number; readonly p95AcquisitionTime: number; readonly p99AcquisitionTime: number; readonly successRate: number; } declare class MetricsCollector { private readonly lockMetrics; private readonly maxMetrics; constructor(maxMetrics?: number); /** * Record a lock operation metric */ recordLockOperation(metrics: LockMetrics): void; /** * Get summary of all recorded metrics */ getSummary(): MetricsSummary; /** * Get metrics for a specific time window */ getMetricsForWindow(windowMs: number): LockMetrics[]; /** * Get metrics grouped by key */ getMetricsByKey(): Map<string, LockMetrics[]>; /** * Clear all recorded metrics */ clear(): void; /** * Get current metrics count */ getMetricsCount(): number; } /** * Health checker for Redis adapters and lock operations */ interface HealthStatus { readonly healthy: boolean; readonly timestamp: number; readonly responseTime: number; readonly error?: string; } interface AdapterHealth { readonly adapter: string; readonly status: HealthStatus; } interface SystemHealth { readonly overall: boolean; readonly adapters: AdapterHealth[]; readonly timestamp: number; } declare class HealthChecker { private readonly adapters; private readonly healthHistory; private readonly maxHistorySize; constructor(maxHistorySize?: number); /** * Register an adapter for health monitoring */ registerAdapter(name: string, adapter: RedisAdapter): void; /** * Unregister an adapter */ unregisterAdapter(name: string): void; /** * Check health of a specific adapter */ checkAdapterHealth(name: string): Promise<HealthStatus>; /** * Check health of all registered adapters */ checkSystemHealth(): Promise<SystemHealth>; /** * Get health history for an adapter */ getHealthHistory(name: string, count?: number): HealthStatus[]; /** * Get health statistics for an adapter */ getHealthStats(name: string, windowMs?: number): { total: number; healthy: number; unhealthy: number; averageResponseTime: number; uptime: number; }; /** * Clear health history for an adapter */ clearHistory(name: string): void; /** * Clear all health history */ clearAllHistory(): void; private recordHealthStatus; } /** * Generate a cryptographically secure random lock value * Uses buffer pool with randomFillSync for zero-allocation crypto */ declare function generateLockValue(): string; /** * Generate a unique lock ID combining timestamp and random data * Format: timestamp-random (e.g., "1703123456789-a1b2c3d4e5f6") */ declare function generateLockId(): string; /** * Compare two strings using timing-safe comparison * Prevents timing attacks on lock value verification * Uses buffer pool to reduce allocations */ declare function safeCompare(a: string, b: string): boolean; /** * Create a lock value with embedded metadata * Format: nodeId:timestamp:random * Uses buffer pool to reduce allocations */ declare function createLockValueWithMetadata(nodeId?: string): string; /** * Parse lock value metadata if it was created with createLockValueWithMetadata */ declare function parseLockValue(value: string): { nodeId: string; timestamp: number; random: string; } | null; /** * Validate that a lock value is properly formatted */ declare function isValidLockValue(value: string): boolean; /** * Error types for the library */ /** * Base class for all redlock-universal errors */ declare abstract class RedlockError extends Error { abstract readonly code: string; cause?: Error; constructor(message: string, cause?: Error); } /** * Thrown when unable to acquire a lock */ declare class LockAcquisitionError extends RedlockError { readonly key: string; readonly attempts: number; readonly code = "LOCK_ACQUISITION_FAILED"; constructor(key: string, attempts: number, cause?: Error); } /** * Thrown when lock release fails */ declare class LockReleaseError extends RedlockError { readonly key: string; readonly reason: 'not_found' | 'wrong_value' | 'redis_error'; readonly code = "LOCK_RELEASE_FAILED"; constructor(key: string, reason: 'not_found' | 'wrong_value' | 'redis_error', cause?: Error); } /** * Thrown when lock extension fails */ declare class LockExtensionError extends RedlockError { readonly key: string; readonly reason: 'not_found' | 'wrong_value' | 'redis_error'; readonly code = "LOCK_EXTENSION_FAILED"; constructor(key: string, reason: 'not_found' | 'wrong_value' | 'redis_error', cause?: Error); } /** * Thrown when Redis adapter configuration is invalid */ declare class AdapterError extends RedlockError { readonly code = "ADAPTER_ERROR"; constructor(message: string, cause?: Error); } /** * Thrown when configuration is invalid */ declare class ConfigurationError extends RedlockError { readonly code = "CONFIGURATION_ERROR"; constructor(message: string); } /** * Library constants */ /** * Default configuration values */ declare const DEFAULTS: { /** Default lock TTL in milliseconds (30 seconds) */ readonly TTL: 30000; /** Default retry attempts */ readonly RETRY_ATTEMPTS: 3; /** Default retry delay in milliseconds */ readonly RETRY_DELAY: 100; /** Default Redis command timeout in milliseconds */ readonly REDIS_TIMEOUT: 5000; /** Default clock drift factor for Redlock */ readonly CLOCK_DRIFT_FACTOR: 0.01; /** Default monitoring interval in milliseconds (1 minute) */ readonly MONITORING_INTERVAL: 60000; /** Default health check interval in milliseconds (30 seconds) */ readonly HEALTH_CHECK_INTERVAL: 30000; /** Circuit breaker failure threshold */ readonly CIRCUIT_BREAKER_THRESHOLD: 5; /** Circuit breaker timeout in milliseconds (1 minute) */ readonly CIRCUIT_BREAKER_TIMEOUT: 60000; /** Auto-extension threshold ratio (extend when 80% of TTL consumed) */ readonly AUTO_EXTENSION_THRESHOLD_RATIO: 0.2; /** Minimum extension interval in milliseconds */ readonly MIN_EXTENSION_INTERVAL: 1000; /** Safety buffer for atomic extension (minimum TTL required) */ readonly ATOMIC_EXTENSION_SAFETY_BUFFER: 2000; /** * Extension buffer ratio for single-node locks (10%) * * This ratio determines how much TTL must remain before atomic extension. * For single-node locks, we use a larger buffer (10%) because: * - Lower coordination overhead allows larger safety margin * - Single point of failure means we can be more conservative * - Network latency to one node is more predictable * * Example: For a 30-second TTL, extension triggers with 3 seconds remaining */ readonly SINGLE_NODE_EXTENSION_BUFFER_RATIO: 0.1; /** * Extension buffer ratio for distributed locks (5%) * * This ratio determines how much TTL must remain before atomic extension. * For distributed locks (RedLock), we use a smaller buffer (5%) because: * - Multiple nodes require more coordination time * - Smaller ratio ensures we extend before ANY node expires * - Clock drift across nodes necessitates earlier extension * - Quorum-based approach means we need tighter timing * * Example: For a 30-second TTL, extension triggers with 1.5 seconds remaining */ readonly DISTRIBUTED_EXTENSION_BUFFER_RATIO: 0.05; }; /** * Lua scripts for atomic operations * These scripts are re-exported from BaseAdapter for public API access */ declare const LUA_SCRIPTS: { /** Script to safely release a lock (check value before delete) */ readonly RELEASE: string; /** Script to safely extend a lock (check value before extend) */ readonly EXTEND: string; }; /** * Library metadata */ declare const LIBRARY_INFO: { readonly NAME: "redlock-universal"; readonly VERSION: "0.1.0"; readonly DESCRIPTION: "Production-ready distributed Redis locks for Node.js"; }; export { AdapterError, type AdapterHealth, type AutoExtensionConfig, BaseAdapter, ConfigurationError, type CreateLockConfig, type CreateRedlockConfig, DEFAULTS, type ExtendedAbortSignal, HealthChecker, type HealthStatus, IoredisAdapter, LIBRARY_INFO, LUA_SCRIPTS, LeanSimpleLock, type Lock, LockAcquisitionError, LockExtensionError, type LockHandle, LockManager, type LockManagerConfig, type LockMetrics, LockReleaseError, type LockStats, type LogEntry, LogLevel, Logger, type LoggerConfig, MetricsCollector, type MetricsSummary, NodeRedisAdapter, RedLock, type RedLockConfig, type RedLockMetrics, type RedisAdapter, type RedisAdapterOptions, RedlockError, SimpleLock, type SimpleLockConfig, type SystemHealth, createLock, createLockValueWithMetadata, createLocks, createPrefixedLock, createRedlock, createRedlocks, executeWithAutoExtension, executeWithSingleLockExtension, generateLockId, generateLockValue, isValidLockValue, logger, parseLockValue, safeCompare };