TypeScript distributed lock library that prevents race conditions across services. Supports Redis and Firestore backends with automatic cleanup, fencing tokens, and bulletproof concurrency control.
# Firestore backend
npm install syncguard @google-cloud/firestore
# Redis backend
npm install syncguard ioredis
import { createLock } from "syncguard/firestore";
import { Firestore } from "@google-cloud/firestore";
const db = new Firestore();
const lock = createLock(db);
// Prevent duplicate payment processing
await lock(
async () => {
const payment = await getPayment(paymentId);
if (payment.status === "pending") {
await processPayment(payment);
await updatePaymentStatus(paymentId, "completed");
}
},
{ key: `payment:${paymentId}`, ttlMs: 60000 },
);
import { createLock } from "syncguard/redis";
import Redis from "ioredis";
const redis = new Redis();
const lock = createLock(redis);
await lock(
async () => {
// Your critical section
},
{ key: "resource:123" },
);
const backend = createRedisBackend(redis);
// Acquire lock manually
const result = await backend.acquire({
key: "batch:daily-report",
ttlMs: 300000, // 5 minutes
});
if (result.ok) {
try {
const { lockId, fence } = result; // Fencing token for stale lock protection
await generateDailyReport(fence);
// Extend lock for long-running tasks
const extended = await backend.extend({ lockId, ttlMs: 300000 });
if (!extended.ok) {
throw new Error("Failed to extend lock");
}
await sendReportEmail();
} finally {
await backend.release({ lockId: result.lockId });
}
} else {
console.log("Resource is locked by another process");
}
import { owns, getByKey } from "syncguard";
// Check if you still own the lock
const stillOwned = await owns(backend, lockId);
// Get lock info by resource key
const info = await getByKey(backend, "resource:123");
if (info) {
console.log(`Lock expires in ${info.expiresAtMs - Date.now()}ms`);
}
// Basic lock options
await lock(workFn, {
key: "resource:123", // Required: unique identifier
ttlMs: 30000, // Lock duration (default: 30s)
timeoutMs: 5000, // Max acquisition wait (default: 5s)
maxRetries: 10, // Retry attempts (default: 10)
});
// Firestore
const lock = createLock(db, {
collection: "app_locks", // Default: "locks"
fenceCollection: "app_fences", // Default: "fence_counters"
});
// Redis
const lock = createLock(redis, {
keyPrefix: "myapp", // Default: "syncguard"
});
::: warning Firestore Index Required
Firestore requires a single-field ascending index on the lockId
field in your locks collection. See Firestore setup docs for details.
:::
import { LockError } from "syncguard";
try {
await lock(
async () => {
// Critical section
},
{ key: "resource:123" },
);
} catch (error) {
if (error instanceof LockError) {
console.error(`Lock error [${error.code}]:`, error.message);
// Error codes: AcquisitionTimeout, ServiceUnavailable, NetworkTimeout, etc.
}
}
const processJob = async (jobId: string) => {
await lock(
async () => {
const job = await getJob(jobId);
if (job.status === "pending") {
await executeJob(job);
await markJobComplete(jobId);
}
},
{ key: `job:${jobId}`, ttlMs: 300000 },
);
};
const backend = createRedisBackend(redis);
const checkRateLimit = async (userId: string) => {
const result = await backend.acquire({
key: `rate:${userId}`,
ttlMs: 60000, // 1 minute window
});
if (!result.ok) {
throw new Error("Rate limit exceeded");
}
// Don't release - let it expire naturally
return performOperation(userId);
};
- π Bulletproof concurrency - Atomic operations prevent race conditions
- π‘οΈ Fencing tokens - Monotonic counters protect against stale writes
- π§Ή Automatic cleanup - TTL-based expiration, no manual cleanup needed
- π Backend flexibility - Redis (fast) or Firestore (serverless)
- π Smart retries - Exponential backoff with jitter handles contention
- π TypeScript-first - Full type safety with compile-time guarantees
- π Optional telemetry - Opt-in observability via decorator pattern
We welcome contributions! Here's how you can help:
- π Bug fixes - Include test cases
- π New backends - Follow specs/interface.md
- π Documentation - Examples, guides, troubleshooting
- π Spec reviews - Validate specs match implementation, propose improvements
- β Tests - Improve coverage
See CONTRIBUTING.md for detailed guidelines.
- Docs: Full documentation
- Specs: Technical specifications - Architecture decisions and backend requirements
- Discord: Join our community
- Issues: GitHub Issues
This project is licensed under the MIT License. See the LICENSE file for details.