[PR #1081] [MERGED] refactor(als): dedupe AsyncLocalStorage globalThis registration #1078

Closed
opened 2026-05-06 13:11:52 +02:00 by BreizhHardware · 0 comments

📋 Pull Request Information

Original PR: https://github.com/cloudflare/vinext/pull/1081
Author: @james-elicx
Created: 5/5/2026
Status: Merged
Merged: 5/5/2026
Merged by: @james-elicx

Base: mainHead: refactor/dedupe-als-registration


📝 Commits (1)

  • 4d0d56e refactor(als): dedupe AsyncLocalStorage globalThis registration

📊 Changes

11 files changed (+77 additions, -46 deletions)

View changed files

📝 packages/vinext/src/shims/cache-runtime.ts (+3 -8)
📝 packages/vinext/src/shims/cache.ts (+3 -7)
📝 packages/vinext/src/shims/fetch-cache.ts (+2 -4)
📝 packages/vinext/src/shims/head-state.ts (+2 -3)
📝 packages/vinext/src/shims/headers.ts (+3 -4)
📝 packages/vinext/src/shims/i18n-state.ts (+2 -3)
packages/vinext/src/shims/internal/als-registry.ts (+53 -0)
📝 packages/vinext/src/shims/navigation-state.ts (+2 -4)
📝 packages/vinext/src/shims/request-context.ts (+2 -5)
📝 packages/vinext/src/shims/router-state.ts (+2 -4)
📝 packages/vinext/src/shims/unified-request-context.ts (+3 -4)

📄 Description

Summary

Extracts the repeated Symbol.for(...) + globalThis ??= new AsyncLocalStorage<T>()
pattern into a single helper, getOrCreateAls<T>(key: string), in
packages/vinext/src/shims/internal/als-registry.ts.

The pattern was inlined in 12 sites across 10 modules:

  • shims/headers.tsvinext.nextHeadersShim.als
  • shims/cache.tsvinext.cache.als, vinext.unstableCache.als
  • shims/cache-runtime.tsvinext.cacheRuntime.contextAls (exported), vinext.cacheRuntime.privateAls
  • shims/fetch-cache.tsvinext.fetchCache.als
  • shims/request-context.tsvinext.requestContext.als
  • shims/unified-request-context.tsvinext.unifiedRequestContext.als
  • shims/router-state.tsvinext.router.als
  • shims/i18n-state.tsvinext.i18n.als
  • shims/head-state.tsvinext.head.als
  • shims/navigation-state.tsvinext.navigation.als

The 12 sites used identical structure: same Symbol.for(key) registry-side
lookup, same ??= semantics, same fallback-type AsyncLocalStorage<T>().
No site initialised the ALS with a sentinel store value or wrapped it in a
container object — they were all clean candidates.

Net: 11 files changed, +77/-46 (the new helper module is the bulk of
+77; call sites collapse from 4 lines to 1).

Cross-bundle singleton property — preserved

This is the load-bearing invariant for ALS bugs and is the reason the
inline pattern existed in the first place. The helper preserves it:

  • Symbol.for(key) consults the global symbol registry and returns the
    same symbol no matter which module instance calls it.
  • globalThis[sym] is one slot shared by every module instance.
  • ??= only assigns when the slot is empty, so the first caller wins and
    every later caller (in any module instance, in any Vite environment)
    reads the same AsyncLocalStorage instance.

The helper module itself never closes over the ALS — it always
round-trips through globalThis. So even if als-registry.ts is loaded
under multiple specifiers (Vite RSC/SSR/client environments, HMR), every
copy hands back the one true ALS for a given key. There is no new
"ownership-by-reference" layer introduced by the helper.

The unrelated _FALLBACK_KEY / _fallbackState pattern in some shims
(plain-object cross-bundle singletons with shape varying per call site)
is intentionally left inline.

Files changed

  • packages/vinext/src/shims/internal/als-registry.ts (new)
  • packages/vinext/src/shims/headers.ts
  • packages/vinext/src/shims/cache.ts
  • packages/vinext/src/shims/cache-runtime.ts
  • packages/vinext/src/shims/fetch-cache.ts
  • packages/vinext/src/shims/request-context.ts
  • packages/vinext/src/shims/unified-request-context.ts
  • packages/vinext/src/shims/router-state.ts
  • packages/vinext/src/shims/i18n-state.ts
  • packages/vinext/src/shims/head-state.ts
  • packages/vinext/src/shims/navigation-state.ts

Follow-up to #1058.

Test plan

  • pnpm fmt --write on touched files
  • npx tsc --noEmit in packages/vinext — clean
  • pnpm knip — clean
  • pnpm vp test run for app-router, pages-router, cache-control, cache-for-request, fetch-cache, head, request-context, unified-request-context, pages-router-concurrency — 683 tests passing. The single suite-level failure (pages-router.test.ts > allowedDevOrigins config afterAll timeout) reproduces on clean main with the changes stashed; it is a pre-existing flaky teardown unrelated to this refactor.
  • CI runs full suite

🤖 Generated with Claude Code


🔄 This issue represents a GitHub Pull Request. It cannot be merged through Gitea due to API limitations.

## 📋 Pull Request Information **Original PR:** https://github.com/cloudflare/vinext/pull/1081 **Author:** [@james-elicx](https://github.com/james-elicx) **Created:** 5/5/2026 **Status:** ✅ Merged **Merged:** 5/5/2026 **Merged by:** [@james-elicx](https://github.com/james-elicx) **Base:** `main` ← **Head:** `refactor/dedupe-als-registration` --- ### 📝 Commits (1) - [`4d0d56e`](https://github.com/cloudflare/vinext/commit/4d0d56ea644a2ea3f9c53d172b2615a194abb297) refactor(als): dedupe AsyncLocalStorage globalThis registration ### 📊 Changes **11 files changed** (+77 additions, -46 deletions) <details> <summary>View changed files</summary> 📝 `packages/vinext/src/shims/cache-runtime.ts` (+3 -8) 📝 `packages/vinext/src/shims/cache.ts` (+3 -7) 📝 `packages/vinext/src/shims/fetch-cache.ts` (+2 -4) 📝 `packages/vinext/src/shims/head-state.ts` (+2 -3) 📝 `packages/vinext/src/shims/headers.ts` (+3 -4) 📝 `packages/vinext/src/shims/i18n-state.ts` (+2 -3) ➕ `packages/vinext/src/shims/internal/als-registry.ts` (+53 -0) 📝 `packages/vinext/src/shims/navigation-state.ts` (+2 -4) 📝 `packages/vinext/src/shims/request-context.ts` (+2 -5) 📝 `packages/vinext/src/shims/router-state.ts` (+2 -4) 📝 `packages/vinext/src/shims/unified-request-context.ts` (+3 -4) </details> ### 📄 Description ## Summary Extracts the repeated `Symbol.for(...) + globalThis ??= new AsyncLocalStorage<T>()` pattern into a single helper, `getOrCreateAls<T>(key: string)`, in `packages/vinext/src/shims/internal/als-registry.ts`. The pattern was inlined in 12 sites across 10 modules: - `shims/headers.ts` — `vinext.nextHeadersShim.als` - `shims/cache.ts` — `vinext.cache.als`, `vinext.unstableCache.als` - `shims/cache-runtime.ts` — `vinext.cacheRuntime.contextAls` (exported), `vinext.cacheRuntime.privateAls` - `shims/fetch-cache.ts` — `vinext.fetchCache.als` - `shims/request-context.ts` — `vinext.requestContext.als` - `shims/unified-request-context.ts` — `vinext.unifiedRequestContext.als` - `shims/router-state.ts` — `vinext.router.als` - `shims/i18n-state.ts` — `vinext.i18n.als` - `shims/head-state.ts` — `vinext.head.als` - `shims/navigation-state.ts` — `vinext.navigation.als` The 12 sites used identical structure: same `Symbol.for(key)` registry-side lookup, same `??=` semantics, same fallback-type `AsyncLocalStorage<T>()`. No site initialised the ALS with a sentinel store value or wrapped it in a container object — they were all clean candidates. Net: 11 files changed, +77/-46 (the new helper module is the bulk of `+77`; call sites collapse from 4 lines to 1). ## Cross-bundle singleton property — preserved This is the load-bearing invariant for ALS bugs and is the reason the inline pattern existed in the first place. The helper preserves it: - `Symbol.for(key)` consults the **global symbol registry** and returns the same symbol no matter which module instance calls it. - `globalThis[sym]` is one slot shared by every module instance. - `??=` only assigns when the slot is empty, so the first caller wins and every later caller (in any module instance, in any Vite environment) reads the same `AsyncLocalStorage` instance. The helper module itself never closes over the ALS — it always round-trips through `globalThis`. So even if `als-registry.ts` is loaded under multiple specifiers (Vite RSC/SSR/client environments, HMR), every copy hands back the one true ALS for a given key. There is no new "ownership-by-reference" layer introduced by the helper. The unrelated `_FALLBACK_KEY` / `_fallbackState` pattern in some shims (plain-object cross-bundle singletons with shape varying per call site) is intentionally left inline. ## Files changed - `packages/vinext/src/shims/internal/als-registry.ts` (new) - `packages/vinext/src/shims/headers.ts` - `packages/vinext/src/shims/cache.ts` - `packages/vinext/src/shims/cache-runtime.ts` - `packages/vinext/src/shims/fetch-cache.ts` - `packages/vinext/src/shims/request-context.ts` - `packages/vinext/src/shims/unified-request-context.ts` - `packages/vinext/src/shims/router-state.ts` - `packages/vinext/src/shims/i18n-state.ts` - `packages/vinext/src/shims/head-state.ts` - `packages/vinext/src/shims/navigation-state.ts` Follow-up to #1058. ## Test plan - [x] `pnpm fmt --write` on touched files - [x] `npx tsc --noEmit` in `packages/vinext` — clean - [x] `pnpm knip` — clean - [x] `pnpm vp test run` for `app-router`, `pages-router`, `cache-control`, `cache-for-request`, `fetch-cache`, `head`, `request-context`, `unified-request-context`, `pages-router-concurrency` — 683 tests passing. The single suite-level failure (`pages-router.test.ts > allowedDevOrigins config` `afterAll` timeout) reproduces on clean `main` with the changes stashed; it is a pre-existing flaky teardown unrelated to this refactor. - [ ] CI runs full suite 🤖 Generated with [Claude Code](https://claude.com/claude-code) --- <sub>🔄 This issue represents a GitHub Pull Request. It cannot be merged through Gitea due to API limitations.</sub>
BreizhHardware 2026-05-06 13:11:52 +02:00
Sign in to join this conversation.
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference
starred/vinext#1078
No description provided.