Skip to content

fix: unstable_cache undefined cache#339

Merged
james-elicx merged 1 commit intocloudflare:mainfrom
JaredStowell:jstowell/fix-undefined-caching
Mar 8, 2026
Merged

fix: unstable_cache undefined cache#339
james-elicx merged 1 commit intocloudflare:mainfrom
JaredStowell:jstowell/fix-undefined-caching

Conversation

@JaredStowell
Copy link
Contributor

Summary

Fix unstable_cache() so cached functions that return top-level undefined actually hit the cache on subsequent calls.

Problem

unstable_cache() was serializing results with JSON.stringify(result) and reading them back with JSON.parse(body).

For a top-level undefined return value, JSON.stringify(undefined) returns undefined rather than a JSON string. On the next read, JSON.parse(undefined) throws, so the shim treated the entry as invalid and recomputed the function every time.

Changes

  • Add a dedicated sentinel payload for top-level undefined results in the unstable_cache() serialization path
  • Teach the read path to recognize that sentinel before falling back to JSON.parse
  • Add a regression test covering an unstable_cache()-wrapped function that returns undefined and should only execute once across repeated calls

Why

This is a real correctness/performance bug in the cache shim: callers still receive undefined, but the cache never produces a true hit for that value.

Testing

  • pnpm test tests/shims.test.ts -t "unstable_cache"

@james-elicx
Copy link
Collaborator

/bigbonk review

@pkg-pr-new
Copy link

pkg-pr-new bot commented Mar 8, 2026

Open in StackBlitz

npm i https://pkg.pr.new/vinext@339

commit: a7850e9

Copy link
Contributor

@ask-bonk ask-bonk bot left a comment

Choose a reason for hiding this comment

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

Clean fix for a real correctness bug. The approach is sound:

  • JSON.stringify(undefined) returns the JS value undefined (not a string), so JSON.parse(undefined) throws, and the cache always misses for undefined results. The sentinel-based workaround is the right call.
  • No sentinel collision risk: if a function returns the literal string "__vinext_unstable_cache_undefined__", JSON.stringify wraps it in quotes, so the serialized form would be '"__vinext_unstable_cache_undefined__"' — never equal to the bare sentinel.
  • Test is well-structured and directly asserts the bug is fixed (callCount stays at 1 after the second call).

One minor observation below, but nothing blocking.

function serializeUnstableCacheResult(value: unknown): string {
return value === undefined
? UNSTABLE_CACHE_UNDEFINED_SENTINEL
: JSON.stringify(value);
Copy link
Contributor

Choose a reason for hiding this comment

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

Nit: JSON.stringify also returns undefined (the JS value) for top-level function and Symbol values. Those are equally un-cacheable with the current code path. Not something that needs fixing in this PR — just noting for awareness that the same class of bug exists for those types. In practice nobody caches functions, so this is fine.

@ask-bonk
Copy link
Contributor

ask-bonk bot commented Mar 8, 2026

Review submitted. Here's a summary:

Approved — this is a clean, correct fix for a real caching bug.

The problem: JSON.stringify(undefined) returns the JS value undefined (not a string), so JSON.parse(undefined) throws on cache read, causing unstable_cache to always miss for functions returning undefined.

The fix: A sentinel string ("__vinext_unstable_cache_undefined__") is used to represent undefined in the serialization/deserialization path. No collision risk exists because JSON.stringify would wrap a legitimate string return value in quotes, making it distinguishable from the bare sentinel.

I left one minor observation noting that JSON.stringify also returns undefined for function and Symbol values (same class of bug), but those are not realistic caching scenarios so it's not a concern for this PR.

github run

@james-elicx james-elicx enabled auto-merge (squash) March 8, 2026 08:57
@james-elicx james-elicx merged commit 8a5192d into cloudflare:main Mar 8, 2026
15 checks passed
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