{"id":29884717,"url":"https://github.com/panta82/better-lock","last_synced_at":"2025-07-31T15:06:41.262Z","repository":{"id":64248617,"uuid":"98677732","full_name":"panta82/better-lock","owner":"panta82","description":"A (better) node.js lock library","archived":false,"fork":false,"pushed_at":"2024-08-12T16:46:24.000Z","size":365,"stargazers_count":4,"open_issues_count":0,"forks_count":0,"subscribers_count":1,"default_branch":"master","last_synced_at":"2025-07-31T12:26:37.335Z","etag":null,"topics":["async","concurrency","lock","locking","mutex"],"latest_commit_sha":null,"homepage":"","language":"TypeScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/panta82.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2017-07-28T18:43:34.000Z","updated_at":"2024-08-12T16:46:28.000Z","dependencies_parsed_at":"2024-09-28T04:01:20.622Z","dependency_job_id":"451ea7b9-c67d-4e06-92fa-33c3b774ff93","html_url":"https://github.com/panta82/better-lock","commit_stats":{"total_commits":52,"total_committers":4,"mean_commits":13.0,"dds":0.5769230769230769,"last_synced_commit":"0847bc22b8935a4c785a864bafeb005f4d9e71c8"},"previous_names":[],"tags_count":0,"template":false,"template_full_name":null,"purl":"pkg:github/panta82/better-lock","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/panta82%2Fbetter-lock","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/panta82%2Fbetter-lock/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/panta82%2Fbetter-lock/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/panta82%2Fbetter-lock/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/panta82","download_url":"https://codeload.github.com/panta82/better-lock/tar.gz/refs/heads/master","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/panta82%2Fbetter-lock/sbom","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":268060190,"owners_count":24188976,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","status":"online","status_checked_at":"2025-07-31T02:00:08.723Z","response_time":66,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["async","concurrency","lock","locking","mutex"],"created_at":"2025-07-31T15:06:34.390Z","updated_at":"2025-07-31T15:06:41.234Z","avatar_url":"https://github.com/panta82.png","language":"TypeScript","readme":"# Better Lock\n\nA (better) node.js lock library.\n\n### Features\n\n- Typescript-ready\n- Named and keyed locks\n- Queue and execution timeouts\n- Queue size limit\n- Lock on multiple keys\n- Extended stack traces\n- Promise and callback interface\n- JSDoc annotations\n- Good error messages\n- Unit tests, good code coverage\n- No dependencies\n\n### Installation\n\n```bash\nnpm install --save better-lock\n# or\nyarn add better-lock\n```\n\n### Documentation\n\nFull docs: \u003chttps://panta82.github.io/better-lock/\u003e\n\n##### Minimal example\n\n```javascript\nconst lock = new BetterLock();\n\ntry {\n  const res = await lock.acquire(async () =\u003e {\n    // Inside the lock. It will stay closed until the promise you return resolves or rejects.\n    await doSomeAsyncTask();\n    return 'my result';\n  });\n\n  // Outside the lock. You will get whatever the promise chain has returned.\n  console.log(res); // \"my result\"\n} catch (err) {\n  // Either your or BetterLock's error\n}\n```\n\n##### Advanced example\n\n```typescript\nconst lock = new BetterLock({\n  name: 'FileLock', // To be used in error reporting and logging\n  log: winstonLogger.debug, // Give it your logger with appropeiate level\n  wait_timeout: 1000 * 30, // Max 30 sec wait in queue\n  execution_timeout: 1000 * 60 * 5, // Time out after 5 minutes\n  queue_size: 1, // At most one pending job\n});\n\nasync function processFile(filename) {\n  try {\n    const result = await lock.acquire(filename, async () =\u003e {\n      const appended = await appendToFile(filename);\n      return updateDb(appended);\n    });\n    return {\n      status: true,\n      result,\n    };\n  } catch (err) {\n    if (err instanceof BetterLock.QueueOverflowError) {\n      // The job was discarded\n      return {\n        status: false,\n      };\n    }\n\n    if (err instanceof BetterLock.ExecutionTimeoutError) {\n      winstonLogger.warn('Potential swallowed callback! Stack trace to the entry site:', err.stack);\n    }\n    throw err;\n  }\n}\n```\n\n##### Locking on multiple keys\n\n```javascript\nconst userLock = new BetterLock({\n  name: 'User lock',\n  executionTimeout: 1000 * 60 * 60, // Note you can also use camelCase\n});\n\nfunction transferBetweenUsers(fromId, toId, amount) {\n  userLock\n    .acquire([fromId, toId], () =\u003e {\n      return Promise.all([User.get(fromId), User.get(toId)]).then(([fromUser, toUser]) =\u003e {\n        fromUser.amount -= amount;\n        toUser.amount += amount;\n        return Promise.all([user1.save(), user2.save()]);\n      });\n    })\n    .then(() =\u003e {\n      console.log('Transfer completed');\n    });\n}\n```\n\n##### Using callback interface\n\n```javascript\nconst BetterLock = require('better-lock');\n\nconst lock = new BetterLock();\n//...\nlock.acquire(\n  done =\u003e {\n    // Inside the lock\n    doMyAsyncStuffHere(err =\u003e {\n      // Call done when done\n      done(err);\n    });\n  },\n  (err, result) =\u003e {\n    // Outside the lock\n    if (err) {\n      // Either your or BetterLock's error\n      console.error(err);\n    }\n  }\n);\n```\n\nYou can see a bunch more usage examples in the spec file, [here](spec/better_lock.spec.ts);\n\n### API\n\n- `new BetterLock(options)`  \n  Create a new instance of `BetterLock`. Options should match interface `BetterLockOptions`. See below for details.\n\n- `BetterLock.acquire([key], executor, [callback], [jobOptions])`  \n  The main method you'll want to call. For each `key`, given `executor` will be called only one at a time. If you don't provide `callback`, it will return a promise that will be resolved with whatever `executor` returns.\n\n  - `key`: Arbitrary string under which to lock. It allows you to use the same lock instance for multiple parallel concerns. Eg. this might be a database record id or filename.\n  - `executor`: Function that will be called within the lock. This function should have one of two forms.\n    1. _Without arguments_, in which case it should return a promise. Lock will remain locked until the promise resolves.\n    2. _With single `done`_ argument. In this case, the executor should call `done(err, res)` once it is done. Arguments passed to done will be passed to the callback of the lock.\n  - `callback`: Optional callback that will be called once executor exits. Results from executor (resolved/rejected value or arguments given to `done`) will be passed along. This can be used in addition to the returned promise.\n  - `jobOptions`: An object that should match interface `BetterLockJobOptions`. A subset of main options that will serve as overrides for this particular job (for example, timeout settings).\n\n- `BetterLock.acquireOr([key], failureResult, executor, [callback], [jobOptions])`  \n  The same as acquire, except if the lock can't be acquired (the executor never gets called), instead of throwing\n  an error, the lock will resolve/callback with the provided failureResult value (for example, a `null`).\n\n- `BetterLock.canAcquire([key])`  \n  Returns true if given key can be acquired.\n\n- `BetterLock.abort([key])`  \n  Abort all jobs for a given key (or from the default job queue, if no key is given). Job executors will not be called. Callbacks will be called with `JobAbortedError`. Currently executing job will not be interrupted.\n\n- `BetterLock.abortAll()`  \n  Abort all jobs for all keys. This is suitable to be called during shutdown of your app.\n\n### Options\n\nAll available options can be seen [here](https://panta82.github.io/better-lock/interfaces/BetterLockOptions).\n\n`BetterLockOptions` are provided when you construct a lock instance. A subset of options given in `LockJobOptions` can be provided when you call `lock.acquire`, as the last argument.\n\nExample:\n\n```javascript\nlock.acquire(executor, callback, {\n  wait_timeout: 1000,\n});\n```\n\nMost commonly used options are:\n\n- `wait_timeout`  \n  How long can jobs wait in queue before timing out (ms). Null to disable timeout.\n\n- `execution_timeout`  \n  How long can a job be executing before timing out (ms). Null to disable timeout.\n  If you do that, though, and you have a swallowed callback, the lock can remain locked permanently.\n\n- `queue_size`  \n  Max queue size for waiting jobs.\n\nDefault options are a static member `DEFAULT_OPTIONS` on the `BetterLock` class. During runtime, you can change the defaults like this:\n\n```javascript\nimport BetterLock from 'better-lock';\n\nBetterLock.DEFAULT_OPTIONS.wait_timeout = 1000;\n```\n\n### When to use\n\nThis library is a good fit if:\n\n- You need a local lock in your node.js application.\n- You have some advanced needs, like multiple keys per lock, timeouts, aborting...\n- You like good error messages, with full stack traces.\n- You like good types, either through typescript or JSDoc comments\n\nThe library is not a good fit if:\n\n- **You need lock reentrancy.**  \n  There is no good solution for this in node.js that I know of. And this library doesn't offer any.\n\n- **You need a shared lock between different nodes**  \n  This library is a single process only. If you need to coordinate multiple apps or services, you need a different library.\n\n### Change log\n\n#### **3.2.0** (_2024/06/21_)\n\nAdd new api `canAcquire`. A helper for \"best effort\" lock acquisition.\n\n#### **3.1.0** (_2024/06/21_)\n\nAdd option `lock_condition`.\n\n#### **3.0.0** (_2024/04/18_)\n\nAdd option `queue_ejection_strategy`.\n\nMajor version bump because the default strategy changed from \"newest\" (the old assumed value) to \"newest\" (makes more sense).\n\nTo migrate, specify:\n\n```javascript\n{\n  \"queue_ejection_strategy\": \"newest\"\n}\n```\n\nin your job options.\n\n#### **2.0.3** (_2022/05/28_)\n\nIntroduce generated API docs. Remove beta tag from npm version.\n\n#### **2.0.0** (_2021/05/30_)\n\nMajor update. The entire library was rewritten in typescript, so you should now get typings in most editors. We also had to switch tests from mocha + chai to jest (easier ts integration).\n\nThe API and features have remained largely the same, just a bit of a refresh.\n\nNon-breaking and internal changes:\n\n- We now export all error names as a type script type. A few other types as well.\n- Errors now have `name` parameter, which matches these names.\n\nBreaking changes:\n\n- You can no longer use camel case versions of external-facing objects. Eg. you can no longer pass `waitTimeout` instead of `wait_timeout`. In retrospect, this was a pretty flaky API to maintain.\n\n- Internal `LockJob` class is no longer exported.\n\n- Also, errors no longer expose internal `LockJob` instances (`err.job`). We now instead provide the most important fields from the job (`id` and `keys`).\n\n- Error names have been renamed to have `BetterLock` prefix. Eg. `WaitTimeoutError` -\u003e `BetterLockWaitTimeoutError`. This will influence `err.name` and `err.message` parameters. The idea here is, if you see `BetterLock` error in the wild, you will know what generated it.\n\n- We have renamed `BetterLock.BetterLockError` to `BetterLock.BaseError` and `BetterLock.BetterLockInternalError` to `BetterLock.InternalError` to better match the naming scheme.\n\n- Since Options are no longer a class but interface, we are no longer exporting them under `BetterLock.Options`. You can do `import {BetterLockOptions} from 'better-lock';` to get the typescript type.\n\n#### **1.0.1** (_2019/01/28_)\n\nHandle empty key list\n\n#### **1.0.0** (_2019/01/28_)\n\nMajor version bump.\n\n- Added multi-key locks and refactored a bunch of internals.\n- Removed `OVERFLOW_STRATEGIES` and related options, which is mostly the reason for the major version bump. The library should otherwise work the same.\n\n#### **0.3.1** (_2018/10/01_)\n\nUpdated CI to use the current node versions (0.8 \u0026 0.10). Older node versions should continue to work, but are no longer tested. Also, README updates.\n\n#### **0.3.0** (_2018/10/01_)\n\nCan abort jobs waiting in queue.\n\n#### **0.2.1** (_2018/09/27_)\n\n- Better and customizable Promise detection.\n- Restored DEFAULT_OPTIONS.\n\n#### **0.2.0** (_2018/09/27_)\n\nCode reformat, better pattern for loading options. No feature upgrades.\n\n#### **0.1.1** (_2018/06/04_)\n\nYou can now use a Number as job name\n\n### Development\n\nFork, then git clone. The project is already set up with a WebStorm project, if that's your cup of tee.\n\nTo run tests, with coverage:\n\n```\nnpm run test\n```\n\nIf you want to contribute, create a branch off master, do your work and then make a pull request against master. Unit tests would be appreciated.\n\n## License\n\n[MIT](./LICENSE)\n","funding_links":[],"categories":[],"sub_categories":[],"project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fpanta82%2Fbetter-lock","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fpanta82%2Fbetter-lock","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fpanta82%2Fbetter-lock/lists"}