vinext 0.1.4 → 0.1.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (126) hide show
  1. package/dist/build/css-url-assets.d.ts +1 -1
  2. package/dist/build/css-url-assets.js +9 -7
  3. package/dist/build/prerender.js +2 -1
  4. package/dist/cache/cache-adapters-virtual.js +1 -1
  5. package/dist/cloudflare/src/cache/kv-data-adapter.runtime.d.ts +1 -1
  6. package/dist/entries/app-rsc-entry.js +24 -20
  7. package/dist/entries/pages-server-entry.js +2 -1
  8. package/dist/index.js +187 -146
  9. package/dist/plugins/css-data-url.js +30 -26
  10. package/dist/plugins/extensionless-dynamic-import.js +27 -24
  11. package/dist/plugins/import-meta-url.js +21 -15
  12. package/dist/plugins/instrumentation-client.js +1 -1
  13. package/dist/plugins/middleware-server-only.js +7 -6
  14. package/dist/plugins/og-assets.js +48 -46
  15. package/dist/plugins/optimize-imports.js +9 -3
  16. package/dist/plugins/remove-console.d.ts +7 -1
  17. package/dist/plugins/remove-console.js +4 -1
  18. package/dist/plugins/require-context.js +21 -20
  19. package/dist/plugins/strip-server-exports.d.ts +7 -1
  20. package/dist/plugins/strip-server-exports.js +4 -1
  21. package/dist/server/app-bfcache-identity.d.ts +26 -0
  22. package/dist/server/app-bfcache-identity.js +127 -0
  23. package/dist/server/app-browser-entry.js +14 -11
  24. package/dist/server/app-browser-navigation-controller.js +1 -1
  25. package/dist/server/app-browser-state.d.ts +2 -21
  26. package/dist/server/app-browser-state.js +4 -128
  27. package/dist/server/app-browser-stream.js +1 -1
  28. package/dist/server/app-browser-visible-commit.js +3 -2
  29. package/dist/server/app-fallback-renderer.d.ts +1 -1
  30. package/dist/server/app-layout-param-observation.d.ts +1 -1
  31. package/dist/server/app-layout-param-observation.js +1 -1
  32. package/dist/server/app-middleware.js +2 -1
  33. package/dist/server/app-page-boundary-render.d.ts +1 -1
  34. package/dist/server/app-page-boundary.js +1 -1
  35. package/dist/server/app-page-cache-finalizer.d.ts +62 -0
  36. package/dist/server/app-page-cache-finalizer.js +122 -0
  37. package/dist/server/app-page-cache-render.d.ts +2 -2
  38. package/dist/server/app-page-cache-render.js +1 -1
  39. package/dist/server/app-page-cache.d.ts +2 -53
  40. package/dist/server/app-page-cache.js +5 -131
  41. package/dist/server/app-page-dispatch.d.ts +2 -2
  42. package/dist/server/app-page-dispatch.js +10 -8
  43. package/dist/server/app-page-probe.js +3 -2
  44. package/dist/server/app-page-render-observation.js +2 -2
  45. package/dist/server/app-page-render.d.ts +3 -3
  46. package/dist/server/app-page-render.js +3 -2
  47. package/dist/server/app-page-stream.d.ts +2 -9
  48. package/dist/server/app-page-stream.js +1 -35
  49. package/dist/server/app-request-context.d.ts +1 -2
  50. package/dist/server/app-request-context.js +2 -1
  51. package/dist/server/app-route-handler-dispatch.js +3 -2
  52. package/dist/server/app-route-handler-execution.d.ts +1 -1
  53. package/dist/server/app-route-handler-execution.js +1 -1
  54. package/dist/server/app-route-handler-response.d.ts +1 -1
  55. package/dist/server/app-router-entry.js +2 -1
  56. package/dist/server/app-rsc-handler.js +22 -16
  57. package/dist/server/app-rsc-response-finalizer.js +1 -1
  58. package/dist/server/app-server-action-execution.d.ts +1 -1
  59. package/dist/server/app-server-action-execution.js +5 -4
  60. package/dist/server/app-ssr-entry.d.ts +1 -1
  61. package/dist/server/app-ssr-entry.js +11 -9
  62. package/dist/server/app-ssr-router-instance.d.ts +6 -0
  63. package/dist/server/app-ssr-router-instance.js +24 -0
  64. package/dist/server/app-ssr-stream.js +1 -1
  65. package/dist/server/artifact-compatibility.js +1 -1
  66. package/dist/server/client-reuse-manifest.js +1 -1
  67. package/dist/server/defer-until-stream-consumed.d.ts +7 -0
  68. package/dist/server/defer-until-stream-consumed.js +34 -0
  69. package/dist/server/dev-server.js +1 -1
  70. package/dist/server/instrumentation.js +1 -1
  71. package/dist/server/isr-cache.d.ts +1 -1
  72. package/dist/server/isr-cache.js +1 -1
  73. package/dist/server/isr-decision.d.ts +1 -1
  74. package/dist/server/middleware-matcher.js +8 -6
  75. package/dist/server/middleware-runtime.js +2 -2
  76. package/dist/server/open-redirect.d.ts +12 -0
  77. package/dist/server/open-redirect.js +21 -0
  78. package/dist/server/pages-page-data.d.ts +1 -1
  79. package/dist/server/pages-page-response.d.ts +1 -1
  80. package/dist/server/pages-page-response.js +2 -2
  81. package/dist/server/prod-server.js +2 -1
  82. package/dist/server/request-pipeline.d.ts +1 -24
  83. package/dist/server/request-pipeline.js +1 -33
  84. package/dist/server/seed-cache.d.ts +1 -1
  85. package/dist/shims/cache-handler.d.ts +106 -0
  86. package/dist/shims/cache-handler.js +176 -0
  87. package/dist/shims/cache-request-state.d.ts +47 -0
  88. package/dist/shims/cache-request-state.js +126 -0
  89. package/dist/shims/cache-runtime.d.ts +2 -2
  90. package/dist/shims/cache-runtime.js +3 -14
  91. package/dist/shims/cache.d.ts +3 -231
  92. package/dist/shims/cache.js +17 -383
  93. package/dist/shims/cdn-cache.d.ts +1 -1
  94. package/dist/shims/cdn-cache.js +1 -1
  95. package/dist/shims/error-boundary-navigation.d.ts +7 -0
  96. package/dist/shims/error-boundary-navigation.js +44 -0
  97. package/dist/shims/error-boundary.js +10 -8
  98. package/dist/shims/error.js +2 -1
  99. package/dist/shims/fetch-cache.js +1 -1
  100. package/dist/shims/form.js +1 -1
  101. package/dist/shims/image.js +67 -9
  102. package/dist/shims/internal/app-page-props-cache-key.d.ts +5 -0
  103. package/dist/shims/internal/app-page-props-cache-key.js +16 -0
  104. package/dist/shims/internal/navigation-untracked.js +2 -1
  105. package/dist/shims/layout-segment-context.d.ts +1 -1
  106. package/dist/shims/layout-segment-context.js +2 -1
  107. package/dist/shims/link.js +2 -2
  108. package/dist/shims/navigation-context-state.d.ts +40 -0
  109. package/dist/shims/navigation-context-state.js +116 -0
  110. package/dist/shims/navigation-errors.d.ts +55 -0
  111. package/dist/shims/navigation-errors.js +110 -0
  112. package/dist/shims/navigation-server.d.ts +3 -0
  113. package/dist/shims/navigation-server.js +3 -0
  114. package/dist/shims/navigation-state.d.ts +1 -2
  115. package/dist/shims/navigation-state.js +2 -1
  116. package/dist/shims/navigation.d.ts +3 -291
  117. package/dist/shims/navigation.js +14 -445
  118. package/dist/shims/navigation.react-server.d.ts +2 -2
  119. package/dist/shims/navigation.react-server.js +3 -1
  120. package/dist/shims/request-state-types.d.ts +3 -3
  121. package/dist/shims/script.js +1 -1
  122. package/dist/shims/slot.js +3 -1
  123. package/dist/shims/unified-request-context.d.ts +2 -2
  124. package/dist/utils/virtual-module.d.ts +5 -0
  125. package/dist/utils/virtual-module.js +0 -0
  126. package/package.json +5 -1
@@ -1,12 +1,13 @@
1
1
  import "./server-globals.js";
2
2
  import { notFoundResponse } from "./http-error-responses.js";
3
- import { isOpenRedirectShaped } from "./request-pipeline.js";
4
- import { isPprFallbackShellAbortError } from "../shims/ppr-fallback-shell.js";
3
+ import { isOpenRedirectShaped } from "./open-redirect.js";
5
4
  import { AppElementsWire } from "./app-elements-wire.js";
6
5
  import "./app-elements.js";
6
+ import { isPprFallbackShellAbortError } from "../shims/ppr-fallback-shell.js";
7
7
  import { AppRouterContext } from "../shims/internal/app-router-context.js";
8
8
  import { appendAssetDeploymentIdQuery } from "../utils/deployment-id.js";
9
- import { ServerInsertedHTMLContext, appRouterInstance, clearServerInsertedHTML, getBfcacheIdMapContext, renderServerInsertedHTML, setNavigationContext, useServerInsertedHTML } from "../shims/navigation.js";
9
+ import { ServerInsertedHTMLContext, clearServerInsertedHTML, getBfcacheIdMapContext, registerServerInsertedHTMLCallback, renderServerInsertedHTML, setNavigationContext } from "../shims/navigation-context-state.js";
10
+ import "../shims/navigation-server.js";
10
11
  import { runWithNavigationContext } from "../shims/navigation-state.js";
11
12
  import { withScriptNonce } from "../shims/script-nonce-context.js";
12
13
  import { createInlineScriptTag, createNonceAttribute, escapeHtmlAttr, safeJsonStringify } from "./html.js";
@@ -15,14 +16,15 @@ import DefaultGlobalError from "../shims/default-global-error.js";
15
16
  import { BfcacheStateKeyMapContext, ElementsContext, Slot } from "../shims/slot.js";
16
17
  import { createSsrErrorMetaRenderer } from "./app-ssr-error-meta.js";
17
18
  import { createNavigationRuntimeRscMetadataScript, createRscEmbedTransform, createTickBufferedTransform } from "./app-ssr-stream.js";
18
- import { BeforeInteractiveContext } from "../shims/before-interactive-context.js";
19
- import { runWithRootParamsScope } from "../shims/root-params.js";
20
- import { createBfcacheSegmentStateKeyMap, createInitialBfcacheIdMap } from "./app-browser-state.js";
19
+ import { createBfcacheSegmentStateKeyMap, createInitialBfcacheIdMap } from "./app-bfcache-identity.js";
21
20
  import { RSC_FORM_STATE_GLOBAL } from "./app-browser-hydration.js";
22
21
  import { createClientReferencePreloader } from "./app-client-reference-preloader.js";
23
- import { deferUntilStreamConsumed } from "./app-page-stream.js";
22
+ import { deferUntilStreamConsumed } from "./defer-until-stream-consumed.js";
23
+ import { runWithRootParamsScope } from "../shims/root-params.js";
24
+ import { BeforeInteractiveContext } from "../shims/before-interactive-context.js";
24
25
  import { renderBeforeInteractiveInlineScripts } from "./before-interactive-head.js";
25
26
  import { createInitialDevServerErrorScript } from "./dev-initial-server-error.js";
27
+ import { ssrAppRouterInstance } from "./app-ssr-router-instance.js";
26
28
  import { Fragment, createElement, use } from "react";
27
29
  import { renderToReadableStream, renderToStaticMarkup } from "react-dom/server.edge";
28
30
  import { createFromReadableStream } from "@vitejs/plugin-rsc/ssr";
@@ -192,8 +194,8 @@ async function handleSsr(rscStream, navContext, fontData, options) {
192
194
  return BfcacheIdMapContext ? createElement(BfcacheIdMapContext.Provider, { value: createInitialBfcacheIdMap(elements) }, stateKeyTree) : stateKeyTree;
193
195
  }
194
196
  const flightRootElement = createElement(VinextFlightRoot);
195
- const root = AppRouterContext ? createElement(AppRouterContext.Provider, { value: appRouterInstance }, flightRootElement) : flightRootElement;
196
- const ssrTree = ServerInsertedHTMLContext ? createElement(ServerInsertedHTMLContext.Provider, { value: useServerInsertedHTML }, root) : root;
197
+ const root = AppRouterContext ? createElement(AppRouterContext.Provider, { value: ssrAppRouterInstance }, flightRootElement) : flightRootElement;
198
+ const ssrTree = ServerInsertedHTMLContext ? createElement(ServerInsertedHTMLContext.Provider, { value: registerServerInsertedHTMLCallback }, root) : root;
197
199
  const beforeInteractiveInlineScripts = [];
198
200
  const registerBeforeInteractiveInlineScript = (script) => {
199
201
  beforeInteractiveInlineScripts.push(script);
@@ -0,0 +1,6 @@
1
+ import { AppRouterInstance } from "../shims/internal/app-router-context.js";
2
+
3
+ //#region src/server/app-ssr-router-instance.d.ts
4
+ declare const ssrAppRouterInstance: AppRouterInstance;
5
+ //#endregion
6
+ export { ssrAppRouterInstance };
@@ -0,0 +1,24 @@
1
+ import { assertSafeNavigationUrl } from "../shims/url-safety.js";
2
+ import "./app-bfcache-id.js";
3
+ //#region src/server/app-ssr-router-instance.ts
4
+ function validateNavigationHref(href) {
5
+ assertSafeNavigationUrl(href);
6
+ }
7
+ const ssrAppRouterInstance = {
8
+ bfcacheId: "0",
9
+ back() {},
10
+ forward() {},
11
+ refresh() {},
12
+ push(href, _options) {
13
+ validateNavigationHref(href);
14
+ },
15
+ replace(href, _options) {
16
+ validateNavigationHref(href);
17
+ },
18
+ prefetch(href) {
19
+ validateNavigationHref(href);
20
+ }
21
+ };
22
+ if (process.env.__NEXT_GESTURE_TRANSITION) ssrAppRouterInstance.experimental_gesturePush = validateNavigationHref;
23
+ //#endregion
24
+ export { ssrAppRouterInstance };
@@ -1,6 +1,6 @@
1
- import { NAVIGATION_RUNTIME_SYMBOL_DESCRIPTION } from "../client/navigation-runtime.js";
2
1
  import { createInlineScriptTag, escapeHtmlAttr, htmlTokenListContains, safeJsonStringify } from "./html.js";
3
2
  import { bytesToBase64, concatUint8Arrays } from "./app-rsc-embedded-chunks.js";
3
+ import { NAVIGATION_RUNTIME_SYMBOL_DESCRIPTION } from "../client/navigation-runtime.js";
4
4
  //#region src/server/app-ssr-stream.ts
5
5
  const NAVIGATION_RUNTIME_REFERENCE = `self[Symbol.for(${safeJsonStringify(NAVIGATION_RUNTIME_SYMBOL_DESCRIPTION)})]`;
6
6
  function navigationRuntimeRscBootstrapExpression() {
@@ -1,5 +1,5 @@
1
- import { fnv1a64 } from "../utils/hash.js";
2
1
  import { isUnknownRecord } from "../utils/record.js";
2
+ import { fnv1a64 } from "../utils/hash.js";
3
3
  //#region src/server/artifact-compatibility.ts
4
4
  const ARTIFACT_COMPATIBILITY_SCHEMA_VERSION = 1;
5
5
  const APP_ELEMENTS_SCHEMA_VERSION = 1;
@@ -1,5 +1,5 @@
1
- import { fnv1a64 } from "../utils/hash.js";
2
1
  import { isUnknownRecord } from "../utils/record.js";
2
+ import { fnv1a64 } from "../utils/hash.js";
3
3
  import { parseArtifactCompatibilityEnvelope } from "./artifact-compatibility.js";
4
4
  import { AppElementsWire } from "./app-elements-wire.js";
5
5
  import { isNonNegativeSafeInteger } from "../utils/number.js";
@@ -0,0 +1,7 @@
1
+ //#region src/server/defer-until-stream-consumed.d.ts
2
+ /**
3
+ * Defers cleanup until the downstream consumer drains or cancels the stream.
4
+ */
5
+ declare function deferUntilStreamConsumed(stream: ReadableStream<Uint8Array>, onFlush: () => void): ReadableStream<Uint8Array>;
6
+ //#endregion
7
+ export { deferUntilStreamConsumed };
@@ -0,0 +1,34 @@
1
+ //#region src/server/defer-until-stream-consumed.ts
2
+ /**
3
+ * Defers cleanup until the downstream consumer drains or cancels the stream.
4
+ */
5
+ function deferUntilStreamConsumed(stream, onFlush) {
6
+ let called = false;
7
+ const once = () => {
8
+ if (!called) {
9
+ called = true;
10
+ onFlush();
11
+ }
12
+ };
13
+ const cleanup = new TransformStream({ flush() {
14
+ once();
15
+ } });
16
+ const reader = stream.pipeThrough(cleanup).getReader();
17
+ return new ReadableStream({
18
+ pull(controller) {
19
+ return reader.read().then(({ done, value }) => {
20
+ if (done) controller.close();
21
+ else controller.enqueue(value);
22
+ }, (error) => {
23
+ once();
24
+ controller.error(error);
25
+ });
26
+ },
27
+ cancel(reason) {
28
+ once();
29
+ return reader.cancel(reason);
30
+ }
31
+ });
32
+ }
33
+ //#endregion
34
+ export { deferUntilStreamConsumed };
@@ -7,10 +7,10 @@ import { normalizeStaticPathname } from "../routing/route-pattern.js";
7
7
  import { importModule, reportRequestError } from "./instrumentation.js";
8
8
  import { buildCacheStateHeaders } from "./cache-headers.js";
9
9
  import { isUnknownRecord } from "../utils/record.js";
10
- import { _runWithCacheState } from "../shims/cache.js";
11
10
  import { NEVER_CACHE_CONTROL, NO_STORE_CACHE_CONTROL } from "./cache-control.js";
12
11
  import { buildMissIsrCacheControl, decideIsr } from "./isr-decision.js";
13
12
  import { PRERENDER_REVALIDATE_HEADER, buildPagesCacheValue, getRevalidateDuration, isOnDemandRevalidateRequest, isrCacheKey, isrGet, isrSet, setRevalidateDuration, triggerBackgroundRegeneration } from "./isr-cache.js";
13
+ import { _runWithCacheState } from "../shims/cache-request-state.js";
14
14
  import { ensureFetchPatch, runWithFetchCache } from "../shims/fetch-cache.js";
15
15
  import { runWithPrivateCache } from "../shims/cache-runtime.js";
16
16
  import { mergeRouteParamsIntoQuery, parseQueryString } from "../utils/query.js";
@@ -51,7 +51,7 @@ async function importModule(runner, id) {
51
51
  const INSTRUMENTATION_LOCATIONS = ["", "src/"];
52
52
  function findInstrumentationHookFile(root, basename, fileMatcher) {
53
53
  for (const dir of INSTRUMENTATION_LOCATIONS) for (const ext of fileMatcher.dottedExtensions) {
54
- const fullPath = path.join(root, dir, `${basename}${ext}`);
54
+ const fullPath = path.posix.join(root, dir, `${basename}${ext}`);
55
55
  if (fs.existsSync(fullPath)) return fullPath;
56
56
  }
57
57
  return null;
@@ -1,5 +1,5 @@
1
1
  import { RenderObservation } from "./cache-proof.js";
2
- import { CacheHandlerValue, CachedAppPageValue, CachedPagesValue, IncrementalCacheValue } from "../shims/cache.js";
2
+ import { CacheHandlerValue, CachedAppPageValue, CachedPagesValue, IncrementalCacheValue } from "../shims/cache-handler.js";
3
3
  import { OnRequestErrorContext } from "./instrumentation.js";
4
4
  import { normalizeMountedSlotsHeader } from "./app-mounted-slots-header.js";
5
5
  import { AppRscRenderMode } from "./app-rsc-render-mode.js";
@@ -1,7 +1,7 @@
1
1
  import { getRequestExecutionContext } from "../shims/request-context.js";
2
2
  import { reportRequestError } from "./instrumentation.js";
3
- import { fnv1a64 } from "../utils/hash.js";
4
3
  import { getCdnCacheAdapter } from "../shims/cdn-cache.js";
4
+ import { fnv1a64 } from "../utils/hash.js";
5
5
  import { normalizeMountedSlotsHeader } from "./app-mounted-slots-header.js";
6
6
  import { APP_RSC_RENDER_MODE_NAVIGATION, getRscRenderModeCacheVariant } from "./app-rsc-render-mode.js";
7
7
  import { normalizeAppPageInterceptionProofPathname } from "./app-page-render-identity.js";
@@ -1,4 +1,4 @@
1
- import { CacheControlMetadata } from "../shims/cache.js";
1
+ import { CacheControlMetadata } from "../shims/cache-handler.js";
2
2
  import { NEVER_CACHE_CONTROL, NO_STORE_CACHE_CONTROL } from "./cache-control.js";
3
3
 
4
4
  //#region src/server/isr-decision.d.ts
@@ -48,17 +48,19 @@ function stripLocalePrefix(pathname, i18nConfig) {
48
48
  const segments = pathname.split("/");
49
49
  const firstSegment = segments[1];
50
50
  if (!firstSegment || !i18nConfig.locales.includes(firstSegment)) return null;
51
- return removeTrailingSlash("/" + segments.slice(2).join("/"));
51
+ return "/" + segments.slice(2).join("/");
52
52
  }
53
53
  function matchPattern(pathname, pattern) {
54
- let cached = _mwPatternCache.get(pattern);
54
+ const normalizedPattern = /[\\():*+?]/.test(pattern) ? pattern : removeTrailingSlash(pattern);
55
+ let cached = _mwPatternCache.get(normalizedPattern);
55
56
  if (cached === void 0) {
56
- cached = compileMatcherPattern(pattern);
57
- _mwPatternCache.set(pattern, cached);
57
+ cached = compileMatcherPattern(normalizedPattern);
58
+ _mwPatternCache.set(normalizedPattern, cached);
58
59
  }
59
60
  if (cached === UNSAFE_MATCHER_PATTERN) return true;
60
- if (cached === null) return pathname === pattern;
61
- return cached.test(pathname);
61
+ if (cached === null) return removeTrailingSlash(pathname) === normalizedPattern;
62
+ if (cached.test(pathname)) return true;
63
+ return pathname.endsWith("/") && cached.test(removeTrailingSlash(pathname));
62
64
  }
63
65
  function extractConstraint(str, re) {
64
66
  if (str[re.lastIndex] !== "(") return null;
@@ -1,5 +1,5 @@
1
1
  import { normalizePathnameForRouteMatchStrict } from "../routing/utils.js";
2
- import { addBasePathToPathname, hasBasePath, stripBasePath } from "../utils/base-path.js";
2
+ import { addBasePathToPathname, hasBasePath, removeTrailingSlash, stripBasePath } from "../utils/base-path.js";
3
3
  import "./server-globals.js";
4
4
  import { getRequestExecutionContext, runWithExecutionContext } from "../shims/request-context.js";
5
5
  import { MIDDLEWARE_REWRITE_HEADER } from "./headers.js";
@@ -130,7 +130,7 @@ async function executeMiddleware(options) {
130
130
  const matchPathname = options.basePath ? stripBasePath(normalizedPathname, options.basePath) : normalizedPathname;
131
131
  if (!matchesMiddleware(matchPathname, middlewareMatcher(options.module), options.request, options.i18nConfig)) return { continue: true };
132
132
  const nextRequest = createNextRequest(options.request, normalizedPathname, options.i18nConfig, options.basePath, options.trailingSlash, hadBasePath);
133
- const fetchEvent = new NextFetchEvent({ page: matchPathname });
133
+ const fetchEvent = new NextFetchEvent({ page: removeTrailingSlash(matchPathname) });
134
134
  let response;
135
135
  try {
136
136
  response = await middlewareFn(nextRequest, fetchEvent);
@@ -0,0 +1,12 @@
1
+ //#region src/server/open-redirect.d.ts
2
+ /**
3
+ * Returns true if a request pathname looks like a protocol-relative open
4
+ * redirect, in either literal or percent-encoded form.
5
+ *
6
+ * A pathname is considered "open redirect shaped" when its first segment,
7
+ * after decoding backslashes and encoded delimiters, would cause a browser
8
+ * to resolve a `Location` containing the pathname as protocol-relative.
9
+ */
10
+ declare function isOpenRedirectShaped(rawPathname: string): boolean;
11
+ //#endregion
12
+ export { isOpenRedirectShaped };
@@ -0,0 +1,21 @@
1
+ //#region src/server/open-redirect.ts
2
+ /**
3
+ * Returns true if a request pathname looks like a protocol-relative open
4
+ * redirect, in either literal or percent-encoded form.
5
+ *
6
+ * A pathname is considered "open redirect shaped" when its first segment,
7
+ * after decoding backslashes and encoded delimiters, would cause a browser
8
+ * to resolve a `Location` containing the pathname as protocol-relative.
9
+ */
10
+ function isOpenRedirectShaped(rawPathname) {
11
+ if (!rawPathname.startsWith("/")) return false;
12
+ const afterSlash = rawPathname.slice(1);
13
+ if (afterSlash.startsWith("/") || afterSlash.startsWith("\\")) return true;
14
+ if (afterSlash.length >= 3 && afterSlash[0] === "%") {
15
+ const encoded = afterSlash.slice(0, 3).toLowerCase();
16
+ if (encoded === "%5c" || encoded === "%2f") return true;
17
+ }
18
+ return false;
19
+ }
20
+ //#endregion
21
+ export { isOpenRedirectShaped };
@@ -1,6 +1,6 @@
1
1
  import { Route } from "../routing/pages-router.js";
2
2
  import { VinextNextData } from "../client/vinext-next-data.js";
3
- import { CachedPagesValue } from "../shims/cache.js";
3
+ import { CachedPagesValue } from "../shims/cache-handler.js";
4
4
  import { ISRCacheEntry } from "./isr-cache.js";
5
5
  import { PagesGsspResponse, PagesI18nRenderContext, PagesNextDataExtras } from "./pages-page-response.js";
6
6
  import { ReactNode } from "react";
@@ -1,5 +1,5 @@
1
1
  import { VinextNextData } from "../client/vinext-next-data.js";
2
- import { CachedPagesValue } from "../shims/cache.js";
2
+ import { CachedPagesValue } from "../shims/cache-handler.js";
3
3
  import { RenderPageEnhancers } from "./pages-document-initial-props.js";
4
4
  import { ComponentType, ReactNode } from "react";
5
5
 
@@ -1,10 +1,10 @@
1
1
  import { getRequestExecutionContext } from "../shims/request-context.js";
2
2
  import { reportRequestError } from "./instrumentation.js";
3
3
  import { setCacheStateHeaders } from "./cache-headers.js";
4
- import { fnv1a52 } from "../utils/hash.js";
5
- import { encodeCacheTag } from "../utils/encode-cache-tag.js";
6
4
  import { NEVER_CACHE_CONTROL, NO_STORE_CACHE_CONTROL, applyCdnResponseHeaders } from "./cache-control.js";
7
5
  import { buildMissIsrCacheControl } from "./isr-decision.js";
6
+ import { fnv1a52 } from "../utils/hash.js";
7
+ import { encodeCacheTag } from "../utils/encode-cache-tag.js";
8
8
  import { appendAssetDeploymentIdQuery } from "../utils/deployment-id.js";
9
9
  import { withScriptNonce } from "../shims/script-nonce-context.js";
10
10
  import { createNonceAttribute, escapeHtmlAttr } from "./html.js";
@@ -3,7 +3,8 @@ import { hasBasePath, stripBasePath } from "../utils/base-path.js";
3
3
  import { VINEXT_PRERENDER_ROUTE_PARAMS_HEADER, VINEXT_PRERENDER_SECRET_HEADER, VINEXT_STATIC_FILE_HEADER } from "./headers.js";
4
4
  import { normalizePath } from "./normalize-path.js";
5
5
  import { notFoundResponse } from "./http-error-responses.js";
6
- import { filterInternalHeaders, isOpenRedirectShaped } from "./request-pipeline.js";
6
+ import { isOpenRedirectShaped } from "./open-redirect.js";
7
+ import { filterInternalHeaders } from "./request-pipeline.js";
7
8
  import { isUnknownRecord } from "../utils/record.js";
8
9
  import { resolveRequestHost, resolveRequestProtocol, trustProxy, trustedHosts } from "./proxy-trust.js";
9
10
  import { DEFAULT_DEVICE_SIZES, DEFAULT_IMAGE_SIZES, isImageOptimizationPath, isSafeImageContentType, parseImageParams } from "./image-optimization.js";
@@ -1,6 +1,7 @@
1
1
  import { NextHeader } from "../config/next-config.js";
2
2
  import { BasePathMatchState, RequestContext } from "../config/config-matchers.js";
3
3
  import { INTERNAL_HEADERS, VINEXT_INTERNAL_HEADERS } from "./headers.js";
4
+ import { isOpenRedirectShaped } from "./open-redirect.js";
4
5
  import { hasBasePath, stripBasePath } from "../utils/base-path.js";
5
6
 
6
7
  //#region src/server/request-pipeline.d.ts
@@ -42,30 +43,6 @@ import { hasBasePath, stripBasePath } from "../utils/base-path.js";
42
43
  * @returns A 404 Response if the path is protocol-relative, or null to continue
43
44
  */
44
45
  declare function guardProtocolRelativeUrl(rawPathname: string): Response | null;
45
- /**
46
- * Returns true if a request pathname looks like a protocol-relative open
47
- * redirect, in either literal or percent-encoded form.
48
- *
49
- * Exported for call sites that need to replicate the guard inline (Pages
50
- * Router worker codegen, Node production server) and for defense-in-depth
51
- * checks inside redirect emitters.
52
- *
53
- * A pathname is considered "open redirect shaped" when its first segment,
54
- * after decoding backslashes and encoded delimiters, would cause a browser
55
- * to resolve a `Location` containing the pathname as protocol-relative:
56
- *
57
- * - literal `//evil.com`
58
- * - literal `/\evil.com` (browsers normalize `\` to `/`)
59
- * - encoded `/%5Cevil.com` (`%5C` decodes to `\` in Location)
60
- * - encoded `/%2F/evil.com` (`%2F` decodes to `/` → `//`)
61
- * - mixed `/%5C%2F`, `/%5C%5C` (and other combinations)
62
- *
63
- * We explicitly do not require a valid percent sequence elsewhere in the
64
- * pathname — we only examine the leading bytes (up to the second real or
65
- * encoded delimiter) so malformed suffixes can still reach the normal
66
- * "400 Bad Request" decode path instead of being masked as "404".
67
- */
68
- declare function isOpenRedirectShaped(rawPathname: string): boolean;
69
46
  type HeaderRecord = Record<string, string | string[]>;
70
47
  type ApplyConfigHeadersOptions = {
71
48
  configHeaders: NextHeader[];
@@ -2,6 +2,7 @@ import { hasBasePath, removeTrailingSlash, stripBasePath } from "../utils/base-p
2
2
  import { INTERNAL_HEADERS, VINEXT_INTERNAL_HEADERS, VINEXT_STATIC_FILE_HEADER } from "./headers.js";
3
3
  import { matchHeaders } from "../config/config-matchers.js";
4
4
  import { forbiddenResponse, notFoundResponse } from "./http-error-responses.js";
5
+ import { isOpenRedirectShaped } from "./open-redirect.js";
5
6
  //#region src/server/request-pipeline.ts
6
7
  /**
7
8
  * Shared request pipeline utilities.
@@ -44,39 +45,6 @@ function guardProtocolRelativeUrl(rawPathname) {
44
45
  if (isOpenRedirectShaped(rawPathname)) return notFoundResponse();
45
46
  return null;
46
47
  }
47
- /**
48
- * Returns true if a request pathname looks like a protocol-relative open
49
- * redirect, in either literal or percent-encoded form.
50
- *
51
- * Exported for call sites that need to replicate the guard inline (Pages
52
- * Router worker codegen, Node production server) and for defense-in-depth
53
- * checks inside redirect emitters.
54
- *
55
- * A pathname is considered "open redirect shaped" when its first segment,
56
- * after decoding backslashes and encoded delimiters, would cause a browser
57
- * to resolve a `Location` containing the pathname as protocol-relative:
58
- *
59
- * - literal `//evil.com`
60
- * - literal `/\evil.com` (browsers normalize `\` to `/`)
61
- * - encoded `/%5Cevil.com` (`%5C` decodes to `\` in Location)
62
- * - encoded `/%2F/evil.com` (`%2F` decodes to `/` → `//`)
63
- * - mixed `/%5C%2F`, `/%5C%5C` (and other combinations)
64
- *
65
- * We explicitly do not require a valid percent sequence elsewhere in the
66
- * pathname — we only examine the leading bytes (up to the second real or
67
- * encoded delimiter) so malformed suffixes can still reach the normal
68
- * "400 Bad Request" decode path instead of being masked as "404".
69
- */
70
- function isOpenRedirectShaped(rawPathname) {
71
- if (!rawPathname.startsWith("/")) return false;
72
- const afterSlash = rawPathname.slice(1);
73
- if (afterSlash.startsWith("/") || afterSlash.startsWith("\\")) return true;
74
- if (afterSlash.length >= 3 && afterSlash[0] === "%") {
75
- const encoded = afterSlash.slice(0, 3).toLowerCase();
76
- if (encoded === "%5c" || encoded === "%2f") return true;
77
- }
78
- return false;
79
- }
80
48
  const FILE_LIKE_PATHNAME_RE = /\.[^/]+\/?$/;
81
49
  function isWellKnownPathname(pathname) {
82
50
  return pathname === "/.well-known" || pathname.startsWith("/.well-known/");
@@ -1,4 +1,4 @@
1
- import { CachedAppPageValue } from "../shims/cache.js";
1
+ import { CachedAppPageValue } from "../shims/cache-handler.js";
2
2
 
3
3
  //#region src/server/seed-cache.d.ts
4
4
  type PrerenderCacheSeedMetadata = {
@@ -0,0 +1,106 @@
1
+ import { RenderObservation } from "../server/cache-proof.js";
2
+
3
+ //#region src/shims/cache-handler.d.ts
4
+ type CacheHandlerValue = {
5
+ lastModified: number;
6
+ age?: number;
7
+ cacheState?: string;
8
+ cacheControl?: CacheControlMetadata;
9
+ value: IncrementalCacheValue | null;
10
+ };
11
+ type CacheControlMetadata = {
12
+ revalidate: number;
13
+ expire?: number;
14
+ };
15
+ type IncrementalCacheValue = CachedFetchValue | CachedAppPageValue | CachedPagesValue | CachedRouteValue | CachedRedirectValue | CachedImageValue;
16
+ type CachedFetchValue = {
17
+ kind: "FETCH";
18
+ data: {
19
+ headers: Record<string, string>;
20
+ body: string;
21
+ url: string;
22
+ status?: number;
23
+ };
24
+ tags?: string[];
25
+ revalidate: number | false;
26
+ };
27
+ type CachedAppPageValue = {
28
+ kind: "APP_PAGE";
29
+ html: string;
30
+ rscData: ArrayBuffer | undefined;
31
+ headers: Record<string, string | string[]> | undefined;
32
+ postponed: string | undefined;
33
+ renderObservation?: RenderObservation;
34
+ status: number | undefined;
35
+ };
36
+ type CachedPagesValue = {
37
+ kind: "PAGES";
38
+ html: string;
39
+ pageData: object;
40
+ generatedFromDataRequest?: boolean;
41
+ headers: Record<string, string | string[]> | undefined;
42
+ status: number | undefined;
43
+ };
44
+ type CachedRouteValue = {
45
+ kind: "APP_ROUTE";
46
+ body: ArrayBuffer;
47
+ status: number;
48
+ headers: Record<string, string | string[]>;
49
+ };
50
+ type CachedRedirectValue = {
51
+ kind: "REDIRECT";
52
+ props: object;
53
+ };
54
+ type CachedImageValue = {
55
+ kind: "IMAGE";
56
+ etag: string;
57
+ buffer: ArrayBuffer;
58
+ extension: string;
59
+ revalidate?: number;
60
+ };
61
+ type CacheHandlerContext = {
62
+ dev?: boolean;
63
+ maxMemoryCacheSize?: number;
64
+ revalidatedTags?: string[];
65
+ [key: string]: unknown;
66
+ };
67
+ type CacheHandler = {
68
+ get(key: string, ctx?: Record<string, unknown>): Promise<CacheHandlerValue | null>;
69
+ set(key: string, data: IncrementalCacheValue | null, ctx?: Record<string, unknown>): Promise<void>;
70
+ revalidateTag(tags: string | string[], durations?: {
71
+ expire?: number;
72
+ }): Promise<void>;
73
+ resetRequestCache?(): void;
74
+ };
75
+ declare class NoOpCacheHandler implements CacheHandler {
76
+ get(_key: string, _ctx?: Record<string, unknown>): Promise<CacheHandlerValue | null>;
77
+ set(_key: string, _data: IncrementalCacheValue | null, _ctx?: Record<string, unknown>): Promise<void>;
78
+ revalidateTag(_tags: string | string[], _durations?: {
79
+ expire?: number;
80
+ }): Promise<void>;
81
+ }
82
+ type MemoryCacheHandlerOptions = Pick<CacheHandlerContext, "maxMemoryCacheSize"> & {
83
+ cacheMaxMemorySize?: number;
84
+ };
85
+ declare class MemoryCacheHandler implements CacheHandler {
86
+ private store;
87
+ private tagRevalidatedAt;
88
+ private readonly maxMemoryCacheSize;
89
+ private currentMemoryCacheSize;
90
+ constructor(options?: number | MemoryCacheHandlerOptions);
91
+ private estimateEntrySize;
92
+ private deleteEntry;
93
+ private touchEntry;
94
+ private evictLeastRecentlyUsed;
95
+ get(key: string, ctx?: Record<string, unknown>): Promise<CacheHandlerValue | null>;
96
+ set(key: string, data: IncrementalCacheValue | null, ctx?: Record<string, unknown>): Promise<void>;
97
+ revalidateTag(tags: string | string[]): Promise<void>;
98
+ resetRequestCache(): void;
99
+ }
100
+ declare function configureMemoryCacheHandler(options?: MemoryCacheHandlerOptions): void;
101
+ declare function setDataCacheHandler(handler: CacheHandler): void;
102
+ declare function getDataCacheHandler(): CacheHandler;
103
+ declare function setCacheHandler(handler: CacheHandler): void;
104
+ declare function getCacheHandler(): CacheHandler;
105
+ //#endregion
106
+ export { CacheControlMetadata, CacheHandler, CacheHandlerContext, CacheHandlerValue, CachedAppPageValue, CachedFetchValue, CachedImageValue, CachedPagesValue, CachedRedirectValue, CachedRouteValue, IncrementalCacheValue, MemoryCacheHandler, NoOpCacheHandler, configureMemoryCacheHandler, getCacheHandler, getDataCacheHandler, setCacheHandler, setDataCacheHandler };