vinext 1.0.0-beta.2 → 1.0.0-beta.3

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/README.md +6 -9
  2. package/dist/build/client-build-config.js +7 -1
  3. package/dist/check.js +4 -0
  4. package/dist/config/config-matchers.js +4 -1
  5. package/dist/config/next-config.d.ts +12 -3
  6. package/dist/config/next-config.js +4 -0
  7. package/dist/entries/app-browser-entry.d.ts +4 -10
  8. package/dist/entries/app-browser-entry.js +20 -4
  9. package/dist/entries/app-rsc-entry.js +3 -0
  10. package/dist/entries/app-rsc-manifest.js +19 -0
  11. package/dist/entries/pages-client-entry.js +0 -1
  12. package/dist/entries/pages-server-entry.js +10 -3
  13. package/dist/index.js +112 -43
  14. package/dist/init-cloudflare.d.ts +3 -1
  15. package/dist/init-cloudflare.js +59 -5
  16. package/dist/plugins/extensionless-dynamic-import.js +23 -18
  17. package/dist/plugins/ignore-dynamic-requests.js +3 -1
  18. package/dist/plugins/import-meta-url.js +17 -6
  19. package/dist/plugins/require-context.js +20 -15
  20. package/dist/plugins/transform-cache.d.ts +19 -0
  21. package/dist/plugins/transform-cache.js +36 -0
  22. package/dist/routing/app-route-graph.d.ts +14 -6
  23. package/dist/routing/app-route-graph.js +106 -16
  24. package/dist/server/api-handler.d.ts +2 -0
  25. package/dist/server/api-handler.js +3 -3
  26. package/dist/server/app-browser-entry.js +103 -31
  27. package/dist/server/app-browser-navigation-controller.d.ts +23 -21
  28. package/dist/server/app-browser-navigation-controller.js +1 -1
  29. package/dist/server/app-page-cache-finalizer.js +1 -1
  30. package/dist/server/app-page-cache.d.ts +1 -1
  31. package/dist/server/app-page-cache.js +16 -2
  32. package/dist/server/app-page-dispatch.d.ts +8 -0
  33. package/dist/server/app-page-dispatch.js +31 -6
  34. package/dist/server/app-page-element-builder.d.ts +2 -0
  35. package/dist/server/app-page-element-builder.js +33 -7
  36. package/dist/server/app-page-head.d.ts +3 -3
  37. package/dist/server/app-page-head.js +58 -13
  38. package/dist/server/app-page-http-access-fallback-metadata.d.ts +2 -2
  39. package/dist/server/app-page-http-access-fallback-metadata.js +9 -1
  40. package/dist/server/app-page-probe.d.ts +4 -0
  41. package/dist/server/app-page-probe.js +6 -2
  42. package/dist/server/app-page-request.d.ts +3 -0
  43. package/dist/server/app-page-request.js +1 -1
  44. package/dist/server/app-page-response.js +1 -1
  45. package/dist/server/app-page-route-wiring.d.ts +7 -0
  46. package/dist/server/app-page-route-wiring.js +211 -49
  47. package/dist/server/app-pages-bridge.d.ts +1 -1
  48. package/dist/server/app-pages-bridge.js +2 -1
  49. package/dist/server/app-route-handler-cache.js +4 -0
  50. package/dist/server/app-route-handler-dispatch.js +9 -3
  51. package/dist/server/app-route-handler-response.js +1 -1
  52. package/dist/server/app-route-module-loader.d.ts +6 -0
  53. package/dist/server/app-route-module-loader.js +3 -0
  54. package/dist/server/app-router-entry.js +6 -3
  55. package/dist/server/app-rsc-errors.js +10 -0
  56. package/dist/server/app-rsc-handler.js +21 -9
  57. package/dist/server/app-rsc-route-matching.d.ts +9 -0
  58. package/dist/server/app-rsc-route-matching.js +6 -0
  59. package/dist/server/app-ssr-entry.js +4 -4
  60. package/dist/server/cache-control.d.ts +2 -2
  61. package/dist/server/cache-control.js +2 -1
  62. package/dist/server/dev-server.d.ts +3 -1
  63. package/dist/server/dev-server.js +145 -312
  64. package/dist/server/headers.d.ts +2 -2
  65. package/dist/server/headers.js +4 -3
  66. package/dist/server/isr-cache.d.ts +10 -33
  67. package/dist/server/isr-cache.js +33 -43
  68. package/dist/server/isr-decision.d.ts +2 -2
  69. package/dist/server/pages-api-route.d.ts +2 -0
  70. package/dist/server/pages-api-route.js +4 -1
  71. package/dist/server/pages-dev-hydration.js +0 -1
  72. package/dist/server/pages-get-initial-props.d.ts +1 -1
  73. package/dist/server/pages-get-initial-props.js +1 -4
  74. package/dist/server/pages-i18n.js +64 -16
  75. package/dist/server/pages-node-compat.d.ts +2 -0
  76. package/dist/server/pages-node-compat.js +7 -3
  77. package/dist/server/pages-page-data.d.ts +61 -9
  78. package/dist/server/pages-page-data.js +334 -74
  79. package/dist/server/pages-page-handler.d.ts +5 -1
  80. package/dist/server/pages-page-handler.js +115 -19
  81. package/dist/server/pages-page-response.d.ts +5 -3
  82. package/dist/server/pages-page-response.js +23 -26
  83. package/dist/server/pages-request-pipeline.d.ts +7 -0
  84. package/dist/server/pages-request-pipeline.js +5 -1
  85. package/dist/server/pages-revalidate.d.ts +1 -1
  86. package/dist/server/pages-revalidate.js +54 -8
  87. package/dist/server/pages-router-entry.d.ts +1 -0
  88. package/dist/server/pages-router-entry.js +10 -4
  89. package/dist/server/prod-server.d.ts +2 -1
  90. package/dist/server/prod-server.js +37 -13
  91. package/dist/server/revalidation-host.d.ts +11 -0
  92. package/dist/server/revalidation-host.js +19 -0
  93. package/dist/server/worker-revalidation-context.d.ts +13 -0
  94. package/dist/server/worker-revalidation-context.js +29 -0
  95. package/dist/shims/cache-handler.d.ts +1 -1
  96. package/dist/shims/cache-handler.js +10 -7
  97. package/dist/shims/cache-runtime.d.ts +6 -0
  98. package/dist/shims/cache-runtime.js +4 -1
  99. package/dist/shims/cache.js +1 -1
  100. package/dist/shims/error.js +1 -1
  101. package/dist/shims/fetch-cache.js +1 -1
  102. package/dist/shims/font-google-base.js +17 -8
  103. package/dist/shims/font-local.js +41 -10
  104. package/dist/shims/headers.d.ts +10 -13
  105. package/dist/shims/headers.js +85 -36
  106. package/dist/shims/internal/cookie-serialize.d.ts +10 -9
  107. package/dist/shims/internal/cookie-serialize.js +9 -8
  108. package/dist/shims/internal/interpolate-as.d.ts +15 -1
  109. package/dist/shims/internal/interpolate-as.js +27 -2
  110. package/dist/shims/link.js +18 -6
  111. package/dist/shims/metadata.d.ts +7 -17
  112. package/dist/shims/metadata.js +74 -24
  113. package/dist/shims/navigation.js +1 -1
  114. package/dist/shims/request-context.d.ts +4 -1
  115. package/dist/shims/router.js +18 -7
  116. package/dist/shims/script.js +1 -1
  117. package/dist/shims/server.d.ts +7 -6
  118. package/dist/shims/server.js +156 -57
  119. package/dist/shims/unified-request-context.d.ts +21 -2
  120. package/dist/shims/unified-request-context.js +82 -1
  121. package/dist/utils/cache-control-metadata.d.ts +2 -1
  122. package/dist/utils/cache-control-metadata.js +5 -1
  123. package/dist/utils/html-limited-bots.js +1 -1
  124. package/dist/utils/protocol-headers.d.ts +7 -1
  125. package/dist/utils/protocol-headers.js +7 -1
  126. package/package.json +1 -1
@@ -3,7 +3,7 @@ import { getRequestContext, isInsideUnifiedScope, runWithUnifiedStateMutation }
3
3
  import { MIDDLEWARE_SET_COOKIE_HEADER } from "../utils/protocol-headers.js";
4
4
  import { FLIGHT_HEADERS, NEXT_HTML_REQUEST_ID_HEADER, NEXT_REQUEST_ID_HEADER } from "../server/headers.js";
5
5
  import { buildRequestHeadersFromMiddlewareResponse } from "../utils/middleware-request-headers.js";
6
- import { serializeSetCookie, validateCookieAttributeValue, validateCookieName } from "./internal/cookie-serialize.js";
6
+ import { serializeSetCookie, validateCookieName } from "./internal/cookie-serialize.js";
7
7
  import { parseEdgeRequestCookieHeader } from "../utils/parse-cookie.js";
8
8
  import { createPprFallbackShellSuspensePromise } from "./ppr-fallback-shell.js";
9
9
  //#region src/shims/headers.ts
@@ -29,7 +29,6 @@ const _fallbackState = _g[_FALLBACK_KEY] ??= {
29
29
  draftModeCookieHeader: null,
30
30
  phase: "render"
31
31
  };
32
- const EXPIRED_COOKIE_DATE = (/* @__PURE__ */ new Date(0)).toUTCString();
33
32
  function splitMiddlewareSetCookieHeader(value) {
34
33
  const cookies = [];
35
34
  let start = 0;
@@ -320,6 +319,7 @@ function peekDynamicUsage() {
320
319
  }
321
320
  function _setStatePhase(state, phase) {
322
321
  const previous = state.phase;
322
+ if (previous === "action" && phase === "render") state.headersContext?.mutableCookies?.[SYNCHRONIZE_REQUEST_COOKIES]();
323
323
  state.phase = phase;
324
324
  return previous;
325
325
  }
@@ -534,7 +534,7 @@ function _sealCookies(cookies) {
534
534
  } });
535
535
  }
536
536
  function _getMutableCookies(ctx) {
537
- if (!ctx.mutableCookies) ctx.mutableCookies = _wrapMutableCookies(new RequestCookies(ctx.cookies));
537
+ if (!ctx.mutableCookies) ctx.mutableCookies = _wrapMutableCookies(new RequestCookies(ctx.cookies, true));
538
538
  return ctx.mutableCookies;
539
539
  }
540
540
  function _getReadonlyCookies(ctx) {
@@ -653,7 +653,6 @@ function getAndClearPendingCookies() {
653
653
  return cookies;
654
654
  }
655
655
  const DRAFT_MODE_COOKIE = "__prerender_bypass";
656
- const DRAFT_MODE_EXPIRED_DATE = (/* @__PURE__ */ new Date(0)).toUTCString();
657
656
  /**
658
657
  * Get any Set-Cookie header generated by draftMode().enable()/disable().
659
658
  * Called by the framework after rendering to attach the header to the response.
@@ -705,10 +704,6 @@ function getActiveDraftModeState() {
705
704
  function isDraftModeEnabled() {
706
705
  return getActiveDraftModeState() ?? false;
707
706
  }
708
- function draftModeCookieAttributes() {
709
- if (typeof process !== "undefined" && process.env?.NODE_ENV === "development") return "Path=/; HttpOnly; SameSite=Lax";
710
- return "Path=/; HttpOnly; SameSite=None; Secure";
711
- }
712
707
  function createDraftModeScopeError(expression) {
713
708
  return /* @__PURE__ */ new Error(`${expression} can only be called from a Server Component, Route Handler, or Server Action.`);
714
709
  }
@@ -746,26 +741,38 @@ async function draftMode() {
746
741
  throwIfInsideCacheScope("draftMode().enable()");
747
742
  const activeContext = requireActiveDraftModeContext(state, context, "draftMode().enable()");
748
743
  markDynamicUsage();
744
+ const cookie = createDraftModeCookie(secret);
745
+ const serialized = serializeMutableCookie(cookie);
746
+ _getMutableCookies(activeContext)[APPLY_RESPONSE_COOKIE](cookie);
749
747
  activeContext.draftModeEnabled = true;
750
- activeContext.cookies.set(DRAFT_MODE_COOKIE, secret);
751
- state.draftModeCookieHeader = `${DRAFT_MODE_COOKIE}=${secret}; ${draftModeCookieAttributes()}`;
748
+ state.draftModeCookieHeader = serialized;
752
749
  },
753
750
  disable() {
754
751
  throwIfInsideCacheScope("draftMode().disable()");
755
752
  const activeContext = requireActiveDraftModeContext(state, context, "draftMode().disable()");
756
753
  markDynamicUsage();
754
+ const cookie = createDraftModeCookie("", /* @__PURE__ */ new Date(0));
755
+ const serialized = serializeMutableCookie(cookie);
756
+ _getMutableCookies(activeContext)[APPLY_RESPONSE_COOKIE](cookie);
757
757
  activeContext.draftModeEnabled = false;
758
- activeContext.cookies.delete(DRAFT_MODE_COOKIE);
759
- state.draftModeCookieHeader = `${DRAFT_MODE_COOKIE}=; ${draftModeCookieAttributes()}; Expires=${DRAFT_MODE_EXPIRED_DATE}`;
758
+ state.draftModeCookieHeader = serialized;
760
759
  }
761
760
  };
762
761
  }
762
+ const APPLY_RESPONSE_COOKIE = Symbol("vinext.apply-response-cookie");
763
+ const SYNCHRONIZE_REQUEST_COOKIES = Symbol("vinext.synchronize-request-cookies");
763
764
  var RequestCookies = class {
764
765
  _cookies;
765
- constructor(cookies) {
766
+ _responseCookies;
767
+ constructor(cookies, mutable = false) {
766
768
  this._cookies = cookies;
769
+ this._responseCookies = mutable ? new Map(Array.from(cookies, ([name, value]) => [name, normalizeMutableCookie({
770
+ name,
771
+ value
772
+ })])) : null;
767
773
  }
768
774
  get(name) {
775
+ if (this._responseCookies) return this._responseCookies.get(name);
769
776
  const value = this._cookies.get(name);
770
777
  if (value === void 0) return void 0;
771
778
  return {
@@ -775,6 +782,10 @@ var RequestCookies = class {
775
782
  }
776
783
  getAll(nameOrOptions) {
777
784
  const name = typeof nameOrOptions === "string" ? nameOrOptions : nameOrOptions?.name;
785
+ if (this._responseCookies) {
786
+ const responseCookies = [...this._responseCookies.values()];
787
+ return name === void 0 ? responseCookies : responseCookies.filter((cookie) => cookie.name === name);
788
+ }
778
789
  const result = [];
779
790
  for (const [cookieName, value] of this._cookies) if (name === void 0 || cookieName === name) result.push({
780
791
  name: cookieName,
@@ -783,7 +794,7 @@ var RequestCookies = class {
783
794
  return result;
784
795
  }
785
796
  has(name) {
786
- return this._cookies.has(name);
797
+ return this._responseCookies?.has(name) ?? this._cookies.has(name);
787
798
  }
788
799
  set(nameOrOptions, value, options) {
789
800
  let cookieName;
@@ -799,43 +810,81 @@ var RequestCookies = class {
799
810
  opts = nameOrOptions;
800
811
  }
801
812
  validateCookieName(cookieName);
802
- this._cookies.set(cookieName, cookieValue);
803
- const sameSite = opts?.sameSite === true ? "Strict" : typeof opts?.sameSite === "string" ? opts.sameSite[0].toUpperCase() + opts.sameSite.slice(1) : void 0;
804
- _getState().pendingSetCookies.push(serializeSetCookie(cookieName, cookieValue, {
805
- ...opts,
806
- expires: typeof opts?.expires === "number" ? new Date(opts.expires) : opts?.expires,
807
- sameSite
808
- }));
813
+ const responseCookie = normalizeMutableCookie({
814
+ name: cookieName,
815
+ value: cookieValue,
816
+ ...opts
817
+ });
818
+ const serialized = serializeMutableCookie(responseCookie);
819
+ this[APPLY_RESPONSE_COOKIE](responseCookie);
820
+ _getState().pendingSetCookies.push(serialized);
809
821
  return this;
810
822
  }
811
823
  delete(nameOrOptions) {
812
824
  const name = typeof nameOrOptions === "string" ? nameOrOptions : nameOrOptions.name;
813
- const path = typeof nameOrOptions === "string" ? "/" : nameOrOptions.path ?? "/";
814
- const domain = typeof nameOrOptions === "string" ? void 0 : nameOrOptions.domain;
815
825
  validateCookieName(name);
816
- validateCookieAttributeValue(path, "Path");
817
- if (domain) validateCookieAttributeValue(domain, "Domain");
818
- this._cookies.delete(name);
819
- const parts = [`${name}=`, `Path=${path}`];
820
- if (domain) parts.push(`Domain=${domain}`);
821
- parts.push(`Expires=${EXPIRED_COOKIE_DATE}`);
822
- _getState().pendingSetCookies.push(parts.join("; "));
826
+ const responseCookie = normalizeMutableCookie({
827
+ ...typeof nameOrOptions === "string" ? void 0 : nameOrOptions,
828
+ name,
829
+ value: "",
830
+ expires: /* @__PURE__ */ new Date(0)
831
+ });
832
+ const serialized = serializeMutableCookie(responseCookie);
833
+ this[APPLY_RESPONSE_COOKIE](responseCookie);
834
+ _getState().pendingSetCookies.push(serialized);
823
835
  return this;
824
836
  }
825
837
  get size() {
826
- return this._cookies.size;
838
+ return this._responseCookies?.size ?? this._cookies.size;
827
839
  }
828
840
  [Symbol.iterator]() {
829
- return new Map(Array.from(this._cookies, ([name, value]) => [name, {
830
- name,
831
- value
832
- }])).entries();
841
+ return new Map(this.getAll().map((cookie) => [cookie.name, cookie])).entries();
833
842
  }
834
843
  toString() {
844
+ if (this._responseCookies) return [...this._responseCookies.values()].map(serializeMutableCookie).join("; ");
835
845
  const parts = [];
836
- for (const [name, value] of this._cookies) parts.push(`${name}=${value}`);
846
+ for (const [name, value] of this._cookies) parts.push(`${name}=${encodeURIComponent(value)}`);
837
847
  return parts.join("; ");
838
848
  }
849
+ _ensureResponseCookies() {
850
+ if (!this._responseCookies) this._responseCookies = new Map(Array.from(this._cookies, ([name, value]) => [name, normalizeMutableCookie({
851
+ name,
852
+ value
853
+ })]));
854
+ return this._responseCookies;
855
+ }
856
+ [APPLY_RESPONSE_COOKIE](cookie) {
857
+ this._ensureResponseCookies().set(cookie.name, cookie);
858
+ }
859
+ [SYNCHRONIZE_REQUEST_COOKIES]() {
860
+ this._cookies.clear();
861
+ for (const cookie of this._ensureResponseCookies().values()) this._cookies.set(cookie.name, cookie.value);
862
+ }
839
863
  };
864
+ function createDraftModeCookie(value, expires) {
865
+ const isDevelopment = typeof process !== "undefined" && process.env?.NODE_ENV === "development";
866
+ return normalizeMutableCookie({
867
+ name: DRAFT_MODE_COOKIE,
868
+ value,
869
+ ...expires === void 0 ? {} : { expires },
870
+ httpOnly: true,
871
+ sameSite: isDevelopment ? "lax" : "none",
872
+ secure: !isDevelopment,
873
+ path: "/"
874
+ });
875
+ }
876
+ function normalizeMutableCookie(cookie) {
877
+ const normalized = { ...cookie };
878
+ if (typeof normalized.expires === "number") normalized.expires = new Date(normalized.expires);
879
+ if (normalized.maxAge) normalized.expires = new Date(Date.now() + normalized.maxAge * 1e3);
880
+ if (normalized.path == null) normalized.path = "/";
881
+ return normalized;
882
+ }
883
+ function serializeMutableCookie(cookie) {
884
+ return serializeSetCookie(cookie.name, cookie.value, {
885
+ ...cookie,
886
+ expires: typeof cookie.expires === "number" ? new Date(cookie.expires) : cookie.expires
887
+ });
888
+ }
840
889
  //#endregion
841
890
  export { applyMiddlewareRequestHeaders, consumeDynamicUsage, consumeInvalidDynamicUsageError, consumeRenderRequestApiUsage, cookies, draftMode, getActiveDraftModeState, getAndClearPendingCookies, getDraftModeCookieHeader, getHeadersAccessPhase, getHeadersContext, headers, headersContextFromRequest, isDraftModeEnabled, isDraftModeRequest, markDynamicUsage, markRenderRequestApiUsage, peekDynamicUsage, peekRenderRequestApiUsage, runWithConnectionProbe, runWithHeadersContext, runWithIsolatedDynamicUsage, setHeadersAccessPhase, setHeadersContext, suspendConnectionProbe, throwIfInsideCacheScope, throwIfStaticGenerationAccessError };
@@ -1,11 +1,11 @@
1
1
  //#region src/shims/internal/cookie-serialize.d.ts
2
2
  /**
3
- * Shared Set-Cookie serialization for the next/headers and next/server shims.
3
+ * Set-Cookie serialization for the mutable next/headers shim.
4
4
  *
5
- * Two call sites — `cookies().set()` in headers.ts and `ResponseCookies.set()`
6
- * in server.ts produce identical Set-Cookie header strings. Keep the
7
- * encoding, attribute order, and validation in one place so subtle RFC 6265
8
- * details (escaping, attribute ordering, etc.) cannot drift between them.
5
+ * `ResponseCookies` in server.ts deliberately uses the exact
6
+ * @edge-runtime/cookies serialization order and casing instead. It also has to
7
+ * preserve the absence of a default path when parsing an existing Set-Cookie
8
+ * header, while this helper always defaults new mutable cookies to `Path=/`.
9
9
  *
10
10
  * Note: this is a value-encoding helper for response cookies only. The
11
11
  * request `Cookie:` header serialization in `RequestCookies._serialize`
@@ -20,7 +20,7 @@ type SerializeSetCookieOptions = {
20
20
  expires?: Date;
21
21
  httpOnly?: boolean;
22
22
  secure?: boolean;
23
- sameSite?: "Strict" | "Lax" | "None";
23
+ sameSite?: true | false | "strict" | "lax" | "none" | "Strict" | "Lax" | "None";
24
24
  partitioned?: boolean;
25
25
  priority?: "low" | "medium" | "high";
26
26
  };
@@ -36,12 +36,13 @@ declare function validateCookieAttributeValue(value: string, attributeName: stri
36
36
  * - Encodes the value with `encodeURIComponent`.
37
37
  * - Defaults `Path` to `/` (matching @edge-runtime/cookies and Next.js).
38
38
  * - Validates path/domain to reject control characters and semicolons.
39
- * - Emits attributes in the order: Path, Domain, Max-Age, Expires, HttpOnly,
40
- * Secure, SameSite, Partitioned, Priority.
39
+ * - Emits attributes in the same order and casing as Next.js's compiled
40
+ * `@edge-runtime/cookies` serializer: Path, Expires, Max-Age, Domain,
41
+ * Secure, HttpOnly, SameSite, Partitioned, Priority.
41
42
  *
42
43
  * The caller is responsible for validating the cookie name (typically before
43
44
  * mutating any internal state) via `validateCookieName`.
44
45
  */
45
- declare function serializeSetCookie(name: string, value: string, options?: SerializeSetCookieOptions): string;
46
+ declare function serializeSetCookie(name: string, value: string | null | undefined, options?: SerializeSetCookieOptions): string;
46
47
  //#endregion
47
48
  export { serializeSetCookie, validateCookieAttributeValue, validateCookieName };
@@ -23,28 +23,29 @@ function validateCookieAttributeValue(value, attributeName) {
23
23
  * - Encodes the value with `encodeURIComponent`.
24
24
  * - Defaults `Path` to `/` (matching @edge-runtime/cookies and Next.js).
25
25
  * - Validates path/domain to reject control characters and semicolons.
26
- * - Emits attributes in the order: Path, Domain, Max-Age, Expires, HttpOnly,
27
- * Secure, SameSite, Partitioned, Priority.
26
+ * - Emits attributes in the same order and casing as Next.js's compiled
27
+ * `@edge-runtime/cookies` serializer: Path, Expires, Max-Age, Domain,
28
+ * Secure, HttpOnly, SameSite, Partitioned, Priority.
28
29
  *
29
30
  * The caller is responsible for validating the cookie name (typically before
30
31
  * mutating any internal state) via `validateCookieName`.
31
32
  */
32
33
  function serializeSetCookie(name, value, options) {
33
- const parts = [`${name}=${encodeURIComponent(value)}`];
34
+ const parts = [`${name}=${encodeURIComponent(value ?? "")}`];
34
35
  const path = options?.path ?? "/";
35
36
  validateCookieAttributeValue(path, "Path");
36
- parts.push(`Path=${path}`);
37
+ if (path) parts.push(`Path=${path}`);
38
+ if (options?.expires) parts.push(`Expires=${options.expires.toUTCString()}`);
39
+ if (options?.maxAge !== void 0) parts.push(`Max-Age=${options.maxAge}`);
37
40
  if (options?.domain) {
38
41
  validateCookieAttributeValue(options.domain, "Domain");
39
42
  parts.push(`Domain=${options.domain}`);
40
43
  }
41
- if (options?.maxAge !== void 0) parts.push(`Max-Age=${options.maxAge}`);
42
- if (options?.expires) parts.push(`Expires=${options.expires.toUTCString()}`);
43
- if (options?.httpOnly) parts.push("HttpOnly");
44
44
  if (options?.secure) parts.push("Secure");
45
+ if (options?.httpOnly) parts.push("HttpOnly");
45
46
  if (options?.sameSite) parts.push(`SameSite=${options.sameSite}`);
46
47
  if (options?.partitioned) parts.push("Partitioned");
47
- if (options?.priority) parts.push(`Priority=${options.priority[0].toUpperCase()}${options.priority.slice(1)}`);
48
+ if (options?.priority) parts.push(`Priority=${options.priority}`);
48
49
  return parts.join("; ");
49
50
  }
50
51
  //#endregion
@@ -14,6 +14,10 @@ type DynamicRouteHrefProjection = {
14
14
  query: ParsedUrlQuery;
15
15
  routePathname: string;
16
16
  };
17
+ type DynamicRouteHrefResolution = {
18
+ /** Route-pattern URL passed to the Pages Router. */href: string; /** Interpolated URL rendered in the anchor and displayed in the browser. */
19
+ as: string;
20
+ };
17
21
  /**
18
22
  * Resolve a bracket-pattern route href against its displayed href. Query
19
23
  * values can be supplied directly (object-form hrefs) or parsed from the route
@@ -21,5 +25,15 @@ type DynamicRouteHrefProjection = {
21
25
  * query delimiter.
22
26
  */
23
27
  declare function interpolateDynamicRouteHref(routeHref: string, asHref: string, queryInput?: UrlQuery): DynamicRouteHrefProjection | null;
28
+ /**
29
+ * Resolve the two URLs that Next.js' Pages Router derives from a dynamic
30
+ * href: the original route-pattern URL used to load the page and the
31
+ * interpolated browser URL. Dynamic params are consumed from the latter's
32
+ * query string while unrelated query values and the hash are retained.
33
+ *
34
+ * Mirrors `resolveHref(router, href, true)` from Next.js:
35
+ * packages/next/src/client/resolve-href.ts.
36
+ */
37
+ declare function resolveDynamicRouteHref(routeHref: string): DynamicRouteHrefResolution | null;
24
38
  //#endregion
25
- export { DynamicRouteHrefProjection, interpolateDynamicRouteHref };
39
+ export { DynamicRouteHrefProjection, DynamicRouteHrefResolution, interpolateDynamicRouteHref, resolveDynamicRouteHref };
@@ -176,11 +176,11 @@ function interpolateAs(route, asPathname, query) {
176
176
  * query delimiter.
177
177
  */
178
178
  function interpolateDynamicRouteHref(routeHref, asHref, queryInput) {
179
- if (!routeHref.includes("[")) return null;
180
179
  const hashIndex = routeHref.indexOf("#");
181
180
  const queryIndex = routeHref.indexOf("?");
182
181
  const pathEnd = [hashIndex, queryIndex].filter((index) => index !== -1).reduce((earliest, index) => Math.min(earliest, index), routeHref.length);
183
182
  const routePathname = routeHref.slice(0, pathEnd);
183
+ if (!routePathname.includes("[")) return null;
184
184
  const trailing = routeHref.slice(pathEnd);
185
185
  const asPathname = asHref.split(/[?#]/, 1)[0];
186
186
  const query = queryInput ? normalizeQuery(queryInput) : parseRouteHrefQuery(routeHref, queryIndex, hashIndex);
@@ -192,5 +192,30 @@ function interpolateDynamicRouteHref(routeHref, asHref, queryInput) {
192
192
  routePathname
193
193
  };
194
194
  }
195
+ /**
196
+ * Resolve the two URLs that Next.js' Pages Router derives from a dynamic
197
+ * href: the original route-pattern URL used to load the page and the
198
+ * interpolated browser URL. Dynamic params are consumed from the latter's
199
+ * query string while unrelated query values and the hash are retained.
200
+ *
201
+ * Mirrors `resolveHref(router, href, true)` from Next.js:
202
+ * packages/next/src/client/resolve-href.ts.
203
+ */
204
+ function resolveDynamicRouteHref(routeHref) {
205
+ const projection = interpolateDynamicRouteHref(routeHref, routeHref);
206
+ if (!projection?.href) return null;
207
+ const hashIndex = projection.href.indexOf("#");
208
+ const hash = hashIndex === -1 ? "" : projection.href.slice(hashIndex);
209
+ const hrefWithoutHash = hashIndex === -1 ? projection.href : projection.href.slice(0, hashIndex);
210
+ const queryIndex = hrefWithoutHash.indexOf("?");
211
+ const pathname = queryIndex === -1 ? hrefWithoutHash : hrefWithoutHash.slice(0, queryIndex);
212
+ const searchParams = new URLSearchParams(queryIndex === -1 ? "" : hrefWithoutHash.slice(queryIndex + 1));
213
+ for (const param of projection.params) searchParams.delete(param);
214
+ const search = searchParams.toString();
215
+ return {
216
+ href: routeHref,
217
+ as: `${pathname}${search ? `?${search}` : ""}${hash}`
218
+ };
219
+ }
195
220
  //#endregion
196
- export { interpolateDynamicRouteHref };
221
+ export { interpolateDynamicRouteHref, resolveDynamicRouteHref };
@@ -2,12 +2,13 @@
2
2
  import { stripBasePath } from "../utils/base-path.js";
3
3
  import { createRouteTrieCache, matchRouteWithTrie } from "../routing/route-matching.js";
4
4
  import { isDangerousScheme, reportBlockedDangerousNavigation } from "./url-safety.js";
5
+ import { RouterContext } from "./internal/router-context.js";
5
6
  import { addLocalePrefix, getDomainLocaleUrl } from "../utils/domain-locale.js";
6
7
  import { getPagesMiddlewareDataHref, prefetchPagesData, resolvePagesDataNavigationTarget } from "./internal/pages-data-target.js";
7
8
  import { markAppRouteDetectedOnPrefetch } from "./internal/app-route-detection.js";
8
9
  import { isAbsoluteOrProtocolRelativeUrl, normalizePathTrailingSlash, toBrowserNavigationHref, toSameOriginAppPath, withBasePath } from "./url-utils.js";
9
10
  import { appendSearchParamsToUrl, urlQueryToSearchParams } from "../utils/query.js";
10
- import { interpolateDynamicRouteHref } from "./internal/interpolate-as.js";
11
+ import { interpolateDynamicRouteHref, resolveDynamicRouteHref } from "./internal/interpolate-as.js";
11
12
  import { getCurrentBrowserLocale } from "./client-locale.js";
12
13
  import { getCurrentRoutePathnameForWarning } from "./internal/route-pattern-for-warning.js";
13
14
  import { isBotUserAgent } from "../utils/html-limited-bots.js";
@@ -258,7 +259,6 @@ function prefetchUrl(href, mode, priority = "low", pagesRouteHref, locale) {
258
259
  const isOptimisticRouteShellPrefetch = !autoPrefetch.cacheForNavigation;
259
260
  const hasSearchParams = new URL(fullHref, window.location.href).search !== "";
260
261
  const isAutomaticSearchParamShell = mode === "auto" && isOptimisticRouteShellPrefetch && hasSearchParams;
261
- if (isOptimisticRouteShellPrefetch && interceptionContext !== null) return;
262
262
  const hasSearchAgnosticShell = isAutomaticSearchParamShell && hasSearchAgnosticPrefetchShellForRoute(await createRscRequestUrl(fullHref, new Headers()), interceptionContext, mountedSlotsHeader);
263
263
  const headers = createRscRequestHeaders({
264
264
  interceptionContext,
@@ -581,17 +581,29 @@ function resolveConcreteRouteHref(href, as) {
581
581
  return interpolateDynamicRouteHref(typeof href === "string" ? href : resolveHref(href), as, typeof href === "string" || !href.query || typeof href.query === "string" ? void 0 : href.query)?.href || null;
582
582
  }
583
583
  const Link = forwardRef(function Link({ href, as, replace = false, prefetch: prefetchProp, scroll = true, shallow = false, children: childrenProp, onClick, onMouseEnter, onTouchStart, onNavigate, unstable_dynamicOnHover = false, legacyBehavior = false, passHref = false, transitionTypes: _transitionTypes, ...rest }, forwardedRef) {
584
+ const pagesRouter = useContext(RouterContext);
584
585
  const asHref = as === void 0 ? void 0 : resolveHref(as);
586
+ const hrefStr = resolveHref(href);
585
587
  const { locale, ...restWithoutLocale } = rest;
586
588
  let children = childrenProp;
587
589
  if (legacyBehavior && (typeof childrenProp === "string" || typeof childrenProp === "number")) children = React.createElement("a", null, childrenProp);
588
- const unresolvedHref = asHref ?? resolveHref(href);
590
+ const hrefForImplicitInterpolation = isAbsoluteOrProtocolRelativeUrl(hrefStr) ? hrefStr.startsWith("//") ? null : toSameOriginAppPath(hrefStr, __basePath) : hrefStr;
591
+ const implicitDynamicRouteHref = HAS_PAGES_ROUTER && pagesRouter !== null && asHref === void 0 && hrefForImplicitInterpolation !== null ? resolveDynamicRouteHref(hrefForImplicitInterpolation) : null;
592
+ const dynamicRouteHref = implicitDynamicRouteHref ? {
593
+ ...implicitDynamicRouteHref,
594
+ href: hrefStr
595
+ } : null;
596
+ const pagesAsHref = asHref ?? dynamicRouteHref?.as;
597
+ const unresolvedHref = pagesAsHref ?? hrefStr;
589
598
  const rawResolvedHref = typeof unresolvedHref === "string" && unresolvedHref.startsWith("#") ? resolvePagesQueryOnlyHref(unresolvedHref) : unresolvedHref;
590
- const routeHrefRaw = (HAS_PAGES_ROUTER ? resolveConcreteRouteHref(href, asHref) : null) ?? (typeof href === "string" ? href : resolveHref(href));
599
+ const concreteRouteHref = HAS_PAGES_ROUTER ? resolveConcreteRouteHref(dynamicRouteHref ? hrefForImplicitInterpolation ?? href : href, pagesAsHref) : null;
600
+ const routeHrefRaw = dynamicRouteHref?.href ?? concreteRouteHref ?? hrefStr;
601
+ const prefetchRouteHrefRaw = concreteRouteHref ?? routeHrefRaw;
602
+ const hasPagesHrefAsPair = HAS_PAGES_ROUTER && typeof pagesAsHref === "string" && typeof routeHrefRaw === "string" && pagesAsHref !== routeHrefRaw;
591
603
  const resolvedHref = typeof rawResolvedHref === "string" ? warnAndNormalizeRepeatedSlashesInHref(rawResolvedHref) : rawResolvedHref;
592
604
  const isDangerous = typeof resolvedHref === "string" && isDangerousScheme(resolvedHref);
593
605
  const normalizedHref = normalizePathTrailingSlash(applyLocaleToHref(isDangerous ? "/" : resolvedHref, locale), __trailingSlash);
594
- const normalizedRouteHref = HAS_PAGES_ROUTER && typeof asHref === "string" && typeof routeHrefRaw === "string" && asHref !== routeHrefRaw ? normalizePathTrailingSlash(applyLocaleToHref(isDangerous ? "/" : routeHrefRaw, locale), __trailingSlash) : normalizedHref;
606
+ const normalizedRouteHref = hasPagesHrefAsPair ? normalizePathTrailingSlash(applyLocaleToHref(isDangerous ? "/" : prefetchRouteHrefRaw, locale), __trailingSlash) : normalizedHref;
595
607
  const fullHref = normalizePathTrailingSlash(withBasePath(normalizedHref, __basePath), __trailingSlash);
596
608
  const [pending, setPending] = useState(false);
597
609
  const mountedRef = useRef(true);
@@ -713,7 +725,7 @@ const Link = forwardRef(function Link({ href, as, replace = false, prefetch: pre
713
725
  e.preventDefault();
714
726
  const hasAppNavigationRuntime = Boolean(getNavigationRuntime()?.functions.navigate);
715
727
  const pagesNavigateHref = HAS_PAGES_ROUTER && resolvedHref.startsWith("?") ? resolvePagesLinkNavigationHref(resolvedHref, locale) : navigateHref;
716
- const pagesAsForLink = HAS_PAGES_ROUTER && typeof asHref === "string" && typeof routeHrefRaw === "string" && asHref !== routeHrefRaw ? pagesNavigateHref : void 0;
728
+ const pagesAsForLink = hasPagesHrefAsPair ? pagesNavigateHref : void 0;
717
729
  const pagesHrefForLink = pagesAsForLink === void 0 ? pagesNavigateHref : routeHrefRaw;
718
730
  const absoluteFullHref = toBrowserNavigationHref(hasAppNavigationRuntime ? navigateHref : pagesNavigateHref, window.location.href, __basePath);
719
731
  if (onNavigate) try {
@@ -1,31 +1,21 @@
1
1
  import { ThenableParamsObserver } from "./thenable-params.js";
2
2
  import React from "react";
3
+ import { ResolvedViewport as ResolvedViewport$1, Viewport as Viewport$1 } from "@vinext/types/next/upstream/dist/lib/metadata/types/metadata-interface";
3
4
 
4
5
  //#region src/shims/metadata.d.ts
5
- type Viewport = {
6
- /** Viewport width (default: "device-width") */width?: string | number; /** Viewport height */
7
- height?: string | number; /** Initial scale */
8
- initialScale?: number; /** Minimum scale */
9
- minimumScale?: number; /** Maximum scale */
10
- maximumScale?: number; /** Whether user can scale */
11
- userScalable?: boolean; /** Theme color — single color or array of { media, color } */
12
- themeColor?: string | Array<{
13
- media?: string;
14
- color: string;
15
- }>; /** Color scheme: 'light' | 'dark' | 'light dark' | 'normal' */
16
- colorScheme?: string;
17
- };
6
+ type Viewport = Viewport$1;
7
+ type ResolvedViewport = ResolvedViewport$1;
18
8
  /**
19
9
  * Resolve viewport config from a module. Handles both static `viewport` export
20
10
  * and async `generateViewport()` function.
21
11
  */
22
- declare function resolveModuleViewport(mod: Record<string, unknown>, params: Record<string, string | string[]>, searchParams?: Record<string, string | string[]>, searchParamsObserver?: ThenableParamsObserver): Promise<Viewport | null>;
12
+ declare function resolveModuleViewport(mod: Record<string, unknown>, params?: Record<string, string | string[]>, searchParams?: Record<string, string | string[]>, parent?: Promise<ResolvedViewport>, searchParamsObserver?: ThenableParamsObserver): Promise<Viewport | null>;
23
13
  /**
24
14
  * Merge viewport configs from multiple sources (layouts + page).
25
15
  * Later entries override earlier ones.
26
16
  */
27
- declare const DEFAULT_VIEWPORT: Viewport;
28
- declare function mergeViewport(viewportList: Viewport[]): Viewport;
17
+ declare const DEFAULT_VIEWPORT: ResolvedViewport;
18
+ declare function mergeViewport(viewportList: readonly Viewport[]): ResolvedViewport;
29
19
  /**
30
20
  * React component that renders viewport meta tags into <head>.
31
21
  */
@@ -283,4 +273,4 @@ declare function MetadataHead({
283
273
  trailingSlash
284
274
  }: MetadataHeadProps): React.JSX.Element;
285
275
  //#endregion
286
- export { DEFAULT_VIEWPORT, Metadata, MetadataHead, MetadataMergeEntry, Viewport, ViewportHead, mergeMetadata, mergeMetadataEntries, mergeViewport, postProcessMetadata, renderMetadataToHtml, resolveModuleMetadata, resolveModuleViewport };
276
+ export { DEFAULT_VIEWPORT, Metadata, MetadataHead, MetadataMergeEntry, ResolvedViewport, Viewport, ViewportHead, mergeMetadata, mergeMetadataEntries, mergeViewport, postProcessMetadata, renderMetadataToHtml, resolveModuleMetadata, resolveModuleViewport };
@@ -9,18 +9,20 @@ import { Fragment as Fragment$1, jsx } from "react/jsx-runtime";
9
9
  * Handles `export const metadata` and `export async function generateMetadata()`.
10
10
  * Resolves metadata from layouts and pages (pages override layouts).
11
11
  */
12
+ const USE_CACHE_FUNCTION_SYMBOL = Symbol.for("vinext.useCacheFunction");
13
+ const USE_CACHE_ACCEPTS_SECOND_ARGUMENT_SYMBOL = Symbol.for("vinext.useCacheAcceptsSecondArgument");
12
14
  /**
13
15
  * Resolve viewport config from a module. Handles both static `viewport` export
14
16
  * and async `generateViewport()` function.
15
17
  */
16
- async function resolveModuleViewport(mod, params, searchParams, searchParamsObserver) {
18
+ async function resolveModuleViewport(mod, params = {}, searchParams, parent = Promise.resolve(mergeViewport([])), searchParamsObserver) {
17
19
  if (typeof mod.generateViewport === "function") {
18
20
  const asyncParams = makeThenableParams(params);
19
21
  const props = searchParams === void 0 ? { params: asyncParams } : {
20
22
  params: asyncParams,
21
23
  searchParams: makeThenableParams(searchParams, searchParamsObserver)
22
24
  };
23
- return await mod.generateViewport(props);
25
+ return await mod.generateViewport(props, parent);
24
26
  }
25
27
  if (mod.viewport && typeof mod.viewport === "object") return mod.viewport;
26
28
  return null;
@@ -31,47 +33,92 @@ async function resolveModuleViewport(mod, params, searchParams, searchParamsObse
31
33
  */
32
34
  const DEFAULT_VIEWPORT = {
33
35
  width: "device-width",
34
- initialScale: 1
36
+ initialScale: 1,
37
+ themeColor: null,
38
+ colorScheme: null
35
39
  };
36
40
  function mergeViewport(viewportList) {
37
41
  const merged = { ...DEFAULT_VIEWPORT };
38
- for (const vp of viewportList) Object.assign(merged, vp);
42
+ for (const viewport of viewportList) for (const viewportKey in viewport) switch (viewportKey) {
43
+ case "themeColor":
44
+ merged.themeColor = resolveThemeColor(viewport.themeColor);
45
+ break;
46
+ case "colorScheme":
47
+ merged.colorScheme = viewport.colorScheme || null;
48
+ break;
49
+ case "width":
50
+ merged.width = viewport.width;
51
+ break;
52
+ case "height":
53
+ merged.height = viewport.height;
54
+ break;
55
+ case "initialScale":
56
+ merged.initialScale = viewport.initialScale;
57
+ break;
58
+ case "minimumScale":
59
+ merged.minimumScale = viewport.minimumScale;
60
+ break;
61
+ case "maximumScale":
62
+ merged.maximumScale = viewport.maximumScale;
63
+ break;
64
+ case "userScalable":
65
+ merged.userScalable = viewport.userScalable;
66
+ break;
67
+ case "viewportFit":
68
+ merged.viewportFit = viewport.viewportFit;
69
+ break;
70
+ case "interactiveWidget":
71
+ merged.interactiveWidget = viewport.interactiveWidget;
72
+ break;
73
+ default:
74
+ }
39
75
  return merged;
40
76
  }
77
+ const VIEWPORT_META_NAMES = {
78
+ width: "width",
79
+ height: "height",
80
+ initialScale: "initial-scale",
81
+ minimumScale: "minimum-scale",
82
+ maximumScale: "maximum-scale",
83
+ userScalable: "user-scalable",
84
+ viewportFit: "viewport-fit",
85
+ interactiveWidget: "interactive-widget"
86
+ };
41
87
  /**
42
88
  * React component that renders viewport meta tags into <head>.
43
89
  */
44
90
  function ViewportHead({ viewport }) {
91
+ const resolvedViewport = mergeViewport([viewport]);
45
92
  const elements = [];
46
93
  let key = 0;
47
94
  const parts = [];
48
- if (viewport.width !== void 0) parts.push(`width=${viewport.width}`);
49
- if (viewport.height !== void 0) parts.push(`height=${viewport.height}`);
50
- if (viewport.initialScale !== void 0) parts.push(`initial-scale=${viewport.initialScale}`);
51
- if (viewport.minimumScale !== void 0) parts.push(`minimum-scale=${viewport.minimumScale}`);
52
- if (viewport.maximumScale !== void 0) parts.push(`maximum-scale=${viewport.maximumScale}`);
53
- if (viewport.userScalable !== void 0) parts.push(`user-scalable=${viewport.userScalable ? "yes" : "no"}`);
95
+ for (const key of Object.keys(VIEWPORT_META_NAMES)) {
96
+ const value = resolvedViewport[key];
97
+ if (value == null) continue;
98
+ parts.push(`${VIEWPORT_META_NAMES[key]}=${key === "userScalable" ? value ? "yes" : "no" : value}`);
99
+ }
54
100
  if (parts.length > 0) elements.push(/* @__PURE__ */ jsx("meta", {
55
101
  name: "viewport",
56
102
  content: parts.join(", ")
57
103
  }, key++));
58
- if (viewport.themeColor) {
59
- if (typeof viewport.themeColor === "string") elements.push(/* @__PURE__ */ jsx("meta", {
60
- name: "theme-color",
61
- content: viewport.themeColor
62
- }, key++));
63
- else if (Array.isArray(viewport.themeColor)) for (const entry of viewport.themeColor) elements.push(/* @__PURE__ */ jsx("meta", {
64
- name: "theme-color",
65
- content: entry.color,
66
- ...entry.media ? { media: entry.media } : {}
67
- }, key++));
68
- }
69
- if (viewport.colorScheme) elements.push(/* @__PURE__ */ jsx("meta", {
104
+ if (resolvedViewport.themeColor) for (const entry of resolvedViewport.themeColor) elements.push(/* @__PURE__ */ jsx("meta", {
105
+ name: "theme-color",
106
+ content: entry.color,
107
+ ...entry.media ? { media: entry.media } : {}
108
+ }, key++));
109
+ if (resolvedViewport.colorScheme) elements.push(/* @__PURE__ */ jsx("meta", {
70
110
  name: "color-scheme",
71
- content: viewport.colorScheme
111
+ content: resolvedViewport.colorScheme
72
112
  }, key++));
73
113
  return /* @__PURE__ */ jsx(Fragment$1, { children: elements });
74
114
  }
115
+ function resolveThemeColor(themeColor) {
116
+ if (!themeColor) return null;
117
+ return (Array.isArray(themeColor) ? themeColor : [themeColor]).map((descriptor) => typeof descriptor === "string" ? { color: descriptor } : {
118
+ color: descriptor.color,
119
+ media: descriptor.media
120
+ });
121
+ }
75
122
  /**
76
123
  * Merge metadata from multiple sources (layouts + page).
77
124
  *
@@ -219,12 +266,15 @@ function mergeMetadataEntries(entries) {
219
266
  */
220
267
  async function resolveModuleMetadata(mod, params = {}, searchParams, parent = Promise.resolve({}), searchParamsObserver) {
221
268
  if (typeof mod.generateMetadata === "function") {
269
+ const generateMetadata = mod.generateMetadata;
222
270
  const asyncParams = makeThenableParams(params);
223
271
  const props = searchParams === void 0 ? { params: asyncParams } : {
224
272
  params: asyncParams,
225
273
  searchParams: makeThenableParams(searchParams, searchParamsObserver)
226
274
  };
227
- return await (mod.generateMetadata.length >= 2 ? mod.generateMetadata(props, parent) : mod.generateMetadata(props));
275
+ const isUseCacheFunction = Reflect.get(generateMetadata, USE_CACHE_FUNCTION_SYMBOL) === true;
276
+ const acceptsSecondArgument = Reflect.get(generateMetadata, USE_CACHE_ACCEPTS_SECOND_ARGUMENT_SYMBOL);
277
+ return await (!isUseCacheFunction || (typeof acceptsSecondArgument === "boolean" ? acceptsSecondArgument : generateMetadata.length >= 2) ? generateMetadata(props, parent) : generateMetadata(props));
228
278
  }
229
279
  if (mod.metadata && typeof mod.metadata === "object") return mod.metadata;
230
280
  return null;