UNPKG

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.

80 lines (79 loc) 3.42 kB
// SPDX-FileCopyrightText: 2025-present Kriasoft // SPDX-License-Identifier: MIT import { attachRawData, checkAborted, LockError, makeStorageKey, normalizeAndValidateKey, sanitizeLockInfo, validateLockId, } from "../../common/backend.js"; import { isLive, TIME_TOLERANCE_MS } from "../../common/time-predicates.js"; import { mapFirestoreError } from "../errors.js"; /** * Retrieves lock info by key or lockId (diagnostic only, non-atomic). * * @remarks * Non-atomic queries acceptable for diagnostic lookups (ADR-011). Omits `.limit(1)` * to detect duplicate lockIds (ADR-014). */ export function createLookupOperation(db, locksCollection, config) { return async (opts) => { try { // Check for cancellation before starting operation checkAborted(opts.signal); let doc; const FIRESTORE_LIMIT_BYTES = 1500; const RESERVE_BYTES = 0; // No derived keys in Firestore if ("key" in opts) { // Key lookup: validate and normalize, then fetch by document ID const normalizedKey = normalizeAndValidateKey(opts.key); const storageKey = makeStorageKey("", normalizedKey, FIRESTORE_LIMIT_BYTES, RESERVE_BYTES); const docRef = locksCollection.doc(storageKey); doc = await docRef.get(); // Check for cancellation after read checkAborted(opts.signal); if (!doc.exists) { return null; } } else { // LockId lookup: validate and query index without .limit(1) (ADR-014) validateLockId(opts.lockId); const querySnapshot = await locksCollection .where("lockId", "==", opts.lockId) .get(); // Duplicate detection (ADR-014): log only for diagnostic lookup if (querySnapshot.docs.length > 1) { console.warn(`[syncguard] Duplicate lockId detected in lookup: ${opts.lockId} (${querySnapshot.docs.length} documents)`); } // Check for cancellation after read checkAborted(opts.signal); if (querySnapshot.empty) { return null; } doc = querySnapshot.docs[0]; const data = doc.data(); // Defense-in-depth: verify lockId match despite WHERE clause if (data?.lockId !== opts.lockId) { return null; } } const data = doc.data(); const nowMs = Date.now(); if (!isLive(data.expiresAtMs, nowMs, TIME_TOLERANCE_MS)) { return null; // Lock expired } const capabilities = { backend: "firestore", supportsFencing: true, timeAuthority: "client", }; const lockInfo = sanitizeLockInfo(data, capabilities); // Attach raw data for debugging (see: common/helpers.ts) return attachRawData(lockInfo, { key: data.key, lockId: data.lockId, }); } catch (error) { if (error instanceof LockError) { throw error; } throw mapFirestoreError(error); } }; }