@@ -5,6 +5,19 @@ import * as crypto from 'crypto';
55import * as fsapi from 'fs-extra' ;
66import * as path from 'path' ;
77
8+ export interface AcquireFileLockOptions {
9+ readonly timeoutMs : number ;
10+ readonly retryIntervalMs : number ;
11+ }
12+
13+ export interface AcquiredFileLock {
14+ readonly release : ( ) => Promise < void > ;
15+ /** Keep the lock and make later acquisition attempts fail immediately. */
16+ readonly retain : ( ) => Promise < void > ;
17+ }
18+
19+ type LockState = 'held' | 'released' | 'retained' ;
20+
821/** Acquire an atomic lock released only explicitly; interrupted operations remain locked. */
922export async function acquireFileLock ( filePath : string , options : AcquireFileLockOptions ) : Promise < AcquiredFileLock > {
1023 const lockPath = `${ path . resolve ( filePath ) } .lock` ;
@@ -40,13 +53,13 @@ export async function acquireFileLock(filePath: string, options: AcquireFileLock
4053 try {
4154 await fsapi . writeFile ( retainedMarker , '' , { flag : 'wx' } ) ;
4255 } catch ( error ) {
43- if ( isAlreadyExistsError ( error ) ) {
56+ if ( hasErrorCode ( error , 'EEXIST' ) ) {
4457 return ;
4558 }
4659 try {
4760 await fsapi . rename ( ownerMarker , retainedMarker ) ;
4861 } catch ( renameError ) {
49- if ( ! isAlreadyExistsError ( renameError ) ) {
62+ if ( ! hasErrorCode ( renameError , 'EEXIST' ) ) {
5063 throw createLockError (
5164 'Failed to mark the lock as retained' ,
5265 'ERETAINFAILED' ,
@@ -64,7 +77,7 @@ export async function acquireFileLock(filePath: string, options: AcquireFileLock
6477 try {
6578 await fsapi . unlink ( ownerMarker ) ;
6679 } catch ( error ) {
67- if ( isFileNotFoundError ( error ) ) {
80+ if ( hasErrorCode ( error , 'ENOENT' ) ) {
6881 throw createLockError ( 'Lock ownership was compromised' , 'ECOMPROMISED' , lockPath ) ;
6982 }
7083 throw error ;
@@ -73,7 +86,7 @@ export async function acquireFileLock(filePath: string, options: AcquireFileLock
7386 } ,
7487 } ;
7588 } catch ( error ) {
76- if ( ! isAlreadyExistsError ( error ) ) {
89+ if ( ! hasErrorCode ( error , 'EEXIST' ) ) {
7790 throw error ;
7891 }
7992 if ( await isRetainedLock ( lockPath ) ) {
@@ -87,20 +100,12 @@ export async function acquireFileLock(filePath: string, options: AcquireFileLock
87100 }
88101}
89102
90- function isAlreadyExistsError ( error : unknown ) : boolean {
91- return hasErrorCode ( error , 'EEXIST' ) ;
92- }
93-
94- function isFileNotFoundError ( error : unknown ) : boolean {
95- return hasErrorCode ( error , 'ENOENT' ) ;
96- }
97-
98103async function isRetainedLock ( lockPath : string ) : Promise < boolean > {
99104 try {
100105 await fsapi . lstat ( path . join ( lockPath , 'retained' ) ) ;
101106 return true ;
102107 } catch ( error ) {
103- if ( isFileNotFoundError ( error ) ) {
108+ if ( hasErrorCode ( error , 'ENOENT' ) ) {
104109 return false ;
105110 }
106111 throw error ;
@@ -120,16 +125,3 @@ function createLockError(message: string, code: string, lockPath: string): NodeJ
120125async function delay ( milliseconds : number ) : Promise < void > {
121126 return new Promise ( ( resolve ) => setTimeout ( resolve , milliseconds ) ) ;
122127}
123-
124- export interface AcquireFileLockOptions {
125- readonly timeoutMs : number ;
126- readonly retryIntervalMs : number ;
127- }
128-
129- export interface AcquiredFileLock {
130- readonly release : ( ) => Promise < void > ;
131- /** Keep the lock and make later acquisition attempts fail immediately. */
132- readonly retain : ( ) => Promise < void > ;
133- }
134-
135- type LockState = 'held' | 'released' | 'retained' ;
0 commit comments