import { ClockError } from './clock'; export interface LockBackend { create(employee:string):Promise<{id:string}>; find(employee:string):Promise<{id:string;created:string}|null>; remove(id:string):Promise; } /** Record ID is the ownership token. Never release a lock by employee filter. */ export async function acquireEmployeeLock(backend:LockBackend,employee:string,now=Date.now()) { try{return (await backend.create(employee)).id;} catch(error) { if(![400,409].includes((error as {status:number}).status))throw error; const previous=await backend.find(employee); if(!previous)throw error; const age=now-Date.parse(previous.created); if(Number.isFinite(age) && age>120_000) { // Concurrent recoverers can only delete this exact expired record. The // unique employee lock and unique event predecessor also fence old writers. try{await backend.remove(previous.id);}catch(e){if((e as {status:number}).status!==404)throw e;} try{return (await backend.create(employee)).id;} catch(e){if(![400,409].includes((e as {status:number}).status))throw e;} } throw new ClockError('Another scan is being saved for this worker. Retry this confirmation; an interrupted scan lock clears after two minutes.',423); } }