Skip to content

mono - chore: Release v6.1.0 - #2192

Open
jaredwray wants to merge 5 commits into
mainfrom
release/v6.1.0
Open

jaredwray wants to merge 5 commits into
mainfrom
release/v6.1.0

Conversation

@jaredwray

@jaredwray jaredwray commented Oct 4, 2026 •

Copy link
Copy Markdown
Owner

Please check if the PR fulfills these requirements

What kind of change does this PR introduce?

Release. Promotes the 6.0 line from release candidate to 6.1.0, the first stable v6 release. It goes to npm's latest tag, and v6 becomes an LTS line alongside v5.

Release summary

  • Bumps all 21 workspace packages from 6.0.0-rc.1 to 6.1.0 with pnpm version:sync (20 published packages, plus the private @keyv/website and the root).
  • Why 6.1.0 and not 6.0.0: npm still records 6.0.0 as published for every package, so that number can't be published again.
  • The workflow needs no change. LATEST_MAJOR is already 6 (mono - fix: stage stable v6 releases under latest, not v6-lts #2148), so a stable 6.1.0 release stages every package under latest.
  • Docs that described v6 as a pre-release now describe it as stable:
    • versioning.md: dist-tags after the switch; keyv@6 now resolves; v6 is an LTS line, so breaking changes land in v7; v6's Node.js floor is 22.19.0.
    • v5-to-v6.md: version examples now use 6.1.0.
    • README, the CONTRIBUTING.md example and the release-publish.ts header.
  • Covers 96 commits since v6.0.0-rc.1.
  • Ports the redis test fix from redis - test: Stop the keep-alive test from racing the connect timeout #2193. getClient > should keep a successful connection alive after connectionTimeout elapses raced a 50 ms timer and failed codecov here and on main. The release workflow's test job runs it too. The test now fakes Keyv's connect timer instead of racing it. Once redis - test: Stop the keep-alive test from racing the connect timeout #2193 merges, the change here is a no-op.

Packages

All packages move in lockstep.

Package Current New npm dist-tag Notes
keyv + all 19 published @keyv/* packages 6.0.0-rc.1 6.1.0 latest rc → stable, 96 commits
@keyv/website 6.0.0-rc.1 6.1.0 — private, not published

Release workflow readiness (checked locally against the live registry)

The staged-publish path (#2057, #2124) has never run. The last release run was on Aug 4, so 6.1.0 is its first real use. I ran its steps locally first:

  • Plan. DRY_RUN=true LATEST_MAJOR=6 pnpm tsx scripts/release-publish.ts produces the plan below. All 20 packages are staged under latest, keyv goes first, and the downgrade guard passes for each one: every current latest is below 6.1.0. The highest is @keyv/cloudflare-kv@6.0.0-beta.4, one of the packages that are new in v6.
    Release version: 6.1.0
    Dist-tag (keyv): latest  (stable v6 === LATEST_MAJOR -> "latest")
    20 to publish, 0 already published.
    latest guard ok: keyv@6.1.0 >= current latest (5.6.0).
    
  • Pack and stage commands. For each of the 20 packages I ran the job's exact pnpm --filter <name> pack --out ./packed/<name>.tgz. Then I ran its exact pnpm stage publish <tgz> --tag latest --no-git-checks --access public --provenance, adding --dry-run and pointing it at a dead local registry. pnpm 11.25.0 accepts every flag, and all 20 report would stage.
  • Tarball contents. Every tarball has version 6.1.0 and a keyv peer range of ^6.1.0. None has a leftover workspace: range or install lifecycle scripts. The files are only dist/, LICENSE, README.md and package.json, plus the documented scripts/migrate-v6.ts in mongo, mysql and postgres.
  • Workflow files. All workflow files validate against GitHub's workflow schema, including concurrency.queue: max.
  • v5 line. The v5 branch's release.mjs picks dist-tags from the registry, so once latest is 6.1.0 it stages v5-line releases under v{major}-lts with no change.

Verification

  • pnpm install --frozen-lockfile — the lockfile doesn't change
  • pnpm build
  • pnpm test:scripts — 22 passed
  • Tests for the 11 packages that don't need Docker (keyv, bigmap, serialize-superjson, serialize-msgpackr, compress-brotli, compress-gzip, compress-lz4, encrypt-node, encrypt-web, sqlite, cloudflare-kv) — 876 passed, 4 todo
  • storage/redis get-client.test.ts against a local Redis — 35 passed, plus the slow-handshake checks in redis - test: Stop the keep-alive test from racing the connect timeout #2193
  • pnpm --filter @keyv/website test (100% coverage) and pnpm website:build; the new links and anchors resolve
  • biome check — clean (one info message about a deprecated config field, which main shows too)
  • Docker-backed suites (redis, valkey, mongo, mysql, postgres, etcd, memcache, dynamo) — no Docker in this sandbox; CI and the release workflow's test job run them

Post-merge: releasing 6.1.0

  1. Dry run first (recommended). Go to Actions → release → Run workflow on main and leave Dry run on. This exercises build, the full test suite with services, the Aikido gate, test:scripts and the plan, and stages nothing. GitHub Releases are immutable here, so finding a problem before tagging is cheaper than after.

  2. Order the pending v5 cut. keyv@5.6.1 and @keyv/sqlite@4.1.0 are bumped on v5 but not on npm. A version's dist-tag is fixed when it is staged and applied when it is approved, so don't interleave the two lines. Pick one order:

    • 6.1.0 first, then v5. Stage and approve 6.1.0, then dispatch the v5 workflow. It stages keyv@5.6.1 under v5-lts and @keyv/sqlite@4.1.0 under v4-lts.
    • v5 first. Stage and approve the v5 cut under latest, then release 6.1.0.

    Never approve a v5 version staged as latest after 6.1.0 is approved: it would move latest back to v5.

  3. Run pnpm stage list and confirm nothing else is waiting in the queue.

  4. Create a GitHub Release with tag v6.1.0 on main at this PR's merge commit, using the notes below. Don't mark it as a pre-release. Publishing it runs release.yaml, which stages the 20 packages under latest. deploy-website.yaml deploys the docs at the same time.

  5. Approve the staged versions in one sitting, keyv first (pnpm stage approve <id>…). Then check that npm view keyv dist-tags shows latest: '6.1.0', and spot-check an adapter.

  6. Optional, by hand with 2FA, because OIDC can't run npm dist-tag:

    • If you released v5 first, point v5-lts at the newest v5: npm dist-tag add keyv@5.6.1 v5-lts.
    • Drop the stale next tags (for example keyv@next → 5.0.0-rc.1) with npm dist-tag rm <pkg> next.

    alpha, beta and rc can stay. The docs say they point at 6.0.0 pre-releases until the next pre-release.

Release notes (for the GitHub Release)

Paste everything below the line into the v6.1.0 GitHub Release, and replace YYYY-MM-DD with the release date. The list counts 97 PRs, including #2193, whose change ships with this release either way.


keyv v6.1.0 — YYYY-MM-DD

First stable release of Keyv v6, now on npm's latest tag. Keyv v5 continues in maintenance: releases from the v5 line no longer move latest, and keyv 5.x releases are tagged v5-lts. To upgrade from v5, follow the v5 to v6 migration guide or point your coding agent at the keyv-migrate skill.

Stable v6 starts at 6.1.0; there is no 6.0.0 release.

All 20 published packages (keyv and 19 @keyv/* packages) release in lockstep at this version. @keyv/website is private and not published.

⚠ BREAKING CHANGES (since 6.0.0-rc.1)

These change behavior for projects already on 6.0.0-rc.1 ("rc.1" below). Coming from v5, start with the migration guide.

  • keyv, adapters: the namespace separator option is namespaceSeparator on every adapter that joins the namespace and the key, and it defaults to :: (d0bc310, mono - feat: namespaceSeparator on every adapter, defaulting to :: #2189)
    The old names have no alias: keySeparator on KeyvMemoryAdapter and KeyvBridgeAdapter, and keyPrefixSeparator on @keyv/redis, @keyv/etcd, @keyv/dynamo and @keyv/cloudflare-kv. The separator changed from : to :: for the memory and bridge adapters, Etcd, DynamoDB, Cloudflare KV, Memcache and Valkey (namespace:<ns>::<key>, sets:<ns>::<key>). Redis already used ::. KeyvStats per-key maps such as hitKeys are keyed namespace::key.
    Migration: rename the option and property in your code. In plain JavaScript the old name is ignored. To keep reading namespaced entries rc.1 wrote, set namespaceSeparator: ':' on the adapter. For a store that Keyv wraps for you, pass your own wrapper, as in new Keyv(new KeyvMemoryAdapter(store, { namespaceSeparator: ':' }), { namespace }), or set keyv.store.namespaceSeparator = ':'. See Namespace Overhaul and keyPrefixSeparator Is Now namespaceSeparator.
  • keyv: the throwOnErrors option, getter and setter are removed, and every method follows the Node.js EventEmitter error rule (860dba7, keyv - fix: follow the EventEmitter error rule and remove throwOnErrors #2155)
    With an error listener attached, a failed call returns a fallback value. With none, it rejects. getMany(), getRaw(), getManyRaw() and iterator() now follow the rule too: with a listener they return undefined values, and the iterator ends, where rc.1 rejected. A failed read counts as stat:error only, no longer also as stat:miss. @keyv/redis reports a failure once, by rejecting or by emitting error, and a failed connect with throwOnConnectError rejects with the connection error as cause.
    Migration: remove throwOnErrors. Attach an error listener to get fallback values, or leave it off to get rejections. @keyv/redis keeps its own throwOnErrors and throwOnConnectError adapter options. See Error Handling Changed and throwOnErrors Was Removed.
  • keyv: a namespace set on the storage adapter is kept when Keyv has none (c6e88e7, keyv - fix: keep a namespace set on the storage adapter #2156)
    rc.1 replaced it with undefined, so these setups wrote keys without a namespace: an adapter given a namespace directly, as in new Keyv(new KeyvRedis(uri, { namespace: 'app' })); createKeyv() from @keyv/memcache or @keyv/etcd with a namespace option; and @keyv/etcd's namespace option, which its constructor ignored. They now use that namespace, and keyv.namespace returns it. KeyvBridgeAdapter also keeps the namespace of a wrapped store that scopes its own keys.
    Migration: data rc.1 wrote in these setups has no namespace. To keep reading it, remove the namespace from the adapter, or let the data refill. A namespace passed to Keyv still takes precedence. See Namespace Overhaul.
  • keyv: a namespaced clear() fails instead of wiping a store it can't scope (790eb3c, keyv - fix: a namespaced clear() fails instead of wiping a store it can't scope #2186)
    When KeyvMemoryAdapter or KeyvBridgeAdapter had a namespace but no way to list the store's keys, rc.1 called the store's own clear() and deleted every namespace. Now nothing is deleted and Keyv emits error, so the call rejects when no listener is attached.
    Migration: give the store keys() (synchronous) or iterator() (async), or move it to the v6 adapter contract. To empty the whole store on purpose, call clear() on a Keyv instance without a namespace. A Map, quick-lru, lru.min and every v6 adapter are not affected. See A Namespaced clear() Fails When the Store Can't List Its Keys.
  • keyv: writes fail when an encryption adapter is set and serialization is off (b73ee16, keyv - fix: Writes fail with an error when encryption is set without serialization #2176)
    rc.1 skipped encryption with serialization: false and stored values as plaintext. Now set(), setMany(), setRaw() and setManyRaw() store nothing, emit error, and return false with a listener attached or reject without one. Reads are unchanged.
    Migration: keep serialization on, which is the default, when you use encryption. Values rc.1 wrote in that setup were stored unencrypted. See Encryption Adapters.
  • postgres, sqlite, mongo, dynamo, cloudflare-kv, etcd: keys that start with <namespace>: are stored as given (9dec04c, mono - fix: keys that start with the namespace no longer collide #2159)
    rc.1 treated such a key as already prefixed. In namespace user, postgres, sqlite and mongo stored set('user:1') as 1, and dynamo, cloudflare-kv and etcd stored it without adding their prefix, so user:1 and 1 were one entry. Other keys are not affected.
    Migration: entries rc.1 wrote under such keys now sit under a different stored key and read as missing. Write them again after upgrading.
  • encrypt-node, encrypt-web: only authenticated ciphers and text-safe encodings are accepted (b06ec8b, mono - fix: authenticated ciphers only, bridge setMany() results, serializer __proto__ #2187)
    @keyv/encrypt-node accepts aes-128-gcm, aes-192-gcm, aes-256-gcm, aes-128-ccm, aes-192-ccm, aes-256-ccm and chacha20-poly1305, with base64, base64url or hex output, typed as NodeAlgorithm and NodeEncoding. @keyv/encrypt-web accepts only aes-128-gcm, aes-192-gcm and aes-256-gcm; AES-CBC is removed. Any other value throws when the adapter is created.
    Migration: the defaults (aes-256-gcm, base64) are unchanged, and values written with a supported algorithm and encoding need no change. Values encrypted with any other algorithm or encoding can't be decrypted after the upgrade. To keep them, re-encrypt before you upgrade: on rc.1, read each value with your current settings and write it back with a supported algorithm and encoding. A cache can instead start empty and refill. See Encryption Adapters.
  • dynamo: keys written without a TTL no longer expire, and sixHoursInMilliseconds is removed (476761d, dynamo - fix: clear() removes every scan page and keys without a ttl no longer expire #2161)
    rc.1 gave such keys an expiry six hours after the write.
    Migration: for a default expiry, set Keyv's ttl option, as in new Keyv(store, { ttl: 6 * 60 * 60 * 1000 }). Remove code that reads or assigns store.sixHoursInMilliseconds. Keys rc.1 wrote keep their expiry until they are written again. See @keyv/dynamo Keys Without a TTL No Longer Expire.
  • etcd: the lease property is removed, and the store ttl applies to each key from its own write (6ba3096, etcd - fix: default ttl applies per key and keeps working after the first lease expires #2160)
    Migration: remove code that reads or assigns store.lease. See @keyv/etcd Default ttl Applies Per Key.
  • etcd: without a namespace, clear() and iterator() only touch entries Keyv wrote without a namespace (8a77946, etcd - fix: Without a namespace, clear() and iterator() only touch Keyv's own entries #2173)
    rc.1 deleted or returned every key in etcd. Values are now stored as { v, e, n }, where n is the namespace they were written under. An entry rc.1 wrote has no n and counts as un-namespaced when its key has no separator. With the new :: default, that includes entries rc.1 wrote under a namespace, such as users:1.
    Migration: set noNamespaceAffectsAll: true to clear and iterate every key, as rc.1 did. While etcd still holds namespaced entries from rc.1, set namespaceSeparator: ':' so a clear() without a namespace leaves them alone. See @keyv/etcd Without a Namespace Only Clears Its Own Entries.
  • memcache: clear() only removes the store's own entries, and values rc.1 wrote read as missing (12415c5, memcache - fix: clear() only removes the store's own entries #2179)
    Each namespace now has a generation token, every value is stored as <token>:<value>, and clear() writes a new token instead of flushing the server. rc.1 flushed the whole server, with or without a namespace.
    Migration: let the cache fill again. To flush the whole server from a store without a namespace, set noNamespaceAffectsAll: true; that store then writes values without a token. See @keyv/memcache clear() Only Removes the Store's Own Entries.
  • bigmap: setting storeSize keeps every entry and no longer emits clear (b80b44d, bigmap - fix: Changing storeSize or storeHashFunction keeps every entry #2178)
    rc.1 emptied the map and emitted clear when storeSize changed. Setting storeSize or storeHashFunction now moves every entry to the store its key maps to. Before, a new storeHashFunction left entries where get() couldn't find them.
    Migration: if code sets storeSize to empty the map, call clear() as well. For a large map, set storeSize and storeHashFunction in the constructor. See @keyv/bigmap Keeps Entries When storeSize or storeHashFunction Changes.

Features

Bug Fixes

Documentation

Security & release pipeline

Internal

Contributors

Full List of Changes

Full diff: v6.0.0-rc.1...v6.1.0


🤖 Generated with Claude Code

https://claude.ai/code/session_01Cwt8ciN4uKrQkEM14TCff2

claude added 2 commits October 4, 2026 20:10
Set the root version to 6.1.0 and ran `pnpm version:sync`, so all 21
workspace packages match. 6.1.0 is the first stable v6 version: npm
still records keyv@6.0.0 as published, so that number can't be
published again.

The publish job already sets LATEST_MAJOR to 6 (#2148), so a stable
6.1.0 release stages every package under `latest`.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Cwt8ciN4uKrQkEM14TCff2
- versioning.md: from 6.1.0 `latest` is v6, and there is no 6.0.0
  release. Releases from the v5 line get a `v{major}-lts` tag, `keyv@6`
  resolves to the newest stable 6.x, and the pre-release tags still
  point at 6.0.0 pre-releases. v6 is an LTS line alongside v5, so
  breaking changes land in v7, and v6's Node.js floor stays 22.19.0.
- v5-to-v6.md: the version examples use 6.1.0, and v5 is in
  maintenance mode now rather than later.
- README.md: v6 is the current stable release.
- CONTRIBUTING.md: the LATEST_MAJOR bump next applies to 7.0.0.
- release-publish.ts: the header no longer says v6 is in beta.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Cwt8ciN4uKrQkEM14TCff2
@chatgpt-codex-connector

chatgpt-codex-connector Bot commented Oct 4, 2026 •

Copy link
Copy Markdown

Codex Review Summary

This comment shows the latest Codex review activity on this pull request.

Review Status Commit Review trigger
📝 Code Review ✅ Completed 2026-10-04T20:14:38.569118Z 47033ae PR opened
ℹ️ 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" or "@codex security review".

Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings.

"should keep a successful connection alive after connectionTimeout
elapses" connected to a real Redis with `connectionTimeout: 50`, so the
TCP connect and the handshake had 50 ms to finish. Under the parallel
test load in CI they sometimes take longer, the connect times out, and
the test fails with "Redis timed out after 50ms". That happened in the
codecov job on main at 6d4b270 and on #2192.

The test now uses 500 ms, the budget the other tests that connect to a
real server use, and waits 600 ms instead of 120 ms, so it still idles
past the timeout before it uses the connection.

Reproduced locally with a TCP proxy that adds 40 ms to every chunk: the
old test fails with the same error, and the new one passes.

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

Copy link
Copy Markdown
Owner Author

codecov / node-24 failed on 47033ae in @keyv/redis: getClient > should keep a successful connection alive after connectionTimeout elapses threw Redis timed out after 50ms.

This PR didn't cause it. The PR doesn't touch any redis code, and the same test failed the same way on main at 6d4b270 earlier today. The test gave a real Redis connect only 50 ms, and a busy CI runner sometimes needs longer. I reproduced it with a proxy that slows the handshake.

The fix is in #2193 (it gives the connect 500 ms). I've ported the same commit here as d16fffe, because the release workflow's test job runs this suite too. Once #2193 merges, the change here is a no-op.


Generated by Claude Code

@codecov

codecov Bot commented Oct 4, 2026 •

Copy link
Copy Markdown

Codecov Report

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

Additional details and impacted files
@@            Coverage Diff            @@
##              main     #2192   +/-   ##
=========================================
  Coverage   100.00%   100.00%           
=========================================
  Files           56        56           
  Lines         5802      5802           
  Branches       996       996           
=========================================
  Hits          5802      5802           

☔ 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.

A 500 ms budget only made the keep-alive test less likely to fail: a
handshake that takes longer still loses the race. Through a proxy that
adds 250 ms to every chunk, the 500 ms version fails with "Redis timed
out after 500ms".

Keyv times the connect with a JS setTimeout, so the test now fakes
setTimeout and clearTimeout while it connects. The race can't time
out, however long the handshake takes, and the test moves the clock
past the timeout instead of sleeping, so it runs in about 50 ms instead
of 600 ms. node-redis's TCP connectTimeout runs on Node's internal
timers, which stay real, so connectionTimeout is now 1 s, the window
the other tests that use the server's clock got in #2182.

The test also checks that the same client is still open after the
timeout. Before, a client torn down when the timeout elapsed went
unnoticed, because the adapter reconnects on the next call. A timer
that destroys the client after the timeout now fails the test.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Cwt8ciN4uKrQkEM14TCff2
jaredwray added a commit that referenced this pull request Oct 4, 2026
#2193)

* redis - test: Give the keep-alive test time to connect on a busy runner

"should keep a successful connection alive after connectionTimeout
elapses" connected to a real Redis with `connectionTimeout: 50`, so the
TCP connect and the handshake had 50 ms to finish. Under the parallel
test load in CI they sometimes take longer, the connect times out, and
the test fails with "Redis timed out after 50ms". That happened in the
codecov job on main at 6d4b270 and on #2192.

The test now uses 500 ms, the budget the other tests that connect to a
real server use, and waits 600 ms instead of 120 ms, so it still idles
past the timeout before it uses the connection.

Reproduced locally with a TCP proxy that adds 40 ms to every chunk: the
old test fails with the same error, and the new one passes.

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

* redis - test: Fake the connect timer instead of racing it

A 500 ms budget only made the keep-alive test less likely to fail: a
handshake that takes longer still loses the race. Through a proxy that
adds 250 ms to every chunk, the 500 ms version fails with "Redis timed
out after 500ms".

Keyv times the connect with a JS setTimeout, so the test now fakes
setTimeout and clearTimeout while it connects. The race can't time
out, however long the handshake takes, and the test moves the clock
past the timeout instead of sleeping, so it runs in about 50 ms instead
of 600 ms. node-redis's TCP connectTimeout runs on Node's internal
timers, which stay real, so connectionTimeout is now 1 s, the window
the other tests that use the server's clock got in #2182.

The test also checks that the same client is still open after the
timeout. Before, a client torn down when the timeout elapsed went
unnoticed, because the adapter reconnects on the next call. A timer
that destroys the client after the timeout now fails the test.

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

---------

Co-authored-by: Claude <noreply@anthropic.com>
Brings in #2193, which this branch already carried as d16fffe and
538c8d0, so the release diff is the version bump and docs again.

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

This branch has not been deployed

No deployments
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.

2 participants