11 Commits

Author SHA1 Message Date
Stephen Zhou 25f6e0fc87 chore: enable typeAware and typeCheck, use vp check (#551)
* chore: enable typeAware and typeCheck, use vp check

* Build

* Try cache false

* Revert "Try cache false"

This reverts commit 5f76ed02f1.

* Update

* workaround for vp check

* Try no workaround

* Fix check

* Update to 0.1.12

* denyWarnings
2026-03-16 11:00:47 -05:00
Nathan Nguyen 39791bf3cc feat: implement revalidateByPathPrefix on KVCacheHandler (#552)
* feat: implement revalidateByPathPrefix on KVCacheHandler

Uses KV list metadata to discover entry tags without extra get() calls.
set() now stores tags in KV metadata, and revalidateByPathPrefix reads
them back via kv.list() — making prefix invalidation O(list_pages)
instead of O(entries × get).

Entries written before metadata support are gracefully skipped.

* fix: guard against KV metadata 1024-byte limit

Cloudflare KV limits metadata to 1024 bytes per key. If an entry's
tags exceed this budget when JSON-serialized, omit metadata entirely
and fall back gracefully — prefix invalidation skips entries without
metadata, and exact-path invalidation via revalidateTag still works.

* test: add regression test for KV metadata 1024-byte overflow
2026-03-16 09:23:45 +00:00
Stephen Zhou c17d6941be chore: migrate to vite plus (#535)
* chore: migrate to vite plus

* Disable typeAware and typeCheck

* Update CI

* Fix CI

* Fix test

* Clean

* Run test with vp

* Try revert

* react: false In test

* Fix test

* Revert "Try revert"

This reverts commit 009da10473.

* Update

* Update

* Try revert ci changes

* revert

* Run vp migrate

* Disable typeAware and typeCheck for now

* Better resolve for test

* Use vp dev instead of vite

* Update expect

* Fix NormalizeManifestModuleId

* Try increase timeout

* Update to use vp

* Try new check

* Bring back npx vp

* Migrate CI

* Make next-intl resolvable

* Update

* Update

* Update
2026-03-15 10:50:13 +00:00
Nathan Nguyen 8d10bc64fd fix: MemoryCacheHandler revalidate:0 creates immediately-stale entries (#503)
* fix: MemoryCacheHandler revalidate:0 creates immediately-stale entries

MemoryCacheHandler.set had an inconsistent guard on data.revalidate:
the ctx path checked `revalidate > 0` but the data path only checked
`typeof data.revalidate === "number"`, allowing revalidate:0 to set
revalidateAt = Date.now() — making entries immediately stale.

This was inconsistent with KVCacheHandler which already had the > 0
guard on both paths. The two handlers would produce different staleness
behavior for the same input: Memory → immediately stale, KV → no TTL.

Add the missing `> 0` guard to align both code paths and both handlers.

* fix: revalidate:0 should skip cache storage entirely

Both MemoryCacheHandler and KVCacheHandler had inconsistent handling of
revalidate:0. In Next.js, revalidate:0 means "don't cache." But the
handlers would still store entries — Memory made them immediately stale
(data path lacked > 0 guard), KV cached them forever (both paths
rejected 0, so revalidateAt=null meant no expiry).

Resolve effective revalidate from both ctx and data paths (data
overrides ctx), and early-return without storing when it's 0. Both
handlers now use identical logic.

Fix existing test data that incidentally used data.revalidate:0 in
tests meant to exercise stale-while-revalidate and tag invalidation.
2026-03-12 17:34:12 +00:00
Divanshu Chauhan (divkix) 1b4a796a3f perf: startup and cache micro-optimizations (#436)
* perf: cache startup filesystem scans, optimize base64 and path normalization

- Cache hasMdxFiles() result per root directory to avoid redundant
  recursive filesystem walks when config() fires per Vite environment
- Cache resolvePostcssStringPlugins() result per project root to skip
  repeated existsSync checks across 17 PostCSS config file candidates
- Replace byte-by-byte base64 encode/decode in KV cache handler with
  Buffer APIs for significantly faster serialization
- Pass pre-normalized URL to nodeToWebRequest() in App Router prod
  server to eliminate redundant path normalization in the RSC handler

* fix: validate base64 structural length and improve test spy cleanup

Add length % 4 check to base64ToArrayBuffer to reject structurally
invalid base64 strings that pass the character-set regex but produce
empty/truncated buffers via Buffer.from(). Replace manual mockRestore()
calls with afterEach(vi.restoreAllMocks) to prevent spy leaks on
assertion failure.

* fix: address bonk review comments — composite mdx cache key, postcss comment order, hoist test import

* fix: cache Promise in resolvePostcssStringPlugins to prevent concurrent scan race; add POST urlOverride test

* fmt

---------

Co-authored-by: James <james@eli.cx>
2026-03-11 16:22:58 +00:00
Divanshu Chauhan (divkix) 769a98ac85 perf(kv): local tag cache to reduce KV round-trips on cache hit (#433)
* perf(kv): add local in-memory tag cache to reduce KV round-trips

When KVCacheHandler.get() validates tags, it was issuing one kv.get()
per tag in parallel. For entries with N tags, that's N KV round-trips
per cache hit. Same pattern in revalidateTag() — N parallel PUTs.

Add a local Map<string, { timestamp, fetchedAt }> with a 5-second TTL
that caches tag invalidation timestamps. Within the TTL window, tag
checks are served from memory with zero I/O. After TTL expiry, the
next request re-fetches from KV.

Key behaviors:
- revalidateTag() updates the local cache immediately so invalidations
  are reflected without waiting for TTL expiry
- resetRequestCache() clears the local cache for per-request isolation
- NaN tag timestamps are cached and correctly treated as invalidation
- Only uncached/expired tags trigger KV reads (partial cache hits work)

* fix(test): guard fake timers with try/finally to prevent leaks

Wrap vi.useFakeTimers() in try/finally so vi.useRealTimers() runs even
if an assertion fails mid-test, preventing timer leaks into subsequent
tests.

* fix(kv): address bonk review comments on local tag cache

- Fix early return bug: split KV tag fetch loop into populate-cache pass
  then invalidation-check pass, so already-fetched tag results are always
  cached even when an earlier tag triggers an early return
- Fix unbounded _tagCache growth: delete stale entries when encountered
  during the TTL check (cheap eviction instead of accumulation forever)
- Make tag cache TTL configurable via tagCacheTtlMs constructor option
  (default 5000ms); eliminates fake timers in TTL test
- Clarify resetRequestCache() docstring: it is not called per-request by
  vinext, it is an opt-in escape hatch for callers that need explicit isolation
- Add resetRequestCache() test: verifies tags are re-fetched from KV after
  calling resetRequestCache()

* refactor(kv): reuse cached timestamps in invalidation check loop

The second pass over uncachedTags was re-calling Number(tagTime) and
re-reading tagResults[i]. Now it reads directly from _tagCache (which was
just populated in the first pass), eliminating the duplicate number
conversion and making the intent clearer.

---------

Co-authored-by: James <james@eli.cx>
2026-03-11 10:59:25 +00:00
Jared Stowell 1551e4406a fix: App Router ISR invalidation parity for fetch and path tags (#445)
* Fix App Router ISR tag parity

* Fix KV tag persistence

* Fix KV cache tag handling
2026-03-11 08:17:50 +00:00
James Anderson aa878d55ac fix(isr): KVCacheHandler.set() now awaits KV put to prevent perpetual STALE (#425)
* fix(isr): KVCacheHandler.set() now awaits KV put to prevent perpetual STALE

Background ISR regen was calling ctx.waitUntil(renderFn()) where renderFn()
resolved before the KV put network operation completed. set() returned
Promise.resolve() immediately, so await __isrSet() in the regen resolved early,
the waitUntil expired, and the Workers runtime killed the in-flight KV write —
leaving the cache entry perpetually STALE on every request after the TTL.

Fix: replace the fire-and-forget _putInBackground() with _put() which returns
the actual kv.put() promise. set() now returns that promise, so await __isrSet()
only resolves after the write is fully persisted. ctx.waitUntil() is also still
registered as belt-and-suspenders.

Adds three regression tests covering the STALE → regen → HIT lifecycle with a
delayed KV put mock to prove the set() promise is not an immediately-resolved stub.

* fix: remove unused variable flagged by lint
2026-03-10 23:11:24 +00:00
James Anderson 7ba1ab5ccf fix: register background KV ops and ISR regen with ctx.waitUntil on Workers (#391)
* fix: register background KV ops and ISR regen with ctx.waitUntil on Workers

On Cloudflare Workers, any async work started after a Response is returned
is killed immediately. This caused three silent failures:

1. ISR background regeneration (stale-while-revalidate) was never
   registered with ctx.waitUntil, so every revalidation on Workers was
   silently dropped.

2. KV deletes for corrupt/invalid/tag-invalidated cache entries were
   awaited on the critical path, blocking the response for pure cleanup work.

3. KV writes in KVCacheHandler.set() were awaited on the critical path;
   the client already has the rendered content so the write can happen
   after the response is sent.

Fixes:
- Thread ExecutionContext through triggerBackgroundRegeneration and call
  ctx.waitUntil(regenPromise) when ctx is present (isr-cache.ts and the
  inlined copy in pages-server-entry.ts).
- Add _deleteInBackground / _putInBackground helpers to KVCacheHandler
  that use ctx.waitUntil when ctx is present and fall back to
  fire-and-forget on Node.js.
- Convert all three delete callsites (corrupt JSON, invalid shape,
  tag-invalidation) and the set() KV write to use the new helpers.
- All changes are backward-compatible: ctx is optional everywhere;
  Node.js dev/prod behaviour is unchanged.

* regen snaps
2026-03-09 23:25:57 +00:00
James Anderson 764a496ce7 add oxfmt formatter (#380)
* add oxfmt formatter: config, scripts, CI, editor setup, docs

* rebuild lockfile

* fix: add Format to required checks list, remove dead ignore pattern

* run fmt

* add format to agents.md again
2026-03-09 14:56:14 +00:00
Steve Faulkner 12fea722b6 Initial public release of vinext 2026-02-24 09:29:39 -06:00