Skip to content

cache-manager - fix: read raw entries with getRaw() for Keyv 6 stores - #1759

Open
yan-almeida wants to merge 7 commits into
jaredwray:mainfrom
yan-almeida:fix/cache-manager-keyv6-getraw
Open

yan-almeida wants to merge 7 commits into
jaredwray:mainfrom
yan-almeida:fix/cache-manager-keyv6-getraw

Conversation

@yan-almeida

Copy link
Copy Markdown

Please check if the PR fulfills these requirements

  • Followed the Contributing guidelines and Code of Conduct
  • Tests for the changes have been added (for bug fixes/features) with 100% code coverage.

What kind of change does this PR introduce? (Bug fix, feature, docs update, ...)

Bug fix.

Fixes #1758

Problem

ttl() and wrap() read the stored entry with store.get(key, { raw: true }). Keyv 6 get() does not have the raw option, so it returns the value. With a Keyv 6 store, wrap() calls the function each time, and ttl() returns undefined.

Change

  • packages/cache-manager/src/index.ts: ttl() and wrap() use store.getRaw(key). Keyv 5.6.0 and Keyv 6 both have getRaw(). The keyv dependency stays at ^5.6.0.
  • packages/cache-manager/package.json: add the dev dependency "keyv6": "npm:keyv@^6.1.0". The new tests use it to make a real Keyv 6 store.

Behavior with Keyv 5:

  • The return values of wrap() and ttl() do not change.
  • Keyv 5 get() catches a store error, but getRaw() does not (see cacheable - fix: count falsy values in delete stats #1743). Both call sites in cache-manager catch the error. wrap() goes to the next store or calls the function, as before. ttl() also emits its ttl event with the error. get() does the same with its get event.
  • Keyv 5 runs the preGetRaw and postGetRaw hooks for these reads, and not preGet and postGet.

Verification

  • pnpm install --frozen-lockfile, pnpm build and node scripts/test-build.mjs pass.
  • pnpm test:ci in packages/cache-manager passes with a local Redis. The Biome check passes, 92 tests pass, and coverage is 100% for statements, branches, functions and lines.
  • pnpm exec tsc --noEmit -p packages/cache-manager passes.
  • Before the fix, the two new tests fail:
    • wrap returns the cached value: the function runs 2 times, expected 1.
    • ttl returns the expiration: received undefined.
  • A script with Redis 7, @keyv/redis 6.1.0 and keyv 6.1.0 calls wrap() 5 times. On 7.2.9 the function runs 5 times, and ttl() returns undefined. On this branch the function runs 1 time, and ttl() returns a number. With keyv 5.6.0 and @keyv/redis 5.1.6, the result is the same on 7.2.9 and on this branch.

Test audit:

  • Added Keyv 6 store > wrap returns the cached value.
    1. Contract: wrap() returns the cached value from a Keyv 6 store and does not call the function again.
    2. Regression: a raw read with get(key, { raw: true }). Keyv 6 returns the value, so wrap() finds no entry.
    3. All current tests use Keyv 5, and Keyv 5 get() accepts raw.
    4. No new export or hook. The test uses createCache() with a real Keyv 6 instance.
  • Added Keyv 6 store > ttl returns the expiration.
    1. Contract: ttl() returns the expiration of a key in a Keyv 6 store.
    2. Regression: the same raw read. ttl() gets the value, and the value has no expires.
    3. All current tests use Keyv 5.
    4. No new export or hook.
  • Changed the store error mocks from get to getRaw in ttl.test.ts (error, error on non-blocking enabled), wrap.test.ts (store get failed) and multiple-stores.test.ts (wrap, wrap - refresh). ttl() and wrap() read with getRaw() now, so a mock on get does not reach the error path. Without this change, store get failed and multiple stores > wrap fail, and lines 194 and 206 of index.ts lose coverage. The tests keep their contract: a store read error does not stop ttl() or wrap().

Add the keyv6 alias (npm:keyv@^6.1.0). The tests use it to make a
Keyv 6 store. The runtime dependency stays at keyv ^5.6.0.
Keyv 6 get() does not have the raw option. It returns the value and
not the stored entry. Thus wrap() calls the function each time, and
ttl() returns undefined. These tests fail before the fix.
ttl() and wrap() read the stored entry with get(key, { raw: true }).
Keyv 6 ignores this option, so these calls got the value and not the
entry. Use getRaw(key). Keyv 5.6.0 and Keyv 6 both have getRaw().
ttl() reads with getRaw() now. A mock on get() does not reach the
error path, and the catch blocks of ttl() lose their coverage.
wrap() reads with getRaw() now. A mock on get() does not make the
store read fail.
wrap() reads with getRaw() now. A mock on get() does not make the
read of the first store fail.
@socket-security

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
Addedkeyv@​6.1.01001001009280

View full report

@yan-almeida
yan-almeida marked this pull request as ready for review November 10, 2026 16:37

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.

cache-manager: wrap() and ttl() do not read the cache when a store is a Keyv 6 instance

1 participant