Maintained compatibility · Node.js

@stackline/lockfile

A small CommonJS lock-file implementation that keeps the published 1.0.4 contract and makes real asynchronous cleanup failures observable.

  • v1.0.6
  • Node.js ≥14.17
  • CommonJS
  • TypeScript declarations
  • ISC

One correction, deliberately bounded

Asynchronous unlock still treats ENOENT as success, but now forwards the original non-ENOENT unlink error exactly once. Successful callbacks and every synchronous behavior stay compatible.

Coordination model

A zero-byte, cooperative lock

The implementation relies on filesystem primitives, not a daemon or an ownership lease. Every participant must follow the same protocol.

On disk

Zero bytes

No owner, PID, heartbeat, policy, or fencing token is encoded.

Within one process

Tracked handles

Known locks receive best-effort cleanup through signal-exit.

Across processes

Filesystem semantics

Exclusive create, visibility, hard-link, unlink, and metadata behavior determine correctness.

Public surface

Six CommonJS methods

All asynchronous operations use callbacks. Sync methods return their documented value or throw, except for best-effort unlockSync.

Async · acquire

lock(path, [options], callback)

Creates the lock or calls back with the original acquisition or contention error. Success is callback().

Sync · acquire

lockSync(path, [options])

Creates the lock and returns undefined, or throws. Sync calls reject waiting options.

Sync · release

unlockSync(path)

Best-effort cleanup. It suppresses every unlink error and returns undefined, matching upstream.

Async · observe

check(path, [options], callback)

Calls callback(error, isLocked). The result is an observation, not an atomic reservation.

Sync · observe

checkSync(path, [options])

Returns whether the lock exists and is not stale, or throws for an unexpected filesystem error.

Mutable compatibility property

lockfile.filetime defaults to ctime on POSIX and mtime on Windows. It selects the stat timestamp used by stale checks.

Timing controls

Options retain upstream meaning

Supported options and their scope
Option Used by Behavior
wait Async lock Poll for up to this many milliseconds, then return the original contention error.
pollPeriod Async lock Polling interval while waiting. Defaults to 100 milliseconds.
stale Lock and check Age in milliseconds after which takeover is allowed. Disabled when omitted.
retries lock, lockSync Number of additional acquisition attempts.
retryWait Async lock Delay between retries. lockSync rejects it.

lockSync rejects wait and retryWait; checkSync rejects wait. POSIX stale checks use ctime, Windows uses mtime, and sync takeover retains the upstream timestamp-resolution rounding rule.

Compatibility boundary

Issue 18 is the sole runtime delta

Published lockfile@1.0.4 discarded every asynchronous unlink error. Version 1.0.5 distinguishes an already-missing lock from a real cleanup failure.

Unlock outcomes compared with the 1.0.4 baseline
Unlink outcome lockfile@1.0.4 @stackline/lockfile@1.0.6
Success callback() callback()
ENOENT callback() callback()
Any other error callback(), error lost callback(originalError), exactly once
Synchronous unlink error Suppressed Suppressed

Added types, unchanged runtime

First-party declarations model callbacks, options, mutable filetime, and real undefined success values. They add no Promise or ESM implementation.

Issue 30 is not fixed

Different callers can still choose conflicting stale thresholds. The format contains no owner or policy metadata, so resolving that conflict requires a new protocol.

Operational safety

The filesystem is part of the protocol

Local tests cannot establish behavior for every mount. Validate the exact operating system, filesystem, topology, and failure modes used in production.

NFS and distributed mounts

Attribute caching, visibility delay, clock differences, coarse timestamps, or weaker atomicity can invalidate stale and contention assumptions.

Stale takeover

A threshold that is too short can remove a live lock. POSIX and Windows deliberately use different metadata timestamps.

Crash cleanup

Process-exit removal is best-effort. Abrupt termination, power loss, and unlink errors can leave a lock behind.

Advisory, not access control

A party able to modify the directory can bypass the protocol. Use trusted paths and appropriate directory ownership and permissions.

check is an observation

Another participant can act after a check. Acquire the lock for mutual exclusion; do not build a check-then-act security decision.

Know when to migrate protocols

Heartbeats, ownership metadata, leases, fencing, or compromise detection need a different design. proper-lockfile is not drop-in compatible.

Adopt deliberately

Existing import or new scoped import?

Use the npm alias for a dependency-only migration. Import the scope directly in new code. In either case, test retained-lock failures and the real filesystem.

Read the migration guide

Reference

Package documentation