UNPKG

@dudousxd/nestjs-telescope

Version:

Laravel Telescope-style observability console for NestJS — core: watchers, recorder, correlation, SQLite store, headless API.

92 lines 4.09 kB
// packages/core/src/prune/prune-lock.ts // // The cross-process prune lock seam. // // The pruner's in-flight guard bounds ONE PROCESS to one prune cycle. A // deployment of N replicas sharing one store still runs N cycles at once, all // deleting the same rows. That is not incorrect — the deletes are idempotent and // one of them wins each row — it is pure waste, and on a store that is already // behind the waste is what keeps it behind. // // So this seam is deliberately SMALL and deliberately ADVISORY. It is a named // lock with a lease: acquire, release, and a way to say "somebody else has it". // There is no fencing token, no renewal, no quorum, and none of that is an // oversight — a lost lease costs one duplicated DELETE, never a wrong result, so // paying for consensus here would be paying for nothing. // // Telescope must not depend on a job engine, a Redis client, or anything else // the host may not have: it mounts into any NestJS app. Hence the seam plus a // default implementation that rides the database Telescope already has (see // `StorageLeasePruneLock`), and an escape hatch for a host that already owns a // better primitive (`prune.lock`). /** * The lock name the pruner asks for. Deliberately a single fleet-wide constant: * every replica pruning the SAME store is exactly the set that should contend, * and two apps sharing one Telescope store share the entries table too, so they * should share the lock as well. */ export const PRUNE_LOCK_KEY = 'telescope:prune'; /** Builds the success arm of a {@link TelescopePruneLockResult}. */ export function pruneLockAcquired(lease) { return { acquired: true, lease }; } /** Builds the "somebody else has it" arm — the healthy, expected refusal. */ export function pruneLockHeld(detail) { return detail === undefined ? { acquired: false, reason: 'held' } : { acquired: false, reason: 'held', detail }; } /** Builds the "the lock mechanism itself failed" arm — the pruner fails OPEN on this. */ export function pruneLockUnavailable(detail) { return detail === undefined ? { acquired: false, reason: 'unavailable' } : { acquired: false, reason: 'unavailable', detail }; } /** * The DEFAULT prune lock: a lease row in the store Telescope is already using. * * Telescope always has a database — that is where the entries live — so a lease * with an owner and an expiry costs no new dependency, no new deployment * concern, and works on every provider that implements the lease SPI. A holder * that dies never releases; the expiry is what reclaims it, which is why the SPI * takes a TTL rather than a plain "locked" flag. * * Used automatically when the configured provider {@link isLeaseCapableStorage} * and the host supplied no `prune.lock` of its own. */ export class StorageLeasePruneLock { storage; constructor(storage) { this.storage = storage; } async acquire(request) { const { key, owner, ttlMs } = request; const acquiredAtMs = Date.now(); let granted; try { granted = await this.storage.tryAcquireLease(key, owner, ttlMs, acquiredAtMs); } catch (error) { // A store that cannot answer must not stop retention: report the MECHANISM // as broken (not "held") so the pruner fails open and prunes unlocked. return pruneLockUnavailable(error instanceof Error ? error.message : String(error)); } if (!granted) return pruneLockHeld(); return pruneLockAcquired({ key, owner, expiresAtMs: acquiredAtMs + ttlMs, release: async () => { try { await this.storage.releaseLease(key, owner); } catch { // Never throw out of release: the TTL already guarantees the lease is // reclaimed, so a failed release delays the next cycle at worst. } }, }); } } //# sourceMappingURL=prune-lock.js.map