Skip to content

cloudflare-kv - feat: Add Cloudflare Workers KV storage adapter - #1985

Merged
jaredwray merged 8 commits into
mainfrom
claude/cloudflare-kv-adapter-l95bte
Jun 25, 2026
Merged

cloudflare-kv - feat: Add Cloudflare Workers KV storage adapter#1985
jaredwray merged 8 commits into
mainfrom
claude/cloudflare-kv-adapter-l95bte

Conversation

@jaredwray

Copy link
Copy Markdown
Owner

Summary

Adds @keyv/cloudflare-kv, a new first-party storage adapter backed by Cloudflare Workers KV. It works in two modes:

  • Binding mode — pass a native Worker KV binding (env.MY_KV) or a Miniflare-created namespace directly.
  • REST mode — pass accountId / namespaceId / apiToken and the adapter talks to the Cloudflare REST API from any Node.js process (via a built-in CloudflareKVRestClient).

The adapter mirrors the conventions of the other first-party adapters (modeled closely on @keyv/dynamo): Hookified base, capabilities.expires v6 contract, namespace/key-prefix helpers, createKeyv helper, batch ops, and a paginated async iterator.

Design notes / best practices

  • String storage + envelope: KV only stores strings, so each value is persisted as a small JSON envelope { value, expires }.
  • Precise TTLs despite KV's 60s minimum: Cloudflare KV's native expiry has a 60-second minimum and is eventually consistent. Expiry is therefore enforced client-side on every read for millisecond precision, while a native KV expiration is also attached for TTLs ≥ 60s so Cloudflare reclaims space on its own.
  • Accurate delete semantics: delete/deleteMany return whether the key actually existed.
  • Graceful decoding: values written outside the adapter (raw strings / unrelated JSON) are still readable.

Testing locally — no Cloudflare account required

Tests run entirely against Miniflare, the official local Workers runtime — no Docker and no credentials needed. The REST client is covered with mocked fetch.

  • Runs the shared @keyv/test-suite compliance suites (keyvTestSuite, keyvIteratorTests, storageTestSuite) plus adapter-specific tests.
  • 116 tests, 100% coverage (statements / branches / functions / lines).

Other changes

  • pnpm-workspace.yaml: allow the workerd build (used by Miniflare); make the surfaced sharp decision explicit (false, preserving prior behavior).
  • Root README.md and website/site/docs/index.md: list the new adapter.

Notes for reviewers

  • No Docker service is needed, so nothing was added to scripts/docker-compose*. CI picks the package up automatically via pnpm -r test:ci.
  • miniflare is a dev-only dependency; runtime deps are just hookified (+ keyv peer).

🤖 Generated with Claude Code


Generated by Claude Code

Adds @keyv/cloudflare-kv, a storage adapter backed by Cloudflare Workers
KV. It works with a native Worker KV binding (or a Miniflare namespace) and
with the Cloudflare REST API from plain Node.js via account credentials.

Highlights:
- Dual mode: pass a `kvNamespace` binding, or `accountId`/`namespaceId`/
  `apiToken` REST credentials (built-in CloudflareKVRestClient).
- Values stored as a JSON envelope `{ value, expires }`; expiry enforced
  client-side for millisecond precision, with a native KV `expiration` set
  for TTLs >= 60s so Cloudflare reclaims space on its own.
- Namespacing, batch ops (get/set/has/deleteMany), and a paginated async
  iterator, mirroring the other first-party adapters.
- Fully testable locally with Miniflare (no Cloudflare account required);
  116 tests at 100% coverage using @keyv/test-suite compliance suites.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_015hhM6Rqsdg9sao9QYNZbX7
@socket-security

socket-security Bot commented Jun 25, 2026

Copy link
Copy Markdown

Review the following changes in direct dependencies. Learn more about Socket for GitHub.

Diff Package Supply Chain
Security
Vulnerability Quality Maintenance License
Addedminiflare@​4.20260617.19910010096100

View full report

@gemini-code-assist gemini-code-assist Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

This pull request introduces a new Cloudflare Workers KV storage adapter (@keyv/cloudflare-kv) for Keyv, supporting both native Worker bindings and the Cloudflare REST API. The feedback highlights three high-severity performance and resource utilization issues in storage/cloudflare-kv/src/index.ts. Specifically, the clear() method should delete keys in batches to prevent memory and rate-limiting issues, and both get() and has() should delete expired keys directly via this._client.delete() to avoid redundant get requests and reduce latency.

Comment thread storage/cloudflare-kv/src/index.ts Outdated
Comment thread storage/cloudflare-kv/src/index.ts Outdated
Comment thread storage/cloudflare-kv/src/index.ts Outdated
Addresses code-review feedback:
- clear() now deletes keys in bounded batches (configurable via
  clearBatchSize, default 100) instead of buffering every key and firing
  unbounded concurrent deletes, avoiding socket exhaustion and KV rate
  limits on large namespaces.
- get() and has() delete expired keys directly via the underlying client
  rather than this.delete(), which performed a redundant read to confirm
  existence we already had.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_015hhM6Rqsdg9sao9QYNZbX7
@codecov

codecov Bot commented Jun 25, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 100.00%. Comparing base (f067213) to head (e36b76e).

Additional details and impacted files
@@            Coverage Diff             @@
##              main     #1985    +/-   ##
==========================================
  Coverage   100.00%   100.00%            
==========================================
  Files           53        55     +2     
  Lines         4665      4887   +222     
  Branches       725       779    +54     
==========================================
+ Hits          4665      4887   +222     

☔ View full report in Codecov by Harness.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: d02aed9331

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread storage/cloudflare-kv/src/index.ts Outdated
A TTL in the 59-60s range (or exactly 60000ms) rounded up via Math.ceil to
ttlSeconds === 60 and attached a native absolute `expiration`. After request
latency or host clock skew that absolute timestamp could fall under
Cloudflare KV's 60-second minimum, so the put was rejected and set() returned
false instead of storing the entry with client-side expiry.

Fix: derive the TTL from the raw millisecond delta with Math.floor and only
attach a native expiry when it is strictly greater than 60s (smallest value
sent is 61s). Use the relative `expirationTtl`, which KV evaluates against its
own clock, so a slow host clock can't push the deadline below the minimum.
Client-side expiry continues to enforce the exact deadline on read.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_015hhM6Rqsdg9sao9QYNZbX7

@jaredwray jaredwray left a comment

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I also dont see iterator on this or capabilities?

Comment thread storage/cloudflare-kv/src/index.ts Outdated
…e tests

Reworks the adapter per review feedback and adds an explicit mode option.

- Storage: stop wrapping values in an adapter-level JSON envelope. Keyv owns
  serialization, so the (already-serialized) value string is stored in KV
  verbatim and the absolute expiry is kept in KV metadata, enforced
  client-side for millisecond-precise TTLs.
- Modes: add a `mode` option ('bind' | 'rest', default 'bind') plus a `.mode`
  getter. 'bind' uses a native KV binding (Worker env or Miniflare); 'rest'
  uses the Cloudflare REST API from plain Node.js. Mode is inferred when
  omitted.
- Tests now run against a real local Cloudflare KV: 'bind' against Miniflare
  directly and 'rest' against a local HTTP bridge that implements the
  Cloudflare REST API on top of the same Miniflare namespace (no mocked
  fetch). Storage compliance suite runs against both paths. 154 tests, 100%
  coverage.
- Add a scheduled `cloudflare-kv-live` GitHub workflow + live integration
  test that exercise the real Cloudflare KV REST API (self-skips without
  secrets).
- README: document bind vs rest modes, the metadata storage model, the local
  emulation/bridge, and the live workflow.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_015hhM6Rqsdg9sao9QYNZbX7
Comment thread storage/cloudflare-kv/test/test.ts Fixed
…esponse

CodeQL flagged the local REST test bridge writing the raw error (potentially a
stack trace) into the HTTP response. Return a generic message instead.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_015hhM6Rqsdg9sao9QYNZbX7
Comment thread .github/workflows/cloudflare-kv-live.yaml Outdated
claude added 3 commits June 25, 2026 18:02
…p cron

Trigger the live integration workflow on pull requests and pushes to main that
touch storage/cloudflare-kv (plus manual dispatch) instead of a weekly cron.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_015hhM6Rqsdg9sao9QYNZbX7
Covers setMany/getMany/hasMany/deleteMany against the real Cloudflare KV REST
API alongside the existing single-key, TTL, and iterator live checks.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_015hhM6Rqsdg9sao9QYNZbX7
Rename the workflow file, name, job, self-path filter, and doc/test references
from cloudflare-kv-live to cloudflare-keyv-integration.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_015hhM6Rqsdg9sao9QYNZbX7
@jaredwray
jaredwray merged commit e004467 into main Jun 25, 2026
16 of 17 checks passed
@jaredwray
jaredwray deleted the claude/cloudflare-kv-adapter-l95bte branch June 25, 2026 18:21
This was referenced Aug 3, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants