mirror of
https://github.com/cloudflare/vinext.git
synced 2026-09-14 19:04:59 +08:00
201f4a64dc
## Summary - Guard `getOrCreateAls` so it no longer calls `new AsyncLocalStorage()` unconditionally; when the runtime does not expose a usable constructor it falls back to a no-op store instead of throwing. - Add a `NoopAsyncLocalStorage` whose `getStore()` returns `undefined`, mirroring Next.js' `FakeAsyncLocalStorage` and this repo's `async-hooks-stub.ts`. - Preserve the cross-bundle singleton behavior (`Symbol.for` + `??=`). ## Root Cause In browser/client bundles `node:async_hooks` can resolve to a stub without a usable `AsyncLocalStorage` constructor (e.g. Vite's `__vite-browser-external`). `getOrCreateAls` ran `new AsyncLocalStorage()` at module-eval time, throwing `TypeError: AsyncLocalStorage is not a constructor`. Because many client-reachable shims (request-context, headers, cache, …) call this helper on import, the whole client bundle died and hydration crashed. Checking `typeof AsyncLocalStorage === "function"` before constructing, with a no-op fallback, matches Next.js' availability guard. Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
37 lines
1.6 KiB
TypeScript
37 lines
1.6 KiB
TypeScript
import { describe, expect, it, vi } from "vite-plus/test";
|
|
|
|
// Simulate a browser/client bundle where `node:async_hooks` resolves to a stub
|
|
// that does NOT expose a usable `AsyncLocalStorage` constructor (e.g. Vite's
|
|
// `__vite-browser-external`). On such a runtime `new AsyncLocalStorage()` would
|
|
// throw `TypeError: AsyncLocalStorage is not a constructor` at module-eval time,
|
|
// crashing every client-reachable shim that calls `getOrCreateAls` on import.
|
|
vi.mock("node:async_hooks", () => ({
|
|
AsyncLocalStorage: undefined,
|
|
}));
|
|
|
|
describe("getOrCreateAls — browser-safe fallback", () => {
|
|
it("does not throw when AsyncLocalStorage is unavailable and returns a no-op store", async () => {
|
|
const { getOrCreateAls } =
|
|
await import("../packages/vinext/src/shims/internal/als-registry.js");
|
|
|
|
let als: ReturnType<typeof getOrCreateAls<{ value: number }>> | undefined;
|
|
expect(() => {
|
|
als = getOrCreateAls<{ value: number }>("vinext.test.als.unavailable");
|
|
}).not.toThrow();
|
|
|
|
// The fallback mirrors Next.js' FakeAsyncLocalStorage: getStore() === undefined
|
|
// so shims fall back to their non-ALS code path instead of crashing.
|
|
expect(als).toBeDefined();
|
|
expect(als!.getStore()).toBeUndefined();
|
|
});
|
|
|
|
it("keeps the global-dedupe behavior: same key returns the same instance", async () => {
|
|
const { getOrCreateAls } =
|
|
await import("../packages/vinext/src/shims/internal/als-registry.js");
|
|
|
|
const a = getOrCreateAls("vinext.test.als.dedupe");
|
|
const b = getOrCreateAls("vinext.test.als.dedupe");
|
|
expect(a).toBe(b);
|
|
});
|
|
});
|