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.
50 lines (49 loc) • 2.31 kB
JavaScript
// SPDX-FileCopyrightText: 2025-present Kriasoft
// SPDX-License-Identifier: MIT
import { decorateAcquireResult } from "../common/disposable.js";
import { normalizeAndValidateKey } from "../common/validation.js";
import { createFirestoreConfig } from "./config.js";
import { createAcquireOperation } from "./operations/acquire.js";
import { createExtendOperation } from "./operations/extend.js";
import { createIsLockedOperation } from "./operations/is-locked.js";
import { createLookupOperation } from "./operations/lookup.js";
import { createReleaseOperation } from "./operations/release.js";
/**
* Creates Firestore-based distributed lock backend using transactions.
*
* IMPORTANT: Requires composite index on lockId field for release()/extend() performance.
* See firebase.json for index configuration.
*
* @param db - Firestore instance from @google-cloud/firestore
* @param options - Backend configuration (collection, ttl, tolerance)
* @returns LockBackend with client-side time authority
* @see docs/specs/firestore-backend.md
*/
export function createFirestoreBackend(db, options = {}) {
const config = createFirestoreConfig(options);
const locksCollection = db.collection(config.collection);
const fenceCounterCollection = db.collection(config.fenceCollection);
const capabilities = {
backend: "firestore",
supportsFencing: true,
timeAuthority: "client",
};
// Create base operations
const acquireCore = createAcquireOperation(db, locksCollection, fenceCounterCollection, config);
const releaseOp = createReleaseOperation(db, locksCollection, config);
const extendOp = createExtendOperation(db, locksCollection, config);
// Create backend object with disposal support
const backend = {
acquire: async (opts) => {
const normalizedKey = normalizeAndValidateKey(opts.key);
const result = await acquireCore(opts);
return decorateAcquireResult(backend, result, normalizedKey, config.onReleaseError, config.disposeTimeoutMs);
},
release: releaseOp,
extend: extendOp,
isLocked: createIsLockedOperation(db, locksCollection, config),
lookup: createLookupOperation(db, locksCollection, config),
capabilities,
};
return backend;
}