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

@dokebi/lock-redis

v0.4.1

Published

Redis lease driver for @dokebi/lock (not a plugin; install with npm).

Readme

@dokebi/lock-redis

The single-Redis lease driver of @dokebi/lock. It is not a plugin. Its package.json has no dokebi manifest, so dokebi add refuses it. Add it with npm install @dokebi/lock-redis. core ^0.4.0, Node 24.0.0 or later, version 0.4.1 (unpublished). AI agents start with AI.md.

Configuration

Write driver: redis on a dokebi-lock backend. The option schema and validation live in @dokebi/lock.

options:
  dokebi-redis:
    connections:
      main:
        url: redis://127.0.0.1:6379/0
  dokebi-lock:
    backends:
      shared:
        driver: redis
        connection: main
        namespace: my-service
        leaseMs: 10000

connection is a name that exists in options.dokebi-redis.connections. Addresses and namespaces never get invented defaults. Apps using the same connection, namespace and key share locks and fencing counters. dokebi-lock prepares the dokebi-redis dependency automatically and imports this package. Without it, dokebi-lock fails to start with DRIVER_NOT_INSTALLED and dokebi doctor reports DRIVER_PACKAGE_MISSING.

Public API

  • createRedisLockDriver(connection, options, logger?): LockDriver — dokebi-lock calls it. options is validated again with parseRedisLockBackend (from @dokebi/lock).
  • peerDependencies: @dokebi/core, @dokebi/lock, @dokebi/redis.

Behavior

backend.acquire(key, { signal?, waitTimeoutMs? }) returns a lease with key/valid/remainingMs/fence/release()/renew(). Release it in finally. Renewal is explicit; there is no automatic renewal.

  • Acquire is one Lua script: when SET <namespace>:lock:{<sha256(key)>} token NX PX leaseMs succeeds it runs INCR <namespace>:lock-fence:{<sha256(key)>} and returns the value as a decimal string. When already held it returns the holder's remaining time (PTTL, integer ms; since 0.1.4, nil up to 0.1.3). No other acquire can slip in between acquiring and incrementing the fence. Both keys use the same hash tag {…}, so they share a slot in Redis Cluster too.
  • RELEASE and RENEW are Lua scripts that compare the owner token. Only when RELEASE deleted the key does it PUBLISH <namespace>:lock-released <sha256(key)> in the same script (0.1.4; no extra round trip, nearly free without subscribers). All three scripts are created with RedisConnection.script() and run as EVALSHA, and are sent once more as EVAL only when the server lacks the script (NOSCRIPT).
  • The fencing counter key never expires; one stays in Redis per unique lock key. A missing counter (the first time, or after Redis lost data) starts at the Redis TIME milliseconds × 1000 (0.1.2, D-0041), so new tokens are larger than old ones even after data loss. The conditions are fewer than 1000 acquires per ms on one key and a server clock that never goes back. Up to 0.1.1 it restarted from 1 (OPS-002 measurement).
  • Deadlines count from command submission, not from receiving the reply, and an expired or unknown-outcome state never becomes valid again.

The wait defaults to 5000 ms; 0 tries once, then LOCK_BUSY. Commands with an unsettled outcome are never retried automatically.

Waiting: release notifications and FIFO inside a process (0.1.4, D-0058)

  • Waiters of this process (backend) for the same key line up, and only the first tries Redis. When the first one acquires or gives up, the next becomes first. However many waiters a key has, there is one Redis attempt per backend. Other keys have their own lines.
  • When a waiter really starts waiting, the backend subscribes to <namespace>:lock-released (subscribe of @dokebi/redis 0.2.2, one subscribe connection) and disconnects when no new waiter comes within 1 second after the last one left. Without contention it never subscribes. The moment the subscription is confirmed the first waiter checks once more (a release before the subscription sends no notification it could receive).
  • When a release notification arrives, the first waiter of that key tries again at once. A release by the same process also wakes waiters only through the notification (handing over inside the process first would starve other servers).
  • From the PTTL of a busy reply it retries after min(PTTL, safety interval, remaining wait). The lease of a holder that died without releasing passes on when it expires.
  • Safety interval: notifyPollIntervalMs (default 250) while the subscription is live; pollIntervalMs (default 25) when there is no subscription (an older @dokebi/redis, debug LOCK_NOTIFY_UNAVAILABLE reason: unsupported once) or it failed (warn LOCK_NOTIFY_UNAVAILABLE once with reason: failed and the full cause; it subscribes again when waiters return more than 5 seconds later). The safety net also covers missed notifications (while the subscription was down, or a release by an old-version process).
  • signal, WAIT_TIMEOUT, QUEUE_FULL (maxWaiters counts all acquires in progress on the backend) and LOCK_BUSY are per waiter. Cancellation or timeout of a waiter in the middle never disturbs the line. waitTimeoutMs: 0 gives LOCK_BUSY at once without asking Redis when this process already has a waiter for the key (it never jumps the line).
  • Order across servers is not guaranteed: after a notification only the first waiter of each process competes. There is no wait line in Redis (cleaning up dead waiters would need constant heartbeat writes).
  • Measurement (4 processes × 8 waiters, one key held 2 ms, benchmarks/lock-contention.bench.mjs): benchmarks/results/2026-10-15-lock-contention-before.json and -after.json. Cancellation does not withdraw a command already submitted; when an acquire is confirmed after cancellation it is cleaned up by token comparison.

By default up to 128 waiters and 10000 tracked leases. Expired leases stay tracked until release or shutdown. Shutdown blocks new acquires, stops waits, then cleans up known tokens in parallel. shutdownTimeoutMs (default 5000) bounds the whole wait for acquires and releases in progress. Past it, it returns CLEANUP_TIMEOUT but keeps cleaning up owner tokens of late replies, and never reports unconfirmed cleanup as success. A cleanup failure is an error (dokebi-lock logs it at error) and the TTL is the final expiry.

Limits of fencing

A single-Redis lease; no Redlock and no global FIFO (FIFO only inside a process). valid is a conservative local estimate. Without the downstream store verifying fencing tokens atomically, mutual exclusion is not guaranteed under GC pauses or delays. A counter going back through Redis failover (replication lag) is outside the guarantee too. Deadlines, cancellation and idempotency of user work are the consumer's responsibility.

Error classification

  • Redis errors are classified by structure, not instanceof: error.plugin === 'dokebi-redis' && error.outcome === 'not-submitted' or code === 'COMMAND_REJECTED' (a server error reply, D-0049) is PROVIDER_FAILED; the rest (timeout, lost connection) is OUTCOME_UNKNOWN. Both keep the Redis error as their cause (D-0106), so a transient one answers @dokebi/redis's retryable 503 through dokebi-web. At start, a Redis whose policy evicts keys logs error REDIS_EVICTION_UNSAFE (Redis for locks must be noeviction). An acquire reply that is neither a positive decimal string (fence), an integer of at least -2 (PTTL) nor nil (busy) is OUTCOME_UNKNOWN.
  • OUTCOME_UNKNOWN may mean the change already happened, so it is never retried automatically.

A key is a non-empty, well-formed UTF-16 string, by default at most 1024 UTF-8 bytes. Stored keys are separated by namespace, feature kind and SHA-256. Tokens, original values and addresses never appear in error messages; a failure's cause is the RedisError, which names none of them.

The runnable example is example 15. Operations checks such as Redis HA and the fault matrix are tracked in OPS-002.