UNPKG

syncguard

Version:

Functional TypeScript library for distributed locking across microservices. Prevents race conditions with Redis, Firestore, and custom backends. Features automatic lock management, timeout handling, and extensible architecture.

39 lines (38 loc) 4.35 kB
/** * Lua script for atomic lock acquisition * This script: * 1. Checks if lock exists and is not expired * 2. If lock doesn't exist or is expired, creates new lock * 3. Properly cleans up old lockId index using keyPrefix * 4. Sets both main lock key and lockId index * 5. Returns 1 for success, 0 for failure */ export declare const ACQUIRE_SCRIPT = "\nlocal lockKey = KEYS[1]\nlocal lockIdKey = KEYS[2]\nlocal lockData = ARGV[1]\nlocal ttlSeconds = tonumber(ARGV[2])\nlocal currentTime = tonumber(ARGV[3])\nlocal keyPrefix = ARGV[4]\n\n-- Check if lock exists\nlocal existingData = redis.call('GET', lockKey)\nif existingData then\n local data = cjson.decode(existingData)\n -- If lock is not expired, return failure\n if data.expiresAt > currentTime then\n return 0\n end\n -- Lock is expired, we can clean up the old lockId index\n if data.lockId then\n local oldLockIdKey = keyPrefix .. 'id:' .. data.lockId\n redis.call('DEL', oldLockIdKey)\n end\nend\n\n-- Acquire the lock\nredis.call('SET', lockKey, lockData, 'EX', ttlSeconds)\nredis.call('SET', lockIdKey, lockKey, 'EX', ttlSeconds)\nreturn 1\n"; /** * Lua script for atomic lock release * This script: * 1. Gets the lock key from the lockId index * 2. Verifies ownership by checking lockId in the lock data * 3. Deletes both main lock and lockId index if ownership is verified * 4. Returns 1 for success, 0 for failure (not found or not owned) */ export declare const RELEASE_SCRIPT = "\nlocal lockIdKey = KEYS[1]\nlocal lockId = ARGV[1]\n\n-- Get the lock key from the lockId index\nlocal lockKey = redis.call('GET', lockIdKey)\nif not lockKey then\n return 0 -- Lock ID not found\nend\n\n-- Get the lock data\nlocal lockData = redis.call('GET', lockKey)\nif not lockData then\n -- Lock key doesn't exist, but lockId index does - clean up index\n redis.call('DEL', lockIdKey)\n return 0\nend\n\n-- Verify ownership\nlocal data = cjson.decode(lockData)\nif data.lockId ~= lockId then\n return 0 -- Not the owner\nend\n\n-- Delete both keys atomically\nredis.call('DEL', lockKey)\nredis.call('DEL', lockIdKey)\nreturn 1\n"; /** * Lua script for atomic lock extension * This script: * 1. Gets the lock key from the lockId index * 2. Verifies ownership and that lock hasn't expired * 3. Updates the lock data with new expiration time * 4. Updates TTL on both keys * 5. Returns 1 for success, 0 for failure */ export declare const EXTEND_SCRIPT = "\nlocal lockIdKey = KEYS[1]\nlocal lockId = ARGV[1]\nlocal ttlMs = tonumber(ARGV[2])\nlocal currentTime = tonumber(ARGV[3])\n\n-- Get the lock key from the lockId index\nlocal lockKey = redis.call('GET', lockIdKey)\nif not lockKey then\n return 0 -- Lock ID not found\nend\n\n-- Get the lock data\nlocal lockData = redis.call('GET', lockKey)\nif not lockData then\n -- Lock key doesn't exist, but lockId index does - clean up index\n redis.call('DEL', lockIdKey)\n return 0\nend\n\n-- Verify ownership and expiration\nlocal data = cjson.decode(lockData)\nif data.lockId ~= lockId then\n return 0 -- Not the owner\nend\n\nif data.expiresAt <= currentTime then\n return 0 -- Lock already expired\nend\n\n-- Update the lock data with new expiration\ndata.expiresAt = currentTime + ttlMs\nlocal ttlSeconds = math.ceil(ttlMs / 1000)\n\n-- Update both keys with new data and TTL\nredis.call('SET', lockKey, cjson.encode(data), 'EX', ttlSeconds)\nredis.call('EXPIRE', lockIdKey, ttlSeconds)\n\nreturn 1\n"; /** * Lua script for checking lock status with cleanup * This script: * 1. Gets the lock data * 2. Checks if it's expired * 3. If expired, cleans up both lock and lockId index (fire-and-forget) * 4. Returns 1 if locked and not expired, 0 otherwise */ export declare const IS_LOCKED_SCRIPT = "\nlocal lockKey = KEYS[1]\nlocal keyPrefix = ARGV[1]\nlocal currentTime = tonumber(ARGV[2])\n\n-- Get the lock data\nlocal lockData = redis.call('GET', lockKey)\nif not lockData then\n return 0 -- No lock exists\nend\n\n-- Check if expired\nlocal data = cjson.decode(lockData)\nif data.expiresAt <= currentTime then\n -- Lock is expired, clean up (fire-and-forget)\n redis.call('DEL', lockKey)\n if data.lockId then\n local lockIdKey = keyPrefix .. 'id:' .. data.lockId\n redis.call('DEL', lockIdKey)\n end\n return 0\nend\n\nreturn 1 -- Lock exists and is not expired\n";