Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
217 changes: 102 additions & 115 deletions packages/vinext/src/entries/app-rsc-entry.ts

Large diffs are not rendered by default.

1 change: 1 addition & 0 deletions packages/vinext/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1005,6 +1005,7 @@ export default function vinext(options: VinextOptions = {}): PluginOption[] {
"vinext/fetch-cache": path.join(shimsDir, "fetch-cache"),
"vinext/cache-runtime": path.join(shimsDir, "cache-runtime"),
"vinext/navigation-state": path.join(shimsDir, "navigation-state"),
"vinext/unified-request-context": path.join(shimsDir, "unified-request-context"),
"vinext/router-state": path.join(shimsDir, "router-state"),
"vinext/head-state": path.join(shimsDir, "head-state"),
"vinext/instrumentation": path.resolve(__dirname, "server", "instrumentation"),
Expand Down
15 changes: 15 additions & 0 deletions packages/vinext/src/shims/cache-runtime.ts
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ import {
_registerCacheContextAccessor,
type CacheLifeConfig,
} from "./cache.js";
import { isInsideUnifiedScope, getRequestContext } from "./unified-request-context.js";

// ---------------------------------------------------------------------------
// Cache execution context — AsyncLocalStorage for cacheLife/cacheTag
Expand Down Expand Up @@ -239,6 +240,13 @@ const _privateFallbackState = (_g[_PRIVATE_FALLBACK_KEY] ??= {
} satisfies PrivateCacheState) as PrivateCacheState;

function _getPrivateState(): PrivateCacheState {
if (isInsideUnifiedScope()) {
const ctx = getRequestContext();
if (ctx._privateCache === null) {
ctx._privateCache = new Map();
}
return { cache: ctx._privateCache };
}
return _privateAls.getStore() ?? _privateFallbackState;
}

Expand All @@ -248,6 +256,9 @@ function _getPrivateState(): PrivateCacheState {
* on concurrent runtimes.
*/
export function runWithPrivateCache<T>(fn: () => T | Promise<T>): T | Promise<T> {
if (isInsideUnifiedScope()) {
return fn();
}
const state: PrivateCacheState = {
cache: new Map(),
};
Expand All @@ -259,6 +270,10 @@ export function runWithPrivateCache<T>(fn: () => T | Promise<T>): T | Promise<T>
* Only needed when not using runWithPrivateCache() (legacy path).
*/
export function clearPrivateCache(): void {
if (isInsideUnifiedScope()) {
getRequestContext()._privateCache = new Map();
return;
}
const state = _privateAls.getStore();
if (state) {
state.cache = new Map();
Expand Down
14 changes: 8 additions & 6 deletions packages/vinext/src/shims/cache.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@
import { markDynamicUsage as _markDynamic } from "./headers.js";
import { AsyncLocalStorage } from "node:async_hooks";
import { fnv1a64 } from "../utils/hash.js";
import { isInsideUnifiedScope, getRequestContext } from "./unified-request-context.js";

// ---------------------------------------------------------------------------
// Lazy accessor for cache context — avoids circular imports with cache-runtime.
Expand Down Expand Up @@ -398,6 +399,9 @@ const _cacheFallbackState = (_g[_FALLBACK_KEY] ??= {
} satisfies CacheState) as CacheState;

function _getCacheState(): CacheState {
if (isInsideUnifiedScope()) {
return getRequestContext() as unknown as CacheState;
}
return _cacheAls.getStore() ?? _cacheFallbackState;
}

Expand All @@ -408,6 +412,9 @@ function _getCacheState(): CacheState {
* @internal
*/
export function _runWithCacheState<T>(fn: () => T | Promise<T>): T | Promise<T> {
if (isInsideUnifiedScope()) {
return fn();
}
const state: CacheState = {
requestScopedCacheLife: null,
};
Expand All @@ -420,12 +427,7 @@ export function _runWithCacheState<T>(fn: () => T | Promise<T>): T | Promise<T>
* @internal
*/
export function _initRequestScopedCacheState(): void {
const state = _cacheAls.getStore();
if (state) {
state.requestScopedCacheLife = null;
} else {
_cacheFallbackState.requestScopedCacheLife = null;
}
_getCacheState().requestScopedCacheLife = null;
}

/**
Expand Down
15 changes: 15 additions & 0 deletions packages/vinext/src/shims/fetch-cache.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@

import { getCacheHandler, type CachedFetchValue } from "./cache.js";
import { AsyncLocalStorage } from "node:async_hooks";
import { isInsideUnifiedScope, getRequestContext } from "./unified-request-context.js";

// ---------------------------------------------------------------------------
// Cache key generation
Expand Down Expand Up @@ -437,6 +438,9 @@ const _fallbackState = (_g[_FALLBACK_KEY] ??= {
} satisfies FetchCacheState) as FetchCacheState;

function _getState(): FetchCacheState {
if (isInsideUnifiedScope()) {
return getRequestContext() as unknown as FetchCacheState;
}
return _als.getStore() ?? _fallbackState;
}

Expand Down Expand Up @@ -733,9 +737,20 @@ export function withFetchCache(): () => void {
*/
export async function runWithFetchCache<T>(fn: () => Promise<T>): Promise<T> {
_ensurePatchInstalled();
if (isInsideUnifiedScope()) {
return fn();
}
return _als.run({ currentRequestTags: [] }, fn);
}

/**
* Install the patched fetch without creating an ALS scope.
* Used by the unified request context which manages its own scope.
*/
export function ensureFetchPatch(): void {
_ensurePatchInstalled();
}

/**
* Get the original (unpatched) fetch function.
* Useful for internal code that should bypass caching.
Expand Down
52 changes: 23 additions & 29 deletions packages/vinext/src/shims/headers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
import { AsyncLocalStorage } from "node:async_hooks";
import { buildRequestHeadersFromMiddlewareResponse } from "../server/middleware-request-headers.js";
import { parseCookieHeader } from "./internal/parse-cookie-header.js";
import { isInsideUnifiedScope, getRequestContext } from "./unified-request-context.js";

// ---------------------------------------------------------------------------
// Request context
Expand Down Expand Up @@ -57,8 +58,10 @@ const _fallbackState = (_g[_FALLBACK_KEY] ??= {
} satisfies VinextHeadersShimState) as VinextHeadersShimState;

function _getState(): VinextHeadersShimState {
const state = _als.getStore();
return state ?? _fallbackState;
if (isInsideUnifiedScope()) {
return getRequestContext() as unknown as VinextHeadersShimState;
}
return _als.getStore() ?? _fallbackState;
}

/**
Expand Down Expand Up @@ -171,36 +174,16 @@ export function getHeadersContext(): HeadersContext | null {
}

export function setHeadersContext(ctx: HeadersContext | null): void {
const state = _getState();
if (ctx !== null) {
// For backward compatibility, set context on the current ALS store
// if one exists, otherwise update the fallback. Callers should
// migrate to runWithHeadersContext() for new-request setup.
const existing = _als.getStore();
if (existing) {
existing.headersContext = ctx;
existing.dynamicUsageDetected = false;
existing.pendingSetCookies = [];
existing.draftModeCookieHeader = null;
existing.phase = "render";
} else {
_fallbackState.headersContext = ctx;
_fallbackState.dynamicUsageDetected = false;
_fallbackState.pendingSetCookies = [];
_fallbackState.draftModeCookieHeader = null;
_fallbackState.phase = "render";
}
return;
}

// End of request cleanup: keep the store (so consumeDynamicUsage and
// cookie flushing can still run), but clear the request headers/cookies.
const state = _als.getStore();
if (state) {
state.headersContext = null;
state.headersContext = ctx;
state.dynamicUsageDetected = false;
state.pendingSetCookies = [];
state.draftModeCookieHeader = null;
state.phase = "render";
} else {
_fallbackState.headersContext = null;
_fallbackState.phase = "render";
state.headersContext = null;
state.phase = "render";
}
}

Expand All @@ -218,6 +201,17 @@ export function runWithHeadersContext<T>(
ctx: HeadersContext,
fn: () => T | Promise<T>,
): T | Promise<T> {
if (isInsideUnifiedScope()) {
// Inside unified scope — update the unified store directly, no extra ALS.
const uCtx = getRequestContext();
uCtx.headersContext = ctx as unknown;
uCtx.dynamicUsageDetected = false;
uCtx.pendingSetCookies = [];
uCtx.draftModeCookieHeader = null;
uCtx.phase = "render";
return fn();
}

const state: VinextHeadersShimState = {
headersContext: ctx,
dynamicUsageDetected: false,
Expand Down
22 changes: 9 additions & 13 deletions packages/vinext/src/shims/navigation-state.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@

import { AsyncLocalStorage } from "node:async_hooks";
import { _registerStateAccessors, type NavigationContext } from "./navigation.js";
import { isInsideUnifiedScope, getRequestContext } from "./unified-request-context.js";

// ---------------------------------------------------------------------------
// ALS setup — same pattern as headers.ts
Expand All @@ -35,6 +36,9 @@ const _fallbackState = (_g[_FALLBACK_KEY] ??= {
} satisfies NavigationState) as NavigationState;

function _getState(): NavigationState {
if (isInsideUnifiedScope()) {
return getRequestContext() as unknown as NavigationState;
}
return _als.getStore() ?? _fallbackState;
}

Expand All @@ -44,6 +48,9 @@ function _getState(): NavigationState {
* useServerInsertedHTML callbacks on concurrent runtimes.
*/
export function runWithNavigationContext<T>(fn: () => T | Promise<T>): T | Promise<T> {
if (isInsideUnifiedScope()) {
return fn();
}
const state: NavigationState = {
serverContext: null,
serverInsertedHTMLCallbacks: [],
Expand All @@ -61,25 +68,14 @@ _registerStateAccessors({
},

setServerContext(ctx: NavigationContext | null): void {
const state = _als.getStore();
if (state) {
state.serverContext = ctx;
} else {
// No ALS scope — fallback for environments without als.run() wrapping.
_fallbackState.serverContext = ctx;
}
_getState().serverContext = ctx;
},

getInsertedHTMLCallbacks(): Array<() => unknown> {
return _getState().serverInsertedHTMLCallbacks;
},

clearInsertedHTMLCallbacks(): void {
const state = _als.getStore();
if (state) {
state.serverInsertedHTMLCallbacks = [];
} else {
_fallbackState.serverInsertedHTMLCallbacks = [];
}
_getState().serverInsertedHTMLCallbacks = [];
},
});
8 changes: 8 additions & 0 deletions packages/vinext/src/shims/request-context.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@
*/

import { AsyncLocalStorage } from "node:async_hooks";
import { isInsideUnifiedScope, getRequestContext } from "./unified-request-context.js";

// ---------------------------------------------------------------------------
// ExecutionContext interface
Expand Down Expand Up @@ -64,6 +65,10 @@ export function runWithExecutionContext<T>(
ctx: ExecutionContextLike,
fn: () => T | Promise<T>,
): T | Promise<T> {
if (isInsideUnifiedScope()) {
getRequestContext().executionContext = ctx;
return fn();
}
return _als.run(ctx, fn);
}

Expand All @@ -75,6 +80,9 @@ export function runWithExecutionContext<T>(
* complete before the Worker isolate is torn down.
*/
export function getRequestExecutionContext(): ExecutionContextLike | null {
if (isInsideUnifiedScope()) {
return getRequestContext().executionContext as ExecutionContextLike | null;
}
// getStore() returns undefined when called outside an ALS scope;
// normalise to null for a consistent return type.
return _als.getStore() ?? null;
Expand Down
Loading
Loading