syncguard
Version:
Functional TypeScript library for distributed locking across microservices. Prevents race conditions with Redis, PostgreSQL, Firestore, and custom backends. Features automatic lock management, timeout handling, and extensible architecture.
64 lines (63 loc) • 3.18 kB
JavaScript
// SPDX-FileCopyrightText: 2025-present Kriasoft
// SPDX-License-Identifier: MIT
import { attachRawData, LockError, makeStorageKey, normalizeAndValidateKey, sanitizeLockInfo, validateLockId, } from "../../common/backend.js";
import { TIME_TOLERANCE_MS } from "../../common/time-predicates.js";
import { checkAborted, mapRedisError } from "../errors.js";
import { LOOKUP_BY_KEY_SCRIPT, LOOKUP_BY_LOCKID_SCRIPT } from "../scripts.js";
/**
* Creates lookup operation for Redis backend.
* @returns Async function that retrieves lock info by key or lockId
* @see ../scripts.ts for Lua script implementations
*/
export function createLookupOperation(redis, config) {
return async (opts) => {
try {
// Pre-dispatch abort check (ioredis does not accept AbortSignal)
checkAborted(opts.signal);
let scriptResult;
const REDIS_LIMIT_BYTES = 1000;
const RESERVE_BYTES = 26; // ":id:" (4 bytes) + 22-char lockId
if ("key" in opts) {
// Key lookup path: validates and normalizes key
const normalizedKey = normalizeAndValidateKey(opts.key);
const lockKey = makeStorageKey(config.keyPrefix, normalizedKey, REDIS_LIMIT_BYTES, RESERVE_BYTES);
// Use Redis function if available, fallback to eval()
scriptResult = redis.getLockInfoByKey
? await redis.getLockInfoByKey(lockKey, TIME_TOLERANCE_MS.toString())
: (await redis.eval(LOOKUP_BY_KEY_SCRIPT, 1, lockKey, TIME_TOLERANCE_MS.toString()));
}
else {
// LockId lookup path: validates lockId format
validateLockId(opts.lockId);
const lockIdKey = makeStorageKey(config.keyPrefix, `id:${opts.lockId}`, REDIS_LIMIT_BYTES, RESERVE_BYTES);
// ADR-013: Use Redis function if available, fallback to eval()
// No longer pass keyPrefix - lockKey is retrieved directly from index
scriptResult = redis.getLockInfoByLockId
? await redis.getLockInfoByLockId(lockIdKey, opts.lockId, TIME_TOLERANCE_MS.toString())
: (await redis.eval(LOOKUP_BY_LOCKID_SCRIPT, 1, // Only 1 key now (lockIdKey)
lockIdKey, opts.lockId, TIME_TOLERANCE_MS.toString()));
}
if (!scriptResult) {
return null; // Lock not found or expired
}
const lockData = JSON.parse(scriptResult);
const capabilities = {
backend: "redis",
supportsFencing: true,
timeAuthority: "server",
};
const lockInfo = sanitizeLockInfo(lockData, capabilities);
// Preserve raw data for debugging (see: getByKeyRaw/getByIdRaw in common/helpers.ts)
return attachRawData(lockInfo, {
key: lockData.key,
lockId: lockData.lockId,
});
}
catch (error) {
if (error instanceof LockError) {
throw error;
}
throw mapRedisError(error);
}
};
}