/** * Optimistic Redlock implementation with version-based conflict detection * Extends the base Redlock algorithm with optimistic concurrency control */ import { RedisClient } from "../core/types"; import { InternalRedlock as Redlock, RedlockOptions } from "./redlock"; export interface OptimisticLockOptions extends RedlockOptions { /** * Maximum number of conflict retry attempts */ conflictRetryCount?: number; /** * Base delay for conflict retries (ms) */ conflictRetryDelay?: number; /** * Exponential backoff factor for conflict retries */ conflictBackoffFactor?: number; } export interface OptimisticResource { resource: string; value: string | null; version: number; ttl?: number; } export interface OptimisticLockResult extends OptimisticResource { validity: number; isReadOnly: boolean; } /** * Lua script to roll back a write that succeeded on a minority of nodes. * Only reverts if the version is exactly the one our failed write produced * (expected + 1) — an unconditional DECR could corrupt a version that * another client has since written. For a first write (expected version 0) * the keys it created are deleted outright. Note: for non-first writes the * previous value cannot be restored (it is unknown), only the version is. * Exported for regression tests. */ export declare const ROLLBACK_WRITE_SCRIPT = "\n local currentVersion = redis.call(\"GET\", KEYS[1] .. \":version\")\n if currentVersion and tonumber(currentVersion) == tonumber(ARGV[1]) + 1 then\n if tonumber(ARGV[1]) == 0 then\n redis.call(\"DEL\", KEYS[1])\n redis.call(\"DEL\", KEYS[1] .. \":version\")\n else\n redis.call(\"DECR\", KEYS[1] .. \":version\")\n end\n return 1\n end\n return 0\n"; /** * Lua script for optimistic write with version check * Only updates if version matches, increments version on success */ export declare const OPTIMISTIC_WRITE_SCRIPT = "\n local currentVersion = redis.call(\"GET\", KEYS[1] .. \":version\")\n \n -- First write (no version exists)\n if not currentVersion and ARGV[2] == \"0\" then\n redis.call(\"SET\", KEYS[1], ARGV[1])\n redis.call(\"SET\", KEYS[1] .. \":version\", \"1\")\n if ARGV[3] and ARGV[3] ~= \"\" then\n redis.call(\"PEXPIRE\", KEYS[1], ARGV[3])\n redis.call(\"PEXPIRE\", KEYS[1] .. \":version\", ARGV[3])\n end\n return 1\n end\n \n -- Version matches - update allowed\n if currentVersion == ARGV[2] then\n local newVersion = tostring(tonumber(currentVersion) + 1)\n redis.call(\"SET\", KEYS[1], ARGV[1])\n redis.call(\"SET\", KEYS[1] .. \":version\", newVersion)\n if ARGV[3] and ARGV[3] ~= \"\" then\n redis.call(\"PEXPIRE\", KEYS[1], ARGV[3])\n redis.call(\"PEXPIRE\", KEYS[1] .. \":version\", ARGV[3])\n end\n return newVersion\n else\n -- Version mismatch - conflict\n return nil\n end\n"; /** * Lua script for compare-and-swap operation * Atomically swaps value if current value matches expected */ export declare const COMPARE_AND_SWAP_SCRIPT = "\n local current = redis.call(\"GET\", KEYS[1])\n if current == ARGV[1] then\n redis.call(\"SET\", KEYS[1], ARGV[2])\n local version = redis.call(\"GET\", KEYS[1] .. \":version\") or \"0\"\n local newVersion = tostring(tonumber(version) + 1)\n redis.call(\"SET\", KEYS[1] .. \":version\", newVersion)\n if ARGV[3] and ARGV[3] ~= \"\" then\n redis.call(\"PEXPIRE\", KEYS[1], ARGV[3])\n redis.call(\"PEXPIRE\", KEYS[1] .. \":version\", ARGV[3])\n end\n return newVersion\n else\n return nil\n end\n"; export declare class OptimisticLock { readonly redlock: OptimisticRedlock; readonly resource: string; readonly value: string | null; readonly version: number; readonly validity: number; readonly isReadOnly: boolean; private _currentVersion; constructor(redlock: OptimisticRedlock, result: OptimisticLockResult); /** * Get current version (may be updated after operations) */ get currentVersion(): number; /** * Update value with optimistic concurrency control */ update(newValue: string, ttl?: number): Promise; /** * Delete resource with version check */ delete(): Promise; /** * Compare and swap operation */ compareAndSwap(expectedValue: string, newValue: string, ttl?: number): Promise; /** * Refresh read to get latest version */ refresh(): Promise; } export declare class OptimisticRedlock extends Redlock { protected optimisticOptions: OptimisticLockOptions; constructor(clients: RedisClient[], options?: Partial); /** * Perform optimistic read operation */ optimisticRead(resource: string): Promise; /** * Perform optimistic write with version check */ optimisticWrite(resource: string, value: string, expectedVersion: number, ttl?: number): Promise; /** * Compare and swap operation */ compareAndSwap(resource: string, expectedValue: string, newValue: string, ttl?: number): Promise; /** * Delete with version check */ optimisticDelete(resource: string, expectedVersion: number): Promise; /** * Acquire optimistic lock for reading */ acquireOptimisticRead(resource: string): Promise; /** * Acquire optimistic lock for writing */ acquireOptimisticWrite(resource: string, ttl: number): Promise; /** * Execute transaction with automatic conflict retry */ transaction(resource: string, operation: (lock: OptimisticLock) => Promise, options?: { ttl?: number; maxRetries?: number; }): Promise; /** * Roll back a write that succeeded on a minority of nodes. * Guarded in Lua: only reverts a version equal to expected + 1, so a * version written by another client in the meantime is left untouched. */ private rollbackWrite; /** * Execute Lua script (use client.eval directly) */ private executeOptimisticScript; /** * Sleep utility (inherited from base class) */ private optimisticSleep; } export default OptimisticRedlock; //# sourceMappingURL=optimistic-redlock.d.ts.map