refactor(git-sync): extract SpaceLockService from the orchestrator (PR #119 review, arch #2)

The per-space single-writer lock — Redis CAS leader lock (SET NX PX, DEL-CAS and
PEXPIRE-CAS Lua), the in-process mutex, the per-process instanceId and the
heartbeat — lived inline in GitSyncOrchestrator. Extract it into a dedicated
@Injectable() SpaceLockService exposing one narrow surface, withSpaceLock(spaceId,
fn), so the lock is the orchestrator's only Redis-lock touch-point and is testable
in isolation. The orchestrator now injects SpaceLockService and both consumers
(runOnce, ingestExternalPush) go through spaceLock.withSpaceLock — behavior
unchanged (same sentinel returns, same 503-on-lock-held contract). Orchestrator
drops 591→472 lines.

Adds space-lock.service.spec.ts asserting the lock SEMANTICS against a fake Redis
(the test-coverage warning from the review): the SET NX/PX args, the DEL-CAS and
PEXPIRE-CAS Lua + ARGV[1]=instanceId, plus the lock-held / in-progress / throw-
still-releases paths. The orchestrator spec is unchanged in count and stays green
(it now builds the real SpaceLockService over its mock Redis).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
claude code agent 227
2026-06-24 01:20:38 +03:00
parent 0318a148dc
commit 306d88c685
5 changed files with 344 additions and 125 deletions

View File

@@ -22,6 +22,7 @@ import {
runPush,
} from '@docmost/git-sync';
import { GitSyncOrchestrator } from './git-sync.orchestrator';
import { SpaceLockService } from './space-lock.service';
type AnyMock = jest.Mock;
@@ -118,12 +119,17 @@ function build(opts: BuildOptions = {}): Built {
const db = {};
// The REAL SpaceLockService, constructed against the mock redis above, so all
// existing lock assertions (lock-held, in-progress, leader lock, release CAS,
// heartbeat) still exercise the same `redis.set`/`redis.eval` mock unchanged.
const spaceLock = new SpaceLockService(redisService as any);
const orchestrator = new GitSyncOrchestrator(
env as any,
dataSource as any,
vaultRegistry as any,
scheduler as any,
redisService as any,
spaceLock as any,
db as any,
);