{"id":18143367,"url":"https://github.com/tristanls/dynamodb-lock-client","last_synced_at":"2025-08-12T16:06:24.704Z","repository":{"id":25955827,"uuid":"106972943","full_name":"tristanls/dynamodb-lock-client","owner":"tristanls","description":"A general purpose distributed locking library built for AWS DynamoDB.","archived":false,"fork":false,"pushed_at":"2023-12-13T15:27:58.000Z","size":77,"stargazers_count":49,"open_issues_count":9,"forks_count":18,"subscribers_count":2,"default_branch":"master","last_synced_at":"2025-08-09T05:21:07.118Z","etag":null,"topics":["distributed-lock","dynamodb","fencing","nodejs"],"latest_commit_sha":null,"homepage":null,"language":"JavaScript","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/tristanls.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":".github/FUNDING.yml","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},"funding":{"github":["tristanls"],"patreon":null,"open_collective":null,"ko_fi":null,"tidelift":null,"community_bridge":null,"liberapay":null,"issuehunt":null,"otechie":null,"custom":null}},"created_at":"2017-10-15T00:38:36.000Z","updated_at":"2025-02-11T15:49:45.000Z","dependencies_parsed_at":"2023-12-13T16:47:11.976Z","dependency_job_id":null,"html_url":"https://github.com/tristanls/dynamodb-lock-client","commit_stats":{"total_commits":84,"total_committers":6,"mean_commits":14.0,"dds":"0.13095238095238093","last_synced_commit":"f8cb1bc4d40eab6962875fe60a7e02c09d09b1ce"},"previous_names":[],"tags_count":29,"template":false,"template_full_name":null,"purl":"pkg:github/tristanls/dynamodb-lock-client","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/tristanls%2Fdynamodb-lock-client","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/tristanls%2Fdynamodb-lock-client/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/tristanls%2Fdynamodb-lock-client/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/tristanls%2Fdynamodb-lock-client/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/tristanls","download_url":"https://codeload.github.com/tristanls/dynamodb-lock-client/tar.gz/refs/heads/master","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/tristanls%2Fdynamodb-lock-client/sbom","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":269693891,"owners_count":24460309,"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-08-10T02:00:08.965Z","response_time":71,"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":["distributed-lock","dynamodb","fencing","nodejs"],"created_at":"2024-11-01T19:07:08.676Z","updated_at":"2025-08-12T16:06:24.634Z","avatar_url":"https://github.com/tristanls.png","language":"JavaScript","funding_links":["https://github.com/sponsors/tristanls"],"categories":[],"sub_categories":[],"readme":"# dynamodb-lock-client\n\n_Stability: 1 - [Experimental](https://github.com/tristanls/stability-index#stability-1---experimental)_\n\n[![NPM version](https://badge.fury.io/js/dynamodb-lock-client.png)](http://npmjs.org/package/dynamodb-lock-client)\n\nA general purpose distributed locking library with fencing tokens built for AWS DynamoDB.\n\nFor AWS SDK v3 version go to: https://github.com/trilogy-group/dynamodb-lock-client-v3\n\n## Contributors\n\n[@tristanls](https://github.com/tristanls), [@Jacob-Lynch](https://github.com/Jacob-Lynch), [@simlu](https://github.com/simlu), Lukas Siemon, [@tomyam1](https://github.com/tomyam1), [@deathgrindfreak](https://github.com/deathgrindfreak), [@jepetko](https://github.com/jepetko), [@fpronto](https://github.com/fpronto)\n\n## Contents\n\n  * [Installation](#installation)\n  * [Usage](#usage)\n  * [Tests](#tests)\n  * [Documentation](#documentation)\n    * [Setting up the lock table in DynamoDB](#setting-up-the-lock-table-in-dynamodb)\n    * [DynamoDBLockClient](#dynamodblockclient)\n  * [Releases](#releases)\n\n## Installation\n\n    npm install dynamodb-lock-client\n\n## Usage\n\nTo run the below example, run:\n\n    npm run readme\n\n```javascript\n\"use strict\";\n\nconst AWS = require(\"aws-sdk\");\nconst DynamoDBLockClient = require(\"../index.js\");\n\nconst dynamodb = new AWS.DynamoDB.DocumentClient(\n    {\n        region: \"us-east-1\"\n    }\n);\n\n// \"fail closed\": if process crashes and lock is not released, lock will\n//                never be released (requires human intervention)\nconst failClosedClient = new DynamoDBLockClient.FailClosed(\n    {\n        dynamodb,\n        lockTable: \"my-lock-table-name\",\n        partitionKey: \"mylocks\",\n        acquirePeriodMs: 1e4\n    }\n);\n\nfailClosedClient.acquireLock(\"my-fail-closed-lock\", (error, lock) =\u003e\n    {\n        if (error)\n        {\n            return console.error(error)\n        }\n        console.log(\"acquired fail closed lock\");\n        // do stuff\n        lock.release(error =\u003e error ? console.error(error) : console.log(\"released fail closed lock\"));\n    }\n);\n\n// \"fail open\": if process crashes and lock is not released, lock will\n//              eventually expire after leaseDurationMs from last heartbeat\n//              sent\nconst failOpenClient = new DynamoDBLockClient.FailOpen(\n    {\n        dynamodb,\n        lockTable: \"my-lock-table-name\",\n        partitionKey: \"mylocks\",\n        heartbeatPeriodMs: 3e3,\n        leaseDurationMs: 1e4\n    }\n);\n\nfailOpenClient.acquireLock(\"my-fail-open-lock\", (error, lock) =\u003e\n    {\n        if (error)\n        {\n            return console.error(error)\n        }\n        console.log(`acquired fail open lock with fencing token ${lock.fencingToken}`);\n        lock.on(\"error\", error =\u003e console.error(\"failed to heartbeat!\"));\n        // do stuff\n        lock.release(error =\u003e error ? console.error(error) : console.log(\"released fail open lock\"));\n    }\n);\n\n```\n\n## Tests\n\nAt this time, test are implemented for FailOpen lock acquisition and release.\n\n```\n    npm test\n```\n\n## Documentation\n\n  * [Setting up the lock table in DynamoDB](#setting-up-the-lock-table-in-dynamodb)\n  * [DynamoDBLockClient](#dynamodblockclient)\n\n### Setting up the lock table in DynamoDB\n\n#### Recommended\n\nThe DynamoDB lock table needs to be created independently. The following is an example CloudFormation template that would create such a lock table:\n\n```yaml\nAWSTemplateFormatVersion: \"2010-09-09\"\n\nResources:\n\n  DistributedLocksStore:\n    Type: AWS::DynamoDB::Table\n    Properties:\n      AttributeDefinitions:\n        - AttributeName: id\n          AttributeType: S\n      KeySchema:\n        - AttributeName: id\n          KeyType: HASH\n      TableName: \"distributed-locks-store\"\n      BillingMode: PAY_PER_REQUEST\n\nOutputs:\n\n  DistributedLocksStore:\n    Value: !GetAtt DistributedLocksStore.Arn\n```\n\nThe template above would make your `config.partitionKey == \"id\"` and your `config.lockTable == \"distributed-locks-store\"`.\n\nYou can choose to call your `config.partitionKey` any valid string except `fencingToken`, `leaseDurationMs`, `lockAcquiredTimeUnixMs`, `owner`, or `guid` (these attribute names are reserved for use by `DynamoDBLockClient` library). Your `config.partitionKey` has to correspond to the partition key (`HASH`) of the Primary Key of your DynamoDB table.\n\n#### Using sort key\n\nIn some cases, you may be constrained to use a DynamoDB table that requires to specify a sort key. The following is an example CloudFormation template that would create such a lock table:\n\n```yaml\nAWSTemplateFormatVersion: \"2010-09-09\"\n\nResources:\n\n  DistributedLocksStore:\n    Type: AWS::DynamoDB::Table\n    Properties:\n      AttributeDefinitions:\n        - AttributeName: id\n          AttributeType: S\n        - AttributeName: sortID\n          AttributeType: S\n      KeySchema:\n        - AttributeName: id\n          KeyType: HASH\n        - AttributeName: sortID\n          KeyType: RANGE\n      TableName: \"distributed-locks-store\"\n      BillingMode: PAY_PER_REQUEST\n\nOutputs:\n\n  DistributedLocksStore:\n    Value: !GetAtt DistributedLocksStore.Arn\n```\n\nThe template above would make your `config.partitionKey == \"id\"`, `config.sortKey = \"sortID\"`, and your `config.lockTable == \"distributed-locks-store\"`.\n\nYou can choose to call your `config.partitionKey` and `config.sortKey` any valid string except `fencingToken`, `leaseDurationMs`, `lockAcquiredTimeUnixMs`, `owner`, or `guid` (these attribute names are reserved for use by `DynamoDBLockClient` library). Your `config.partitionKey` has to correspond to the partition key (`HASH`) of the Primary Key of your DynamoDB table. Your `config.sortKey` has to correspond to the sort key (`RANGE`) of the Primary Key of your DynamoDB table.\n\n### DynamoDBLockClient\n\n**Public API**\n\n  * [new DynamoDBLockClient.FailClosed(config)](#new-dynamodblockclientfailclosedconfig)\n  * [new DynamoDBLockClient.FailOpen(config)](#new-dynamodblockclientfailopenconfig)\n  * [client.acquireLock(id, callback)](#clientacquirelockid-callback)\n  * [lock.release(callback)](#lockreleasecallback)\n\n### new DynamoDBLockClient.FailClosed(config)\n\n  * `config`: _Object_\n    * `dynamodb`: _AWS.DynamoDB.DocumentClient_ Instance of AWS DynamoDB DocumentClient.\n    * `lockTable`: _String_ Name of lock table to use.\n    * `partitionKey`: _String_ Name of table partition key (hash key) to use.\n    * `sortKey`: _String_ _(Default: undefined)_ Optional name of table sort key (range key) to use. If specified, all lock ids will be required to contain a `sortKey`.\n    * `acquirePeriodMs`: _Number_ How long to wait for the lock before giving up. Whatever operation this lock is protecting should take less time than `acquirePeriodMs`.\n    * `owner`: _String_ Customize owner name for lock (optional).\n    * `retryCount`: _Number_ _(Default: 1)_ Number of times to retry lock acquisition after initial failure. No retries will occur if set to `0`.\n  * Return: _Object_ Fail closed client.\n\nCreates a \"fail closed\" client that acquires \"fail closed\" locks. If process crashes and lock is not released, lock will never be released. This means that some sort of intervention will be required to put the system back into operational state if lock is held and a process crashes while holding the lock.\n\n### new DynamoDBLockClient.FailOpen(config)\n\n  * `config`: _Object_\n    * `dynamodb`: _AWS.DynamoDB.DocumentClient_ Instance of AWS DynamoDB DocumentClient.\n    * `lockTable`: _String_ Name of lock table to use.\n    * `partitionKey`: _String_ Name of table partition key (hash key) to use.\n    * `sortKey`: _String_ _(Default: undefined)_ Optional name of table sort key (range key) to use. If specified, all lock ids will be required to contain a `sortKey`.\n    * `heartbeatPeriodMs`: _Number_ _(Default: undefined)_ Optional period at which to send heartbeats in order to keep the lock locked. Providing this option will cause heartbeats to be sent.\n    * `leaseDurationMs`: _Number_ The length of lock lease duration. If the lock is not renewed via a heartbeat within `leaseDurationMs` it will be automatically released.\n    * `owner`: _String_ Customize owner name for lock (optional).\n    * `retryCount`: _Number_ _(Default: 1)_ Number of times to retry lock acquisition after initial failure. No retries will occur if set to `0`.\n    * `trustLocalTime`: _Boolean_ _(Default: false)_ If set to `true`, when the client retrieves an existing lock, it will use local time to determine if `leaseDurationMs` has elapsed (and shorten its wait time accordingly) instead of always waiting the full `leaseDurationMs` milliseconds before making an acquisition attempt.\n  * Return: _Object_ Fail open client.\n\nCreates a \"fail open\" client that acquires \"fail open\" locks. If process crashes and lock is not released, lock will eventually expire after `leaseDurationMs` from last heartbeat sent (if any). This means that if process acquires a lock, goes to sleep for more than `leaseDurationMs`, and then wakes up assuming it still has a lock, then it can perform an operation ignoring other processes that may assume they have a lock on the operation.\n\n### client.acquireLock(id, callback)\n\n  * `id`: _String\\|Buffer\\|Number\\|Object_ Unique identifier for the lock. If the type of `id` is _String\\|Buffer\\|Number_ the type must correspond to lock table's partition key type. If the type of `id` is _Object_, it is expected to have the following format:\n     ```\n     {\n       [config.partitionKey]: String|Buffer|Number,\n       [config.sortKey]: String|Buffer|Number\n     }\n     ```\n     For example, if `config.partitionKey = \"myPartitionKey\"` and `config.sortKey = \"mySortKey\"` and partition key value is `id1234` and sort key value is `abcd`, then the _Object_ would be:\n     ```\n     {\n       myPartitionKey: \"id1234\",\n       mySortKey: \"abcd\"\n     }\n     ```\n     Sort key part of `id` is only required if lock is configured with a sort key. The types of partition key and sort key must correspond to lock table's partition key and sort key types.\n  * `callback`: _Function_ `(error, lock) =\u003e {}`\n    * `error`: _Error_ Error, if any.\n    * `lock`: _DynamoDBLockClient.Lock_ Successfully acquired lock object. Lock object is an instance of `EventEmitter`. If the `lock` is acquired via a fail open `client` configured to heartbeat, then the returned `lock` may emit an `error` event if a `heartbeat` operation fails.\n      * `fencingToken`: _Integer_ **fail open locks only** Integer monotonically incremented with every \"fail open\" lock acquisition to be used for [fencing](https://martin.kleppmann.com/2016/02/08/how-to-do-distributed-locking.html#making-the-lock-safe-with-fencing). Heartbeats do not increment `fencingToken`.\n\nAttempts to acquire a lock. If lock acquisition fails, callback will be called with an `error` and `lock` will be falsy. If lock acquisition succeeds, callback will be called with `lock`, and `error` will be falsy.\n\nFail closed client will attempt to acquire a lock. On failure, client will retry after `acquirePeriodMs` up to `retryCount` times. After `retryCount` failures, client will fail lock acquisition. On successful acquisition, lock will be locked until `lock.release()` is called successfuly.\n\nFail open client will attempt to acquire a lock. On failure, if `trustLocalTime` is `false` (the default), client will retry after `leaseDurationMs`. If `trustLocalTime` is `true`, the client will retry after `Math.max(0, leaseDurationMs - (localTimeMs - lockAcquiredTimeMs))` where `localTimeMs` is \"now\" and `lockAcquiredTimeMs` is the lock acquisition time recorded in the retrieved lock. Lock acquisition will be retried up to `retryCount` times. After `retryCount` failures, client will fail lock acquisition. On successful acquisition, if `heartbeatPeriodMs` option is not specified (heartbeats off), lock will expire after `leaseDurartionMs`. If `heartbeatPeriodMs` option is specified, lock will be renewed at `heartbeatPeriodMs` intervals until `lock.release()` is called successfuly. Additionally, if `heartbeatPeriodMs` option is specified, lock may emit an `error` event if it fails a heartbeat operation.\n\n### lock.release(callback)\n\n  * `callback`: _Function_ `error =\u003e {}`\n    * `error`: _Error_ Error, if any. No error implies successful lock release.\n\nReleases previously acquired lock.\n\nFail closed lock is deleted, so that it can be acquired again.\n\nFail open lock heartbeats stop, and its `leaseDurationMs` is set to 1 millisecond so that it expires \"immediately\". The datastructure is left in the datastore in order to provide continuity of `fencingToken` monotonicity guarantee.\n\n## Releases\n\nWe follow semantic versioning policy (see: [semver.org](http://semver.org/)):\n\n\u003e Given a version number MAJOR.MINOR.PATCH, increment the:\n\u003e\n\u003eMAJOR version when you make incompatible API changes,\u003cbr/\u003e\n\u003eMINOR version when you add functionality in a backwards-compatible manner, and\u003cbr/\u003e\n\u003ePATCH version when you make backwards-compatible bug fixes.\n\n**caveat**: Major version zero is a special case indicating development version that may make incompatible API changes without incrementing MAJOR version.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Ftristanls%2Fdynamodb-lock-client","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Ftristanls%2Fdynamodb-lock-client","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Ftristanls%2Fdynamodb-lock-client/lists"}