vinext 1.0.0-beta.2 → 1.0.0-beta.4

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 (174) hide show
  1. package/README.md +6 -9
  2. package/dist/build/client-build-config.js +7 -1
  3. package/dist/build/precompress.js +1 -1
  4. package/dist/build/prerender.d.ts +2 -1
  5. package/dist/build/prerender.js +12 -2
  6. package/dist/check.js +4 -0
  7. package/dist/cli-args.d.ts +2 -1
  8. package/dist/cli-args.js +10 -0
  9. package/dist/cli.js +14 -8
  10. package/dist/client/navigation-runtime.d.ts +6 -0
  11. package/dist/client/navigation-runtime.js +1 -1
  12. package/dist/config/config-matchers.js +4 -1
  13. package/dist/config/dotenv.d.ts +3 -4
  14. package/dist/config/dotenv.js +30 -22
  15. package/dist/config/next-config.d.ts +16 -5
  16. package/dist/config/next-config.js +7 -0
  17. package/dist/entries/app-browser-entry.d.ts +4 -10
  18. package/dist/entries/app-browser-entry.js +20 -4
  19. package/dist/entries/app-rsc-entry.js +21 -3
  20. package/dist/entries/app-rsc-manifest.js +19 -0
  21. package/dist/entries/pages-client-entry.js +0 -1
  22. package/dist/entries/pages-server-entry.js +13 -3
  23. package/dist/index.js +122 -49
  24. package/dist/init-cloudflare.d.ts +3 -1
  25. package/dist/init-cloudflare.js +59 -5
  26. package/dist/plugins/client-reference-dedup.js +7 -5
  27. package/dist/plugins/extensionless-dynamic-import.js +23 -18
  28. package/dist/plugins/ignore-dynamic-requests.js +3 -1
  29. package/dist/plugins/import-meta-url.js +17 -6
  30. package/dist/plugins/require-context.js +20 -15
  31. package/dist/plugins/sass.js +3 -4
  32. package/dist/plugins/transform-cache.d.ts +19 -0
  33. package/dist/plugins/transform-cache.js +36 -0
  34. package/dist/routing/app-route-graph.d.ts +14 -6
  35. package/dist/routing/app-route-graph.js +106 -16
  36. package/dist/server/api-handler.d.ts +2 -0
  37. package/dist/server/api-handler.js +7 -6
  38. package/dist/server/app-bfcache-identity.js +28 -2
  39. package/dist/server/app-browser-entry.js +175 -75
  40. package/dist/server/app-browser-navigation-controller.d.ts +23 -21
  41. package/dist/server/app-browser-navigation-controller.js +30 -5
  42. package/dist/server/app-browser-prefetch-response.d.ts +9 -0
  43. package/dist/server/app-browser-prefetch-response.js +32 -0
  44. package/dist/server/app-browser-state.d.ts +8 -3
  45. package/dist/server/app-browser-state.js +9 -3
  46. package/dist/server/app-page-cache-finalizer.js +1 -1
  47. package/dist/server/app-page-cache.d.ts +1 -1
  48. package/dist/server/app-page-cache.js +16 -2
  49. package/dist/server/app-page-dispatch.d.ts +9 -1
  50. package/dist/server/app-page-dispatch.js +37 -8
  51. package/dist/server/app-page-element-builder.d.ts +4 -1
  52. package/dist/server/app-page-element-builder.js +34 -7
  53. package/dist/server/app-page-head.d.ts +3 -3
  54. package/dist/server/app-page-head.js +58 -13
  55. package/dist/server/app-page-http-access-fallback-metadata.d.ts +2 -2
  56. package/dist/server/app-page-http-access-fallback-metadata.js +9 -1
  57. package/dist/server/app-page-probe.d.ts +4 -0
  58. package/dist/server/app-page-probe.js +6 -2
  59. package/dist/server/app-page-render.d.ts +1 -1
  60. package/dist/server/app-page-render.js +13 -3
  61. package/dist/server/app-page-request.d.ts +3 -1
  62. package/dist/server/app-page-request.js +4 -13
  63. package/dist/server/app-page-response.d.ts +2 -0
  64. package/dist/server/app-page-response.js +7 -2
  65. package/dist/server/app-page-route-wiring.d.ts +10 -2
  66. package/dist/server/app-page-route-wiring.js +269 -59
  67. package/dist/server/app-pages-bridge.d.ts +2 -1
  68. package/dist/server/app-pages-bridge.js +3 -1
  69. package/dist/server/app-route-handler-cache.js +4 -0
  70. package/dist/server/app-route-handler-dispatch.js +9 -3
  71. package/dist/server/app-route-handler-response.js +1 -1
  72. package/dist/server/app-route-module-loader.d.ts +6 -0
  73. package/dist/server/app-route-module-loader.js +3 -0
  74. package/dist/server/app-router-entry.js +6 -3
  75. package/dist/server/app-rsc-cache-busting.d.ts +7 -0
  76. package/dist/server/app-rsc-cache-busting.js +6 -0
  77. package/dist/server/app-rsc-errors.js +10 -0
  78. package/dist/server/app-rsc-handler.d.ts +2 -0
  79. package/dist/server/app-rsc-handler.js +30 -13
  80. package/dist/server/app-rsc-render-mode.d.ts +3 -2
  81. package/dist/server/app-rsc-render-mode.js +4 -1
  82. package/dist/server/app-rsc-request-normalization.js +65 -4
  83. package/dist/server/app-rsc-route-matching.d.ts +9 -0
  84. package/dist/server/app-rsc-route-matching.js +6 -0
  85. package/dist/server/app-server-action-execution.d.ts +1 -1
  86. package/dist/server/app-ssr-entry.js +4 -4
  87. package/dist/server/app-visited-response-cache.d.ts +6 -1
  88. package/dist/server/app-visited-response-cache.js +49 -1
  89. package/dist/server/cache-control.d.ts +2 -2
  90. package/dist/server/cache-control.js +2 -1
  91. package/dist/server/dev-server.d.ts +3 -1
  92. package/dist/server/dev-server.js +204 -348
  93. package/dist/server/edge-api-runtime.d.ts +10 -1
  94. package/dist/server/edge-api-runtime.js +29 -1
  95. package/dist/server/headers.d.ts +4 -2
  96. package/dist/server/headers.js +6 -3
  97. package/dist/server/isr-cache.d.ts +10 -33
  98. package/dist/server/isr-cache.js +33 -43
  99. package/dist/server/isr-decision.d.ts +2 -2
  100. package/dist/server/pages-api-route.d.ts +4 -0
  101. package/dist/server/pages-api-route.js +6 -3
  102. package/dist/server/pages-asset-tags.d.ts +1 -0
  103. package/dist/server/pages-asset-tags.js +6 -4
  104. package/dist/server/pages-dev-hydration.js +0 -1
  105. package/dist/server/pages-document-asset-props.d.ts +26 -0
  106. package/dist/server/pages-document-asset-props.js +69 -0
  107. package/dist/server/pages-get-initial-props.d.ts +1 -1
  108. package/dist/server/pages-get-initial-props.js +1 -4
  109. package/dist/server/pages-i18n.js +64 -16
  110. package/dist/server/pages-node-compat.d.ts +2 -0
  111. package/dist/server/pages-node-compat.js +10 -6
  112. package/dist/server/pages-page-data.d.ts +61 -9
  113. package/dist/server/pages-page-data.js +334 -74
  114. package/dist/server/pages-page-handler.d.ts +6 -1
  115. package/dist/server/pages-page-handler.js +119 -20
  116. package/dist/server/pages-page-response.d.ts +7 -3
  117. package/dist/server/pages-page-response.js +41 -33
  118. package/dist/server/pages-request-pipeline.d.ts +7 -0
  119. package/dist/server/pages-request-pipeline.js +5 -1
  120. package/dist/server/pages-revalidate.d.ts +1 -1
  121. package/dist/server/pages-revalidate.js +54 -8
  122. package/dist/server/pages-router-entry.d.ts +1 -0
  123. package/dist/server/pages-router-entry.js +10 -4
  124. package/dist/server/prerender-manifest.d.ts +1 -0
  125. package/dist/server/prod-server.d.ts +2 -1
  126. package/dist/server/prod-server.js +38 -13
  127. package/dist/server/revalidation-host.d.ts +11 -0
  128. package/dist/server/revalidation-host.js +19 -0
  129. package/dist/server/seed-cache.js +1 -1
  130. package/dist/server/worker-revalidation-context.d.ts +13 -0
  131. package/dist/server/worker-revalidation-context.js +30 -0
  132. package/dist/shims/app-router-scroll-state.d.ts +2 -1
  133. package/dist/shims/app-router-scroll-state.js +7 -2
  134. package/dist/shims/cache-handler.d.ts +1 -1
  135. package/dist/shims/cache-handler.js +10 -7
  136. package/dist/shims/cache-runtime.d.ts +6 -0
  137. package/dist/shims/cache-runtime.js +4 -1
  138. package/dist/shims/cache.js +1 -1
  139. package/dist/shims/document.js +15 -2
  140. package/dist/shims/error.js +1 -1
  141. package/dist/shims/fetch-cache.js +1 -1
  142. package/dist/shims/font-google-base.js +17 -8
  143. package/dist/shims/font-local.js +41 -10
  144. package/dist/shims/hash-scroll.d.ts +1 -1
  145. package/dist/shims/hash-scroll.js +2 -1
  146. package/dist/shims/headers.d.ts +10 -13
  147. package/dist/shims/headers.js +85 -36
  148. package/dist/shims/internal/cookie-serialize.d.ts +10 -9
  149. package/dist/shims/internal/cookie-serialize.js +9 -8
  150. package/dist/shims/internal/interpolate-as.d.ts +15 -1
  151. package/dist/shims/internal/interpolate-as.js +27 -2
  152. package/dist/shims/link.d.ts +2 -0
  153. package/dist/shims/link.js +77 -24
  154. package/dist/shims/metadata.d.ts +18 -34
  155. package/dist/shims/metadata.js +98 -56
  156. package/dist/shims/navigation.d.ts +18 -2
  157. package/dist/shims/navigation.js +70 -21
  158. package/dist/shims/request-context.d.ts +6 -2
  159. package/dist/shims/router.js +18 -7
  160. package/dist/shims/script.js +1 -1
  161. package/dist/shims/server.d.ts +7 -6
  162. package/dist/shims/server.js +156 -57
  163. package/dist/shims/slot.d.ts +2 -1
  164. package/dist/shims/slot.js +6 -2
  165. package/dist/shims/streamed-icons.d.ts +9 -0
  166. package/dist/shims/streamed-icons.js +33 -0
  167. package/dist/shims/unified-request-context.d.ts +21 -2
  168. package/dist/shims/unified-request-context.js +82 -1
  169. package/dist/utils/cache-control-metadata.d.ts +2 -1
  170. package/dist/utils/cache-control-metadata.js +5 -1
  171. package/dist/utils/html-limited-bots.js +1 -1
  172. package/dist/utils/protocol-headers.d.ts +7 -1
  173. package/dist/utils/protocol-headers.js +7 -1
  174. package/package.json +1 -1
@@ -1,9 +1,10 @@
1
+ import { bindRequestContextSnapshot, getRequestContext, isInsideUnifiedScope, queueAfterCallback } from "./unified-request-context.js";
1
2
  import { hasBasePath, stripBasePath } from "../utils/base-path.js";
2
3
  import { getRequestExecutionContext } from "./request-context.js";
3
4
  import { MIDDLEWARE_SET_COOKIE_HEADER } from "../utils/protocol-headers.js";
4
5
  import { MIDDLEWARE_NEXT_HEADER, MIDDLEWARE_REWRITE_HEADER } from "../server/headers.js";
5
6
  import { encodeMiddlewareRequestHeaders } from "../utils/middleware-request-headers.js";
6
- import { serializeSetCookie, validateCookieName } from "./internal/cookie-serialize.js";
7
+ import { validateCookieAttributeValue, validateCookieName } from "./internal/cookie-serialize.js";
7
8
  import { parseEdgeRequestCookieHeader } from "../utils/parse-cookie.js";
8
9
  import { assertSafeNavigationUrl } from "./url-safety.js";
9
10
  //#region src/shims/server.ts
@@ -158,12 +159,8 @@ var NextResponse = class NextResponse extends Response {
158
159
  * Create a JSON response.
159
160
  */
160
161
  static json(body, init) {
161
- const headers = new Headers(init?.headers);
162
- if (!headers.has("content-type")) headers.set("content-type", "application/json");
163
- return new NextResponse(JSON.stringify(body), {
164
- ...init,
165
- headers
166
- });
162
+ const response = Response.json(body, init);
163
+ return new NextResponse(response.body, response);
167
164
  }
168
165
  /**
169
166
  * Create a redirect response.
@@ -417,7 +414,7 @@ var NextURL = class NextURL {
417
414
  };
418
415
  if (this._trailingSlash) nextConfig.trailingSlash = true;
419
416
  const config = {
420
- basePath: this._basePath,
417
+ basePath: this._configBasePath,
421
418
  nextConfig: Object.keys(nextConfig).length > 0 ? nextConfig : void 0
422
419
  };
423
420
  return new NextURL(this.href, void 0, config);
@@ -554,79 +551,54 @@ var ResponseCookies = class {
554
551
  _parsed = /* @__PURE__ */ new Map();
555
552
  constructor(headers) {
556
553
  this._headers = headers;
557
- for (const header of headers.getSetCookie()) {
558
- const eq = header.indexOf("=");
559
- if (eq === -1) continue;
560
- const cookieName = header.slice(0, eq);
561
- const semi = header.indexOf(";", eq);
562
- const raw = header.slice(eq + 1, semi === -1 ? void 0 : semi);
563
- let value;
564
- try {
565
- value = decodeURIComponent(raw);
566
- } catch {
567
- value = raw;
568
- }
569
- this._parsed.set(cookieName, {
570
- serialized: header,
571
- entry: {
572
- name: cookieName,
573
- value
574
- }
575
- });
554
+ const setCookie = headers.getSetCookie?.() ?? headers.get("set-cookie") ?? [];
555
+ const cookieStrings = Array.isArray(setCookie) ? setCookie : splitSetCookieString(setCookie);
556
+ for (const header of cookieStrings) {
557
+ const entry = parseSetCookieHeader(header);
558
+ if (entry) this._parsed.set(entry.name, entry);
576
559
  }
577
560
  }
578
561
  set(...args) {
579
562
  const [name, value, opts] = parseCookieSetArgs(args);
580
563
  validateCookieName(name);
581
- const sameSite = opts?.sameSite === true ? "Strict" : typeof opts?.sameSite === "string" ? opts.sameSite[0].toUpperCase() + opts.sameSite.slice(1) : void 0;
582
- const serialized = serializeSetCookie(name, value, {
583
- ...opts,
584
- expires: typeof opts?.expires === "number" ? new Date(opts.expires) : opts?.expires,
585
- sameSite
586
- });
587
- this._parsed.set(name, {
588
- serialized,
589
- entry: {
590
- name,
591
- value
592
- }
593
- });
564
+ const entry = normalizeResponseCookie(name, value, opts);
565
+ validateResponseCookieAttributes(entry);
566
+ this._parsed.set(name, entry);
594
567
  this._syncHeaders();
595
568
  return this;
596
569
  }
597
570
  get(...args) {
598
571
  const key = typeof args[0] === "string" ? args[0] : args[0].name;
599
- return this._parsed.get(key)?.entry;
572
+ return this._parsed.get(key);
600
573
  }
601
574
  has(name) {
602
575
  return this._parsed.has(name);
603
576
  }
604
577
  getAll(...args) {
605
- const all = [...this._parsed.values()].map((v) => v.entry);
578
+ const all = [...this._parsed.values()];
606
579
  if (args.length === 0) return all;
607
580
  const key = typeof args[0] === "string" ? args[0] : args[0].name;
608
581
  return all.filter((c) => c.name === key);
609
582
  }
610
583
  delete(...args) {
611
- const [name, opts] = typeof args[0] === "string" ? [args[0], void 0] : [args[0].name, args[0]];
584
+ const [name, options] = typeof args[0] === "string" ? [args[0], void 0] : [args[0].name, args[0]];
612
585
  return this.set({
586
+ ...options,
613
587
  name,
614
588
  value: "",
615
- expires: /* @__PURE__ */ new Date(0),
616
- path: opts?.path,
617
- domain: opts?.domain,
618
- httpOnly: opts?.httpOnly,
619
- secure: opts?.secure,
620
- sameSite: opts?.sameSite
589
+ expires: /* @__PURE__ */ new Date(0)
621
590
  });
622
591
  }
623
592
  [Symbol.iterator]() {
624
- return new Map([...this._parsed.values()].map(({ entry }) => [entry.name, entry])).entries();
593
+ return new Map([...this._parsed.values()].map((entry) => [entry.name, entry])).entries();
594
+ }
595
+ toString() {
596
+ return [...this._parsed.values()].map(stringifyResponseCookie).join("; ");
625
597
  }
626
598
  /** Delete all Set-Cookie headers and re-append from the internal map. */
627
599
  _syncHeaders() {
628
600
  this._headers.delete("Set-Cookie");
629
- for (const { serialized } of this._parsed.values()) this._headers.append("Set-Cookie", serialized);
601
+ for (const entry of this._parsed.values()) this._headers.append("Set-Cookie", stringifyResponseCookie(entry));
630
602
  }
631
603
  };
632
604
  var MiddlewareResponseCookies = class extends ResponseCookies {
@@ -654,6 +626,115 @@ var MiddlewareResponseCookies = class extends ResponseCookies {
654
626
  this._responseHeaders.set(MIDDLEWARE_SET_COOKIE_HEADER, cookies.join(","));
655
627
  }
656
628
  };
629
+ function normalizeResponseCookie(name, value, options) {
630
+ const cookie = {
631
+ name,
632
+ value,
633
+ ...options
634
+ };
635
+ if (typeof cookie.expires === "number") cookie.expires = new Date(cookie.expires);
636
+ if (cookie.maxAge) cookie.expires = new Date(Date.now() + cookie.maxAge * 1e3);
637
+ if (cookie.path == null) cookie.path = "/";
638
+ return cookie;
639
+ }
640
+ function parseSetCookieHeader(header) {
641
+ if (!header) return void 0;
642
+ const [[name, value], ...attributes] = parseResponseCookiePairs(header);
643
+ const { domain, expires, httponly, maxage, path, samesite, secure, partitioned, priority } = Object.fromEntries(attributes.map(([key, attributeValue]) => [key.toLowerCase().replaceAll("-", ""), attributeValue]));
644
+ return compactResponseCookie({
645
+ name,
646
+ value: decodeURIComponent(value),
647
+ domain,
648
+ ...expires && { expires: new Date(expires) },
649
+ ...httponly && { httpOnly: true },
650
+ ...typeof maxage === "string" && { maxAge: Number(maxage) },
651
+ path,
652
+ ...samesite && { sameSite: parseResponseCookieSameSite(samesite) },
653
+ ...secure && { secure: true },
654
+ ...priority && { priority: parseResponseCookiePriority(priority) },
655
+ ...partitioned && { partitioned: true }
656
+ });
657
+ }
658
+ function parseResponseCookiePairs(cookie) {
659
+ const map = /* @__PURE__ */ new Map();
660
+ for (const pair of cookie.split(/; */)) {
661
+ if (!pair) continue;
662
+ const splitAt = pair.indexOf("=");
663
+ if (splitAt === -1) {
664
+ map.set(pair, "true");
665
+ continue;
666
+ }
667
+ const key = pair.slice(0, splitAt);
668
+ const value = pair.slice(splitAt + 1);
669
+ try {
670
+ map.set(key, decodeURIComponent(value ?? "true"));
671
+ } catch {}
672
+ }
673
+ return map;
674
+ }
675
+ function compactResponseCookie(cookie) {
676
+ const compact = {};
677
+ for (const key in cookie) if (cookie[key]) compact[key] = cookie[key];
678
+ return compact;
679
+ }
680
+ function parseResponseCookieSameSite(value) {
681
+ const sameSite = value.toLowerCase();
682
+ return sameSite === "strict" || sameSite === "lax" || sameSite === "none" ? sameSite : void 0;
683
+ }
684
+ function parseResponseCookiePriority(value) {
685
+ const priority = value.toLowerCase();
686
+ return priority === "low" || priority === "medium" || priority === "high" ? priority : void 0;
687
+ }
688
+ function validateResponseCookieAttributes(cookie) {
689
+ if (cookie.path) validateCookieAttributeValue(cookie.path, "Path");
690
+ if (cookie.domain) validateCookieAttributeValue(cookie.domain, "Domain");
691
+ }
692
+ function stringifyResponseCookie(cookie) {
693
+ const attributes = [
694
+ cookie.path && `Path=${cookie.path}`,
695
+ (cookie.expires || cookie.expires === 0) && `Expires=${(typeof cookie.expires === "number" ? new Date(cookie.expires) : cookie.expires).toUTCString()}`,
696
+ typeof cookie.maxAge === "number" && `Max-Age=${cookie.maxAge}`,
697
+ cookie.domain && `Domain=${cookie.domain}`,
698
+ cookie.secure && "Secure",
699
+ cookie.httpOnly && "HttpOnly",
700
+ cookie.sameSite && `SameSite=${cookie.sameSite}`,
701
+ cookie.partitioned && "Partitioned",
702
+ cookie.priority && `Priority=${cookie.priority}`
703
+ ].filter(Boolean);
704
+ const stringified = `${cookie.name}=${encodeURIComponent(cookie.value ?? "")}`;
705
+ return attributes.length === 0 ? stringified : `${stringified}; ${attributes.join("; ")}`;
706
+ }
707
+ function splitSetCookieString(value) {
708
+ const cookies = [];
709
+ let position = 0;
710
+ let start = 0;
711
+ const skipWhitespace = () => {
712
+ while (position < value.length && /\s/.test(value.charAt(position))) position++;
713
+ return position < value.length;
714
+ };
715
+ while (position < value.length) {
716
+ start = position;
717
+ let separatorFound = false;
718
+ while (skipWhitespace()) {
719
+ if (value.charAt(position) !== ",") {
720
+ position++;
721
+ continue;
722
+ }
723
+ const lastComma = position++;
724
+ skipWhitespace();
725
+ const nextStart = position;
726
+ while (position < value.length && !"=;,".includes(value.charAt(position))) position++;
727
+ if (position < value.length && value.charAt(position) === "=") {
728
+ separatorFound = true;
729
+ position = nextStart;
730
+ cookies.push(value.substring(start, lastComma));
731
+ start = position;
732
+ } else position = lastComma + 1;
733
+ }
734
+ if (!separatorFound || position >= value.length) cookies.push(value.substring(start));
735
+ }
736
+ return cookies;
737
+ }
657
738
  /**
658
739
  * Parse the overloaded arguments for ResponseCookies.set():
659
740
  * - (name, value, options?) — positional form
@@ -730,18 +811,36 @@ function userAgent({ headers }) {
730
811
  *
731
812
  * Uses the platform's `waitUntil` (via the per-request ExecutionContext) when
732
813
  * available so the task survives past the response on Cloudflare Workers.
733
- * Falls back to a fire-and-forget microtask on runtimes without an execution
734
- * context (e.g. Node.js dev server).
814
+ * Node.js dev drains callbacks from its response finish/close lifecycle.
735
815
  *
736
816
  * Throws when called inside a cached scope — request-specific
737
817
  * side-effects must not leak into cached results.
738
818
  */
739
819
  function after(task) {
740
820
  _throwIfInsideCacheScope("after()");
741
- const guarded = (typeof task === "function" ? Promise.resolve().then(task) : task).catch((err) => {
742
- console.error("[vinext] after() task failed:", err);
743
- });
744
- getRequestExecutionContext()?.waitUntil(guarded);
821
+ const requestContext = isInsideUnifiedScope() ? getRequestContext() : null;
822
+ if (!requestContext) {
823
+ const executionContext = getRequestExecutionContext();
824
+ if (executionContext) {
825
+ if (typeof task !== "function" && (task == null || typeof task.then !== "function")) throw new TypeError("`after()`: Argument must be a promise or a function");
826
+ const promise = typeof task === "function" ? Promise.resolve().then(task) : task;
827
+ const guarded = Promise.resolve(promise).catch((error) => {
828
+ console.error("[vinext] after() task failed:", error);
829
+ });
830
+ executionContext.waitUntil(guarded);
831
+ return;
832
+ }
833
+ throw new Error("`after()` was called outside a request scope");
834
+ }
835
+ if (typeof task !== "function") {
836
+ if (task == null || typeof task.then !== "function") throw new TypeError("`after()`: Argument must be a promise or a function");
837
+ const guarded = Promise.resolve(task).catch((error) => {
838
+ console.error("[vinext] after() task failed:", error);
839
+ });
840
+ getRequestExecutionContext()?.waitUntil(guarded);
841
+ return;
842
+ }
843
+ queueAfterCallback(requestContext, bindRequestContextSnapshot(requestContext, task));
745
844
  }
746
845
  /**
747
846
  * connection() — signals that the response requires a live connection
@@ -25,6 +25,7 @@ type MergeElementsOptions = {
25
25
  preservePreviousSlotIds?: readonly string[];
26
26
  };
27
27
  declare function updateBfcacheSlotEntryOrder(previousOrder: readonly string[], activeStateKey: string, maxEntries?: number): string[];
28
+ declare function getNonCacheComponentsSegmentKey(id: string, activeStateKey: string): string | undefined;
28
29
  declare function mergeElements(prev: AppElements, next: AppElements, options?: MergeElementsOptions | boolean): AppElements;
29
30
  declare function Slot({
30
31
  id,
@@ -42,4 +43,4 @@ declare function ParallelSlot({
42
43
  name: string;
43
44
  }): string | number | bigint | boolean | Iterable<React$1.ReactNode> | Promise<string | number | bigint | boolean | Iterable<React$1.ReactNode> | React$1.ReactElement<unknown, string | React$1.JSXElementConstructor<any>> | React$1.ReactPortal | null | undefined> | React$1.ReactElement<unknown, string | React$1.JSXElementConstructor<any>> | null;
44
45
  //#endregion
45
- export { BfcacheSlotEntry, BfcacheStateKeyMapContext, Children, ChildrenContext, ElementsContext, ParallelSlot, ParallelSlotsContext, Slot, UNMATCHED_SLOT, mergeElements, updateBfcacheSlotEntryOrder };
46
+ export { BfcacheSlotEntry, BfcacheStateKeyMapContext, Children, ChildrenContext, ElementsContext, ParallelSlot, ParallelSlotsContext, Slot, UNMATCHED_SLOT, getNonCacheComponentsSegmentKey, mergeElements, updateBfcacheSlotEntryOrder };
@@ -137,6 +137,10 @@ function BfcacheActivitySlotBoundary({ activeStateKey, content, elements, id, Se
137
137
  })
138
138
  }, entry.stateKey)) });
139
139
  }
140
+ function getNonCacheComponentsSegmentKey(id, activeStateKey) {
141
+ const parsed = AppElementsWire.parseElementKey(id);
142
+ return parsed?.kind === "page" || parsed?.kind === "slot" && parsed.name === "children" ? activeStateKey : void 0;
143
+ }
140
144
  function BfcacheSlotBoundary({ content, id }) {
141
145
  const SegmentContext = BfcacheSegmentIdContext;
142
146
  const elements = React$1.useContext(ElementsContext);
@@ -150,7 +154,7 @@ function BfcacheSlotBoundary({ content, id }) {
150
154
  if (!isCacheComponentsEnabled()) return /* @__PURE__ */ jsx(SegmentContext.Provider, {
151
155
  value: id,
152
156
  children: content
153
- });
157
+ }, getNonCacheComponentsSegmentKey(id, activeStateKey));
154
158
  return /* @__PURE__ */ jsx(BfcacheActivitySlotBoundary, {
155
159
  activeStateKey,
156
160
  content,
@@ -224,4 +228,4 @@ function ParallelSlot({ name }) {
224
228
  return React$1.useContext(ParallelSlotsContext)?.[name] ?? null;
225
229
  }
226
230
  //#endregion
227
- export { BfcacheStateKeyMapContext, Children, ChildrenContext, ElementsContext, ParallelSlot, ParallelSlotsContext, Slot, UNMATCHED_SLOT, mergeElements, updateBfcacheSlotEntryOrder };
231
+ export { BfcacheStateKeyMapContext, Children, ChildrenContext, ElementsContext, ParallelSlot, ParallelSlotsContext, Slot, UNMATCHED_SLOT, getNonCacheComponentsSegmentKey, mergeElements, updateBfcacheSlotEntryOrder };
@@ -0,0 +1,9 @@
1
+ //#region src/shims/streamed-icons.d.ts
2
+ declare function reconcileStreamedIcons(metadataKey: string): void;
3
+ declare function StreamedIconsInsertion({
4
+ metadataKey
5
+ }: {
6
+ metadataKey: string;
7
+ }): null;
8
+ //#endregion
9
+ export { StreamedIconsInsertion, reconcileStreamedIcons };
@@ -0,0 +1,33 @@
1
+ "use client";
2
+ import { useLayoutEffect } from "react";
3
+ //#region src/shims/streamed-icons.tsx
4
+ const STREAMED_ICON_ATTRIBUTE = "data-vinext-streamed-icon";
5
+ function getStreamedIconOrder(icon, metadataKey) {
6
+ const marker = icon.getAttribute(STREAMED_ICON_ATTRIBUTE);
7
+ const prefix = `${metadataKey}:`;
8
+ if (!marker?.startsWith(prefix)) return null;
9
+ const order = Number(marker.slice(prefix.length));
10
+ return Number.isInteger(order) && order >= 0 ? order : null;
11
+ }
12
+ function reconcileStreamedIcons(metadataKey) {
13
+ document.querySelectorAll(`body link[${STREAMED_ICON_ATTRIBUTE}]`).forEach((icon) => document.head.appendChild(icon));
14
+ const ownedIcons = [...document.querySelectorAll(`head link[${STREAMED_ICON_ATTRIBUTE}]`)];
15
+ const retainedIcons = /* @__PURE__ */ new Map();
16
+ for (const icon of ownedIcons) {
17
+ const order = getStreamedIconOrder(icon, metadataKey);
18
+ if (order === null) {
19
+ icon.remove();
20
+ continue;
21
+ }
22
+ const previousIcon = retainedIcons.get(order);
23
+ if (previousIcon) previousIcon.remove();
24
+ retainedIcons.set(order, icon);
25
+ }
26
+ for (const [, icon] of [...retainedIcons].sort(([leftOrder], [rightOrder]) => leftOrder - rightOrder)) document.head.appendChild(icon);
27
+ }
28
+ function StreamedIconsInsertion({ metadataKey }) {
29
+ useLayoutEffect(() => reconcileStreamedIcons(metadataKey), [metadataKey]);
30
+ return null;
31
+ }
32
+ //#endregion
33
+ export { StreamedIconsInsertion, reconcileStreamedIcons };
@@ -18,13 +18,32 @@ import { NavigationState } from "./navigation-state.js";
18
18
  */
19
19
  type UnifiedRequestContext = {
20
20
  /** Cloudflare Workers ExecutionContext, or null on Node.js dev. */executionContext: ExecutionContextLike | null; /** Per-request cache for cacheForRequest(). Keyed by factory function reference. */
21
- requestCache: WeakMap<(...args: any[]) => any, unknown>;
21
+ requestCache: WeakMap<(...args: any[]) => any, unknown>; /** Shared lifecycle state for work deferred until the response closes. */
22
+ afterContext: AfterRequestContext;
22
23
  } & VinextHeadersShimState & I18nState & NavigationState & CacheState & PrivateCacheState & FetchCacheState & RouterState & HeadState & RootParamsState;
24
+ type AfterRequestContext = {
25
+ callbacks: Array<() => unknown>;
26
+ responseClosed: boolean;
27
+ pendingCallbacks: number;
28
+ completion: Promise<void> | null;
29
+ resolveCompletion: (() => void) | null;
30
+ };
23
31
  /**
24
32
  * Create a fresh `UnifiedRequestContext` with defaults for all fields.
25
33
  * Pass partial overrides for the fields you need to pre-populate.
26
34
  */
27
35
  declare function createRequestContext(opts?: Partial<UnifiedRequestContext>): UnifiedRequestContext;
36
+ /** Queue a callback until response close, or start it immediately once closed. */
37
+ declare function queueAfterCallback(ctx: UnifiedRequestContext, callback: () => unknown): void;
38
+ /** Bind a callback to every AsyncLocalStorage context active at registration. */
39
+ declare function bindRequestContextSnapshot<T>(ctx: UnifiedRequestContext, callback: () => T): () => T;
40
+ /**
41
+ * Release function-form `after()` work once the response body has closed.
42
+ * All queued callbacks start together, matching Next.js' unbounded PromiseQueue.
43
+ */
44
+ declare function closeAfterResponse(ctx: UnifiedRequestContext): Promise<void>;
45
+ /** Wrap a response so deferred callbacks start on stream completion or cancellation. */
46
+ declare function closeAfterResponseWithBody(response: Response, ctx: UnifiedRequestContext): Response;
28
47
  /**
29
48
  * Run `fn` within a unified request context scope.
30
49
  * All shim modules will read/write their state from `ctx` for the
@@ -65,4 +84,4 @@ declare function getRequestContext(): UnifiedRequestContext;
65
84
  */
66
85
  declare function isInsideUnifiedScope(): boolean;
67
86
  //#endregion
68
- export { UnifiedRequestContext, createRequestContext, getRequestContext, isInsideUnifiedScope, runWithRequestContext, runWithUnifiedStateMutation };
87
+ export { AfterRequestContext, UnifiedRequestContext, bindRequestContextSnapshot, closeAfterResponse, closeAfterResponseWithBody, createRequestContext, getRequestContext, isInsideUnifiedScope, queueAfterCallback, runWithRequestContext, runWithUnifiedStateMutation };
@@ -43,6 +43,13 @@ function createRequestContext(opts) {
43
43
  currentFetchDedupeEntries: /* @__PURE__ */ new Map(),
44
44
  executionContext: _getInheritedExecutionContext(),
45
45
  requestCache: /* @__PURE__ */ new WeakMap(),
46
+ afterContext: {
47
+ callbacks: [],
48
+ responseClosed: false,
49
+ pendingCallbacks: 0,
50
+ completion: null,
51
+ resolveCompletion: null
52
+ },
46
53
  ssrContext: null,
47
54
  ssrHeadChildren: [],
48
55
  documentInitialHead: [],
@@ -50,6 +57,80 @@ function createRequestContext(opts) {
50
57
  ...opts
51
58
  };
52
59
  }
60
+ function ensureAfterCompletion(ctx) {
61
+ const state = ctx.afterContext;
62
+ if (state.resolveCompletion && state.completion) return;
63
+ let resolveCompletion;
64
+ const completion = new Promise((resolve) => {
65
+ resolveCompletion = resolve;
66
+ });
67
+ state.completion = completion;
68
+ state.resolveCompletion = resolveCompletion;
69
+ ctx.executionContext?.waitUntil(completion);
70
+ }
71
+ function finishAfterCallbacksIfIdle(ctx) {
72
+ const state = ctx.afterContext;
73
+ if (!state.responseClosed || state.pendingCallbacks !== 0 || state.callbacks.length !== 0 || !state.resolveCompletion) return;
74
+ const resolveCompletion = state.resolveCompletion;
75
+ state.resolveCompletion = null;
76
+ resolveCompletion();
77
+ }
78
+ function startAfterCallback(ctx, callback) {
79
+ const state = ctx.afterContext;
80
+ ensureAfterCompletion(ctx);
81
+ state.pendingCallbacks += 1;
82
+ Promise.resolve().then(callback).catch((error) => {
83
+ console.error("[vinext] after() task failed:", error);
84
+ }).finally(() => {
85
+ state.pendingCallbacks -= 1;
86
+ finishAfterCallbacksIfIdle(ctx);
87
+ });
88
+ }
89
+ /** Queue a callback until response close, or start it immediately once closed. */
90
+ function queueAfterCallback(ctx, callback) {
91
+ ensureAfterCompletion(ctx);
92
+ if (ctx.afterContext.responseClosed) startAfterCallback(ctx, callback);
93
+ else ctx.afterContext.callbacks.push(callback);
94
+ }
95
+ /** Bind a callback to every AsyncLocalStorage context active at registration. */
96
+ function bindRequestContextSnapshot(ctx, callback) {
97
+ const constructor = _als.constructor;
98
+ try {
99
+ const runInSnapshot = constructor.snapshot?.();
100
+ if (runInSnapshot) return () => runInSnapshot(callback);
101
+ } catch {}
102
+ return () => _als.run(ctx, callback);
103
+ }
104
+ /**
105
+ * Release function-form `after()` work once the response body has closed.
106
+ * All queued callbacks start together, matching Next.js' unbounded PromiseQueue.
107
+ */
108
+ async function closeAfterResponse(ctx) {
109
+ const state = ctx.afterContext;
110
+ if (!state.responseClosed) {
111
+ state.responseClosed = true;
112
+ const callbacks = state.callbacks.splice(0);
113
+ for (const callback of callbacks) startAfterCallback(ctx, callback);
114
+ finishAfterCallbacksIfIdle(ctx);
115
+ }
116
+ return state.completion ?? Promise.resolve();
117
+ }
118
+ /** Wrap a response so deferred callbacks start on stream completion or cancellation. */
119
+ function closeAfterResponseWithBody(response, ctx) {
120
+ if (!response.body) {
121
+ queueMicrotask(() => void closeAfterResponse(ctx));
122
+ return response;
123
+ }
124
+ const passthrough = new TransformStream();
125
+ response.body.pipeTo(passthrough.writable).then(() => void closeAfterResponse(ctx), () => void closeAfterResponse(ctx));
126
+ const wrapped = new Response(passthrough.readable, {
127
+ status: response.status,
128
+ statusText: response.statusText,
129
+ headers: response.headers
130
+ });
131
+ wrapped.__vinextStreamedHtmlResponse = response.__vinextStreamedHtmlResponse;
132
+ return wrapped;
133
+ }
53
134
  function runWithRequestContext(ctx, fn) {
54
135
  return _als.run(ctx, fn);
55
136
  }
@@ -86,4 +167,4 @@ function isInsideUnifiedScope() {
86
167
  return _als.getStore() != null;
87
168
  }
88
169
  //#endregion
89
- export { createRequestContext, getRequestContext, isInsideUnifiedScope, runWithRequestContext, runWithUnifiedStateMutation };
170
+ export { bindRequestContextSnapshot, closeAfterResponse, closeAfterResponseWithBody, createRequestContext, getRequestContext, isInsideUnifiedScope, queueAfterCallback, runWithRequestContext, runWithUnifiedStateMutation };
@@ -1,4 +1,5 @@
1
1
  //#region src/utils/cache-control-metadata.d.ts
2
2
  declare function readCacheControlNumberField(ctx: Record<string, unknown> | undefined, field: string): number | undefined;
3
+ declare function readCacheControlRevalidateField(ctx: Record<string, unknown> | undefined): number | false | undefined;
3
4
  //#endregion
4
- export { readCacheControlNumberField };
5
+ export { readCacheControlNumberField, readCacheControlRevalidateField };
@@ -8,5 +8,9 @@ function readCacheControlNumberField(ctx, field) {
8
8
  const value = readRecordField(ctx, "cacheControl")?.[field] ?? ctx?.[field];
9
9
  return typeof value === "number" ? value : void 0;
10
10
  }
11
+ function readCacheControlRevalidateField(ctx) {
12
+ const value = readRecordField(ctx, "cacheControl")?.revalidate ?? ctx?.revalidate;
13
+ return typeof value === "number" || value === false ? value : void 0;
14
+ }
11
15
  //#endregion
12
- export { readCacheControlNumberField };
16
+ export { readCacheControlNumberField, readCacheControlRevalidateField };
@@ -1,5 +1,5 @@
1
1
  //#region src/utils/html-limited-bots.ts
2
- const HTML_LIMITED_BOT_UA_RE_STRING = String.raw`[\w-]+-Google|Google-[\w-]+|Chrome-Lighthouse|Slurp|DuckDuckBot|baiduspider|yandex|sogou|bitlybot|tumblr|vkShare|quora link preview|redditbot|ia_archiver|Bingbot|BingPreview|applebot|facebookexternalhit|facebookcatalog|Twitterbot|LinkedInBot|Slackbot|Discordbot|WhatsApp|SkypeUriPreview|Yeti|googleweblight`;
2
+ const HTML_LIMITED_BOT_UA_RE_STRING = String.raw`[\w-]+-Google|Google-[\w-]+|Chrome-Lighthouse|Slurp|DuckDuckBot|baiduspider|yandex|sogou|bitlybot|tumblr|vkShare|quora link preview|redditbot|ia_archiver|Bingbot|BingPreview|applebot|facebookexternalhit|facebookcatalog|meta-externalagent|meta-externalfetcher|Twitterbot|LinkedInBot|Slackbot|Discordbot|WhatsApp|SkypeUriPreview|Yeti|googleweblight`;
3
3
  const HEADLESS_BROWSER_BOT_UA_RE = /Googlebot(?!-)|Googlebot$/i;
4
4
  const htmlLimitedBotRegexCache = /* @__PURE__ */ new Map();
5
5
  function getHtmlLimitedBotRegex(htmlLimitedBots) {
@@ -7,6 +7,8 @@ declare const VINEXT_PRERENDER_SECRET_HEADER = "x-vinext-prerender-secret";
7
7
  declare const VINEXT_PRERENDER_ROUTE_PARAMS_HEADER = "x-vinext-prerender-route-params";
8
8
  /** Indicates a build-time prerender render is probing whether a route can be static. */
9
9
  declare const VINEXT_PRERENDER_SPECULATIVE_HEADER = "x-vinext-prerender-speculative";
10
+ /** Logical hostname carried only by authenticated Node revalidation loopbacks. */
11
+ declare const VINEXT_REVALIDATE_HOST_HEADER = "x-vinext-revalidate-host";
10
12
  /** Prefix for forwarded request headers (e.g. `x-middleware-request-cookie`). */
11
13
  declare const MIDDLEWARE_REQUEST_HEADER_PREFIX = "x-middleware-request-";
12
14
  /** Comma-separated list of header names that middleware wants to override. */
@@ -19,5 +21,9 @@ declare const MIDDLEWARE_CACHE_HEADER = "x-middleware-cache";
19
21
  declare const MIDDLEWARE_SKIP_HEADER = "x-middleware-skip";
20
22
  /** Generic prefix for all middleware internal headers. */
21
23
  declare const MIDDLEWARE_HEADER_PREFIX = "x-middleware-";
24
+ /** Authenticates an internal Pages Router on-demand revalidation request. */
25
+ declare const PRERENDER_REVALIDATE_HEADER = "x-prerender-revalidate";
26
+ /** Restricts on-demand revalidation to paths that were already generated. */
27
+ declare const PRERENDER_REVALIDATE_ONLY_GENERATED_HEADER = "x-prerender-revalidate-if-generated";
22
28
  //#endregion
23
- export { MIDDLEWARE_CACHE_HEADER, MIDDLEWARE_HEADER_PREFIX, MIDDLEWARE_OVERRIDE_HEADERS, MIDDLEWARE_REQUEST_HEADER_PREFIX, MIDDLEWARE_SET_COOKIE_HEADER, MIDDLEWARE_SKIP_HEADER, VINEXT_MW_CTX_HEADER, VINEXT_PRERENDER_ROUTE_PARAMS_HEADER, VINEXT_PRERENDER_SECRET_HEADER, VINEXT_PRERENDER_SPECULATIVE_HEADER };
29
+ export { MIDDLEWARE_CACHE_HEADER, MIDDLEWARE_HEADER_PREFIX, MIDDLEWARE_OVERRIDE_HEADERS, MIDDLEWARE_REQUEST_HEADER_PREFIX, MIDDLEWARE_SET_COOKIE_HEADER, MIDDLEWARE_SKIP_HEADER, PRERENDER_REVALIDATE_HEADER, PRERENDER_REVALIDATE_ONLY_GENERATED_HEADER, VINEXT_MW_CTX_HEADER, VINEXT_PRERENDER_ROUTE_PARAMS_HEADER, VINEXT_PRERENDER_SECRET_HEADER, VINEXT_PRERENDER_SPECULATIVE_HEADER, VINEXT_REVALIDATE_HOST_HEADER };
@@ -7,6 +7,8 @@ const VINEXT_PRERENDER_SECRET_HEADER = "x-vinext-prerender-secret";
7
7
  const VINEXT_PRERENDER_ROUTE_PARAMS_HEADER = "x-vinext-prerender-route-params";
8
8
  /** Indicates a build-time prerender render is probing whether a route can be static. */
9
9
  const VINEXT_PRERENDER_SPECULATIVE_HEADER = "x-vinext-prerender-speculative";
10
+ /** Logical hostname carried only by authenticated Node revalidation loopbacks. */
11
+ const VINEXT_REVALIDATE_HOST_HEADER = "x-vinext-revalidate-host";
10
12
  /** Prefix for forwarded request headers (e.g. `x-middleware-request-cookie`). */
11
13
  const MIDDLEWARE_REQUEST_HEADER_PREFIX = "x-middleware-request-";
12
14
  /** Comma-separated list of header names that middleware wants to override. */
@@ -19,5 +21,9 @@ const MIDDLEWARE_CACHE_HEADER = "x-middleware-cache";
19
21
  const MIDDLEWARE_SKIP_HEADER = "x-middleware-skip";
20
22
  /** Generic prefix for all middleware internal headers. */
21
23
  const MIDDLEWARE_HEADER_PREFIX = "x-middleware-";
24
+ /** Authenticates an internal Pages Router on-demand revalidation request. */
25
+ const PRERENDER_REVALIDATE_HEADER = "x-prerender-revalidate";
26
+ /** Restricts on-demand revalidation to paths that were already generated. */
27
+ const PRERENDER_REVALIDATE_ONLY_GENERATED_HEADER = "x-prerender-revalidate-if-generated";
22
28
  //#endregion
23
- export { MIDDLEWARE_CACHE_HEADER, MIDDLEWARE_HEADER_PREFIX, MIDDLEWARE_OVERRIDE_HEADERS, MIDDLEWARE_REQUEST_HEADER_PREFIX, MIDDLEWARE_SET_COOKIE_HEADER, MIDDLEWARE_SKIP_HEADER, VINEXT_MW_CTX_HEADER, VINEXT_PRERENDER_ROUTE_PARAMS_HEADER, VINEXT_PRERENDER_SECRET_HEADER, VINEXT_PRERENDER_SPECULATIVE_HEADER };
29
+ export { MIDDLEWARE_CACHE_HEADER, MIDDLEWARE_HEADER_PREFIX, MIDDLEWARE_OVERRIDE_HEADERS, MIDDLEWARE_REQUEST_HEADER_PREFIX, MIDDLEWARE_SET_COOKIE_HEADER, MIDDLEWARE_SKIP_HEADER, PRERENDER_REVALIDATE_HEADER, PRERENDER_REVALIDATE_ONLY_GENERATED_HEADER, VINEXT_MW_CTX_HEADER, VINEXT_PRERENDER_ROUTE_PARAMS_HEADER, VINEXT_PRERENDER_SECRET_HEADER, VINEXT_PRERENDER_SPECULATIVE_HEADER, VINEXT_REVALIDATE_HOST_HEADER };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "vinext",
3
- "version": "1.0.0-beta.2",
3
+ "version": "1.0.0-beta.4",
4
4
  "description": "Run Next.js apps on Vite. Drop-in replacement for the next CLI.",
5
5
  "license": "MIT",
6
6
  "repository": {