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

@tslock/cloudflare-kv

v2.3.1

Published

Advisory Cloudflare Workers KV lock provider for TSLock (eventually consistent, not a Durable Objects substitute)

Readme

@tslock/cloudflare-kv

Advisory lock provider backed by Cloudflare Workers KV.

This is not mutual exclusion. Workers KV is eventually consistent, has no compare-and-swap, and caches reads (including “key missing”) for about 60 seconds. Two Workers can both acquire the same name. Use @tslock/cloudflare-do when overlapping execution is unacceptable.

Construction requires acknowledgeAdvisoryLock: true so that choice is explicit in code.

When a duplicate run is possible

| Failure | What you observe | |---|---| | Stale or cached read | Both callers miss the live record (or see an old miss) and both PUT their own token. | | Delayed delete | After unlock(), another location still sees the previous value and skips, or a stale copy of your token makes DELETE remove a newer holder. | | 60 second TTL floor | expirationTtl cannot be below 60. The lease you check is lockUntil. The key can outlive a short lease, and KV can also drop the key on its own clock once the TTL elapses. | | 1 write/second/key | A second put or delete inside one second throws (HTTP 429). That includes a fast unlock and keep-alive when lockAtMostFor is under about two seconds. |

Details and the in-memory reproductions live in Failure modes.

A confirm read after PUT (default confirmWrite: true) only checks that this caller saw its token. It does not stop another location from doing the same.

Installation

pnpm add @tslock/core @tslock/cloudflare-kv

No Cloudflare types package is required. Pass any object with get, put, and delete matching a Workers KVNamespace binding.

Workers binding

import { createLockConfig, DefaultLockingTaskExecutor } from '@tslock/core';
import { createCloudflareKvLockProvider } from '@tslock/cloudflare-kv';

interface Env {
  LOCKS: KVNamespace;
}

export default {
  async scheduled(_event: ScheduledEvent, env: Env): Promise<void> {
    const provider = createCloudflareKvLockProvider({
      kv: env.LOCKS,
      acknowledgeAdvisoryLock: true,
    });
    const executor = new DefaultLockingTaskExecutor(provider);
    await executor.executeWithLock(() => myScheduledTask(), createLockConfig('my-task', '5m', '1m'));
  },
};

Prefer Durable Objects for this scheduled task if two overlapping runs would be harmful:

import { createCloudflareDoLockProvider } from '@tslock/cloudflare-do';

REST (Node, optional)

The REST helper talks to the Cloudflare API. It uses the same get/put/delete protocol and does not make the lock strongly consistent.

import { createCloudflareKvLockProvider, createCloudflareKvRestNamespace } from '@tslock/cloudflare-kv';

const provider = createCloudflareKvLockProvider({
  acknowledgeAdvisoryLock: true,
  kv: createCloudflareKvRestNamespace({
    accountId: process.env.CLOUDFLARE_ACCOUNT_ID!,
    namespaceId: process.env.CLOUDFLARE_KV_NAMESPACE_ID!,
    apiToken: process.env.CLOUDFLARE_KV_API_TOKEN!,
  }),
});

Configuration

| Option | Default | Description | |---|---|---| | kv | — | KVNamespace binding or REST namespace (get / put / delete). | | acknowledgeAdvisoryLock | — | Must be true. | | keyPrefix | tslock: | Prepended to the lock name. Storage key must be 1..512 UTF-8 bytes. | | confirmWrite | true | Re-read after put. Mismatch returns “not acquired” and does not delete. |

Records are JSON: { lockUntil, lockedAt, lockedBy, token }. token is the ownership id for unlock and extend. lockedBy is the hostname and is not an ownership check.

expirationTtl is max(60, floor(remainingMs / 1000) + 1). Acquire and extend compare lockUntil to ClockProvider.now(), not to whether the key exists.

Tests

Unit and shared extensible contracts use an in-memory namespace (fresh reads). Failure-mode tests cover two cached misses, a delayed delete, the TTL floor (MutableClock), write-rate rejection, and a stale unlock that deletes a newer holder.

Miniflare / Wrangler is not part of CI. Real KV will not satisfy “exactly one winner” or sub-second unlock/reacquire.

License

Apache 2.0 — see LICENSE for details.