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.

87 lines (86 loc) 2.87 kB
/* SPDX-FileCopyrightText: 2025-present Kriasoft */ /* SPDX-License-Identifier: MIT */ /** * Error thrown when lock operations fail */ export class LockError extends Error { code; constructor(message, code) { super(message); this.code = code; this.name = "LockError"; } } /** * Standard error codes for lock operations */ export const LockErrorCodes = { ACQUISITION_FAILED: "ACQUISITION_FAILED", TIMEOUT: "TIMEOUT", ALREADY_LOCKED: "ALREADY_LOCKED", NOT_FOUND: "NOT_FOUND", }; /** * Creates a distributed lock function with automatic lock management * @param backend The lock backend implementation * @returns A function that provides both automatic and manual lock operations */ export function createLock(backend) { const withLock = async (fn, config) => { const lockResult = await backend.acquire(config); if (!lockResult.success) { throw new LockError(`Failed to acquire lock: ${lockResult.error}`, "ACQUISITION_FAILED"); } try { return await fn(); } finally { try { await backend.release(lockResult.lockId); } catch (error) { // Log release failure but don't throw to avoid masking the main execution result // In production, this should be logged to your observability system console.warn(`Failed to release lock "${config.key}" (${lockResult.lockId}): ${error instanceof Error ? error.message : error}. Lock will expire naturally after TTL.`); } } }; // Attach manual operations as properties withLock.acquire = (config) => backend.acquire(config); withLock.release = (lockId) => backend.release(lockId); withLock.extend = (lockId, ttl) => backend.extend(lockId, ttl); withLock.isLocked = (key) => backend.isLocked(key); return withLock; } /** * Utility function to generate a unique lock ID using crypto.randomUUID for better uniqueness */ export function generateLockId() { // Use crypto.randomUUID if available (Node.js 14.17+), fallback to timestamp + random if (typeof crypto !== "undefined" && crypto.randomUUID) { return crypto.randomUUID(); } // Fallback for older environments - use slice instead of deprecated substr return `${Date.now()}-${Math.random().toString(36).slice(2, 11)}`; } /** * Utility function to create a delay */ export function delay(ms) { return new Promise((resolve) => setTimeout(resolve, ms)); } /** * Default configuration values */ export const DEFAULT_CONFIG = { ttlMs: 30000, retryDelayMs: 100, maxRetries: 10, timeoutMs: 5000, }; /** * Utility function to merge lock configuration with defaults */ export function mergeLockConfig(config) { return { ...DEFAULT_CONFIG, ...config }; }