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

v0.4.1

Published

Named lock backends for Dokebi with fencing tokens: in-process or Redis leases.

Downloads

516

Readme

@dokebi/lock

An optional per-app lock plugin. Each backend picks a driver: local (default, exclusion inside one event loop) or redis (a lease shared by processes and servers; needs @dokebi/lock-redis). core ^0.4.0, Node.js 24.0.0 or later, version 0.4.1 (unpublished). Configure it under options.dokebi-lock.backends in the single YAML. AI agents start with AI.md.

schemaVersion: 1
options:
  dokebi-lock:
    backends:
      orders:
        driver: local # may be omitted
        maxKeys: 10000
        maxWaiters: 128
        waitTimeoutMs: 5000
        maxKeyBytes: 1024
      jobs: {}
      shared:
        driver: redis
        connection: main # a name from dokebi-redis.connections
        namespace: my-service
        leaseMs: 10000

Registration and use

  • The factory input is { app, options?, entry? } (entry is the module of the entry file src/infra/lock/extension.ts, see "Extension" below). createPlugin({ app: app.view }) reads app.config['dokebi-lock']; given options are used instead. Invalid options are INVALID_PLUGIN_OPTIONS (cause: the core INVALID_OPTIONS, paths in details.issues). The options are one defineOptions declaration in src/options.ts, and the build generates options.schema.json from it (D-0063).
  • Register, start, then get app.get(plugin).backend('orders'). A consumer plugin puts import { dependency } from '@dokebi/lock' in metadata.dependencies and uses context.getDependency(dependency).
const lease = await backend.acquire('order:42', { waitTimeoutMs: 5000 });
try {
  // critical section for this backend and key. Pass lease.fence along with writes to an outside store.
} finally {
  await lease.release();
}

Lease contract (both drivers)

| Field | local | redis | |---|---|---| | key | the acquired key | same | | valid | true until release or shutdown | a conservative local estimate; false after expiry, release or an unknown outcome | | remainingMs | Number.POSITIVE_INFINITY (0 when invalid) | remaining lease time | | fence | decimal string; grows with each acquire in the backend | decimal string; grows per key with Redis INCR | | renew() | returns valid | extends the TTL after comparing the owner token | | release() | true when ownership was handed on, false when already released | deletes after comparing the owner token |

Fencing tokens

  • fence grows with each acquire of the same backend and key (not necessarily by one). local shares one counter per backend, so it does not remember released keys. redis keeps the counter <namespace>:lock-fence:<sha256(key)> without expiry (one Redis key stays per unique key).
  • Without the downstream store verifying the fencing token atomically, mutual exclusion is not guaranteed under GC pauses or delays. Example: UPDATE … SET fence = $1 WHERE fence < $1. Writing the value without comparing does not stop the late write of an expired former owner.
  • The token is a string. Compare with BigInt(fence).

Drivers

| Key (local) | Type | Default | Description | |---|---|---|---| | maxKeys | integer | 10000 | Keys held at once | | maxWaiters | integer | 128 | Waiters of the backend | | waitTimeoutMs | integer ms | 5000 | Default wait | | maxKeyBytes | integer | 1024 | Key UTF-8 bytes |

  • driver: local: the provider offers names/backend(name) and the backend offers acquire(key, { signal?, waitTimeoutMs? }). The same key is FIFO and different keys can be held at once. maxWaiters counts waiters of the whole backend, maxKeys the keys being held. waitTimeoutMs=0 tries at once, then LOCK_BUSY; with maxWaiters=0 a free key can still be acquired at once. An acquired lock never expires or releases on its own, so release it in finally. Not shared across apps, backends, threads or processes.
  • driver: redis: fields connection, namespace, leaseMs (required), waitTimeoutMs (5000), pollIntervalMs (25), notifyPollIntervalMs (250, the safety-net retry while release notifications are live, D-0058), maxWaiters (4096, 1–100000: every acquire in progress counts, D-0106), maxLeases (10000), maxKeyBytes (1024), shutdownTimeoutMs (5000). Waiting for the same key is FIFO only inside a process (no order across servers). Behavior is described in @dokebi/lock-redis.
  • With at least one redis backend, dokebi-redis ^0.4.0 becomes a runtime dependency. When not registered, it is created from the @dokebi/redis package. With local only there is no dependency and the Redis package is never loaded.
  • The driver package is imported in setup. When it is not installed, start fails with LockError DRIVER_NOT_INSTALLED and the fix says npm install @dokebi/lock-redis. A missing connection name is UNKNOWN_CONNECTION.
  • The driver contract is LockDriver { view: LockBackend; close(): Promise<void> }.

Extension (D-0064)

  • dokebi add creates the user-owned entry file src/infra/lock/extension.ts (an empty LockExtension subclass). Left empty, behavior is the default and costs nothing (hooks you did not override are never called).
  • Hooks (protected override, three): dependencies(), createDriver(input), wrapBackend(input). Constructor parameters or shadowing a non-hook member fail with INVALID_EXTENSION (fix, data.reason) when the plugin is created.
  • Driver registry: the built-in local and redis stay, and app drivers are added with export const drivers = { <name>: (input) => driver } in the entry file or with defineLockPlugin({ drivers }). YAML uses driver: <name> and settings. Details in AI.md §8 and examples/29-extension.

Install (CLI)

  • Add it with npx dokebi add @dokebi/lock. @dokebi/lock-redis is not a plugin; add it with npm install.
  • planInstall and check of the install module (dist/install.js) return requires: ['dokebi-redis'] when a redis backend exists. check reports an error diagnostic DRIVER_PACKAGE_MISSING per backend (path backends.<name>.driver) when package.json lacks @dokebi/lock-redis.
  • The manifest is schemaVersion 2 and declares dokebi-redis as an optional dependency. @dokebi/redis and @dokebi/lock-redis are optional peerDependencies.

Shutdown and logs

  • signal applies only before acquiring and while waiting. After the consumers' reverse-order cleanup the provider closes the drivers. local rejects waiters and invalidates held leases; redis releases held leases within a bounded deadline. User code is never stopped forcibly. Afterwards backend(name) is CLOSED.
  • Logs: debug LOCK_BACKEND_READY when a backend is ready, error when a driver close fails (the driver's error code such as CLEANUP_TIMEOUT or CLEANUP_FAILED, or LOCK_CLOSE_FAILED). Fields are backend and code only.
  • Only well-formed, non-empty UTF-16 keys are accepted, with a limited UTF-8 length.

Main LockError.code values: INVALID_OPTIONS, INVALID_KEY, UNKNOWN_BACKEND, UNKNOWN_CONNECTION, CLOSED, LOCK_BUSY, KEY_LIMIT, QUEUE_FULL, ABORTED, WAIT_TIMEOUT, DRIVER_NOT_INSTALLED, DRIVER_INVALID, (redis) PROVIDER_FAILED, OUTCOME_UNKNOWN, LEASE_LOST, CLEANUP_FAILED, CLEANUP_TIMEOUT (with the Redis error as cause). Through dokebi-web QUEUE_FULL/KEY_LIMIT answer 503 LOCK_OVERLOADED and CLOSED 503 LOCK_UNAVAILABLE (Retry-After: 1, D-0106); a transient Redis cause answers @dokebi/redis's 503 (D-0102). LockError is a DokebiError (plugin: 'dokebi-lock', fix), and other packages classify it by code. Extension: UNKNOWN_DRIVER, DRIVER_CONFLICT, INVALID_ENTRY, INVALID_EXTENSION. Keys and original data never appear in errors.

Runnable examples: example 14 (local), example 15 (redis), and for extensions examples/29-extension.