### Install proper-lockfile Source: https://github.com/moxystudio/node-proper-lockfile/blob/master/_autodocs/README.md Install the package via npm to begin using the library in your Node.js project. ```bash npm install proper-lockfile ``` -------------------------------- ### Configure lockfile options Source: https://github.com/moxystudio/node-proper-lockfile/blob/master/_autodocs/00-START-HERE.md Example of applying common configuration options such as stale timeouts, update intervals, and retry logic. ```javascript const release = await lockfile.lock('file.txt', { stale: 10000, // Lock timeout (ms) update: 5000, // mtime refresh interval (ms) retries: 3, // Retry attempts realpath: true, // Resolve symlinks lockfilePath: 'file.txt.lock' // Custom lock path }); ``` -------------------------------- ### Configure Aggressive Retry Strategy Source: https://github.com/moxystudio/node-proper-lockfile/blob/master/_autodocs/configuration.md Example configuration for rapid lock attempts with short timeouts. ```javascript { retries: 3, minTimeout: 50, maxTimeout: 200 } ``` -------------------------------- ### Handling ELOCKED error Source: https://github.com/moxystudio/node-proper-lockfile/blob/master/_autodocs/errors.md Example of implementing retry logic when a file is already locked. ```javascript try { const release = await lockfile.lock('file.txt'); } catch (err) { if (err.code === 'ELOCKED') { console.log('File is already locked by another process'); // Implement retry logic with backoff setTimeout(() => retryLock(), 1000); } } ``` -------------------------------- ### Configure Patient Retry Strategy Source: https://github.com/moxystudio/node-proper-lockfile/blob/master/_autodocs/configuration.md Example configuration for long-held locks using higher retry counts and randomized timeouts. ```javascript { retries: 10, minTimeout: 500, maxTimeout: 10000, randomize: true } ``` -------------------------------- ### In a synchronous initialization routine Source: https://github.com/moxystudio/node-proper-lockfile/blob/master/_autodocs/api-reference/check-sync.md Checks for an existing lock file during application startup to prevent multiple instances from running. ```javascript const lockfile = require('proper-lockfile'); const fs = require('fs'); function initializeApp() { // Check if another instance is running if (lockfile.checkSync('.app-lock', { realpath: false })) { console.error('Another instance is already running'); process.exit(1); } // Acquire our lock lockfile.lockSync('.app-lock', { realpath: false }); // Continue initialization console.log('App initialized'); } ``` -------------------------------- ### Application Initialization with PID Lock Source: https://github.com/moxystudio/node-proper-lockfile/blob/master/_autodocs/usage-patterns.md Ensures only one instance of an application runs by locking a PID file during startup. Exits the process if the lock cannot be acquired. ```javascript const lockfile = require('proper-lockfile'); const pidFile = '.app.pid'; function initializeApp() { // Ensure only one instance runs try { const pidContent = process.pid.toString(); lockfile.lockSync(pidFile, { realpath: false }); fs.writeFileSync(pidFile, pidContent); console.log('App initialized, lock acquired'); } catch (err) { if (err.code === 'ELOCKED') { console.error('Another instance is already running'); process.exit(1); } throw err; } } // Call at app startup initializeApp(); ``` -------------------------------- ### Configure testing and development environments Source: https://github.com/moxystudio/node-proper-lockfile/blob/master/_autodocs/configuration.md Optimized for fast feedback loops in test environments with aggressive timeouts. ```javascript { stale: 2000, // Quick detection update: 1000, // Fast updates retries: { retries: 1, minTimeout: 10, maxTimeout: 100 } } ``` -------------------------------- ### With custom filesystem Source: https://github.com/moxystudio/node-proper-lockfile/blob/master/_autodocs/api-reference/check-sync.md Uses a custom filesystem module for the check operation. ```javascript const customFs = require('fs'); const lockfile = require('proper-lockfile'); const isLocked = lockfile.checkSync('file.txt', { fs: customFs }); ``` -------------------------------- ### check(file, [options]) / checkSync(file, [options]) Source: https://github.com/moxystudio/node-proper-lockfile/blob/master/_autodocs/configuration.md Configuration options for the check and checkSync functions. ```APIDOC ## check(file, [options]) / checkSync(file, [options]) ### Description Options for controlling lock status detection. ### Parameters #### Options - **stale** (number) - Optional - Duration in milliseconds for considering a lock stale. Min: 5000, Default: 10000. - **realpath** (boolean) - Optional - If true, resolve symlinks. Default: true. - **fs** (object) - Optional - Custom filesystem module. Default: graceful-fs. - **lockfilePath** (string) - Optional - Custom path for the lock directory. Default: {file}.lock ``` -------------------------------- ### Implementing basic lock with error handling Source: https://github.com/moxystudio/node-proper-lockfile/blob/master/_autodocs/README.md Demonstrates acquiring a lock with retry configuration and ensuring release in a finally block. ```javascript let release; try { release = await lockfile.lock('resource.txt', { retries: { retries: 3, minTimeout: 100 } }); // Critical operations } catch (err) { if (err.code === 'ELOCKED') { console.log('Resource locked by another process'); } } finally { if (release) await release(); } ``` -------------------------------- ### Search documentation using grep Source: https://github.com/moxystudio/node-proper-lockfile/blob/master/_autodocs/00-START-HERE.md Use these commands to locate specific terms, functions, or error codes within the documentation directory. ```bash grep -r "your-search-term" . ``` ```bash grep -r "lockSync" . ``` ```bash grep -r "ELOCKED" . ``` -------------------------------- ### Display Documentation Directory Structure Source: https://github.com/moxystudio/node-proper-lockfile/blob/master/_autodocs/00-START-HERE.md A visual representation of the project's documentation file hierarchy. ```text ├─ README.md ........................... Overview & quick start ├─ INDEX.md ............................ Master index & reading paths ├─ MANIFEST.md ......................... What was generated (this summary) │ ├─ api-reference/ │ ├─ lock.md .......................... Main async function │ ├─ lockSync.md ...................... Sync version │ ├─ unlock.md ........................ Manual release │ ├─ unlockSync.md .................... Sync release │ ├─ check.md ......................... Check lock status │ ├─ checkSync.md ..................... Sync check │ └─ mtime-precision.md ............... Internal precision detection │ ├─ configuration.md .................... All options explained ├─ errors.md ........................... Error codes & recovery ├─ architecture.md ..................... Design & internals ├─ lock-lifecycle.md ................... State machine & lifecycle └─ usage-patterns.md ................... Real-world examples ``` -------------------------------- ### Mocking Filesystem for Testing Source: https://github.com/moxystudio/node-proper-lockfile/blob/master/_autodocs/usage-patterns.md Implements a custom filesystem class to simulate lockfile operations in a test environment without touching the disk. ```javascript const lockfile = require('proper-lockfile'); class MockFS { constructor() { this.dirs = new Set(); this.stats = new Map(); } mkdir(path, callback) { if (this.dirs.has(path)) { const err = new Error('EEXIST'); err.code = 'EEXIST'; return process.nextTick(() => callback(err)); } this.dirs.add(path); this.stats.set(path, { mtime: new Date() }); process.nextTick(() => callback(null)); } rmdir(path, callback) { this.dirs.delete(path); this.stats.delete(path); process.nextTick(() => callback(null)); } stat(path, callback) { const stat = this.stats.get(path); if (!stat) { const err = new Error('ENOENT'); err.code = 'ENOENT'; return process.nextTick(() => callback(err)); } process.nextTick(() => callback(null, stat)); } utimes(path, atime, mtime, callback) { const stat = this.stats.get(path); if (stat) stat.mtime = mtime; process.nextTick(() => callback(null)); } realpath(path, callback) { process.nextTick(() => callback(null, path)); } } // Usage in tests const mockFS = new MockFS(); const release = await lockfile.lock('test-file', { fs: mockFS }); await release(); ``` -------------------------------- ### Option Normalization Pipeline Source: https://github.com/moxystudio/node-proper-lockfile/blob/master/_autodocs/lock-lifecycle.md Visual representation of the transformation process from raw user options to normalized internal configuration. ```text Raw Options ↓ 1. Shallow merge with defaults: - stale: 10000 - update: null (computed as stale/2) - realpath: true - retries: 0 - fs: graceful-fs - onCompromised: (err) => { throw err } ↓ 2. Retry normalization: - If retries is number: {retries} - If retries is object: use as-is ↓ 3. Timeout validation: - stale = Math.max(stale || 0, 2000) [min 5000, but allows override] - update = stale / 2 if null - update = Math.max(Math.min(update, stale / 2), 1000) ↓ 4. Retry operation created with: - retry.operation(options.retries) ↓ Normalized Options Ready for Use ``` -------------------------------- ### Basic lock and release in JavaScript Source: https://github.com/moxystudio/node-proper-lockfile/blob/master/_autodocs/api-reference/lock.md Demonstrates the standard pattern for acquiring a lock, performing operations, and ensuring the lock is released in a finally block. ```javascript const lockfile = require('proper-lockfile'); let release; try { release = await lockfile.lock('file.txt'); // File is now locked, perform critical operations console.log('Lock acquired'); } catch (err) { // Could not acquire lock console.error('Failed to lock:', err.message); } finally { if (release) { try { await release(); console.log('Lock released'); } catch (err) { console.error('Failed to release lock:', err.message); } } } ``` -------------------------------- ### Acquire a basic synchronous lock Source: https://github.com/moxystudio/node-proper-lockfile/blob/master/_autodocs/api-reference/lock-sync.md Demonstrates the standard pattern for acquiring a lock synchronously and ensuring the asynchronous release function is called in a finally block. ```javascript const lockfile = require('proper-lockfile'); let release; try { release = lockfile.lockSync('file.txt'); console.log('Lock acquired synchronously'); // Perform synchronous critical operations performSyncWork(); } catch (err) { console.error('Failed to acquire lock:', err.message); } finally { if (release) { // Release is still async, must be awaited release().catch(err => { console.error('Failed to release lock:', err.message); }); } } ``` -------------------------------- ### Importing and accessing proper-lockfile functions Source: https://github.com/moxystudio/node-proper-lockfile/blob/master/_autodocs/README.md Shows the available public functions exported by the module and the default export usage. ```javascript const lockfile = require('proper-lockfile'); // These are all available: lockfile.lock() lockfile.lockSync() lockfile.unlock() lockfile.unlockSync() lockfile.check() lockfile.checkSync() // Or as default export: const lock = require('proper-lockfile'); // Equivalent to lockfile.lock ``` -------------------------------- ### unlock(file, [options]) / unlockSync(file, [options]) Source: https://github.com/moxystudio/node-proper-lockfile/blob/master/_autodocs/configuration.md Configuration options for the unlock and unlockSync functions. ```APIDOC ## unlock(file, [options]) / unlockSync(file, [options]) ### Description Options for configuring the unlock process. ### Parameters #### Options - **realpath** (boolean) - Optional - If true, resolve symlinks to match lock acquisition. Default: true. - **fs** (object) - Optional - Custom filesystem module. Default: graceful-fs. - **lockfilePath** (string) - Optional - Custom path for the lock directory. Default: {file}.lock ``` -------------------------------- ### Checking across processes Source: https://github.com/moxystudio/node-proper-lockfile/blob/master/_autodocs/api-reference/check.md Demonstrates how one process can verify a lock held by another process. ```javascript const lockfile = require('proper-lockfile'); // Process A acquires a lock await lockfile.lock('shared.dat', { stale: 15000 }); // Process B can check the lock status without acquiring it const isLocked = await lockfile.check('shared.dat', { stale: 15000 }); console.log(isLocked); // true ``` -------------------------------- ### Using custom filesystem implementation in JavaScript Source: https://github.com/moxystudio/node-proper-lockfile/blob/master/_autodocs/api-reference/lock.md Injects a custom filesystem object to be used for lock operations. ```javascript const customFs = require('fs'); // Or any fs-compatible object const release = await lockfile.lock('file.txt', { fs: customFs }); ``` -------------------------------- ### With custom lockfile path Source: https://github.com/moxystudio/node-proper-lockfile/blob/master/_autodocs/api-reference/check.md Specifies a custom path for the lockfile, which must match the path used during acquisition. ```javascript const lockfile = require('proper-lockfile'); const isLocked = await lockfile.check('my-dir', { lockfilePath: 'my-dir/.lock' }); ``` -------------------------------- ### Module Structure Source: https://github.com/moxystudio/node-proper-lockfile/blob/master/_autodocs/architecture.md The directory layout of the proper-lockfile project. ```text proper-lockfile/ ├── index.js (public API wrapper) ├── lib/ │ ├── lockfile.js (core lock/unlock/check logic) │ ├── adapter.js (sync/async adapter) │ └── mtime-precision.js (filesystem precision detection) └── package.json ``` -------------------------------- ### Unlock with custom filesystem Source: https://github.com/moxystudio/node-proper-lockfile/blob/master/_autodocs/api-reference/unlock-sync.md Utilizes a custom filesystem module that must be consistent with the one used during the lock acquisition. ```javascript const customFs = require('fs'); const lockfile = require('proper-lockfile'); lockfile.lockSync('file.txt', { fs: customFs }); try { // Synchronous work } finally { lockfile.unlockSync('file.txt', { fs: customFs }); } ``` -------------------------------- ### Configure reliable production systems Source: https://github.com/moxystudio/node-proper-lockfile/blob/master/_autodocs/configuration.md Designed for critical sections on network filesystems, including error handling and metrics reporting via onCompromised. ```javascript { stale: 30000, // Generous timeout for slow networks update: 10000, // Frequent updates retries: { retries: 5, minTimeout: 500, maxTimeout: 5000, randomize: true // Avoid thundering herd }, onCompromised: (err) => { logger.error('Lock compromised:', err); metrics.increment('lock.compromised'); process.exit(1); } } ``` -------------------------------- ### Visualize Documentation Relationships Source: https://github.com/moxystudio/node-proper-lockfile/blob/master/_autodocs/INDEX.md A tree diagram illustrating the hierarchy and cross-references between documentation files in the project. ```text README.md (Entry Point) ├── Quick Reference ├── Links to all other docs └── Common Patterns api-reference/ (API Details) ├── lock.md ├── lockSync.md ├── unlock.md ├── unlockSync.md ├── check.md ├── checkSync.md └── mtime-precision.md configuration.md ◄─── Used by all API refs errors.md ◄─────────── Referenced in API refs & usage-patterns architecture.md ◄───── Deep dive on internals lock-lifecycle.md ◄─── State machine & recovery usage-patterns.md ◄─── Examples using API refs (Cross-references throughout) ``` -------------------------------- ### Configure high-traffic shared resources Source: https://github.com/moxystudio/node-proper-lockfile/blob/master/_autodocs/configuration.md Use these settings for resources accessed by many processes to ensure faster stale detection and frequent updates. ```javascript { stale: 5000, // Detect stale locks faster update: 2000, // Update more frequently retries: { retries: 3, minTimeout: 100, maxTimeout: 500 } } ``` -------------------------------- ### Lock Acquisition Phase Logic Source: https://github.com/moxystudio/node-proper-lockfile/blob/master/_autodocs/architecture.md Describes the step-by-step process for acquiring a lock, including retry strategies and atomic directory creation. ```text 1. Parse options and apply defaults 2. Resolve file path (optionally via realpath) 3. Create retry operation with configured retry strategy 4. On each retry attempt: a. Try mkdir(lockfilePath) — atomic lock creation b. If succeeds → go to acquisition phase c. If EEXIST → check if existing lock is stale d. If ENOENT (lockfile deleted) → recursively retry e. If stale → remove it and retry 5. Probe mtime precision of filesystem 6. Register lock in locks registry 7. Schedule first mtime update 8. Return release function ``` -------------------------------- ### Manual unlock with matching options Source: https://github.com/moxystudio/node-proper-lockfile/blob/master/_autodocs/api-reference/unlock.md When unlocking manually, ensure that options like realpath and lockfilePath match those used during the lock acquisition. ```javascript const lockfile = require('proper-lockfile'); await lockfile.lock('data.json', { stale: 30000, realpath: false, lockfilePath: 'data.json.custom.lock' }); try { // Later... } finally { // Must use the same options when unlocking await lockfile.unlock('data.json', { realpath: false, lockfilePath: 'data.json.custom.lock' }); } ``` -------------------------------- ### Synchronous Configuration Read Source: https://github.com/moxystudio/node-proper-lockfile/blob/master/_autodocs/usage-patterns.md Acquires a lock synchronously to read a configuration file safely. Note that the release function returned by lockSync remains asynchronous. ```javascript const lockfile = require('proper-lockfile'); function readConfigSync(configPath) { let release; try { // Acquire lock synchronously release = lockfile.lockSync(configPath, { realpath: false }); // Read synchronously const config = JSON.parse(fs.readFileSync(configPath, 'utf8')); return config; } finally { if (release) { // Release is still async release().catch(err => console.error('Unlock failed:', err)); } } } ``` -------------------------------- ### lockSync(file, options) Source: https://github.com/moxystudio/node-proper-lockfile/blob/master/_autodocs/api-reference/lock-sync.md Acquires a lock on a file synchronously and returns a release function. ```APIDOC ## lockSync(file, options) ### Description Synchronous version of lock. Acquires a lock on a file and returns a release function. The lock acquisition is blocking, while the returned release function is asynchronous. ### Signature `lockSync(file: string, options?: LockOptions): ReleaseFunction` ### Parameters - **file** (string) - Required - Path to the file to lock. Must exist if `realpath` is `true`. - **options** (LockOptions) - Optional - Configuration object. #### LockOptions - **stale** (number) - Optional - Time in ms after which the lock is considered stale (default: 10000, min: 5000). - **update** (number) - Optional - Interval in ms for updating the lockfile's mtime (default: stale/2, min: 1000). - **retries** (number | RetryOptions) - Optional - Not allowed in sync mode; throws ESYNC. - **realpath** (boolean) - Optional - Resolve symlinks using realpath (default: true). - **fs** (object) - Optional - Custom filesystem module. - **onCompromised** (function) - Optional - Callback invoked when the lock becomes compromised. - **lockfilePath** (string) - Optional - Custom path for the lockfile. ### Return Type `ReleaseFunction` (Returns a promise that resolves when the lock has been released). ### Throws - **ELOCKED**: File is already locked and the lock is not stale. - **ENOENT**: File does not exist or directory for lockfile doesn't exist. - **ESYNC**: Retries are specified in the options. - **ENOTACQUIRED**: Release function called but lock was never acquired. - **ERELEASED**: Lock already released. - **ECOMPROMISED**: Lock was compromised. ``` -------------------------------- ### lock(file, [options]) Source: https://github.com/moxystudio/node-proper-lockfile/blob/master/README.md Tries to acquire a lock on a file. Returns a promise that resolves with a release function if successful, or rejects on error. ```APIDOC ## .lock(file, [options]) ### Description Tries to acquire a lock on `file`. If successful, returns a `release` function that should be called to unlock the file. ### Parameters - **file** (string) - Required - The path to the file to lock. - **options** (object) - Optional - Configuration object including `stale`, `update`, `retries`, `realpath`, `fs`, `onCompromised`, and `lockfilePath`. ``` -------------------------------- ### Use lockSync within an async function Source: https://github.com/moxystudio/node-proper-lockfile/blob/master/_autodocs/api-reference/lock-sync.md Shows how to integrate synchronous lock acquisition into an asynchronous workflow, ensuring the release function is awaited. ```javascript async function processFile() { let release; try { // Acquire lock synchronously release = lockfile.lockSync('data.txt', { stale: 5000 }); // Quick synchronous operation const data = readFileSync('data.txt'); processData(data); writeFileSync('data.txt', modifiedData); } catch (err) { console.error('Lock acquisition failed:', err.message); } finally { if (release) { // Release asynchronously await release(); } } } ``` -------------------------------- ### Implement robust lock acquisition and error handling Source: https://github.com/moxystudio/node-proper-lockfile/blob/master/_autodocs/errors.md Uses the async API with retry logic, stale timeouts, and a compromised handler to safely manage file locks. ```javascript const lockfile = require('proper-lockfile'); async function safelyAccessResource(file) { let release; try { // Acquire lock with resilience release = await lockfile.lock(file, { stale: 30000, retries: { retries: 5, minTimeout: 100, maxTimeout: 1000 }, onCompromised: (err) => { logger.error('Lock compromised', { error: err, file }); process.exit(1); } }); // Critical section performCriticalOperation(); } catch (err) { if (err.code === 'ELOCKED') { logger.warn('Resource locked, will retry externally', { file }); throw new RetryableError(err); } else if (err.code === 'ENOENT') { logger.error('File not found', { file }); throw new NotFoundError(err); } else { logger.error('Lock error', { error: err, code: err.code }); throw err; } } finally { if (release) { try { await release(); } catch (err) { logger.error('Failed to release lock', { error: err, file }); } } } } ``` -------------------------------- ### Unlock procedure steps Source: https://github.com/moxystudio/node-proper-lockfile/blob/master/_autodocs/lock-lifecycle.md The unlock process resolves the path, clears update timeouts, marks the lock as released, and removes the lockfile from the filesystem. ```javascript unlock(file, options, callback): 1. Resolve path 2. Find lock in registry: if not found: → callback(Error('ENOTACQUIRED')) else: → continue 3. Stop updates: clearTimeout(lock.updateTimeout) 4. Mark as released: lock.released = true delete locks[file] 5. Remove lockfile: fs.rmdir(lockfilePath) { error if code !== 'ENOENT': → callback(error) else: → callback(null) } ``` -------------------------------- ### lock(file: string, options?: LockOptions) Source: https://github.com/moxystudio/node-proper-lockfile/blob/master/_autodocs/api-reference/lock.md Acquires a lock on a file and returns a promise that resolves to a release function. ```APIDOC ## lock(file: string, options?: LockOptions) ### Description Acquires a lock on a file and returns a release function to unlock it later. ### Parameters #### Path Parameters - **file** (string) - Required - Path to the file to lock. Must exist if `realpath` is `true`. #### Options (LockOptions) - **stale** (number) - Optional - Time in milliseconds after which the lock is considered stale. Minimum value is 5000. Default: 10000. - **update** (number) - Optional - Interval in milliseconds for updating the lockfile's mtime. Default: stale/2. - **retries** (number | RetryOptions) - Optional - Number of retries or a retry options object. Default: 0. - **realpath** (boolean) - Optional - Resolve symlinks using realpath. Default: true. - **fs** (object) - Optional - Custom filesystem module. Default: graceful-fs. - **onCompromised** (function) - Optional - Callback invoked when the lock becomes compromised. - **lockfilePath** (string) - Optional - Custom path for the lockfile. ``` -------------------------------- ### Atomic Lock Creation Phase Source: https://github.com/moxystudio/node-proper-lockfile/blob/master/_autodocs/lock-lifecycle.md Logic for creating the lock directory and handling existing lock files or stale states. ```text acquireLock(file, options, callback): lockfilePath = getLockFile(file, options) fs.mkdir(lockfilePath) { success: → mtimePrecision.probe() EEXIST: → fs.stat(lockfilePath) → isLockStale(stat, options)? true: remove and retry false: error ELOCKED other error: → error (EIO, EACCES, etc.) } ``` -------------------------------- ### Memory Management for Lock Entries Source: https://github.com/moxystudio/node-proper-lockfile/blob/master/_autodocs/lock-lifecycle.md Illustrates the lifecycle of entries within the global locks object during acquisition, release, and compromise. ```javascript // Added when lock acquired locks[canonicalPath] = { ... } // Removed when: // 1. Released successfully delete locks[canonicalPath]; // 2. Lock marked as compromised if (locks[file] === lock) { delete locks[file]; } // 3. Process exit (signal handler) // Locks are removed but registry not cleared ``` -------------------------------- ### Basic Lock and Release with Async/Await Source: https://github.com/moxystudio/node-proper-lockfile/blob/master/_autodocs/usage-patterns.md Uses a try-finally block to ensure the lock is released regardless of whether the critical section succeeds or fails. ```javascript const lockfile = require('proper-lockfile'); async function updateSharedResource() { let release; try { release = await lockfile.lock('resource.txt'); // Critical section — file is now locked const data = JSON.parse(fs.readFileSync('resource.txt', 'utf8')); data.lastUpdated = Date.now(); fs.writeFileSync('resource.txt', JSON.stringify(data)); } catch (err) { console.error('Failed to access resource:', err.message); throw err; } finally { // Always release if (release) await release(); } } ``` -------------------------------- ### lock(file, [options]) Source: https://github.com/moxystudio/node-proper-lockfile/blob/master/_autodocs/api-reference/lock.md Acquires a lock on the specified file or directory. Returns a promise that resolves to a release function. ```APIDOC ## lock(file, [options]) ### Description Attempts to acquire a lock on the specified file or directory. Returns a promise that resolves to an async release function. ### Parameters - **file** (string) - Required - The path to the file or directory to lock. - **options** (object) - Optional - Configuration object: - **retries** (object) - Optional - Retry configuration (retries, minTimeout, maxTimeout). - **stale** (number) - Optional - Time in milliseconds after which a lock is considered stale. - **update** (number) - Optional - Interval in milliseconds to update the lock mtime. - **onCompromised** (function) - Optional - Callback triggered if the lock is compromised. - **lockfilePath** (string) - Optional - Custom path for the lockfile. - **fs** (object) - Optional - Custom filesystem implementation. ### Returns - **Promise** - Resolves to a function that releases the lock when called. ### Errors - **ELOCKED**: File is already locked and not stale. - **ENOENT**: File or directory does not exist. - **ESYNC**: Retries specified in async mode. - **ENOTACQUIRED**: Release called on a lock not held by this process. - **ERELEASED**: Lock already released. - **ECOMPROMISED**: Lock mtime could not be updated. ``` -------------------------------- ### probe(file, fs, callback) Source: https://github.com/moxystudio/node-proper-lockfile/blob/master/_autodocs/api-reference/mtime-precision.md Detects filesystem mtime precision by setting and measuring modification time on a specific file. ```APIDOC ## probe(file, fs, callback) ### Description Detects filesystem mtime precision by setting and measuring modification time. It checks for cached precision values or performs a probe by writing to the file system. ### Parameters - **file** (string) - Required - Path to the lockfile being probed. - **fs** (object) - Required - Filesystem module with `stat`, `utimes` methods. - **callback** (function) - Required - Called with `(err, mtime, precision)` where precision is 's' or 'ms'. ### Example ```javascript const fs = require('graceful-fs'); const mtimePrecision = require('./lib/mtime-precision'); mtimePrecision.probe('/path/to/file.lock', fs, (err, mtime, precision) => { if (err) { console.error('Failed to probe precision:', err); } else { console.log('Precision:', precision); // 's' or 'ms' console.log('Current mtime:', mtime); } }); ``` ``` -------------------------------- ### Unlock with custom lockfile path Source: https://github.com/moxystudio/node-proper-lockfile/blob/master/_autodocs/api-reference/unlock-sync.md Uses a specific lockfile path that must match the path provided during the lock acquisition. ```javascript const lockfile = require('proper-lockfile'); lockfile.lockSync('my-dir', { lockfilePath: 'my-dir/.lock' }); try { // Work... } finally { lockfile.unlockSync('my-dir', { lockfilePath: 'my-dir/.lock' }); } ``` -------------------------------- ### check(file, options?) Source: https://github.com/moxystudio/node-proper-lockfile/blob/master/_autodocs/README.md Checks if a file is currently locked. ```APIDOC ## check(file, options?) ### Description Check if a file is locked. Returns a promise that resolves to a boolean. ### Signature `check(file: string, options?: object): Promise` ``` -------------------------------- ### unlockSync(file: string, options?: UnlockOptions): void Source: https://github.com/moxystudio/node-proper-lockfile/blob/master/_autodocs/api-reference/unlock-sync.md Releases a previously acquired lock on a file synchronously. ```APIDOC ## unlockSync(file: string, options?: UnlockOptions): void ### Description Releases a previously acquired lock on a file synchronously. This function must be called with the same parameters used during the lock acquisition. ### Parameters - **file** (string) - Required - Path to the file to unlock. Must match the path used to acquire the lock. - **options** (UnlockOptions) - Optional - Configuration object for the unlock operation. #### UnlockOptions - **realpath** (boolean) - Optional - Resolve symlinks using realpath. Default: true. - **fs** (object) - Optional - Custom filesystem module to use. Default: graceful-fs. - **lockfilePath** (string) - Optional - Custom path for the lockfile. Default: {file}.lock. ### Return Type void (Returns nothing. Throws if the operation fails.) ### Throws - **ENOTACQUIRED**: The lock is not acquired by this process. - **ENOENT**: File does not exist or filesystem error during removal. - **EIO**: I/O error when removing the lockfile. ``` -------------------------------- ### Path Resolution Phase Source: https://github.com/moxystudio/node-proper-lockfile/blob/master/_autodocs/lock-lifecycle.md Logic for determining the canonical or absolute path of the target file based on the realpath option. ```text resolveCanonicalPath(file, options, callback): if options.realpath === true: fs.realpath(file) → canonical path else: path.resolve(file) → absolute path ``` -------------------------------- ### Handling ENOENT Errors Source: https://github.com/moxystudio/node-proper-lockfile/blob/master/_autodocs/errors.md Handle ENOENT by creating the missing file or disabling realpath resolution. ```javascript try { await lockfile.lock('file.txt'); } catch (err) { if (err.code === 'ENOENT') { // File doesn't exist, create it first await fs.promises.writeFile('file.txt', ''); // Then retry const release = await lockfile.lock('file.txt'); } } // Or use realpath: false if file doesn't need to exist const release = await lockfile.lock('file.txt', { realpath: false }); ``` -------------------------------- ### Handle Lock Release Source: https://github.com/moxystudio/node-proper-lockfile/blob/master/_autodocs/lock-lifecycle.md Describes the state changes and registry cleanup performed when release() is invoked. ```javascript // Immediately marked as released { // ... released: true, // Flag set updateTimeout: null, // Timer cancelled } // After rmdir succeeds delete locks[canonicalPath]; // Removed from registry ``` -------------------------------- ### Implement comprehensive error handling Source: https://github.com/moxystudio/node-proper-lockfile/blob/master/_autodocs/usage-patterns.md Handle specific lockfile error codes and define an onCompromised callback to manage critical lock failures. ```javascript const lockfile = require('proper-lockfile'); async function safeAccess(file, operation) { let release; try { release = await lockfile.lock(file, { stale: 15000, retries: { retries: 3, minTimeout: 200 }, onCompromised: (err) => { // Log and exit on compromise logger.fatal('Lock compromised', { file, error: err }); process.exit(1); } }); return await operation(); } catch (err) { switch (err.code) { case 'ELOCKED': logger.warn('Resource locked', { file }); throw new Error(`${file} is in use by another process`); case 'ENOENT': logger.error('File not found', { file }); throw new Error(`${file} does not exist`); case 'EACCES': logger.error('Permission denied', { file }); throw new Error(`No permission to lock ${file}`); default: logger.error('Lock error', { file, code: err.code, error: err }); throw err; } } finally { if (release) { try { await release(); } catch (releaseErr) { logger.error('Failed to release lock', { file, error: releaseErr }); } } } } ``` -------------------------------- ### Configure lock options Source: https://github.com/moxystudio/node-proper-lockfile/blob/master/_autodocs/README.md Defines the configuration object for the lock method, including timeout, retry, and filesystem settings. ```javascript const release = await lockfile.lock('file.txt', { stale: 10000, // Lock stale timeout (ms), min 5000 update: 5000, // mtime update interval (ms) retries: 0, // Retry attempts on lock conflict realpath: true, // Resolve symlinks fs: gracefulFs, // Custom fs module lockfilePath: 'file.txt.lock', // Custom lock path onCompromised: (err) => { throw err; } // Compromise handler }); ``` -------------------------------- ### Library architecture structure Source: https://github.com/moxystudio/node-proper-lockfile/blob/master/_autodocs/README.md Displays the file structure and modular organization of the library. ```text index.js → Public API (sync/async adapters) lib/lockfile.js → Core implementation (callback-based) lib/adapter.js → Bridges callbacks to promises/sync lib/mtime-precision.js → Filesystem precision detection ``` -------------------------------- ### Error handling for unlockSync Source: https://github.com/moxystudio/node-proper-lockfile/blob/master/_autodocs/api-reference/unlock-sync.md Demonstrates catching specific error codes like ENOTACQUIRED when attempting to unlock a file that was not locked by the current process. ```javascript const lockfile = require('proper-lockfile'); try { lockfile.unlockSync('file.txt'); } catch (err) { if (err.code === 'ENOTACQUIRED') { console.log('File was never locked'); } else { console.error('Unexpected error:', err.message); } } ``` -------------------------------- ### Manual Retry Loop for External Coordination Source: https://github.com/moxystudio/node-proper-lockfile/blob/master/_autodocs/usage-patterns.md Implements a custom loop to handle ELOCKED errors, allowing for specific wait logic between attempts. ```javascript async function acquireWithManualRetry(file, maxAttempts = 5) { for (let i = 0; i < maxAttempts; i++) { try { return await lockfile.lock(file); } catch (err) { if (err.code === 'ELOCKED') { console.log(`Attempt ${i + 1}/${maxAttempts} failed, lock is held`); if (i < maxAttempts - 1) { // Wait before retrying await new Promise(r => setTimeout(r, 1000 * (i + 1))); } } else { throw err; // Other errors are fatal } } } throw new Error('Failed to acquire lock after all retries'); } ``` -------------------------------- ### Initialize Lock Registry Entry Source: https://github.com/moxystudio/node-proper-lockfile/blob/master/_autodocs/lock-lifecycle.md Represents the initial structure created in the global locks registry upon a successful lock() call. ```javascript locks[canonicalPath] = { lockfilePath: string, // e.g., '/path/to/file.txt.lock' mtime: Date, // e.g., 2026-07-23T15:30:45.000Z mtimePrecision: 's' | 'ms', // Filesystem precision options: {...}, // Original options object lastUpdate: number, // Date.now() at creation released: false, // Not yet released updateTimeout: Timer|null, // Pending mtime update updateDelay: null // Exponential backoff delay }; ``` -------------------------------- ### lock(path, options) Source: https://github.com/moxystudio/node-proper-lockfile/blob/master/_autodocs/README.md Acquires a lock on the specified file. Returns a release function to unlock the file. ```APIDOC ## lock(path, options) ### Description Acquires a lock on the specified file. Returns a promise that resolves to a release function. ### Parameters #### Path Parameters - **path** (string) - Required - The path to the file to lock. #### Options - **stale** (number) - Optional - Lock stale timeout in ms (min 5000). - **update** (number) - Optional - mtime update interval in ms. - **retries** (number) - Optional - Number of retry attempts on lock conflict. - **realpath** (boolean) - Optional - Whether to resolve symlinks. - **fs** (object) - Optional - Custom fs module. - **lockfilePath** (string) - Optional - Custom path for the lock file. - **onCompromised** (function) - Optional - Callback for when the lock becomes unsafe. ``` -------------------------------- ### Wait for process completion Source: https://github.com/moxystudio/node-proper-lockfile/blob/master/_autodocs/usage-patterns.md Implements a polling loop to wait until a specific lock file is released. Includes a timeout mechanism to prevent infinite waiting. ```javascript async function waitForProcessCompletion(lockFile, timeout = 300000) { const startTime = Date.now(); while (Date.now() - startTime < timeout) { try { // If check succeeds with false, lock is released const isLocked = await lockfile.check(lockFile); if (!isLocked) { return { success: true, waitTime: Date.now() - startTime }; } } catch (err) { if (err.code === 'ENOENT') { return { success: true, waitTime: Date.now() - startTime }; } } // Wait 1 second before checking again await new Promise(r => setTimeout(r, 1000)); } return { success: false, waitTime: timeout }; } ``` -------------------------------- ### Configure Retry Options Source: https://github.com/moxystudio/node-proper-lockfile/blob/master/_autodocs/configuration.md Defines the structure for retry configuration using the retry module format. ```javascript { retries: 5, // Number of retry attempts (default: 0) factor: 2, // Exponential backoff factor (default: 2) minTimeout: 100, // Minimum wait time between retries in ms (default: 1000) maxTimeout: 30000, // Maximum wait time between retries in ms (default: Infinity) randomize: false // Whether to add randomization (default: false) } ``` -------------------------------- ### Acquire a file lock Source: https://github.com/moxystudio/node-proper-lockfile/blob/master/README.md Acquires a lock on a file and returns a release function. The release function should be called to unlock the file once operations are complete. ```javascript const lockfile = require('proper-lockfile'); lockfile.lock('some/file') .then((release) => { // Do something while the file is locked // Call the provided release function when you're done, // which will also return a promise return release(); }) .catch((e) => { // either lock could not be acquired // or releasing it failed console.error(e) }); // Alternatively, you may use lockfile('some/file') directly. ``` -------------------------------- ### Lock Activation Phase Source: https://github.com/moxystudio/node-proper-lockfile/blob/master/_autodocs/lock-lifecycle.md Final steps performed after successful lock acquisition, including registry entry and release function return. ```text On successful acquisition: 1. Create lock registry entry 2. Call updateLock(file, options) → Schedules first mtime update → Timer is unreferenced 3. Return release function ``` -------------------------------- ### Acquire and release a file lock Source: https://github.com/moxystudio/node-proper-lockfile/blob/master/_autodocs/00-START-HERE.md Basic implementation for locking a file and ensuring the release function is called in a finally block. ```javascript const lockfile = require('proper-lockfile'); const release = await lockfile.lock('file.txt'); try { // Critical section } finally { await release(); } ``` -------------------------------- ### Configure unlock options Source: https://github.com/moxystudio/node-proper-lockfile/blob/master/_autodocs/README.md Defines the configuration object for the unlock method, which must match the options used during the lock call. ```javascript await lockfile.unlock('file.txt', { realpath: true, // Must match lock() option fs: gracefulFs, // Must match lock() option lockfilePath: 'file.txt.lock' // Must match lock() option }); ``` -------------------------------- ### unlock(file, options?) Source: https://github.com/moxystudio/node-proper-lockfile/blob/master/_autodocs/README.md Manually releases a lock on the specified file. ```APIDOC ## unlock(file, options?) ### Description Release a lock manually. It is preferred to use the release function returned by the lock method. ### Signature `unlock(file: string, options?: object): Promise` ``` -------------------------------- ### Locking a directory with custom lockfile location in JavaScript Source: https://github.com/moxystudio/node-proper-lockfile/blob/master/_autodocs/api-reference/lock.md Specifies a custom path for the lockfile, useful when locking directories. ```javascript const release = await lockfile.lock('my-dir', { lockfilePath: 'my-dir/.lock' // Lock file stored inside the directory }); ``` -------------------------------- ### check(file, options) Source: https://github.com/moxystudio/node-proper-lockfile/blob/master/_autodocs/api-reference/check.md Checks if a file is locked and if the lockfile is not stale. Does not acquire a lock. ```APIDOC ## check(file: string, options?: CheckOptions): Promise ### Description Checks if a file is locked and if the lockfile is not stale. This is a read-only operation that does not acquire a lock. ### Parameters #### Path Parameters - **file** (string) - Required - Path to the file to check. Must exist if `realpath` is `true`. #### Options (CheckOptions) - **stale** (number) - Optional - Time in milliseconds after which a lock is considered stale. Minimum value is 5000. Default: 10000. - **realpath** (boolean) - Optional - Resolve symlinks using realpath. Default: true. - **fs** (object) - Optional - Custom filesystem module to use. Default: graceful-fs. - **lockfilePath** (string) - Optional - Custom path for the lockfile. Default: {file}.lock. ### Return Type - **Promise** - Resolves to `true` if the file is locked and the lockfile is not stale, or `false` otherwise. ### Throws/Rejects - **ENOENT**: File does not exist (when `realpath` is `true`). - **EIO**: I/O error when checking lockfile status. - **EACCES**: Permission denied when accessing lockfile. ``` -------------------------------- ### Lock Release Phase Logic Source: https://github.com/moxystudio/node-proper-lockfile/blob/master/_autodocs/architecture.md Describes the cleanup process for releasing a lock, including timer cancellation and directory removal. ```text 1. Resolve file path 2. Check lock exists in registry 3. Cancel pending update timer 4. Mark lock as released 5. Remove from locks registry 6. Remove lockfile directory ``` -------------------------------- ### Handle ESYNC error for unsupported retries Source: https://github.com/moxystudio/node-proper-lockfile/blob/master/_autodocs/api-reference/lock-sync.md Illustrates that attempting to use the retries option in sync mode results in an ESYNC error. ```javascript try { // This will throw immediately because retries are not allowed lockfile.lockSync('file.txt', { retries: 3 }); } catch (err) { if (err.code === 'ESYNC') { console.error('Cannot use retries in sync mode'); } } ``` -------------------------------- ### Handling Graceful Shutdown Source: https://github.com/moxystudio/node-proper-lockfile/blob/master/_autodocs/usage-patterns.md Ensures that acquired locks are released when the process receives SIGTERM or SIGINT signals. ```javascript const lockfile = require('proper-lockfile'); let activeLock = null; async function startServer() { try { activeLock = await lockfile.lock('.server.lock', { realpath: false }); console.log('Server started'); // Server runs... } catch (err) { console.error('Failed to acquire server lock:', err.message); process.exit(1); } } // Handle graceful shutdown process.on('SIGTERM', async () => { console.log('SIGTERM received, shutting down gracefully'); if (activeLock) { try { await activeLock(); console.log('Lock released'); } catch (err) { console.error('Failed to release lock:', err); } } process.exit(0); }); process.on('SIGINT', async () => { console.log('SIGINT received, shutting down gracefully'); if (activeLock) { try { await activeLock(); console.log('Lock released'); } catch (err) { console.error('Failed to release lock:', err); } } process.exit(0); }); startServer().catch(err => { console.error('Server error:', err); process.exit(1); }); ``` -------------------------------- ### Handle lock compromise with custom recovery Source: https://github.com/moxystudio/node-proper-lockfile/blob/master/_autodocs/usage-patterns.md Defines an onCompromised callback to log errors, save application state, and exit the process to prevent data corruption. ```javascript const lockfile = require('proper-lockfile'); async function criticalOperation(file) { const release = await lockfile.lock(file, { stale: 30000, update: 10000, onCompromised: (err) => { logger.error('Lock compromised, dumping state', { error: err.message, timestamp: new Date().toISOString(), file }); // Save everything we can try { dumpApplicationState(); metrics.increment('lock.compromised'); } catch (saveErr) { logger.error('Failed to dump state', saveErr); } // Exit to prevent data corruption process.exit(1); } }); try { performCriticalWork(); } finally { await release(); } } ``` -------------------------------- ### Using the release function Source: https://github.com/moxystudio/node-proper-lockfile/blob/master/_autodocs/api-reference/unlock.md The preferred way to release a lock is by calling the function returned by the lock() method. ```javascript const lockfile = require('proper-lockfile'); const release = await lockfile.lock('file.txt'); try { // Perform critical operations updateFile(); } finally { // Use the returned release function await release(); } ``` -------------------------------- ### Lock release sequence logic Source: https://github.com/moxystudio/node-proper-lockfile/blob/master/_autodocs/lock-lifecycle.md The release process checks for existing release status before invoking the unlock procedure. ```javascript release(releasedCallback): if lock.released: return releasedCallback(Error('ERELEASED')) // Prevent further updates unlock(file, {...options, realpath: false}, releasedCallback) ```