vinext 1.4.0 → 1.4.1

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 (104) hide show
  1. package/README.md +20 -20
  2. package/dist/build/prerender.js +3 -2
  3. package/dist/check.js +3 -6
  4. package/dist/client/dev-error-overlay-store.d.ts +1 -0
  5. package/dist/client/dev-error-overlay.d.ts +2 -0
  6. package/dist/client/dev-error-overlay.js +19 -3
  7. package/dist/config/config-matchers.js +2 -0
  8. package/dist/config/destination-params.d.ts +2 -1
  9. package/dist/config/destination-params.js +52 -2
  10. package/dist/config/next-config.d.ts +6 -0
  11. package/dist/config/next-config.js +18 -3
  12. package/dist/entries/app-rsc-entry.js +0 -1
  13. package/dist/entries/pages-server-entry.js +3 -1
  14. package/dist/index.js +29 -20
  15. package/dist/plugins/strip-server-exports.js +2 -1
  16. package/dist/server/api-handler.js +2 -1
  17. package/dist/server/app-browser-entry.js +7 -3
  18. package/dist/server/app-browser-rsc-redirect.js +3 -3
  19. package/dist/server/app-browser-server-action-client.js +2 -2
  20. package/dist/server/app-browser-visible-commit.js +2 -2
  21. package/dist/server/app-optimistic-routing.js +2 -3
  22. package/dist/server/app-page-boundary-render.js +5 -10
  23. package/dist/server/app-page-dispatch.d.ts +0 -1
  24. package/dist/server/app-page-dispatch.js +0 -1
  25. package/dist/server/app-page-element-builder.js +3 -3
  26. package/dist/server/app-page-execution.js +9 -6
  27. package/dist/server/app-page-render.d.ts +0 -1
  28. package/dist/server/app-page-render.js +14 -14
  29. package/dist/server/app-page-response.js +1 -1
  30. package/dist/server/app-page-route-wiring.js +22 -5
  31. package/dist/server/app-page-stream.d.ts +10 -8
  32. package/dist/server/app-page-stream.js +2 -1
  33. package/dist/server/app-request-stage-independent-entry.js +1 -1
  34. package/dist/server/app-route-handler-execution.js +3 -2
  35. package/dist/server/app-route-handler-runtime.d.ts +1 -1
  36. package/dist/server/app-route-handler-runtime.js +11 -7
  37. package/dist/server/app-router-entry.js +1 -1
  38. package/dist/server/app-rsc-cache-busting.d.ts +10 -10
  39. package/dist/server/app-rsc-cache-busting.js +27 -68
  40. package/dist/server/app-rsc-error-handler.js +1 -1
  41. package/dist/server/app-rsc-errors.js +4 -1
  42. package/dist/server/app-rsc-handler.js +53 -62
  43. package/dist/server/app-rsc-request-normalization.d.ts +10 -12
  44. package/dist/server/app-rsc-request-normalization.js +11 -14
  45. package/dist/server/app-rsc-response-stage.js +4 -12
  46. package/dist/server/app-server-action-execution.js +8 -4
  47. package/dist/server/app-ssr-entry.d.ts +6 -0
  48. package/dist/server/app-ssr-entry.js +13 -8
  49. package/dist/server/app-worker-stages.d.ts +12 -3
  50. package/dist/server/app-worker-stages.js +37 -4
  51. package/dist/server/bfcache-identity.d.ts +7 -0
  52. package/dist/server/bfcache-identity.js +10 -1
  53. package/dist/server/cacheability-manifest.js +1 -2
  54. package/dist/server/dev-origin-check.d.ts +2 -1
  55. package/dist/server/dev-origin-check.js +2 -1
  56. package/dist/server/dev-server.js +2 -1
  57. package/dist/server/image-optimization.d.ts +5 -0
  58. package/dist/server/image-optimization.js +6 -1
  59. package/dist/server/isr-cache.js +4 -1
  60. package/dist/server/metadata-route-response.js +1 -1
  61. package/dist/server/middleware-matcher.js +19 -6
  62. package/dist/server/middleware-runtime.js +6 -2
  63. package/dist/server/multi-stage.d.ts +7 -3
  64. package/dist/server/pages-node-compat.d.ts +1 -1
  65. package/dist/server/pages-node-compat.js +2 -0
  66. package/dist/server/pages-page-data.js +2 -1
  67. package/dist/server/pages-page-handler.js +17 -6
  68. package/dist/server/pages-page-response.d.ts +9 -0
  69. package/dist/server/pages-page-response.js +18 -4
  70. package/dist/server/pages-request-stage-entry.js +24 -4
  71. package/dist/server/pages-router-entry.js +3 -2
  72. package/dist/server/prod-server.d.ts +2 -2
  73. package/dist/server/prod-server.js +42 -8
  74. package/dist/server/request-error-log.d.ts +9 -0
  75. package/dist/server/request-error-log.js +13 -0
  76. package/dist/server/request-pipeline.d.ts +9 -4
  77. package/dist/server/request-pipeline.js +23 -15
  78. package/dist/server/rsc-completion-metadata.js +3 -0
  79. package/dist/server/static-file-cache.d.ts +9 -0
  80. package/dist/server/static-file-cache.js +13 -2
  81. package/dist/shims/cache-request-state.d.ts +6 -6
  82. package/dist/shims/cache-request-state.js +6 -36
  83. package/dist/shims/cache-runtime.d.ts +3 -1
  84. package/dist/shims/cache-runtime.js +77 -47
  85. package/dist/shims/cache.d.ts +10 -5
  86. package/dist/shims/cache.js +35 -22
  87. package/dist/shims/client-hook-error.d.ts +2 -0
  88. package/dist/shims/client-hook-error.js +16 -1
  89. package/dist/shims/fetch-cache.js +6 -2
  90. package/dist/shims/headers.d.ts +2 -1
  91. package/dist/shims/headers.js +5 -3
  92. package/dist/shims/internal/cache-context-accessor.d.ts +1 -1
  93. package/dist/shims/link.js +5 -10
  94. package/dist/shims/navigation.js +2 -3
  95. package/dist/shims/request-state-types.d.ts +2 -1
  96. package/dist/shims/slot.d.ts +6 -2
  97. package/dist/shims/slot.js +10 -3
  98. package/dist/shims/unified-request-context.d.ts +6 -0
  99. package/dist/shims/unified-request-context.js +1 -0
  100. package/dist/utils/cache-life-profiles.d.ts +33 -0
  101. package/dist/utils/cache-life-profiles.js +111 -0
  102. package/dist/utils/middleware-request-headers.d.ts +14 -3
  103. package/dist/utils/middleware-request-headers.js +54 -7
  104. package/package.json +1 -1
@@ -1,3 +1,4 @@
1
+ import { CacheLifeConfig } from "../utils/cache-life-profiles.js";
1
2
  import { NavigationContext } from "./navigation-context-state.js";
2
3
  import "./navigation.js";
3
4
  import { ExecutionContextLike } from "./request-context.js";
@@ -6,7 +7,7 @@ import { FetchCacheState } from "./fetch-cache-state.js";
6
7
  import "./fetch-cache.js";
7
8
  import { HeadersAccessPhase, VinextHeadersShimState } from "./internal/headers-state.js";
8
9
  import { HeadersContext } from "./headers.js";
9
- import { CacheLifeConfig, CacheState } from "./cache-request-state.js";
10
+ import { CacheState } from "./cache-request-state.js";
10
11
  import { RouterState, SSRContext } from "./router-state.js";
11
12
  import { PrivateCacheState } from "./cache-runtime.js";
12
13
  import { HeadState } from "./head-state.js";
@@ -40,6 +40,7 @@ type MergeElementsOptions = {
40
40
  clearAbsentSlots?: boolean;
41
41
  preserveAbsentSlots?: boolean;
42
42
  preserveElementIds?: readonly string[];
43
+ preserveElementSeedIds?: readonly string[];
43
44
  preservePreviousSlotIds?: readonly string[];
44
45
  };
45
46
  export declare function updateBfcacheSlotEntryOrder(previousOrder: readonly string[], activeStateKey: string, maxEntries?: number): string[];
@@ -67,9 +68,12 @@ export declare function BfcacheSegmentBoundary({ children, id, stateKey }: {
67
68
  * the parent's layout router, above the child's boundaries, so a page left for a
68
69
  * sibling with its own layout keeps its state when navigating back.
69
70
  */
70
- export declare function BfcacheChildrenBoundary({ children, id }: {
71
+ export declare function BfcacheChildrenBoundary({ children, id, stateKey }: {
71
72
  children: React$1.ReactNode;
72
- id: string;
73
+ /** The child element whose identity keys the entries. */
74
+ id?: string;
75
+ /** A fixed key, for a segment level with no element of its own. */
76
+ stateKey?: string;
73
77
  }): React$1.JSX.Element;
74
78
  export declare function getNonCacheComponentsSegmentKey(id: string, activeStateKey: string): string | undefined;
75
79
  export declare function resolveBfcacheSegmentStateKey(id: string, identityMap: Readonly<Record<string, string>>, bfcacheIdMap: Readonly<Record<string, string>> | null): string | undefined;
@@ -213,21 +213,22 @@ function BfcacheSegmentBoundary({ children, id, stateKey }) {
213
213
  * the parent's layout router, above the child's boundaries, so a page left for a
214
214
  * sibling with its own layout keeps its state when navigating back.
215
215
  */
216
- function BfcacheChildrenBoundary({ children, id }) {
216
+ function BfcacheChildrenBoundary({ children, id, stateKey }) {
217
217
  if (!BfcacheIdMapContext || !BfcacheSegmentIdContext || !isCacheComponentsEnabled()) return /* @__PURE__ */ jsx(Fragment$1, { children });
218
218
  return /* @__PURE__ */ jsx(BfcacheChildrenActivityBoundary, {
219
219
  id,
220
220
  IdMapContext: BfcacheIdMapContext,
221
221
  SegmentContext: BfcacheSegmentIdContext,
222
+ stateKey,
222
223
  children
223
224
  });
224
225
  }
225
- function BfcacheChildrenActivityBoundary({ children, id, IdMapContext, SegmentContext }) {
226
+ function BfcacheChildrenActivityBoundary({ children, id, IdMapContext, SegmentContext, stateKey }) {
226
227
  const elements = React$1.useContext(ElementsContext);
227
228
  const identityMap = React$1.useContext(BfcacheIdentityMapContext);
228
229
  const bfcacheIdMap = React$1.useContext(IdMapContext);
229
230
  const segmentId = React$1.useContext(SegmentContext);
230
- const activeStateKey = resolveBfcacheSegmentStateKey(id, identityMap, bfcacheIdMap);
231
+ const activeStateKey = stateKey ?? (id === void 0 ? void 0 : resolveBfcacheSegmentStateKey(id, identityMap, bfcacheIdMap));
231
232
  if (segmentId === null || activeStateKey === void 0) return /* @__PURE__ */ jsx(Fragment$1, { children });
232
233
  return /* @__PURE__ */ jsx(BfcacheActivitySlotBoundary, {
233
234
  activeStateKey,
@@ -272,6 +273,7 @@ function mergeElements(prev, next, options = {}) {
272
273
  const clearAbsentSlots = typeof options === "boolean" ? options : options.clearAbsentSlots ?? false;
273
274
  const preserveAbsentSlots = typeof options === "boolean" ? !options : options.preserveAbsentSlots ?? true;
274
275
  const preserveElementIds = typeof options === "boolean" ? [] : options.preserveElementIds ?? [];
276
+ const preserveElementSeedIds = typeof options === "boolean" ? [] : options.preserveElementSeedIds ?? [];
275
277
  const preservePreviousSlotIds = typeof options === "boolean" ? [] : options.preservePreviousSlotIds ?? [];
276
278
  const merged = { ...next };
277
279
  const preservedIdentityIds = /* @__PURE__ */ new Set();
@@ -282,6 +284,11 @@ function mergeElements(prev, next, options = {}) {
282
284
  preservedIdentityIds.add(id);
283
285
  }
284
286
  }
287
+ for (const id of preserveElementSeedIds) {
288
+ if (preservedIdentityIds.has(id) || !Object.hasOwn(prev, id)) continue;
289
+ const value = prev[id];
290
+ if (value !== void 0) merged[id] = value;
291
+ }
285
292
  const slotKeys = new Set([...Object.keys(prev), ...Object.keys(next)].filter((key) => AppElementsWire.isSlotId(key) && !isNestedBfcacheSlotSegmentId(key)));
286
293
  if (clearAbsentSlots) {
287
294
  for (const key of slotKeys) if (!Object.hasOwn(next, key)) delete merged[key];
@@ -30,6 +30,12 @@ export type UnifiedRequestContext = {
30
30
  requestCache: WeakMap<(...args: any[]) => any, unknown>;
31
31
  /** Shared lifecycle state for work deferred until the response closes. */
32
32
  afterContext: AfterRequestContext;
33
+ /**
34
+ * Draft-mode state of the headers context this request last cleared. The
35
+ * render can outlive its headers context (cleared once the response body
36
+ * closes), and so do after() callbacks; cache bypasses keep reading this.
37
+ */
38
+ draftModeAtHeadersClear: boolean;
33
39
  } & VinextHeadersShimState & I18nState & NavigationState & CacheState & PrivateCacheState & FetchCacheState & RouterState & HeadState & RootParamsState;
34
40
  export type AfterRequestContext = {
35
41
  callbacks: Array<() => unknown>;
@@ -53,6 +53,7 @@ function createRequestContext(opts) {
53
53
  executionContext: _getInheritedExecutionContext(),
54
54
  requestSignal: null,
55
55
  requestCache: /* @__PURE__ */ new WeakMap(),
56
+ draftModeAtHeadersClear: false,
56
57
  afterContext: {
57
58
  callbacks: [],
58
59
  responseClosed: false,
@@ -0,0 +1,33 @@
1
+ //#region src/utils/cache-life-profiles.d.ts
2
+ export type CacheLifeConfig = {
3
+ stale?: number;
4
+ revalidate?: number;
5
+ expire?: number;
6
+ };
7
+ /** Next.js's built-in profiles (`defaultConfig.cacheLife` in config-shared.ts). */
8
+ export declare const BUILT_IN_CACHE_LIFE_PROFILES: Readonly<Record<string, Readonly<CacheLifeConfig>>>;
9
+ /**
10
+ * Merge next.config `cacheLife` profiles over the built-ins, as Next.js does
11
+ * in `assignDefaultsAndValidate` (server/config.ts): a user profile replaces
12
+ * the built-in of the same name whole, and only `default` gets its missing
13
+ * fields filled: `stale` from `staleTimes.static`, `revalidate` from the
14
+ * built-in, `expire` from `expireTime`. That includes the built-in `default`,
15
+ * whose `stale` is unset in Next.js's defaults too. The other profiles keep
16
+ * their gaps, which the "use cache" store resolves from `default`.
17
+ *
18
+ * Next.js only schema-checks these profiles (fields must be numbers) and
19
+ * warns; `revalidate > expire` is checked for `cacheLife({...})` calls, not
20
+ * for config profiles.
21
+ */
22
+ export declare function resolveCacheLifeProfiles(userProfiles: unknown, defaults: {
23
+ expireTime: number;
24
+ staticStaleTime: number;
25
+ }, warn: (message: string) => void): Record<string, CacheLifeConfig>;
26
+ /** Serialize resolved profiles for the `__VINEXT_CACHE_LIFE_PROFILES` define. */
27
+ export declare function serializeCacheLifeProfiles(profiles: Record<string, CacheLifeConfig>): string;
28
+ /**
29
+ * Read the `__VINEXT_CACHE_LIFE_PROFILES` define. When absent, the profiles a
30
+ * next.config without `cacheLife` resolves to.
31
+ */
32
+ export declare function parseCacheLifeProfiles(serialized: string | undefined): Record<string, CacheLifeConfig>;
33
+ //#endregion
@@ -0,0 +1,111 @@
1
+ import { isUnknownRecord } from "./record.js";
2
+ //#region src/utils/cache-life-profiles.ts
3
+ /** Next.js's built-in profiles (`defaultConfig.cacheLife` in config-shared.ts). */
4
+ const BUILT_IN_CACHE_LIFE_PROFILES = {
5
+ default: {
6
+ revalidate: 900,
7
+ expire: 4294967294
8
+ },
9
+ seconds: {
10
+ stale: 30,
11
+ revalidate: 1,
12
+ expire: 60
13
+ },
14
+ minutes: {
15
+ stale: 300,
16
+ revalidate: 60,
17
+ expire: 3600
18
+ },
19
+ hours: {
20
+ stale: 300,
21
+ revalidate: 3600,
22
+ expire: 86400
23
+ },
24
+ days: {
25
+ stale: 300,
26
+ revalidate: 86400,
27
+ expire: 604800
28
+ },
29
+ weeks: {
30
+ stale: 300,
31
+ revalidate: 604800,
32
+ expire: 2592e3
33
+ },
34
+ max: {
35
+ stale: 300,
36
+ revalidate: 2592e3,
37
+ expire: 31536e3
38
+ }
39
+ };
40
+ const CACHE_LIFE_FIELDS = [
41
+ "stale",
42
+ "revalidate",
43
+ "expire"
44
+ ];
45
+ function copyBuiltInProfiles() {
46
+ const profiles = {};
47
+ for (const [name, profile] of Object.entries(BUILT_IN_CACHE_LIFE_PROFILES)) profiles[name] = { ...profile };
48
+ return profiles;
49
+ }
50
+ /**
51
+ * Merge next.config `cacheLife` profiles over the built-ins, as Next.js does
52
+ * in `assignDefaultsAndValidate` (server/config.ts): a user profile replaces
53
+ * the built-in of the same name whole, and only `default` gets its missing
54
+ * fields filled: `stale` from `staleTimes.static`, `revalidate` from the
55
+ * built-in, `expire` from `expireTime`. That includes the built-in `default`,
56
+ * whose `stale` is unset in Next.js's defaults too. The other profiles keep
57
+ * their gaps, which the "use cache" store resolves from `default`.
58
+ *
59
+ * Next.js only schema-checks these profiles (fields must be numbers) and
60
+ * warns; `revalidate > expire` is checked for `cacheLife({...})` calls, not
61
+ * for config profiles.
62
+ */
63
+ function resolveCacheLifeProfiles(userProfiles, defaults, warn) {
64
+ const profiles = copyBuiltInProfiles();
65
+ for (const [name, rawProfile] of Object.entries(isUnknownRecord(userProfiles) ? userProfiles : {})) {
66
+ if (!isUnknownRecord(rawProfile)) {
67
+ warn(`[vinext] Invalid next.config option \`cacheLife.${name}\`: expected an object.`);
68
+ continue;
69
+ }
70
+ const profile = {};
71
+ for (const field of CACHE_LIFE_FIELDS) {
72
+ const value = rawProfile[field];
73
+ if (value === void 0) continue;
74
+ if (typeof value !== "number" || Number.isNaN(value)) {
75
+ warn(`[vinext] Invalid next.config option \`cacheLife.${name}.${field}\`: expected a number of seconds.`);
76
+ continue;
77
+ }
78
+ profile[field] = value;
79
+ }
80
+ profiles[name] = profile;
81
+ }
82
+ const defaultProfile = profiles.default;
83
+ defaultProfile.stale ??= defaults.staticStaleTime;
84
+ defaultProfile.revalidate ??= BUILT_IN_CACHE_LIFE_PROFILES.default.revalidate;
85
+ defaultProfile.expire ??= defaults.expireTime;
86
+ return profiles;
87
+ }
88
+ function encodeNonFiniteNumber(_key, value) {
89
+ return value === Infinity || value === -Infinity ? String(value) : value;
90
+ }
91
+ function decodeNonFiniteNumber(_key, value) {
92
+ return value === "Infinity" || value === "-Infinity" ? Number(value) : value;
93
+ }
94
+ /** Serialize resolved profiles for the `__VINEXT_CACHE_LIFE_PROFILES` define. */
95
+ function serializeCacheLifeProfiles(profiles) {
96
+ return JSON.stringify(profiles, encodeNonFiniteNumber);
97
+ }
98
+ /**
99
+ * Read the `__VINEXT_CACHE_LIFE_PROFILES` define. When absent, the profiles a
100
+ * next.config without `cacheLife` resolves to.
101
+ */
102
+ function parseCacheLifeProfiles(serialized) {
103
+ if (!serialized) {
104
+ const profiles = copyBuiltInProfiles();
105
+ profiles.default.stale = 300;
106
+ return profiles;
107
+ }
108
+ return JSON.parse(serialized, decodeNonFiniteNumber);
109
+ }
110
+ //#endregion
111
+ export { BUILT_IN_CACHE_LIFE_PROFILES, parseCacheLifeProfiles, resolveCacheLifeProfiles, serializeCacheLifeProfiles };
@@ -7,7 +7,17 @@ type MiddlewareHeaderSource = Headers | Record<string, MiddlewareHeaderValue>;
7
7
  * under their literal protocol-header names on both the request and response.
8
8
  */
9
9
  export declare function getUnconsumedMiddlewareRequestHeaders(source: MiddlewareHeaderSource): Map<string, string>;
10
- /** Whether middleware changed the headers seen by downstream application code. */
10
+ /**
11
+ * `baseHeaders` with only Next.js's generic merge of ordinary middleware
12
+ * response headers applied, without the request-header protocol.
13
+ */
14
+ export declare function applyMiddlewareResponseHeadersToRequest(baseHeaders: Headers, middlewareHeaders: MiddlewareHeaderSource): Headers;
15
+ /**
16
+ * Whether middleware changed the headers seen by downstream application code
17
+ * through its request-header protocol. Ordinary response headers are copied to
18
+ * the request as well, but like a client's own headers they do not opt a
19
+ * render out of the shared response stage.
20
+ */
11
21
  export declare function hasMiddlewareRequestHeaderOverrides(source: MiddlewareHeaderSource | null): boolean;
12
22
  export declare function encodeMiddlewareRequestHeaders(targetHeaders: Headers, requestHeaders: Headers): void;
13
23
  /**
@@ -16,8 +26,9 @@ export declare function encodeMiddlewareRequestHeaders(targetHeaders: Headers, r
16
26
  * middleware. Never re-add absent headers from the base request — that would
17
27
  * resurrect credentials the app explicitly stripped before an external
18
28
  * rewrite. Next.js treats the empty value emitted for `new Headers()` as no
19
- * override. Any unconsumed `x-middleware-request-*` values are subsequently
20
- * copied to the request under their literal protocol-header names.
29
+ * override. Any unconsumed `x-middleware-request-*` values and every other
30
+ * truthy middleware response header are subsequently copied to the request
31
+ * under their literal names, replacing values the client sent.
21
32
  */
22
33
  export declare function buildRequestHeadersFromMiddlewareResponse(baseHeaders: Headers, middlewareHeaders: MiddlewareHeaderSource): Headers | null;
23
34
  export declare function shouldKeepMiddlewareHeader(key: string): boolean;
@@ -34,7 +34,52 @@ function getUnconsumedMiddlewareRequestHeaders(source) {
34
34
  for (const [key, value] of getForwardedRequestHeaders(source)) if (value && !overriddenHeaders?.has(key)) unconsumedHeaders.set(`${MIDDLEWARE_REQUEST_HEADER_PREFIX}${key}`, value);
35
35
  return unconsumedHeaders;
36
36
  }
37
- /** Whether middleware changed the headers seen by downstream application code. */
37
+ const UNMERGED_MIDDLEWARE_RESPONSE_HEADERS = /* @__PURE__ */ new Set([
38
+ "accept-encoding",
39
+ "connection",
40
+ "content-encoding",
41
+ "content-length",
42
+ "expect",
43
+ "keep-alive",
44
+ "keepalive",
45
+ "transfer-encoding"
46
+ ]);
47
+ /**
48
+ * Return the truthy ordinary middleware response headers. Next.js's generic
49
+ * middleware-header merge writes each of them to the request too, so a value
50
+ * middleware sets replaces one the client sent under the same name. The
51
+ * `x-middleware-*` namespace carries vinext's middleware protocol and is
52
+ * handled separately.
53
+ */
54
+ function getMergedMiddlewareResponseHeaders(source) {
55
+ const mergedHeaders = new Headers();
56
+ const entries = source instanceof Headers ? source : Object.entries(source).flatMap(([key, value]) => Array.isArray(value) ? value.map((item) => [key, item]) : [[key, value]]);
57
+ for (const [key, value] of entries) {
58
+ const lowerKey = key.toLowerCase();
59
+ if (value && !lowerKey.startsWith("x-middleware-") && !UNMERGED_MIDDLEWARE_RESPONSE_HEADERS.has(lowerKey)) mergedHeaders.append(lowerKey, value);
60
+ }
61
+ return mergedHeaders;
62
+ }
63
+ function applyMergedMiddlewareHeaders(targetHeaders, unconsumedHeaders, mergedHeaders) {
64
+ for (const [key, value] of unconsumedHeaders) targetHeaders.set(key, value);
65
+ for (const key of new Set(mergedHeaders.keys())) targetHeaders.delete(key);
66
+ for (const [key, value] of mergedHeaders) targetHeaders.append(key, value);
67
+ }
68
+ /**
69
+ * `baseHeaders` with only Next.js's generic merge of ordinary middleware
70
+ * response headers applied, without the request-header protocol.
71
+ */
72
+ function applyMiddlewareResponseHeadersToRequest(baseHeaders, middlewareHeaders) {
73
+ const nextHeaders = new Headers(baseHeaders);
74
+ applyMergedMiddlewareHeaders(nextHeaders, /* @__PURE__ */ new Map(), getMergedMiddlewareResponseHeaders(middlewareHeaders));
75
+ return nextHeaders;
76
+ }
77
+ /**
78
+ * Whether middleware changed the headers seen by downstream application code
79
+ * through its request-header protocol. Ordinary response headers are copied to
80
+ * the request as well, but like a client's own headers they do not opt a
81
+ * render out of the shared response stage.
82
+ */
38
83
  function hasMiddlewareRequestHeaderOverrides(source) {
39
84
  if (!source) return false;
40
85
  return Boolean(getMiddlewareHeaderValue(source, "x-middleware-override-headers")) || getUnconsumedMiddlewareRequestHeaders(source).size > 0;
@@ -50,17 +95,19 @@ function encodeMiddlewareRequestHeaders(targetHeaders, requestHeaders) {
50
95
  * middleware. Never re-add absent headers from the base request — that would
51
96
  * resurrect credentials the app explicitly stripped before an external
52
97
  * rewrite. Next.js treats the empty value emitted for `new Headers()` as no
53
- * override. Any unconsumed `x-middleware-request-*` values are subsequently
54
- * copied to the request under their literal protocol-header names.
98
+ * override. Any unconsumed `x-middleware-request-*` values and every other
99
+ * truthy middleware response header are subsequently copied to the request
100
+ * under their literal names, replacing values the client sent.
55
101
  */
56
102
  function buildRequestHeadersFromMiddlewareResponse(baseHeaders, middlewareHeaders) {
57
103
  const forwardedHeaders = getForwardedRequestHeaders(middlewareHeaders);
58
104
  const unconsumedHeaders = getUnconsumedMiddlewareRequestHeaders(middlewareHeaders);
105
+ const mergedHeaders = getMergedMiddlewareResponseHeaders(middlewareHeaders);
59
106
  const rawOverrideHeader = getMiddlewareHeaderValue(middlewareHeaders, MIDDLEWARE_OVERRIDE_HEADERS);
60
107
  if (!rawOverrideHeader) {
61
- if (unconsumedHeaders.size === 0) return null;
108
+ if (unconsumedHeaders.size === 0 && mergedHeaders.keys().next().done) return null;
62
109
  const nextHeaders = new Headers(baseHeaders);
63
- for (const [key, value] of unconsumedHeaders) nextHeaders.set(key, value);
110
+ applyMergedMiddlewareHeaders(nextHeaders, unconsumedHeaders, mergedHeaders);
64
111
  return nextHeaders;
65
112
  }
66
113
  const overrideHeaderNames = parseOverrideHeaderNames(rawOverrideHeader);
@@ -69,11 +116,11 @@ function buildRequestHeadersFromMiddlewareResponse(baseHeaders, middlewareHeader
69
116
  const value = forwardedHeaders.get(key);
70
117
  if (value !== void 0) nextHeaders.set(key, value);
71
118
  }
72
- for (const [key, value] of unconsumedHeaders) nextHeaders.set(key, value);
119
+ applyMergedMiddlewareHeaders(nextHeaders, unconsumedHeaders, mergedHeaders);
73
120
  return nextHeaders;
74
121
  }
75
122
  function shouldKeepMiddlewareHeader(key) {
76
123
  return key === "x-middleware-override-headers" || key === "x-middleware-cache" || key === "x-middleware-set-cookie" || key.startsWith("x-middleware-request-");
77
124
  }
78
125
  //#endregion
79
- export { buildRequestHeadersFromMiddlewareResponse, encodeMiddlewareRequestHeaders, getUnconsumedMiddlewareRequestHeaders, hasMiddlewareRequestHeaderOverrides, shouldKeepMiddlewareHeader };
126
+ export { applyMiddlewareResponseHeadersToRequest, buildRequestHeadersFromMiddlewareResponse, encodeMiddlewareRequestHeaders, getUnconsumedMiddlewareRequestHeaders, hasMiddlewareRequestHeaderOverrides, shouldKeepMiddlewareHeader };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "vinext",
3
- "version": "1.4.0",
3
+ "version": "1.4.1",
4
4
  "description": "Run Next.js apps on Vite. Drop-in replacement for the next CLI.",
5
5
  "homepage": "https://vinext.dev/",
6
6
  "license": "MIT",