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

cf-ioredis

v0.1.1

Published

ioredis-style client backed by Cloudflare KV over HTTP and WebSocket

Readme

cf-ioredis

cf-ioredis provides an ioredis-style API over a Cloudflare KV backend with both HTTP and WebSocket transports.

This is not a real Redis transport. It is a Redis-shaped client for the subset of operations that can map cleanly to a Worker-backed Cloudflare KV service.

Install

npm install cf-ioredis

Deploy Worker

Deploy to Cloudflare

Use this button to deploy the companion Cloudflare Worker to your own account before pointing the client at its HTTP or WebSocket URL.

Configuration

Config precedence is:

  1. constructor URL
  2. constructor options
  3. environment variables
  4. defaults

Environment variables

  • CLOUDFLARE_KV_URL
  • CLOUDFLARE_KV_TOKEN
  • CLOUDFLARE_KV_TIMEOUT_MS
  • CLOUDFLARE_KV_KEY_PREFIX
  • CLOUDFLARE_KV_TRANSPORT
  • CLOUDFLARE_KV_WS_URL

URL format

Use cfkv:// or redis+cfkv://.

Example:

cfkv://[email protected]/kv?timeoutMs=5000&keyPrefix=app:

The URL is converted to an HTTPS Worker base URL internally.

Usage

Read from env

import { Redis } from 'cf-ioredis'

const redis = new Redis()
const value = await redis.get('user:1')

Override env with URL

import { Redis } from 'cf-ioredis'

const redis = new Redis('cfkv://[email protected]/kv?keyPrefix=demo:')
await redis.set('user:1', 'alice')

Use options object

import { Redis } from 'cf-ioredis'

const redis = new Redis({
  url: 'cfkv://[email protected]/kv',
  timeoutMs: 3000,
  keyPrefix: 'app:'
})

Use WebSocket transport

import { Redis } from 'cf-ioredis'

const redis = new Redis({
  url: 'cfkv://[email protected]/kv',
  transport: 'ws',
  wsUrl: 'wss://worker.example.com/ws',
  allowEmulatedCommands: true
})

Run the repo example

npm run build
npm run example:ws

This example lives in examples/node-websocket/ and shows the correct shutdown pattern for WebSocket transport with await redis.quit().

Pub/Sub

import { Redis } from 'cf-ioredis'

const publisher = new Redis({
  url: 'cfkv://[email protected]/kv',
  wsUrl: 'wss://worker.example.com/ws'
})

const subscriber = new Redis({
  url: 'cfkv://[email protected]/kv',
  wsUrl: 'wss://worker.example.com/ws'
})

subscriber.on('message', (channel, message) => {
  console.log(channel, message)
})

await subscriber.subscribe('updates')
await publisher.publish('updates', 'hello')
await subscriber.unsubscribe('updates')
await Promise.all([publisher.quit(), subscriber.quit()])

Pub/sub behavior:

  • subscribe and unsubscribe use a dedicated WebSocket pub/sub connection
  • publish prefers WebSocket when there is an active pub/sub socket for that channel
  • publish falls back to HTTP POST /publish when no active pub/sub socket is available
  • v1 supports exact channel names only, with live delivery only

Run the pub/sub example

npm run build
npm run example:pubsub

This example lives in examples/node-pubsub/.

Supported API

The current surface focuses on string and key operations.

| Method | Status | Caveat | | --- | --- | --- | | get | supported | returns string | null | | set | supported | returns "OK" or null for rejected conditional writes | | del | supported | integer reply | | exists | supported | integer reply | | mget | supported | ordered array of values | | mset | supported | object-based input in v1 | | expire | supported | seconds mapped to Worker ttl ms | | pexpire | supported | millisecond ttl | | ttl | supported | derived from Worker ms ttl | | pttl | supported | raw ms ttl | | persist | supported | removes ttl if Worker supports it | | type | supported | returns string or none | | pipeline | supported | local queued batch executor | | multi | emulated | requires allowEmulatedCommands: true, not atomic | | publish | supported | uses pub/sub WS when active, otherwise HTTP fallback | | subscribe | supported | exact channel names only, requires wsUrl and WebSocket support | | unsubscribe | supported | exact channel names only, requires wsUrl and WebSocket support | | quit | supported | returns "OK" | | disconnect | supported | no-op compatibility method |

Unsupported API Families

  • hashes
  • lists
  • sets
  • sorted sets
  • streams
  • scripting
  • watch/unwatch
  • cluster/sentinel/server commands

Unsupported methods should throw UnsupportedCommandError.

Pipeline semantics

pipeline() queues commands locally and executes them in order.

const result = await redis.pipeline().get('a').set('a', '2').del('a').exec()

Result format matches common ioredis tuple style:

[
  [null, '1'],
  [null, 'OK'],
  [null, 1]
]

This is not Redis wire pipelining.

Transaction semantics

multi() is an emulated transaction-shaped wrapper on top of the same local queue.

  • not atomic
  • no optimistic locking
  • no watch
  • no rollback

Enable it explicitly:

const redis = new Redis({
  url: 'cfkv://[email protected]/kv',
  allowEmulatedCommands: true
})

const result = await redis.multi().set('a', '1').get('a').exec()

Included Worker

The repo includes a first-party Cloudflare Worker backend under worker/.

  • worker/src/index.ts is the Worker entrypoint
  • worker/src/router.ts uses hono to handle HTTP routes and auth middleware
  • worker/src/ws.ts handles WebSocket request/response messages
  • worker/src/kv.ts is the single source of truth for KV persistence and TTL metadata behavior

The Worker stores value payloads and TTL metadata in separate KV keys so ttl, pttl, and persist behave consistently across HTTP and WS.

Local Worker development

cd worker
npm install
npm test
npx wrangler dev

For a public template/deploy-button flow, worker/wrangler.jsonc is set up for automatic KV provisioning. Configure AUTH_TOKEN as a Worker secret if you want bearer-token protection.

Worker backend contract

The library expects a Worker or HTTP service that exposes operations like:

  • GET /get?key=...
  • POST /set
  • POST /mget
  • POST /mset
  • DELETE /delete
  • POST /exists
  • POST /expire
  • GET /ttl?key=...
  • POST /persist
  • GET /type?key=...
  • POST /publish
  • GET /ws for WebSocket upgrade
  • GET /pubsub/ws?channel=... for pub/sub WebSocket upgrade

Payloads are JSON and values are encoded into a small envelope so future non-string types can be introduced without changing storage format.

WebSocket messages use the same action model as the transport layer:

{
  "id": "1",
  "action": "get",
  "payload": {
    "key": "user:1"
  }
}

Responses are correlated by id and return either data or a typed error payload.

Pub/sub uses a separate WebSocket protocol with frames like:

{ "type": "subscribe", "channels": ["updates"] }

and message deliveries like:

{ "type": "message", "channel": "updates", "message": "hello" }

Development

npm install
npm test
npm run build
npm --prefix worker install
npm --prefix worker test

Local End-To-End Tests

Run the real Worker locally with wrangler dev and exercise the real client over both HTTP and WebSocket:

npm run test:integration:local

This suite:

  • starts the Worker from worker/
  • injects a local AUTH_TOKEN=test
  • tests supported client methods over HTTP
  • tests supported client methods over WebSocket
  • prints warm local latency samples

To print only the local latency benchmark output:

npm run bench:local

Current illustrative deployed measurement from the live workers.dev test run:

  • warm HTTP get average: about 189ms
  • warm WebSocket get average: about 112ms

Treat these as directional numbers only; latency depends on region, Cloudflare account state, network path, and whether the test is local or deployed.