npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@codylabs/redlock

v0.1.0

Published

Package @codylabs/redlock from Codylabs monorepo

Readme

@codylabs/redlock

A TypeScript implementation of the Redlock algorithm for distributed locking with Redis. Provides mutual exclusion, deadlock freedom, and fault tolerance across multiple independent Redis instances.

Features

  • Redlock algorithm with majority quorum consensus
  • Early quorum resolution — resolves as soon as N/2+1 nodes agree, without waiting for slow/failing nodes
  • EVALSHA caching — sends Lua script hash instead of full script after first use; falls back to EVAL automatically
  • Multi-resource (multi-key) atomic locking with deadlock prevention via sorted key ordering
  • withLock helper with AbortSignal support — routine can detect when auto-extension fails and abort safely
  • Per-call option overrides for acquire and withLock (retry delay, jitter, max attempts)
  • Unlimited retries (maxRetryAttempts: -1)
  • Automatic lock extension
  • Cryptographically secure token generation
  • Retry with configurable delay and symmetric random jitter (avoids thundering herd)
  • Clock drift compensation per the Redlock spec
  • Atomic acquire / release / extend via Lua scripts
  • quit() for graceful connection teardown

Installation

# npm
npm install @codylabs/redlock redis

# pnpm
pnpm add @codylabs/redlock redis

redis (v5+) is a required peer dependency.

Usage

Setup

The Redlock algorithm requires N independent Redis instances (not replicas). The recommended setup is 5 instances, giving a quorum of 3.

import { createClient } from 'redis';
import { Redlock } from '@codylabs/redlock';

const clients = [
  createClient({ url: 'redis://redis1:6379' }),
  createClient({ url: 'redis://redis2:6379' }),
  createClient({ url: 'redis://redis3:6379' }),
  createClient({ url: 'redis://redis4:6379' }),
  createClient({ url: 'redis://redis5:6379' }),
];

await Promise.all(clients.map((c) => c.connect()));

const redlock = new Redlock(clients, {
  driftFactor: 0.01, // clock drift compensation (1% of TTL + 2ms constant)
  retryDelayMs: 200, // base retry delay
  retryJitterMs: 100, // symmetric jitter (±100ms) to avoid thundering herd
  maxRetryAttempts: 3, // max acquisition attempts; use -1 for unlimited
});

Basic: acquire / release

const lock = await redlock.acquire('my-resource', 30_000); // TTL: 30s

if (!lock) {
  throw new Error('Could not acquire lock');
}

try {
  // critical section
} finally {
  await lock.release();
}

Recommended: withLock

Automatically acquires, optionally extends, and always releases the lock. The routine receives an AbortSignal that is aborted if auto-extension fails mid-execution:

const result = await redlock.withLock('my-resource', 30_000, async (signal) => {
  const data = await fetchData();
  if (signal.aborted) throw signal.reason; // lock was lost
  return processData(data);
});

With automatic extension (extends the lock before it expires while the function runs):

const result = await redlock.withLock(
  'my-resource',
  30_000,
  async (signal) => {
    const data = await fetchData();
    if (signal.aborted) throw signal.reason;
    return processData(data);
  },
  { extensionThresholdMs: 5_000 }, // extend 5s before expiry
);

Per-call option overrides

Override instance-level defaults for a single acquire or withLock call:

// Retry aggressively for a high-priority resource
const lock = await redlock.acquire('critical-job', 10_000, {
  retryDelayMs: 50,
  retryJitterMs: 20,
  maxRetryAttempts: 10,
});

// Fail fast for a low-priority resource
await redlock.withLock('best-effort', 5_000, async (signal) => doWork(signal), {
  maxRetryAttempts: 0, // try once, don't retry
});

Unlimited retries

const redlock = new Redlock(clients, {
  maxRetryAttempts: -1, // keep trying until the lock is acquired
  retryDelayMs: 200,
  retryJitterMs: 100,
});

Multi-resource locking

Acquire locks on multiple resources atomically. Resources are sorted lexicographically before locking to prevent deadlocks.

const lock = await redlock.acquire(['user:123', 'order:456'], 10_000);

if (!lock) {
  throw new Error('Could not acquire locks');
}

try {
  // critical section affecting both resources
} finally {
  await lock.release();
}

Or with withLock:

await redlock.withLock(['user:123', 'order:456'], 10_000, async (signal) => {
  if (signal.aborted) throw signal.reason;
  await transferFunds(userId, orderId);
});

Manual extension

const lock = await redlock.acquire('my-resource', 5_000);

// ... later, extend by another 5s
const extended = await lock.extend(5_000);
if (!extended) {
  // lock was lost; abort the operation
}

Auto-extension

startAutoExtension accepts an optional onFailure callback. If omitted a warning is logged; if provided, the callback is called with the error instead (useful for aborting work on lock loss):

const lock = await redlock.acquire('my-resource', 30_000);
lock.startAutoExtension(5_000, (err) => {
  console.error('Lock lost!', err);
  // signal your work to stop
});

try {
  await longRunningWork();
} finally {
  lock.stopAutoExtension();
  await lock.release();
}

Inspecting lock state

lock.isValid; // true if not released and not expired
lock.isReleased; // true after release() is called
lock.isExpired; // true if past the TTL
lock.expirationTime; // Date when the lock expires
lock.resourceKeys; // string[] of locked keys

Lifecycle / cleanup

// Gracefully close all Redis connections managed by Redlock
await redlock.quit();

Using Redis Cluster or Sentinel

Redlock accepts RedisClientType, RedisClusterType, and RedisSentinelType from the redis package.

Redis Cluster — the cluster handles replication internally, so a single cluster client counts as one Redlock node:

import { createCluster } from 'redis';

const cluster = createCluster({
  rootNodes: [{ url: 'redis://node1:6379' }, { url: 'redis://node2:6379' }, { url: 'redis://node3:6379' }],
});
await cluster.connect();

const redlock = new Redlock([cluster]);

Redis Sentinel — Sentinel provides HA for a single logical instance, also treated as one Redlock node:

import { createSentinel } from 'redis';

const sentinel = createSentinel({
  sentinelRootNodes: [
    { host: 'sentinel1', port: 26379 },
    { host: 'sentinel2', port: 26379 },
    { host: 'sentinel3', port: 26379 },
  ],
  name: 'mymaster',
});
await sentinel.connect();

const redlock = new Redlock([sentinel]);

Note: For the strongest fault tolerance guarantees of the Redlock algorithm, use multiple independent Redis instances (not replicas of each other). A single Cluster or Sentinel client gives HA for one logical node but does not provide cross-node quorum.

Configuration

| Option | Type | Default | Description | | ------------------ | -------- | ------- | ---------------------------------------------------------------------------- | | driftFactor | number | 0.01 | Clock drift factor (0–0.1). Applied as driftFactor × TTL + 2ms. | | retryDelayMs | number | 200 | Base delay in ms between acquisition attempts. | | retryJitterMs | number | 100 | Symmetric jitter (±N ms) added to retry delay to avoid thundering herd. | | maxRetryAttempts | number | 3 | Total acquisition attempts before giving up. Use -1 for unlimited retries. |

All options can be overridden per acquire / withLock call via AcquireOptions / WithLockOptions.

Error types

| Class | error.name | When thrown | | ----------------------- | ------------------------- | ------------------------------------------------- | | InvalidParameterError | 'InvalidParameterError' | Invalid arguments (empty key, negative TTL, etc.) | | RedisConnectionError | 'RedisConnectionError' | Redis operation failure |

Algorithm notes

This implementation follows the official Redlock specification:

  • Quorum: a lock is considered acquired only when ⌊N/2⌋ + 1 instances confirm it.
  • Early quorum resolution: operations resolve as soon as the outcome is determined — no waiting for slow or failing nodes.
  • Effective validity: TTL - elapsed - (driftFactor × TTL + 2ms) — the usable lock lifetime after drift and network latency are subtracted.
  • Timing: if the effective validity ≤ 1ms after acquisition, the attempt is rejected even with majority consensus.
  • EVALSHA caching: Lua scripts are identified by their SHA1 hash. Redis caches them after the first EVAL; subsequent calls use EVALSHA (faster, less bandwidth). The library falls back transparently on NOSCRIPT errors.
  • Lua atomicity: acquire, release, and extend all use atomic Lua scripts to prevent race conditions within each Redis instance.
  • Multi-resource deadlock prevention: keys are sorted lexicographically before locking so concurrent callers always acquire in the same order.