Initial Commit

This commit is contained in:
2026-05-27 23:47:53 +01:00
parent fbc70f96e6
commit 9b28db724f
18103 changed files with 1600554 additions and 0 deletions

View File

@@ -0,0 +1,63 @@
/**
* Redis-backed distributed lock provider using atomic `SET NX` operations.
*
* Implements the `LockProvider` interface with owner-based release semantics:
* a lock is only released if the requesting instance owns it, preventing
* accidental release of another process's lock after TTL expiry.
*
* @module redis-lock-provider
*/
import type { RedisClientAdapter } from "../store/adapter.types.js";
import type { LockProvider } from "../types/idempotency.types.js";
/** Configuration for `RedisLockProvider`. */
export interface RedisLockProviderConfig {
/** Redis client adapter. */
readonly client: RedisClientAdapter;
/** Key prefix for lock keys. Default: `'lock:'`. */
readonly prefix?: string;
/** Unique identifier for this lock owner (e.g. worker ID). */
readonly ownerId?: string;
}
/**
* Redis-backed distributed lock using `SET NX` with TTL.
*
* Owner-verified release ensures that only the process that acquired the
* lock can release it — safe for use in distributed multi-process deployments.
*/
export class RedisLockProvider implements LockProvider {
protected readonly client: RedisClientAdapter;
protected readonly prefix: string;
protected readonly ownerId: string;
constructor(config: RedisLockProviderConfig) {
this.client = config.client;
this.prefix = config.prefix ?? "lock:";
this.ownerId =
config.ownerId ??
`owner-${Date.now()}-${Math.random().toString(36).slice(2)}`;
}
/**
* Attempt to acquire the lock for `resource` with the given TTL.
* Returns `true` if the lock was acquired, `false` if already held.
*/
async acquire(resource: string, ttlMs: number): Promise<boolean> {
const key = `${this.prefix}${resource}`;
return this.client.setNx(key, this.ownerId, ttlMs);
}
/**
* Release the lock for `resource` if this instance owns it.
* No-ops if the lock is not held or was taken by another owner.
*/
async release(resource: string): Promise<void> {
const key = `${this.prefix}${resource}`;
const value = await this.client.get(key);
if (value === this.ownerId) {
await this.client.del(key);
}
}
}