mrkeyoor.com_
Wed 23 Sept 00:33 UTC
npmUtilsupdated 22 Sept 2026

lockfile review

lockfile 1.0.4 coordinates Node processes on one local filesystem by creating an empty file with exclusive-create semantics. The winner performs its work and removes the file; other callers can fail, poll, retry, or treat an old timestamp as stale. Six callback and synchronous functions cover acquire, release, and status checks. The 1.0.4 release replaced direct process-exit handling with `signal-exit`, added debugging, and updated tests and CI; it did not change the lock API. This package does not read npm package lockfiles, and GitHub archived it in 2020.

Verdict

lockfile 1.0.4 took 0.6 seconds and 1 MB in our sandbox with 0 audit findings, but its repository has been archived since 2020 and the lock contains no owner or heartbeat. Keep it only for compatible legacy local-filesystem workflows; proper-lockfile is the closer maintained choice for new Node code.

We installed it

Lab card: what happened when we installed lockfileScreenshot of lockfile documentation
Install✓ · 0.6s2 packages on disk · 1 MB
ImportESM import works · require() works · CommonJS package
Browsern/acould not be bundled for the browser (Node-only code, most likely)
Typesno TypeScript types found
Known vulns00 critical · 0 high · 0 moderate · 0 low (npm audit)

Answers from our run

Does lockfile install cleanly?

Yes. In a fresh container with an empty cache, npm install lockfile finished in 0.6s, leaving 2 packages and 1 MB on disk. npm audit reported no known vulnerabilities.

Can lockfile run in a browser?

Not directly: esbuild could not bundle it for the browser in our run, which normally means it depends on Node built-ins. Use it on the server, or find a browser-targeted alternative.

Does lockfile work with both ESM and CommonJS?

Yes. Both import 'lockfile' and require('lockfile') worked in Node 22 in our run. The package is published as CommonJS.

Does lockfile include TypeScript types?

No type declarations were found in our install, so TypeScript users need their own declarations.

lockfile or proper-lockfile: which should you use?

proper-lockfile: Use it for maintained promise-based file locks with mtime updates and ownership-aware release. lockfile 1.0.4 took 0.6 seconds and 1 MB in our sandbox with 0 audit findings, but its repository has been archived since 2020 and the lock contains no owner or heartbeat.

When should you not use lockfile?

You are choosing a lock package for new code. GitHub archived this repository in 2020, 12 issues and pull requests remain open, and npm has not received a release since 2018.

API stability5/5The six-function API in version 1.0.4 has not changed since its April 2018 publication: callback and synchronous forms acquire, release, and check a path. Its options still use wait, polling, staleness, retries, and retry delay. That is stability by dormancy rather than active compatibility work. Existing callers can expect no deliberate churn, but known semantics such as options mutation will also remain unchanged.
Docs3/5The README names all 6 functions and explains the 5 options in a short page, including the 100 ms polling default and best-effort exit cleanup. It does not disclose several source-visible constraints: lock files are empty, release does not verify ownership, the caller's options object is mutated, sync waits are rejected, and timestamp staleness has no heartbeat. Correct use requires reading the small implementation as well as the README.
Maintenance1/5npm published 1.0.4 on April 17, 2018, the last source commits landed in January 2019, and GitHub archived the project on December 18, 2020. The read-only repository now reports 12 open issues and pull requests. A weekly download count of 4,224,538 shows continued dependency-tree use, but it does not provide a maintainer who can merge fixes or modernize the API.
Ecosystem2/5lockfile has high inherited reach at 4,224,538 weekly downloads, yet the package supplies no TypeScript declarations, Promise interface, browser path, owner metadata, network lock backend, or adapter system. Our install found only 1 direct dependency and working Node module interop, which helps legacy consumers. New work has clearer destinations in proper-lockfile, async-mutex, Redis-based locks, or operating-system advisory locking.

Use it if

  • A legacy Node script already uses callback-style file locks and replacing the behavior would be riskier than retaining it.
  • Several processes on one machine must serialize access to a generated file, cache directory, or maintenance step.
  • An operator should be able to see and remove the coordination file without running a lock service.
  • Best-effort cleanup at process exit and a simple age-based stale rule are enough for the failure model.
Skip it if

Setup reality

We installed lockfile 1.0.4 in 0.6 seconds in a fresh Node 22 Bookworm sandbox. It left 2 packages and 1 MB on disk. The package has 1 direct dependency, no peers, an 88 KB unpacked size, and an ISC license. npm audit returned 0 known vulnerabilities. Our CommonJS require() and ESM import checks both worked. No TypeScript declarations were present.

There are no credentials, native builds, or config files. The package is CommonJS without an exports map, and our browser bundle failed because its work depends on Node filesystem and process APIs. The visible lock file is empty: it contains no PID, hostname, lease identifier, or payload. Put the path on a local filesystem and ensure its parent directory already exists with permissions shared by every cooperating process.

The options object is mutable state in 1.0.4. lock() adds req and start, then resets retries to 0 while it wraps the callback. Reusing one options object across acquisitions silently changes later behavior, so create a fresh object each time. wait is a total polling budget, pollPeriod defaults to 100 ms, and the final callback receives the original EEXIST rather than a distinct timeout code. Synchronous acquisition rejects wait and retryWait.

Staleness uses ctime on POSIX and mtime on Windows. There is no heartbeat, so choose a threshold above the longest valid run or a second process can steal a live lock. Exit cleanup uses signal-exit, but SIGKILL, power loss, or an OOM kill can leave the file behind. Release is also unverified: any caller with filesystem permission can unlink another process's lock. These limits are why proper-lockfile or a service-backed lock is a safer default for new systems.

Patterns

Hold a lock around callback work acquire-and-release

const lockfile = require('lockfile');

lockfile.lock('build.lock', { wait: 5_000 }, (error) => {
  if (error) return done(error);
  build((buildError) => {
    lockfile.unlock('build.lock', () => done(buildError));
  });
});

Version 1.0.4 only makes a best effort at exit cleanup. Release the file in every normal completion and error path.

Adapt the callbacks to promises wrap-with-promises

const { promisify } = require('node:util');
const lockfile = require('lockfile');
const lock = promisify(lockfile.lock);
const unlock = promisify(lockfile.unlock);

await lock('build.lock', { wait: 5_000, stale: 60_000 });
try {
  await build();
} finally {
  await unlock('build.lock');
}

`promisify` fits the error-first callbacks, but release still does not check that this process owns the path.

Avoid reused mutable options create-fresh-options

const options = () => ({
  wait: 2_000,
  retries: 3,
  retryWait: 100,
  stale: 60_000,
});

lockfile.lock('first.lock', options(), onLock);
lockfile.lock('second.lock', options(), onLock);

`lock()` adds internal fields and resets `retries` to 0. A newly created object prevents one acquisition from changing the next.

Wait for another process to release poll-for-lock

lockfile.lock('migration.lock', {
  wait: 30_000,
  pollPeriod: 250,
}, (error) => {
  if (error) return console.error(error.code);
  runMigration();
});

The 30-second wait is a total budget. Expiry returns the original `EEXIST`, so the error code does not distinguish immediate contention from timeout.

Allow takeover after a fixed age recover-stale-file

lockfile.lock('worker.lock', {
  stale: 120_000,
  wait: 5_000,
}, (error) => {
  if (error) return fail(error);
  runWorker();
});

The 120-second threshold is timestamp based and has no heartbeat. A valid job that runs longer can lose its lock.

Check whether a current lock exists check-lock-state

lockfile.check('worker.lock', { stale: 120_000 }, (error, held) => {
  if (error) return fail(error);
  console.log(held ? 'held' : 'missing or stale');
});

The check is advisory. Another process can acquire after it returns, and an old file reports false when `stale` is set.

Serialize a synchronous build script lock-sync-script

let acquired = false;
try {
  lockfile.lockSync('build.lock', { retries: 3, stale: 60_000 });
  acquired = true;
  buildSync();
} finally {
  if (acquired) lockfile.unlockSync('build.lock');
}

`lockSync` throws if `wait` or `retryWait` is present. Its retries happen without an asynchronous delay.

Recognize immediate contention handle-eexist

lockfile.lock('job.lock', {}, (error) => {
  if (error?.code === 'EEXIST') {
    console.error('another process holds the lock');
    return;
  }
  if (error) throw error;
  runJob();
});

The same `EEXIST` appears if this process tries to acquire the path twice because lockfile is not reentrant.

Confirm the lock has no owner record inspect-lock-metadata

const fs = require('node:fs');

lockfile.lockSync('job.lock');
const stat = fs.statSync('job.lock');
console.log({ bytes: stat.size, clock: lockfile.filetime });

The file is 0 bytes. `filetime` is `ctime` on POSIX and `mtime` on Windows; neither identifies the holder.

Use a maintained file-lock API migrate-to-proper-lockfile

const properLockfile = require('proper-lockfile');

const release = await properLockfile.lock('resource', {
  stale: 30_000,
  retries: { retries: 5, minTimeout: 100 },
});
try {
  await updateResource();
} finally {
  await release();
}

proper-lockfile updates mtime while held and verifies release ownership, two protections absent from lockfile 1.0.4.

Alternatives

PackageRegistryPick it when
proper-lockfilenpmUse it for maintained promise-based file locks with mtime updates and ownership-aware release.
async-mutexnpmUse it when only asynchronous tasks inside one Node process contend for the resource.
redlocknpmUse it when separate machines coordinate through Redis and the team can operate that dependency.
fs-extnpmUse it for operating-system advisory locks when a native addon and `flock` semantics are acceptable.

More utils guides

lru-cache · ajv · type-fest · p-limit · find-up · js-yaml · the whole shelf →

How this guide is made: grounded in the library's documentation, release notes, changelog, and issue history, on a fixed rubric — not a hands-on install of every release. The 50 most-downloaded entries are additionally install-verified in clean containers. Corrections: contact the desk.