/** * Lua Scripts for Atomic Redis Operations * Optimized scripts based on analysis of multiple implementations */ /** * Script for acquiring locks with optional data storage */ export declare const ACQUIRE_SCRIPT = "\n-- RedlockToolkit Acquire Script\n-- KEYS[1..n]: lock keys\n-- ARGV[1]: lock identifier (UUID)\n-- ARGV[2]: TTL in milliseconds\n-- ARGV[3]: data to store (JSON string, optional)\n\nlocal identifier = ARGV[1]\nlocal ttl = tonumber(ARGV[2])\nlocal data = ARGV[3]\n\n-- Check if any of the resources are already locked\nfor i = 1, #KEYS do\n local current = redis.call('GET', KEYS[i])\n if current and current ~= identifier then\n return 0 -- Lock exists and is held by different client\n end\nend\n\n-- Acquire all locks atomically\nfor i = 1, #KEYS do\n redis.call('SET', KEYS[i], identifier, 'PX', ttl)\n \n -- Store data if provided\n if data and data ~= '' then\n local dataKey = KEYS[i] .. ':data'\n redis.call('SET', dataKey, data, 'PX', ttl)\n end\nend\n\nreturn #KEYS -- Return number of locks acquired\n"; /** * Script for extending locks */ export declare const EXTEND_SCRIPT = "\n-- RedlockToolkit Extend Script\n-- KEYS[1..n]: lock keys\n-- ARGV[1]: lock identifier (UUID)\n-- ARGV[2]: new TTL in milliseconds\n\nlocal identifier = ARGV[1]\nlocal ttl = tonumber(ARGV[2])\n\n-- Verify ownership of all locks before extending\nfor i = 1, #KEYS do\n local current = redis.call('GET', KEYS[i])\n if current ~= identifier then\n return 0 -- Not owner of all locks\n end\nend\n\n-- Extend all locks atomically\nfor i = 1, #KEYS do\n redis.call('SET', KEYS[i], identifier, 'PX', ttl)\n \n -- Extend data TTL if exists\n local dataKey = KEYS[i] .. ':data'\n if redis.call('EXISTS', dataKey) == 1 then\n redis.call('EXPIRE', dataKey, math.floor(ttl / 1000))\n end\nend\n\nreturn #KEYS -- Return number of locks extended\n"; /** * Script for releasing locks */ export declare const RELEASE_SCRIPT = "\n-- RedlockToolkit Release Script\n-- KEYS[1..n]: lock keys\n-- ARGV[1]: lock identifier (UUID)\n\nlocal identifier = ARGV[1]\nlocal released = 0\n\n-- Release only locks owned by this identifier\nfor i = 1, #KEYS do\n local current = redis.call('GET', KEYS[i])\n if current == identifier then\n redis.call('DEL', KEYS[i])\n \n -- Clean up data if exists\n local dataKey = KEYS[i] .. ':data'\n redis.call('DEL', dataKey)\n \n released = released + 1\n end\nend\n\nreturn released -- Return number of locks released\n"; /** * Script for force releasing locks (admin operation) */ export declare const FORCE_RELEASE_SCRIPT = "\n-- RedlockToolkit Force Release Script\n-- KEYS[1..n]: lock keys\n\nlocal released = 0\n\nfor i = 1, #KEYS do\n if redis.call('EXISTS', KEYS[i]) == 1 then\n redis.call('DEL', KEYS[i])\n \n -- Clean up data if exists\n local dataKey = KEYS[i] .. ':data'\n redis.call('DEL', dataKey)\n \n released = released + 1\n end\nend\n\nreturn released -- Return number of locks released\n"; /** * Script for checking lock status */ export declare const STATUS_SCRIPT = "\n-- RedlockToolkit Status Script\n-- KEYS[1..n]: lock keys\n\nlocal result = {}\n\nfor i = 1, #KEYS do\n local key = KEYS[i]\n local value = redis.call('GET', key)\n local ttl = redis.call('PTTL', key)\n \n if value then\n result[#result + 1] = {\n key = key,\n holder = value,\n ttl = ttl\n }\n else\n result[#result + 1] = {\n key = key,\n holder = nil,\n ttl = -2 -- Key doesn't exist\n }\n end\nend\n\nreturn cjson.encode(result)\n"; /** * Script for acquiring locks with wait-if-locked functionality */ export declare const ACQUIRE_WITH_WAIT_SCRIPT = "\n-- RedlockToolkit Acquire with Wait Script\n-- KEYS[1..n]: lock keys\n-- ARGV[1]: lock identifier (UUID)\n-- ARGV[2]: TTL in milliseconds\n-- ARGV[3]: max wait time in milliseconds\n-- ARGV[4]: data to store (JSON string, optional)\n\nlocal identifier = ARGV[1]\nlocal ttl = tonumber(ARGV[2])\nlocal maxWait = tonumber(ARGV[3])\nlocal data = ARGV[4]\n\n-- Check current lock status\nlocal blockedBy = {}\nlocal minTtl = maxWait\n\nfor i = 1, #KEYS do\n local current = redis.call('GET', KEYS[i])\n if current and current ~= identifier then\n local keyTtl = redis.call('PTTL', KEYS[i])\n if keyTtl > 0 and keyTtl < minTtl then\n minTtl = keyTtl\n end\n blockedBy[#blockedBy + 1] = {\n key = KEYS[i],\n holder = current,\n ttl = keyTtl\n }\n end\nend\n\n-- If blocked and min TTL is within wait time, return wait suggestion\nif #blockedBy > 0 and minTtl <= maxWait then\n return {\n acquired = 0,\n waitTime = minTtl,\n blockedBy = blockedBy\n }\nend\n\n-- If blocked but TTL too long, fail immediately\nif #blockedBy > 0 then\n return {\n acquired = 0,\n waitTime = -1,\n blockedBy = blockedBy\n }\nend\n\n-- Acquire all locks\nfor i = 1, #KEYS do\n redis.call('SET', KEYS[i], identifier, 'PX', ttl)\n \n if data and data ~= '' then\n local dataKey = KEYS[i] .. ':data'\n redis.call('SET', dataKey, data, 'PX', ttl)\n end\nend\n\nreturn {\n acquired = #KEYS,\n waitTime = 0,\n blockedBy = {}\n}\n"; /** * Script for cleaning up expired locks (maintenance) */ export declare const CLEANUP_SCRIPT = "\n-- RedlockToolkit Cleanup Script\n-- KEYS[1]: pattern for lock keys (e.g., \"locks:*\")\n-- ARGV[1]: batch size (default 100)\n\nlocal pattern = KEYS[1]\nlocal batchSize = tonumber(ARGV[1]) or 100\nlocal cursor = 0\nlocal cleaned = 0\n\nrepeat\n local result = redis.call('SCAN', cursor, 'MATCH', pattern, 'COUNT', batchSize)\n cursor = tonumber(result[1])\n local keys = result[2]\n\n for i = 1, #keys do\n local key = keys[i]\n local ttl = redis.call('PTTL', key)\n\n -- Clean up expired or invalid locks\n if ttl == -2 then -- Key doesn't exist\n -- Already cleaned\n elseif ttl == -1 then -- Key exists but no TTL\n redis.call('DEL', key) -- Remove persistent locks\n cleaned = cleaned + 1\n end\n end\n\nuntil cursor == 0\n\nreturn cleaned\n"; /** * Script for optimistic locking with version control */ export declare const OPTIMISTIC_ACQUIRE_SCRIPT = "\n-- RedlockToolkit Optimistic Acquire Script\n-- KEYS[1..n]: lock keys\n-- ARGV[1]: lock identifier (UUID)\n-- ARGV[2]: TTL in milliseconds\n-- ARGV[3]: expected version (optional)\n-- ARGV[4]: expected value (JSON, optional)\n\nlocal identifier = ARGV[1]\nlocal ttl = tonumber(ARGV[2])\nlocal expectedVersion = ARGV[3] and tonumber(ARGV[3]) or nil\nlocal expectedValue = ARGV[4]\n\n-- Check version and value constraints for optimistic locking\nfor i = 1, #KEYS do\n local key = KEYS[i]\n local current = redis.call('GET', key)\n local versionKey = key .. ':version'\n local currentVersion = redis.call('GET', versionKey)\n\n -- If key exists and is locked by someone else\n if current and current ~= identifier then\n return { conflict = true, currentVersion = currentVersion, reason = 'locked' }\n end\n\n -- Check version constraint\n if expectedVersion and currentVersion then\n local ver = tonumber(currentVersion)\n if ver and ver ~= expectedVersion then\n return { conflict = true, currentVersion = ver, reason = 'version_mismatch' }\n end\n end\n\n -- Check value constraint\n if expectedValue and expectedValue ~= '' then\n local valueKey = key .. ':value'\n local currentValue = redis.call('GET', valueKey)\n if currentValue and currentValue ~= expectedValue then\n return { conflict = true, currentVersion = currentVersion, reason = 'value_mismatch' }\n end\n end\nend\n\n-- Acquire all locks with version increment\nlocal newVersion = (expectedVersion or 0) + 1\nfor i = 1, #KEYS do\n local key = KEYS[i]\n redis.call('SET', KEYS[i], identifier, 'PX', ttl)\n\n -- Update version\n local versionKey = key .. ':version'\n redis.call('SET', versionKey, tostring(newVersion), 'PX', ttl)\n\n -- Store expected value if provided\n if expectedValue and expectedValue ~= '' then\n local valueKey = key .. ':value'\n redis.call('SET', valueKey, expectedValue, 'PX', ttl)\n end\nend\n\nreturn { success = true, newVersion = newVersion, acquired = #KEYS }\n"; /** * Script for optimistic update with conflict detection */ export declare const OPTIMISTIC_UPDATE_SCRIPT = "\n-- RedlockToolkit Optimistic Update Script\n-- KEYS[1..n]: lock keys\n-- ARGV[1]: lock identifier (UUID)\n-- ARGV[2]: new TTL in milliseconds\n-- ARGV[3]: expected version\n-- ARGV[4]: new value (JSON, optional)\n\nlocal identifier = ARGV[1]\nlocal ttl = tonumber(ARGV[2])\nlocal expectedVersion = tonumber(ARGV[3])\nlocal newValue = ARGV[4]\n\n-- Verify ownership and version\nfor i = 1, #KEYS do\n local key = KEYS[i]\n local current = redis.call('GET', key)\n local versionKey = key .. ':version'\n local currentVersion = redis.call('GET', versionKey)\n\n -- Check ownership\n if not current or current ~= identifier then\n return { conflict = true, reason = 'not_owner' }\n end\n\n -- Check version\n if currentVersion then\n local ver = tonumber(currentVersion)\n if ver and ver ~= expectedVersion then\n return { conflict = true, currentVersion = ver, reason = 'version_mismatch' }\n end\n end\nend\n\n-- Update all locks with new version\nlocal newVersion = expectedVersion + 1\nfor i = 1, #KEYS do\n local key = KEYS[i]\n redis.call('SET', KEYS[i], identifier, 'PX', ttl)\n\n -- Update version\n local versionKey = key .. ':version'\n redis.call('SET', versionKey, tostring(newVersion), 'PX', ttl)\n\n -- Update value if provided\n if newValue and newValue ~= '' then\n local valueKey = key .. ':value'\n redis.call('SET', valueKey, newValue, 'PX', ttl)\n end\nend\n\nreturn { success = true, newVersion = newVersion, updated = #KEYS }\n"; /** * Pre-computed script hashes for performance */ export declare const SCRIPT_HASHES: { acquire: string; extend: string; release: string; forceRelease: string; status: string; acquireWithWait: string; cleanup: string; optimisticAcquire: string; optimisticUpdate: string; }; /** * Script definition with hash */ export interface LuaScript { source: string; hash: string; } /** * All available scripts */ export declare const SCRIPTS: Record; /** * Export raw scripts for compatibility */ export declare const scripts: { acquire: string; extend: string; release: string; forceRelease: string; status: string; acquireWithWait: string; cleanup: string; optimisticAcquire: string; optimisticUpdate: string; }; /** * Utility function to calculate script hash */ export declare function calculateScriptHash(script: string): string; /** * Validate that pre-computed hashes are correct */ export declare function validateScriptHashes(): boolean; /** * Get script by name with runtime validation */ export declare function getScript(name: keyof typeof SCRIPTS): LuaScript; //# sourceMappingURL=scripts.d.ts.map