@opennextjs/aws 3.9.16 → 3.10.0

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 (37) hide show
  1. package/dist/adapters/cache.d.ts +3 -1
  2. package/dist/adapters/cache.js +75 -17
  3. package/dist/adapters/composable-cache.d.ts +12 -0
  4. package/dist/adapters/composable-cache.js +82 -2
  5. package/dist/adapters/dynamo-provider.js +2 -0
  6. package/dist/adapters/revalidate.js +2 -1
  7. package/dist/build/compileCache.js +2 -3
  8. package/dist/build/copyTracedFiles.d.ts +1 -0
  9. package/dist/build/copyTracedFiles.js +20 -0
  10. package/dist/build/createAssets.js +4 -1
  11. package/dist/build/helper.d.ts +2 -10
  12. package/dist/build/helper.js +1 -55
  13. package/dist/build/patch/patches/patchNextServer.d.ts +1 -0
  14. package/dist/build/patch/patches/patchNextServer.js +19 -0
  15. package/dist/core/routing/cacheInterceptor.js +2 -1
  16. package/dist/core/routing/i18n/index.js +4 -2
  17. package/dist/overrides/proxyExternalRequest/fetch.js +10 -1
  18. package/dist/overrides/tagCache/dummy.js +3 -0
  19. package/dist/overrides/tagCache/dynamodb-lite.js +94 -8
  20. package/dist/overrides/tagCache/dynamodb-nextMode.d.ts +2 -1
  21. package/dist/overrides/tagCache/dynamodb-nextMode.js +112 -27
  22. package/dist/overrides/tagCache/dynamodb.js +104 -22
  23. package/dist/overrides/tagCache/fs-dev-nextMode.d.ts +2 -1
  24. package/dist/overrides/tagCache/fs-dev-nextMode.js +37 -6
  25. package/dist/overrides/tagCache/fs-dev.js +40 -8
  26. package/dist/types/cache.d.ts +20 -0
  27. package/dist/types/global.d.ts +5 -2
  28. package/dist/types/next-types.d.ts +1 -0
  29. package/dist/types/overrides.d.ts +42 -1
  30. package/dist/utils/cache.d.ts +22 -2
  31. package/dist/utils/cache.js +44 -4
  32. package/dist/utils/promise.js +2 -0
  33. package/dist/utils/requestCache.d.ts +20 -0
  34. package/dist/utils/requestCache.js +27 -0
  35. package/dist/utils/semver.d.ts +10 -0
  36. package/dist/utils/semver.js +55 -0
  37. package/package.json +1 -1
@@ -11,7 +11,9 @@ export default class Cache {
11
11
  getFetchCache(key: string, softTags?: string[], tags?: string[]): Promise<CacheHandlerValue | null>;
12
12
  getIncrementalCache(key: string): Promise<CacheHandlerValue | null>;
13
13
  set(key: string, data?: IncrementalCacheValue, ctx?: IncrementalCacheContext): Promise<void>;
14
- revalidateTag(tags: string | string[]): Promise<void>;
14
+ revalidateTag(tags: string | string[], durations?: {
15
+ expire?: number;
16
+ }): Promise<void>;
15
17
  private updateTagsOnSet;
16
18
  private extractRevalidateForSet;
17
19
  }
@@ -1,5 +1,6 @@
1
- import { getTagsFromValue, hasBeenRevalidated, writeTags } from "../utils/cache";
1
+ import { getTagsFromValue, hasBeenRevalidated, isStale, writeTags, } from "../utils/cache";
2
2
  import { isBinaryContentType } from "../utils/binary";
3
+ import { compareSemver } from "../utils/semver";
3
4
  import { debug, error, warn } from "./logger";
4
5
  export const SOFT_TAG_PREFIX = "_N_T_/";
5
6
  function isFetchCache(options) {
@@ -57,8 +58,11 @@ export default class Cache {
57
58
  }
58
59
  }
59
60
  }
61
+ const _isStale = cachedEntry.shouldBypassTagCache
62
+ ? false
63
+ : await isStale(key, _tags, _lastModified);
60
64
  return {
61
- lastModified: _lastModified,
65
+ lastModified: _isStale ? 1 : _lastModified,
62
66
  value: cachedEntry.value,
63
67
  };
64
68
  }
@@ -77,21 +81,27 @@ export default class Cache {
77
81
  const cacheData = cachedEntry.value;
78
82
  const meta = cacheData.meta;
79
83
  const tags = getTagsFromValue(cacheData);
80
- const _lastModified = cachedEntry.lastModified ?? Date.now();
84
+ let _lastModified = cachedEntry.lastModified ?? Date.now();
81
85
  const _hasBeenRevalidated = cachedEntry.shouldBypassTagCache
82
86
  ? false
83
87
  : await hasBeenRevalidated(key, tags, cachedEntry);
84
88
  if (_hasBeenRevalidated)
85
89
  return null;
90
+ const _isStale = cachedEntry.shouldBypassTagCache
91
+ ? false
92
+ : await isStale(key, tags, _lastModified);
86
93
  const store = globalThis.__openNextAls.getStore();
87
94
  if (store) {
88
- store.lastModified = _lastModified;
95
+ store.lastModified = _isStale ? 1 : _lastModified;
96
+ _lastModified = store.lastModified;
89
97
  }
90
98
  if (cacheData?.type === "route") {
91
99
  return {
92
100
  lastModified: _lastModified,
93
101
  value: {
94
- kind: globalThis.isNextAfter15 ? "APP_ROUTE" : "ROUTE",
102
+ kind: compareSemver(globalThis.nextVersion, ">=", "15.0.0")
103
+ ? "APP_ROUTE"
104
+ : "ROUTE",
95
105
  body: Buffer.from(cacheData.body ?? Buffer.alloc(0), isBinaryContentType(String(meta?.headers?.["content-type"]))
96
106
  ? "base64"
97
107
  : "utf8"),
@@ -101,7 +111,8 @@ export default class Cache {
101
111
  };
102
112
  }
103
113
  if (cacheData?.type === "page" || cacheData?.type === "app") {
104
- if (globalThis.isNextAfter15 && cacheData?.type === "app") {
114
+ if (compareSemver(globalThis.nextVersion, ">=", "15.0.0") &&
115
+ cacheData?.type === "app") {
105
116
  const segmentData = new Map();
106
117
  if (cacheData.segmentData) {
107
118
  for (const [segmentPath, segmentContent] of Object.entries(cacheData.segmentData ?? {})) {
@@ -124,7 +135,9 @@ export default class Cache {
124
135
  return {
125
136
  lastModified: _lastModified,
126
137
  value: {
127
- kind: globalThis.isNextAfter15 ? "PAGES" : "PAGE",
138
+ kind: compareSemver(globalThis.nextVersion, ">=", "15.0.0")
139
+ ? "PAGES"
140
+ : "PAGE",
128
141
  html: cacheData.html,
129
142
  pageData: cacheData.type === "page" ? cacheData.json : cacheData.rsc,
130
143
  status: meta?.status,
@@ -256,7 +269,7 @@ export default class Cache {
256
269
  detachedPromise?.resolve();
257
270
  }
258
271
  }
259
- async revalidateTag(tags) {
272
+ async revalidateTag(tags, durations) {
260
273
  const config = globalThis.openNextConfig.dangerous;
261
274
  if (config?.disableTagCache || config?.disableIncrementalCache) {
262
275
  return;
@@ -268,7 +281,25 @@ export default class Cache {
268
281
  try {
269
282
  if (globalThis.tagCache.mode === "nextMode") {
270
283
  const paths = (await globalThis.tagCache.getPathsByTags?.(_tags)) ?? [];
271
- await writeTags(_tags);
284
+ const now = Date.now();
285
+ const tagsToWrite = _tags.map((tag) => {
286
+ if (durations) {
287
+ // Use provided durations
288
+ return {
289
+ tag,
290
+ stale: now,
291
+ expire: durations.expire !== undefined
292
+ ? now + durations.expire * 1000
293
+ : undefined,
294
+ };
295
+ }
296
+ // Immediate expiration, default behavior before next 16, now only with {expire: 0}
297
+ return {
298
+ tag,
299
+ expire: now,
300
+ };
301
+ });
302
+ await writeTags(tagsToWrite);
272
303
  if (paths.length > 0) {
273
304
  // TODO: we should introduce a new method in cdnInvalidationHandler to invalidate paths by tags for cdn that supports it
274
305
  // It also means that we'll need to provide the tags used in every request to the wrapper or converter.
@@ -291,10 +322,25 @@ export default class Cache {
291
322
  // Find all keys with the given tag
292
323
  const paths = await globalThis.tagCache.getByTag(tag);
293
324
  debug("Items", paths);
294
- const toInsert = paths.map((path) => ({
295
- path,
296
- tag,
297
- }));
325
+ const now = Date.now();
326
+ const toInsert = paths.map((path) => {
327
+ const baseEntry = { path, tag };
328
+ if (durations) {
329
+ // Use provided durations
330
+ return {
331
+ ...baseEntry,
332
+ stale: now,
333
+ expire: durations.expire !== undefined
334
+ ? now + durations.expire * 1000
335
+ : undefined,
336
+ };
337
+ }
338
+ // Default behavior: immediate expiration
339
+ return {
340
+ ...baseEntry,
341
+ expire: now,
342
+ };
343
+ });
298
344
  // If the tag is a soft tag, we should also revalidate the hard tags
299
345
  if (tag.startsWith(SOFT_TAG_PREFIX)) {
300
346
  for (const path of paths) {
@@ -305,10 +351,22 @@ export default class Cache {
305
351
  for (const hardTag of hardTags) {
306
352
  const _paths = await globalThis.tagCache.getByTag(hardTag);
307
353
  debug({ hardTag, _paths });
308
- toInsert.push(..._paths.map((path) => ({
309
- path,
310
- tag: hardTag,
311
- })));
354
+ toInsert.push(..._paths.map((path) => {
355
+ const baseEntry = { path, tag: hardTag };
356
+ if (durations) {
357
+ return {
358
+ ...baseEntry,
359
+ stale: now,
360
+ expire: durations.expire !== undefined
361
+ ? now + durations.expire * 1000
362
+ : undefined,
363
+ };
364
+ }
365
+ return {
366
+ ...baseEntry,
367
+ expire: now,
368
+ };
369
+ }));
312
370
  }
313
371
  }
314
372
  }
@@ -21,5 +21,17 @@ declare const _default: {
21
21
  */
22
22
  expireTags(...tags: string[]): Promise<void>;
23
23
  receiveExpiredTags(...tags: string[]): Promise<void>;
24
+ /**
25
+ * Added in Next.js 16. Updates tags with optional stale/expire durations.
26
+ * Mirrors the logic in `Cache.revalidateTag` but without CDN invalidation
27
+ * since composable cache keys are not URL paths.
28
+ *
29
+ * When `durations` is provided, marks tags as stale immediately and optionally
30
+ * sets an expiry timestamp. When omitted, immediately expires tags (no grace period).
31
+ * durations.expire is in seconds, but we convert it to milliseconds for storage and comparison.
32
+ */
33
+ updateTags(tags: string[], durations?: {
34
+ expire?: number;
35
+ }): Promise<void>;
24
36
  };
25
37
  export default _default;
@@ -1,4 +1,4 @@
1
- import { writeTags } from "../utils/cache";
1
+ import { isStale, writeTags } from "../utils/cache";
2
2
  import { fromReadableStream, toReadableStream } from "../utils/stream";
3
3
  import { debug } from "./logger";
4
4
  const pendingWritePromiseMap = new Map();
@@ -21,14 +21,22 @@ export default {
21
21
  return undefined;
22
22
  }
23
23
  debug("composable cache result", result);
24
- // We need to check if the tags associated with this entry has been revalidated
24
+ let revalidate = result.value.revalidate;
25
25
  if (globalThis.tagCache.mode === "nextMode" &&
26
26
  result.value.tags.length > 0) {
27
+ // We need to check if the tags associated with this entry has been revalidated
27
28
  const hasBeenRevalidated = result.shouldBypassTagCache
28
29
  ? false
29
30
  : await globalThis.tagCache.hasBeenRevalidated(result.value.tags, result.lastModified);
30
31
  if (hasBeenRevalidated)
31
32
  return undefined;
33
+ // Check if tags are stale – entry is valid but needs background revalidation
34
+ const isCacheStale = result.shouldBypassTagCache
35
+ ? false
36
+ : await isStale(cacheKey, result.value.tags, result.lastModified);
37
+ if (isCacheStale) {
38
+ revalidate = -1;
39
+ }
32
40
  }
33
41
  else if (globalThis.tagCache.mode === "original" ||
34
42
  globalThis.tagCache.mode === undefined) {
@@ -37,9 +45,17 @@ export default {
37
45
  : (await globalThis.tagCache.getLastModified(cacheKey, result.lastModified)) === -1;
38
46
  if (hasBeenRevalidated)
39
47
  return undefined;
48
+ // Check if tags are stale – entry is valid but needs background revalidation
49
+ const isCacheStale = result.shouldBypassTagCache
50
+ ? false
51
+ : await isStale(cacheKey, result.value.tags, result.lastModified);
52
+ if (isCacheStale) {
53
+ revalidate = -1;
54
+ }
40
55
  }
41
56
  return {
42
57
  ...result.value,
58
+ revalidate,
43
59
  value: toReadableStream(result.value.value),
44
60
  };
45
61
  }
@@ -118,4 +134,68 @@ export default {
118
134
  // This function does absolutely nothing
119
135
  return;
120
136
  },
137
+ /**
138
+ * Added in Next.js 16. Updates tags with optional stale/expire durations.
139
+ * Mirrors the logic in `Cache.revalidateTag` but without CDN invalidation
140
+ * since composable cache keys are not URL paths.
141
+ *
142
+ * When `durations` is provided, marks tags as stale immediately and optionally
143
+ * sets an expiry timestamp. When omitted, immediately expires tags (no grace period).
144
+ * durations.expire is in seconds, but we convert it to milliseconds for storage and comparison.
145
+ */
146
+ async updateTags(tags, durations) {
147
+ const config = globalThis.openNextConfig.dangerous;
148
+ if (config?.disableTagCache || config?.disableIncrementalCache) {
149
+ return;
150
+ }
151
+ if (tags.length === 0) {
152
+ return;
153
+ }
154
+ try {
155
+ const now = Date.now();
156
+ if (globalThis.tagCache.mode === "nextMode") {
157
+ const tagsToWrite = tags.map((tag) => {
158
+ if (durations) {
159
+ return {
160
+ tag,
161
+ stale: now,
162
+ expire: durations.expire !== undefined
163
+ ? now + durations.expire * 1000
164
+ : undefined,
165
+ };
166
+ }
167
+ // Default: immediate expiry, no grace period
168
+ return { tag, expire: now };
169
+ });
170
+ await writeTags(tagsToWrite);
171
+ }
172
+ else {
173
+ // Original mode: resolve tag → path mappings first
174
+ const originalTagCache = globalThis.tagCache;
175
+ const pathsPerTag = await Promise.all(tags.map(async (tag) => {
176
+ const paths = await originalTagCache.getByTag(tag);
177
+ return paths.map((path) => {
178
+ if (durations) {
179
+ return {
180
+ path,
181
+ tag,
182
+ stale: now,
183
+ expire: durations.expire !== undefined
184
+ ? now + durations.expire * 1000
185
+ : undefined,
186
+ };
187
+ }
188
+ return { path, tag, expire: now };
189
+ });
190
+ }));
191
+ const toWrite = pathsPerTag.flat();
192
+ if (toWrite.length > 0) {
193
+ await writeTags(toWrite);
194
+ }
195
+ }
196
+ }
197
+ catch (e) {
198
+ debug("Failed to update tags", e);
199
+ }
200
+ },
121
201
  };
@@ -30,6 +30,8 @@ async function insert(requestType) {
30
30
  tag: item.tag.S,
31
31
  path: item.path.S,
32
32
  revalidatedAt: Number.parseInt(item.revalidatedAt.N),
33
+ ...(item.stale && { stale: Number.parseInt(item.stale.N) }),
34
+ ...(item.expire && { expire: Number.parseInt(item.expire.N) }),
33
35
  }));
34
36
  await tagCache.writeTags(parsedData);
35
37
  return {
@@ -4,6 +4,7 @@ import path from "node:path";
4
4
  import { createGenericHandler } from "../core/createGenericHandler.js";
5
5
  import { debug, error } from "./logger.js";
6
6
  const prerenderManifest = loadPrerenderManifest();
7
+ const ALLOWED_STATUS_CODES = [200, 307, 308, 404];
7
8
  const defaultHandler = async (event) => {
8
9
  const failedRecords = [];
9
10
  for (const record of event.records) {
@@ -32,7 +33,7 @@ const defaultHandler = async (event) => {
32
33
  headers: res.headers,
33
34
  statusCode: res.statusCode,
34
35
  });
35
- if (res.statusCode !== 200 ||
36
+ if (!ALLOWED_STATUS_CODES.includes(res.statusCode ?? 0) ||
36
37
  res.headers["x-nextjs-cache"] !== "REVALIDATED") {
37
38
  failedRecords.push(record);
38
39
  }
@@ -11,7 +11,6 @@ export function compileCache(options, format = "cjs") {
11
11
  const { config } = options;
12
12
  const ext = format === "cjs" ? "cjs" : "mjs";
13
13
  const compiledCacheFile = path.join(options.buildDir, `cache.${ext}`);
14
- const isAfter15 = buildHelper.compareSemver(options.nextVersion, ">=", "15.0.0");
15
14
  // Normal cache
16
15
  buildHelper.esbuildSync({
17
16
  external: ["next", "styled-jsx", "react", "@aws-sdk/*"],
@@ -23,7 +22,7 @@ export function compileCache(options, format = "cjs") {
23
22
  js: [
24
23
  `globalThis.disableIncrementalCache = ${config.dangerous?.disableIncrementalCache ?? false};`,
25
24
  `globalThis.disableDynamoDBCache = ${config.dangerous?.disableTagCache ?? false};`,
26
- `globalThis.isNextAfter15 = ${isAfter15};`,
25
+ `globalThis.nextVersion = "${options.nextVersion}";`,
27
26
  ].join(""),
28
27
  },
29
28
  }, options);
@@ -41,7 +40,7 @@ export function compileCache(options, format = "cjs") {
41
40
  js: [
42
41
  `globalThis.disableIncrementalCache = ${config.dangerous?.disableIncrementalCache ?? false};`,
43
42
  `globalThis.disableDynamoDBCache = ${config.dangerous?.disableTagCache ?? false};`,
44
- `globalThis.isNextAfter15 = ${isAfter15};`,
43
+ `globalThis.nextVersion = "${options.nextVersion}";`,
45
44
  ].join(""),
46
45
  },
47
46
  }, options);
@@ -6,6 +6,7 @@
6
6
  */
7
7
  export declare function copyFileAndMakeOwnerWritable(src: string, dest: string): void;
8
8
  export declare function isExcluded(srcPath: string): boolean;
9
+ export declare function isNonLinuxPlatformPackage(srcPath: string): boolean;
9
10
  interface CopyTracedFilesOptions {
10
11
  buildOutputPath: string;
11
12
  packagePath: string;
@@ -40,6 +40,12 @@ export function isExcluded(srcPath) {
40
40
  escape: false,
41
41
  })));
42
42
  }
43
+ const NON_LINUX_PLATFORMS = ["darwin", "win32", "freebsd"];
44
+ const platformPattern = NON_LINUX_PLATFORMS.join("|");
45
+ const nonLinuxPlatformRegex = getCrossPlatformPathRegex(`/node_modules/(?:@[^/]+/)?(?:[^/]+-)?(${platformPattern})-[^/]+/`, { escape: false });
46
+ export function isNonLinuxPlatformPackage(srcPath) {
47
+ return nonLinuxPlatformRegex.test(srcPath);
48
+ }
43
49
  function copyPatchFile(outputDir) {
44
50
  const patchFile = path.join(__dirname, "patch", "patchedAsyncStorage.js");
45
51
  const outputPatchFile = path.join(outputDir, "patchedAsyncStorage.cjs");
@@ -190,11 +196,22 @@ File ${serverPath} does not exist
190
196
  const tracedFiles = [];
191
197
  const erroredFiles = [];
192
198
  //Actually copy the files
199
+ const skippedPlatformPackages = new Set();
193
200
  filesToCopy.forEach((to, from) => {
194
201
  // We don't want to copy excluded packages (e.g. sharp)
195
202
  if (isExcluded(from)) {
196
203
  return;
197
204
  }
205
+ // Skip non-Linux platform-specific native binaries (e.g. @swc/core-darwin-arm64)
206
+ // Set OPEN_NEXT_SKIP_PLATFORM_FILTER=true to bypass this check
207
+ if (!process.env.OPEN_NEXT_SKIP_PLATFORM_FILTER &&
208
+ isNonLinuxPlatformPackage(from)) {
209
+ const match = from.match(/node_modules\/(?:\.pnpm\/.*\/node_modules\/)?((?:@[^/]+\/)?[^/]+)/);
210
+ if (match) {
211
+ skippedPlatformPackages.add(match[1]);
212
+ }
213
+ return;
214
+ }
198
215
  tracedFiles.push(to);
199
216
  mkdirSync(path.dirname(to), { recursive: true });
200
217
  let symlink = null;
@@ -229,6 +246,9 @@ File ${serverPath} does not exist
229
246
  }
230
247
  }
231
248
  });
249
+ if (skippedPlatformPackages.size > 0) {
250
+ logger.debug("Skipped non-Linux platform packages:", [...skippedPlatformPackages].join(", "));
251
+ }
232
252
  readdirSync(standaloneNextDir)
233
253
  .filter((fileOrDir) => !statSync(path.join(standaloneNextDir, fileOrDir)).isDirectory())
234
254
  .forEach((file) => {
@@ -39,7 +39,10 @@ export function createStaticAssets(options, { useBasePath = false } = {}) {
39
39
  fs.copyFileSync(path.join(appBuildOutputPath, ".next/BUILD_ID"), path.join(outputPath, "BUILD_ID"));
40
40
  fs.cpSync(path.join(appBuildOutputPath, ".next/static"), path.join(outputPath, "_next", "static"), { recursive: true });
41
41
  if (fs.existsSync(appPublicPath)) {
42
- fs.cpSync(appPublicPath, outputPath, { recursive: true });
42
+ fs.cpSync(appPublicPath, outputPath, {
43
+ recursive: true,
44
+ dereference: true,
45
+ });
43
46
  }
44
47
  const appSrcPath = fs.existsSync(path.join(appPath, "src"))
45
48
  ? "src/app"
@@ -62,16 +62,8 @@ export declare function getHtmlPages(dotNextPath: string): Set<string>;
62
62
  export declare function getBuildId(options: BuildOptions): string;
63
63
  export declare function getOpenNextVersion(): string;
64
64
  export declare function getNextVersion(appPath: string): string;
65
- export type SemverOp = "=" | ">=" | "<=" | ">" | "<";
66
- /**
67
- * Compare two semver versions.
68
- *
69
- * @param v1 - First version. Can be "latest", otherwise it should be a valid semver version in the format of `major.minor.patch`. Usually is the next version from the package.json without canary suffix. If minor or patch are missing, they are considered 0.
70
- * @param v2 - Second version. Should not be "latest", it should be a valid semver version in the format of `major.minor.patch`. If minor or patch are missing, they are considered 0.
71
- * @example
72
- * compareSemver("2.0.0", ">=", "1.0.0") === true
73
- */
74
- export declare function compareSemver(v1: string, operator: SemverOp, v2: string): boolean;
65
+ export type { SemverOp } from "../utils/semver.js";
66
+ export { compareSemver } from "../utils/semver.js";
75
67
  /**
76
68
  * Check if a Next.js version is supported by the Next.js team.
77
69
  * Major versions are supported for 2 years following their initial release.
@@ -214,61 +214,7 @@ export function getNextVersion(appPath) {
214
214
  // Drop the -canary.n suffix
215
215
  return version.split("-")[0];
216
216
  }
217
- /**
218
- * Compare two semver versions.
219
- *
220
- * @param v1 - First version. Can be "latest", otherwise it should be a valid semver version in the format of `major.minor.patch`. Usually is the next version from the package.json without canary suffix. If minor or patch are missing, they are considered 0.
221
- * @param v2 - Second version. Should not be "latest", it should be a valid semver version in the format of `major.minor.patch`. If minor or patch are missing, they are considered 0.
222
- * @example
223
- * compareSemver("2.0.0", ">=", "1.0.0") === true
224
- */
225
- export function compareSemver(v1, operator, v2) {
226
- // - = 0 when versions are equal
227
- // - > 0 if v1 > v2
228
- // - < 0 if v2 > v1
229
- let versionDiff = 0;
230
- if (v1 === "latest") {
231
- versionDiff = 1;
232
- }
233
- else {
234
- if (/^[^\d]/.test(v1)) {
235
- // biome-ignore lint/style/noParameterAssign:
236
- v1 = v1.substring(1);
237
- }
238
- if (/^[^\d]/.test(v2)) {
239
- // biome-ignore lint/style/noParameterAssign:
240
- v2 = v2.substring(1);
241
- }
242
- const [major1, minor1 = 0, patch1 = 0] = v1.split(".").map(Number);
243
- const [major2, minor2 = 0, patch2 = 0] = v2.split(".").map(Number);
244
- if (Number.isNaN(major1) || Number.isNaN(major2)) {
245
- throw new Error("The major version is required.");
246
- }
247
- if (major1 !== major2) {
248
- versionDiff = major1 - major2;
249
- }
250
- else if (minor1 !== minor2) {
251
- versionDiff = minor1 - minor2;
252
- }
253
- else if (patch1 !== patch2) {
254
- versionDiff = patch1 - patch2;
255
- }
256
- }
257
- switch (operator) {
258
- case "=":
259
- return versionDiff === 0;
260
- case ">=":
261
- return versionDiff >= 0;
262
- case "<=":
263
- return versionDiff <= 0;
264
- case ">":
265
- return versionDiff > 0;
266
- case "<":
267
- return versionDiff < 0;
268
- default:
269
- throw new Error(`Unsupported operator: ${operator}`);
270
- }
271
- }
217
+ export { compareSemver } from "../utils/semver.js";
272
218
  /**
273
219
  * Next.js major version release dates (YYYY-MM-DD).
274
220
  * Each major version is supported for 2 years from its release date.
@@ -2,6 +2,7 @@ import type { CodePatcher } from "../codePatcher.js";
2
2
  export declare const disablePreloadingRule = "\nrule:\n kind: statement_block\n inside:\n kind: if_statement\n any:\n - has:\n kind: member_expression\n pattern: this.nextConfig.experimental.preloadEntriesOnStart\n stopBy: end\n - has:\n kind: binary_expression\n pattern: appDocumentPreloading === true\n stopBy: end\nfix:\n '{}'\n";
3
3
  export declare const removeMiddlewareManifestRule = "\nrule:\n kind: statement_block\n inside:\n kind: method_definition\n has:\n kind: property_identifier\n regex: ^getMiddlewareManifest$\nfix:\n '{return null;}'\n";
4
4
  export declare const emptyHandleNextImageRequestRule = "\nrule:\n kind: assignment_expression\n pattern: this.handleNextImageRequest = $VALUE\n inside:\n kind: method_definition\n stopBy: end\n has:\n kind: property_identifier\n regex: ^constructor$\n inside:\n kind: class_body\nfix:\n this.handleNextImageRequest = async (req, res, parsedUrl) => false\n";
5
+ export declare const provideInternalWaitUntil = "\nrule:\n kind: return_statement\n inside:\n kind: method_definition\n stopBy: end\n has:\n kind: property_identifier\n regex: getInternalWaitUntil\n\nfix:\n return globalThis.__openNextAls.getStore()?.waitUntil;\n";
5
6
  /**
6
7
  * Swaps the body for a throwing implementation
7
8
  *
@@ -47,6 +47,19 @@ rule:
47
47
  fix:
48
48
  this.handleNextImageRequest = async (req, res, parsedUrl) => false
49
49
  `;
50
+ export const provideInternalWaitUntil = `
51
+ rule:
52
+ kind: return_statement
53
+ inside:
54
+ kind: method_definition
55
+ stopBy: end
56
+ has:
57
+ kind: property_identifier
58
+ regex: getInternalWaitUntil
59
+
60
+ fix:
61
+ return globalThis.__openNextAls.getStore()?.waitUntil;
62
+ `;
50
63
  /**
51
64
  * Swaps the body for a throwing implementation
52
65
  *
@@ -119,6 +132,12 @@ export const patchNextServer = {
119
132
  contentFilter: /getMiddlewareManifest/,
120
133
  patchCode: createPatchCode(removeMiddlewareManifestRule),
121
134
  },
135
+ {
136
+ versions: ">=16.0.0",
137
+ pathFilter,
138
+ contentFilter: /getInternalWaitUntil/,
139
+ patchCode: createPatchCode(provideInternalWaitUntil),
140
+ },
122
141
  ...babelPatches,
123
142
  ],
124
143
  };
@@ -91,7 +91,8 @@ function getBodyForAppRouter(event, cachedValue) {
91
91
  try {
92
92
  const segmentHeader = `${event.headers[NEXT_SEGMENT_PREFETCH_HEADER]}`;
93
93
  const isSegmentResponse = Boolean(segmentHeader) &&
94
- segmentHeader in (cachedValue.segmentData || {});
94
+ segmentHeader in (cachedValue.segmentData || {}) &&
95
+ !NextConfig.experimental?.prefetchInlining;
95
96
  const body = isSegmentResponse
96
97
  ? cachedValue.segmentData[segmentHeader]
97
98
  : cachedValue.rsc;
@@ -1,7 +1,7 @@
1
1
  import { NextConfig } from "../../../adapters/config/index.js";
2
2
  import { emptyReadableStream } from "../../../utils/stream.js";
3
3
  import { debug } from "../../../adapters/logger.js";
4
- import { constructNextUrl } from "../util.js";
4
+ import { constructNextUrl, convertToQueryString } from "../util.js";
5
5
  import { acceptLanguage } from "./accept-header";
6
6
  function isLocalizedPath(path) {
7
7
  return (NextConfig.i18n?.locales.includes(path.split("/")[1].toLowerCase()) ?? false);
@@ -119,11 +119,13 @@ export function handleLocaleRedirect(internalEvent) {
119
119
  }
120
120
  const defaultLocale = domainLocale?.defaultLocale ?? i18n.defaultLocale;
121
121
  if (detectedLocale.toLowerCase() !== defaultLocale.toLowerCase()) {
122
+ const nextUrl = constructNextUrl(internalEvent.url, `/${detectedLocale}${NextConfig.trailingSlash ? "/" : ""}`);
123
+ const queryString = convertToQueryString(internalEvent.query);
122
124
  return {
123
125
  type: "core",
124
126
  statusCode: 307,
125
127
  headers: {
126
- Location: constructNextUrl(internalEvent.url, `/${detectedLocale}`),
128
+ Location: `${nextUrl}${queryString}`,
127
129
  },
128
130
  body: emptyReadableStream(),
129
131
  isBase64Encoded: false,
@@ -12,7 +12,16 @@ const fetchProxy = {
12
12
  });
13
13
  const responseHeaders = {};
14
14
  response.headers.forEach((value, key) => {
15
- responseHeaders[key] = value;
15
+ const cur = responseHeaders[key];
16
+ if (cur === undefined) {
17
+ responseHeaders[key] = value;
18
+ }
19
+ else if (Array.isArray(cur)) {
20
+ cur.push(value);
21
+ }
22
+ else {
23
+ responseHeaders[key] = [cur, value];
24
+ }
16
25
  });
17
26
  return {
18
27
  type: "core",
@@ -14,5 +14,8 @@ const dummyTagCache = {
14
14
  writeTags: async () => {
15
15
  return;
16
16
  },
17
+ isStale: async (_path) => {
18
+ return false;
19
+ },
17
20
  };
18
21
  export default dummyTagCache;