@opennextjs/aws 3.4.1 → 3.5.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 (86) hide show
  1. package/dist/adapters/cache.d.ts +2 -61
  2. package/dist/adapters/cache.js +75 -37
  3. package/dist/adapters/config/index.d.ts +1 -0
  4. package/dist/adapters/config/index.js +3 -1
  5. package/dist/adapters/config/util.d.ts +2 -1
  6. package/dist/adapters/config/util.js +13 -3
  7. package/dist/adapters/dynamo-provider.js +8 -0
  8. package/dist/adapters/edge-adapter.js +4 -10
  9. package/dist/adapters/image-optimization-adapter.d.ts +3 -2
  10. package/dist/adapters/image-optimization-adapter.js +3 -3
  11. package/dist/adapters/middleware.js +12 -8
  12. package/dist/build/compileConfig.js +1 -1
  13. package/dist/build/compileTagCacheProvider.js +1 -0
  14. package/dist/build/constant.d.ts +1 -0
  15. package/dist/build/constant.js +2 -0
  16. package/dist/build/copyTracedFiles.d.ts +10 -1
  17. package/dist/build/copyTracedFiles.js +22 -12
  18. package/dist/build/createAssets.d.ts +3 -1
  19. package/dist/build/createAssets.js +3 -3
  20. package/dist/build/createMiddleware.js +18 -5
  21. package/dist/build/createServerBundle.js +7 -1
  22. package/dist/build/edge/createEdgeBundle.js +14 -12
  23. package/dist/build/generateOutput.js +5 -7
  24. package/dist/build/helper.d.ts +1 -1
  25. package/dist/build/helper.js +9 -2
  26. package/dist/build/middleware/buildNodeMiddleware.d.ts +3 -0
  27. package/dist/build/middleware/buildNodeMiddleware.js +97 -0
  28. package/dist/core/createMainHandler.js +0 -1
  29. package/dist/core/edgeFunctionHandler.js +3 -1
  30. package/dist/core/nodeMiddlewareHandler.d.ts +4 -0
  31. package/dist/core/nodeMiddlewareHandler.js +25 -0
  32. package/dist/core/requestHandler.d.ts +3 -2
  33. package/dist/core/requestHandler.js +58 -25
  34. package/dist/core/routing/cacheInterceptor.js +10 -6
  35. package/dist/core/routing/i18n/index.d.ts +2 -0
  36. package/dist/core/routing/i18n/index.js +1 -1
  37. package/dist/core/routing/matcher.js +17 -14
  38. package/dist/core/routing/middleware.js +14 -29
  39. package/dist/core/routing/queue.d.ts +1 -0
  40. package/dist/core/routing/queue.js +41 -0
  41. package/dist/core/routing/util.d.ts +12 -3
  42. package/dist/core/routing/util.js +24 -50
  43. package/dist/core/routingHandler.d.ts +2 -1
  44. package/dist/core/routingHandler.js +10 -4
  45. package/dist/core/util.d.ts +1 -4
  46. package/dist/core/util.js +7 -15
  47. package/dist/overrides/converters/aws-apigw-v1.js +4 -3
  48. package/dist/overrides/converters/aws-apigw-v2.js +4 -3
  49. package/dist/overrides/converters/aws-cloudfront.js +6 -4
  50. package/dist/overrides/converters/edge.js +1 -10
  51. package/dist/overrides/converters/node.js +10 -8
  52. package/dist/overrides/converters/utils.d.ts +7 -0
  53. package/dist/overrides/converters/utils.js +9 -0
  54. package/dist/overrides/imageLoader/s3-lite.d.ts +3 -0
  55. package/dist/overrides/imageLoader/s3-lite.js +61 -0
  56. package/dist/overrides/incrementalCache/multi-tier-ddb-s3.js +1 -1
  57. package/dist/overrides/proxyExternalRequest/fetch.js +2 -1
  58. package/dist/overrides/tagCache/dummy.js +1 -0
  59. package/dist/overrides/tagCache/dynamodb-lite.js +1 -0
  60. package/dist/overrides/tagCache/dynamodb-nextMode.d.ts +7 -0
  61. package/dist/overrides/tagCache/dynamodb-nextMode.js +119 -0
  62. package/dist/overrides/tagCache/dynamodb.js +1 -0
  63. package/dist/overrides/tagCache/fs-dev.js +1 -0
  64. package/dist/overrides/wrappers/aws-lambda-streaming.js +1 -1
  65. package/dist/overrides/wrappers/aws-lambda.js +3 -1
  66. package/dist/overrides/wrappers/cloudflare-edge.js +3 -2
  67. package/dist/overrides/wrappers/cloudflare-node.js +10 -8
  68. package/dist/overrides/wrappers/dummy.js +2 -2
  69. package/dist/overrides/wrappers/express-dev.js +4 -4
  70. package/dist/overrides/wrappers/node.js +2 -2
  71. package/dist/plugins/edge.d.ts +1 -3
  72. package/dist/plugins/edge.js +4 -11
  73. package/dist/plugins/externalMiddleware.d.ts +2 -0
  74. package/dist/plugins/externalMiddleware.js +10 -0
  75. package/dist/plugins/resolve.js +2 -2
  76. package/dist/types/cache.d.ts +34 -4
  77. package/dist/types/global.d.ts +19 -31
  78. package/dist/types/next-types.d.ts +13 -0
  79. package/dist/types/open-next.d.ts +12 -5
  80. package/dist/types/overrides.d.ts +73 -7
  81. package/dist/utils/cache.d.ts +3 -0
  82. package/dist/utils/cache.js +32 -0
  83. package/dist/utils/promise.d.ts +3 -1
  84. package/dist/utils/promise.js +18 -11
  85. package/dist/utils/regex.js +1 -1
  86. package/package.json +5 -4
@@ -3,7 +3,7 @@ import { compile, match } from "path-to-regexp";
3
3
  import { emptyReadableStream, toReadableStream } from "../../utils/stream";
4
4
  import { debug } from "../../adapters/logger";
5
5
  import { localizePath } from "./i18n";
6
- import { convertFromQueryString, convertToQueryString, escapeRegex, getUrlParts, isExternal, unescapeRegex, } from "./util";
6
+ import { constructNextUrl, convertFromQueryString, convertToQueryString, escapeRegex, getUrlParts, isExternal, unescapeRegex, } from "./util";
7
7
  const routeHasMatcher = (headers, cookies, query) => (redirect) => {
8
8
  switch (redirect.type) {
9
9
  case "header":
@@ -96,7 +96,7 @@ export function getNextConfigHeaders(event, configHeaders) {
96
96
  return requestHeaders;
97
97
  }
98
98
  export function handleRewrites(event, rewrites) {
99
- const { rawPath, headers, query, cookies } = event;
99
+ const { rawPath, headers, query, cookies, url } = event;
100
100
  const localizedRawPath = localizePath(event);
101
101
  const matcher = routeHasMatcher(headers, cookies, query);
102
102
  const computeHas = computeParamHas(headers, cookies, query);
@@ -107,7 +107,7 @@ export function handleRewrites(event, rewrites) {
107
107
  checkHas(matcher, route.missing, true));
108
108
  });
109
109
  let finalQuery = query;
110
- let rewrittenUrl = rawPath;
110
+ let rewrittenUrl = url;
111
111
  const isExternalRewrite = isExternal(rewrite?.destination);
112
112
  debug("isExternalRewrite", isExternalRewrite);
113
113
  if (rewrite) {
@@ -121,12 +121,12 @@ export function handleRewrites(event, rewrites) {
121
121
  // %2B does not get interpreted as a space in the URL thus breaking the query string.
122
122
  const encodePlusQueryString = queryString.replaceAll("+", "%20");
123
123
  debug("urlParts", { pathname, protocol, hostname, queryString });
124
- const toDestinationPath = compile(escapeRegex(pathname ?? "") ?? "");
125
- const toDestinationHost = compile(escapeRegex(hostname ?? "") ?? "");
126
- const toDestinationQuery = compile(escapeRegex(encodePlusQueryString ?? "") ?? "");
124
+ const toDestinationPath = compile(escapeRegex(pathname));
125
+ const toDestinationHost = compile(escapeRegex(hostname));
126
+ const toDestinationQuery = compile(escapeRegex(encodePlusQueryString));
127
127
  const params = {
128
128
  // params for the source
129
- ...getParamsFromSource(match(escapeRegex(rewrite?.source) ?? ""))(pathToUse),
129
+ ...getParamsFromSource(match(escapeRegex(rewrite.source)))(pathToUse),
130
130
  // params for the has
131
131
  ...rewrite.has?.reduce((acc, cur) => {
132
132
  return Object.assign(acc, computeHas(cur));
@@ -147,27 +147,29 @@ export function handleRewrites(event, rewrites) {
147
147
  }
148
148
  rewrittenUrl = isExternalRewrite
149
149
  ? `${protocol}//${rewrittenHost}${rewrittenPath}`
150
- : `${rewrittenPath}`;
150
+ : new URL(rewrittenPath, event.url).href;
151
151
  // Should we merge the query params or use only the ones from the rewrite?
152
152
  finalQuery = {
153
153
  ...query,
154
154
  ...convertFromQueryString(rewrittenQuery),
155
155
  };
156
+ rewrittenUrl += convertToQueryString(finalQuery);
156
157
  debug("rewrittenUrl", { rewrittenUrl, finalQuery, isUsingParams });
157
158
  }
158
159
  return {
159
160
  internalEvent: {
160
161
  ...event,
161
- rawPath: rewrittenUrl,
162
- url: `${rewrittenUrl}${convertToQueryString(finalQuery)}`,
162
+ rawPath: new URL(rewrittenUrl).pathname,
163
+ url: rewrittenUrl,
163
164
  },
164
165
  __rewrite: rewrite,
165
166
  isExternalRewrite,
166
167
  };
167
168
  }
168
169
  function handleTrailingSlashRedirect(event) {
169
- const url = new URL(event.url, "http://localhost");
170
- const emptyBody = emptyReadableStream();
170
+ // When rawPath is `//domain`, `url.host` would be `domain`.
171
+ // https://github.com/opennextjs/opennextjs-aws/issues/355
172
+ const url = new URL(event.rawPath, "http://localhost");
171
173
  if (
172
174
  // Someone is trying to redirect to a different origin, let's not do that
173
175
  url.host !== "localhost" ||
@@ -176,6 +178,7 @@ function handleTrailingSlashRedirect(event) {
176
178
  event.rawPath.startsWith("/api/")) {
177
179
  return false;
178
180
  }
181
+ const emptyBody = emptyReadableStream();
179
182
  if (NextConfig.trailingSlash &&
180
183
  !event.headers["x-nextjs-data"] &&
181
184
  !event.rawPath.endsWith("/") &&
@@ -249,7 +252,7 @@ export function fixDataPage(internalEvent, buildId) {
249
252
  ...internalEvent,
250
253
  rawPath: newPath,
251
254
  query,
252
- url: `${newPath}${convertToQueryString(query)}`,
255
+ url: new URL(`${newPath}${convertToQueryString(query)}`, internalEvent.url).href,
253
256
  };
254
257
  }
255
258
  return internalEvent;
@@ -276,7 +279,7 @@ export function handleFallbackFalse(internalEvent, prerenderManifest) {
276
279
  event: {
277
280
  ...internalEvent,
278
281
  rawPath: "/404",
279
- url: "/404",
282
+ url: constructNextUrl(internalEvent.url, "/404"),
280
283
  headers: {
281
284
  ...internalEvent.headers,
282
285
  "x-invoke-status": "404",
@@ -1,4 +1,4 @@
1
- import { MiddlewareManifest, NextConfig } from "../../adapters/config/index.js";
1
+ import { FunctionsConfigManifest, MiddlewareManifest, NextConfig, } from "../../adapters/config/index.js";
2
2
  import { emptyReadableStream } from "../../utils/stream.js";
3
3
  import { localizePath } from "./i18n/index.js";
4
4
  //NOTE: we should try to avoid importing stuff from next as much as possible
@@ -10,7 +10,8 @@ import { localizePath } from "./i18n/index.js";
10
10
  // } = require("next/dist/server/web/spec-extension/adapters/next-request");
11
11
  import { convertBodyToReadableStream, convertToQueryString, getMiddlewareMatch, isExternal, } from "./util.js";
12
12
  const middlewareManifest = MiddlewareManifest;
13
- const middleMatch = getMiddlewareMatch(middlewareManifest);
13
+ const functionsConfigManifest = FunctionsConfigManifest;
14
+ const middleMatch = getMiddlewareMatch(middlewareManifest, functionsConfigManifest);
14
15
  function defaultMiddlewareLoader() {
15
16
  // @ts-expect-error - This is bundled
16
17
  return import("./middleware.mjs");
@@ -30,16 +31,9 @@ export async function handleMiddleware(internalEvent, middlewareLoader = default
30
31
  const hasMatch = middleMatch.some((r) => r.test(normalizedPath));
31
32
  if (!hasMatch)
32
33
  return internalEvent;
33
- // Retrieve the protocol:
34
- // - In lambda, the url only contains the rawPath and the query - default to https
35
- // - In cloudflare, the protocol is usually http in dev and https in production
36
- const protocol = internalEvent.url.startsWith("http://") ? "http:" : "https:";
37
- const host = headers.host
38
- ? `${protocol}//${headers.host}`
39
- : "http://localhost:3000";
40
- const initialUrl = new URL(normalizedPath, host);
34
+ const initialUrl = new URL(normalizedPath, internalEvent.url);
41
35
  initialUrl.search = convertToQueryString(internalEvent.query);
42
- const url = initialUrl.toString();
36
+ const url = initialUrl.href;
43
37
  const middleware = await middlewareLoader();
44
38
  const result = await middleware.default({
45
39
  // `geo` is pre Next 15.
@@ -100,9 +94,7 @@ export async function handleMiddleware(internalEvent, middlewareLoader = default
100
94
  // the redirected url and end the response.
101
95
  if (statusCode >= 300 && statusCode < 400) {
102
96
  resHeaders.location =
103
- responseHeaders
104
- .get("location")
105
- ?.replace("http://localhost:3000", `${protocol}//${internalEvent.headers.host}`) ?? resHeaders.location;
97
+ responseHeaders.get("location") ?? resHeaders.location;
106
98
  // res.setHeader("Location", location);
107
99
  return {
108
100
  body: emptyReadableStream(),
@@ -120,28 +112,23 @@ export async function handleMiddleware(internalEvent, middlewareLoader = default
120
112
  let middlewareQueryString = internalEvent.query;
121
113
  let newUrl = internalEvent.url;
122
114
  if (rewriteUrl) {
115
+ newUrl = rewriteUrl;
116
+ rewritten = true;
123
117
  // If not a string, it should probably throw
124
- if (isExternal(rewriteUrl, internalEvent.headers.host)) {
125
- newUrl = rewriteUrl;
126
- rewritten = true;
118
+ if (isExternal(newUrl, internalEvent.headers.host)) {
127
119
  isExternalRewrite = true;
128
120
  }
129
121
  else {
130
122
  const rewriteUrlObject = new URL(rewriteUrl);
131
- newUrl = rewriteUrlObject.pathname;
132
123
  // Reset the query params if the middleware is a rewrite
133
- if (middlewareQueryString.__nextDataReq) {
134
- middlewareQueryString = {
124
+ middlewareQueryString = middlewareQueryString.__nextDataReq
125
+ ? {
135
126
  __nextDataReq: middlewareQueryString.__nextDataReq,
136
- };
137
- }
138
- else {
139
- middlewareQueryString = {};
140
- }
127
+ }
128
+ : {};
141
129
  rewriteUrlObject.searchParams.forEach((v, k) => {
142
130
  middlewareQueryString[k] = v;
143
131
  });
144
- rewritten = true;
145
132
  }
146
133
  }
147
134
  // If the middleware returned a `NextResponse`, pipe the body to res. This will return
@@ -160,9 +147,7 @@ export async function handleMiddleware(internalEvent, middlewareLoader = default
160
147
  return {
161
148
  responseHeaders: resHeaders,
162
149
  url: newUrl,
163
- rawPath: rewritten
164
- ? (newUrl ?? internalEvent.rawPath)
165
- : internalEvent.rawPath,
150
+ rawPath: new URL(newUrl).pathname,
166
151
  type: internalEvent.type,
167
152
  headers: { ...internalEvent.headers, ...reqHeaders },
168
153
  body: internalEvent.body,
@@ -0,0 +1 @@
1
+ export declare function generateMessageGroupId(rawPath: string): string;
@@ -0,0 +1,41 @@
1
+ // Since we're using a FIFO queue, every messageGroupId is treated sequentially
2
+ // This could cause a backlog of messages in the queue if there is too much page to
3
+ // revalidate at once. To avoid this, we generate a random messageGroupId for each
4
+ // revalidation request.
5
+ // We can't just use a random string because we need to ensure that the same rawPath
6
+ // will always have the same messageGroupId.
7
+ // https://stackoverflow.com/questions/521295/seeding-the-random-number-generator-in-javascript#answer-47593316
8
+ export function generateMessageGroupId(rawPath) {
9
+ let a = cyrb128(rawPath);
10
+ // We use mulberry32 to generate a random int between 0 and MAX_REVALIDATE_CONCURRENCY
11
+ let t = (a += 0x6d2b79f5);
12
+ t = Math.imul(t ^ (t >>> 15), t | 1);
13
+ t ^= t + Math.imul(t ^ (t >>> 7), t | 61);
14
+ const randomFloat = ((t ^ (t >>> 14)) >>> 0) / 4294967296;
15
+ // This will generate a random int between 0 and MAX_REVALIDATE_CONCURRENCY
16
+ // This means that we could have 1000 revalidate request at the same time
17
+ const maxConcurrency = Number.parseInt(process.env.MAX_REVALIDATE_CONCURRENCY ?? "10");
18
+ const randomInt = Math.floor(randomFloat * maxConcurrency);
19
+ return `revalidate-${randomInt}`;
20
+ }
21
+ // Used to generate a hash int from a string
22
+ function cyrb128(str) {
23
+ let h1 = 1779033703;
24
+ let h2 = 3144134277;
25
+ let h3 = 1013904242;
26
+ let h4 = 2773480762;
27
+ for (let i = 0, k; i < str.length; i++) {
28
+ k = str.charCodeAt(i);
29
+ h1 = h2 ^ Math.imul(h1 ^ k, 597399067);
30
+ h2 = h3 ^ Math.imul(h2 ^ k, 2869860233);
31
+ h3 = h4 ^ Math.imul(h3 ^ k, 951274213);
32
+ h4 = h1 ^ Math.imul(h4 ^ k, 2716044179);
33
+ }
34
+ h1 = Math.imul(h3 ^ (h1 >>> 18), 597399067);
35
+ h2 = Math.imul(h4 ^ (h2 >>> 22), 2869860233);
36
+ h3 = Math.imul(h1 ^ (h3 >>> 17), 951274213);
37
+ h4 = Math.imul(h2 ^ (h4 >>> 19), 2716044179);
38
+ // biome-ignore lint/style/noCommaOperator:
39
+ (h1 ^= h2 ^ h3 ^ h4), (h2 ^= h1), (h3 ^= h1), (h4 ^= h1);
40
+ return h1 >>> 0;
41
+ }
@@ -1,7 +1,7 @@
1
1
  import type { OutgoingHttpHeaders } from "node:http";
2
2
  import type { IncomingMessage } from "../../http/index.js";
3
3
  import { OpenNextNodeResponse } from "../../http/openNextResponse.js";
4
- import type { MiddlewareManifest } from "../../types/next-types";
4
+ import type { FunctionsConfigManifest, MiddlewareManifest } from "../../types/next-types";
5
5
  import type { InternalEvent, InternalResult, RoutingResult, StreamCreator } from "../../types/open-next.js";
6
6
  /**
7
7
  *
@@ -19,6 +19,16 @@ export declare function getUrlParts(url: string, isExternal: boolean): {
19
19
  protocol: string;
20
20
  queryString: string;
21
21
  };
22
+ /**
23
+ * Creates an URL to a Next page
24
+ *
25
+ * @param baseUrl Used to get the origin
26
+ * @param path The pathname
27
+ * @returns The Next URL considering the basePath
28
+ *
29
+ * @__PURE__
30
+ */
31
+ export declare function constructNextUrl(baseUrl: string, path: string): string;
22
32
  /**
23
33
  *
24
34
  * @__PURE__
@@ -41,7 +51,7 @@ export declare function convertToQuery(querystring: string): Record<string, stri
41
51
  *
42
52
  * @__PURE__
43
53
  */
44
- export declare function getMiddlewareMatch(middlewareManifest: MiddlewareManifest): RegExp[];
54
+ export declare function getMiddlewareMatch(middlewareManifest: MiddlewareManifest, functionsManifest?: FunctionsConfigManifest): RegExp[];
45
55
  /**
46
56
  *
47
57
  * @__PURE__
@@ -76,7 +86,6 @@ export declare function addOpenNextHeader(headers: OutgoingHttpHeaders): void;
76
86
  * @__PURE__
77
87
  */
78
88
  export declare function revalidateIfRequired(host: string, rawPath: string, headers: OutgoingHttpHeaders, req?: IncomingMessage): Promise<void>;
79
- export declare function generateMessageGroupId(rawPath: string): string;
80
89
  /**
81
90
  *
82
91
  * @__PURE__
@@ -6,6 +6,7 @@ import { parseHeaders } from "../../http/util.js";
6
6
  import { debug, error } from "../../adapters/logger.js";
7
7
  import { isBinaryContentType } from "../../utils/binary.js";
8
8
  import { localizePath } from "./i18n/index.js";
9
+ import { generateMessageGroupId } from "./queue.js";
9
10
  /**
10
11
  *
11
12
  * @__PURE__
@@ -56,6 +57,20 @@ export function getUrlParts(url, isExternal) {
56
57
  queryString: match[4]?.slice(1) ?? "",
57
58
  };
58
59
  }
60
+ /**
61
+ * Creates an URL to a Next page
62
+ *
63
+ * @param baseUrl Used to get the origin
64
+ * @param path The pathname
65
+ * @returns The Next URL considering the basePath
66
+ *
67
+ * @__PURE__
68
+ */
69
+ export function constructNextUrl(baseUrl, path) {
70
+ const nextBasePath = NextConfig.basePath;
71
+ const url = new URL(`${nextBasePath}${path}`, baseUrl);
72
+ return url.href;
73
+ }
59
74
  /**
60
75
  *
61
76
  * @__PURE__
@@ -119,7 +134,10 @@ export function convertToQuery(querystring) {
119
134
  *
120
135
  * @__PURE__
121
136
  */
122
- export function getMiddlewareMatch(middlewareManifest) {
137
+ export function getMiddlewareMatch(middlewareManifest, functionsManifest) {
138
+ if (functionsManifest?.functions?.["/_middleware"]) {
139
+ return (functionsManifest.functions["/_middleware"].matchers?.map(({ regexp }) => new RegExp(regexp)) ?? [/.*/]);
140
+ }
123
141
  const rootMiddleware = middlewareManifest.middleware["/"];
124
142
  if (!rootMiddleware?.matchers)
125
143
  return [];
@@ -246,10 +264,7 @@ export async function revalidateIfRequired(host, rawPath, headers, req) {
246
264
  // If data has the same etag during these 5 min dedup window, it will be deduplicated and not revalidated.
247
265
  try {
248
266
  const hash = (str) => crypto.createHash("md5").update(str).digest("hex");
249
- const requestId = globalThis.__openNextAls.getStore()?.requestId ?? "";
250
- const lastModified = globalThis.lastModified[requestId] > 0
251
- ? globalThis.lastModified[requestId]
252
- : "";
267
+ const lastModified = globalThis.__openNextAls.getStore()?.lastModified ?? 0;
253
268
  // For some weird cases, lastModified is not set, haven't been able to figure out yet why
254
269
  // For those cases we add the etag to the deduplication id, it might help
255
270
  const etag = headers.etag ?? headers.ETag ?? "";
@@ -264,47 +279,6 @@ export async function revalidateIfRequired(host, rawPath, headers, req) {
264
279
  }
265
280
  }
266
281
  }
267
- // Since we're using a FIFO queue, every messageGroupId is treated sequentially
268
- // This could cause a backlog of messages in the queue if there is too much page to
269
- // revalidate at once. To avoid this, we generate a random messageGroupId for each
270
- // revalidation request.
271
- // We can't just use a random string because we need to ensure that the same rawPath
272
- // will always have the same messageGroupId.
273
- // https://stackoverflow.com/questions/521295/seeding-the-random-number-generator-in-javascript#answer-47593316
274
- export function generateMessageGroupId(rawPath) {
275
- let a = cyrb128(rawPath);
276
- // We use mulberry32 to generate a random int between 0 and MAX_REVALIDATE_CONCURRENCY
277
- let t = (a += 0x6d2b79f5);
278
- t = Math.imul(t ^ (t >>> 15), t | 1);
279
- t ^= t + Math.imul(t ^ (t >>> 7), t | 61);
280
- const randomFloat = ((t ^ (t >>> 14)) >>> 0) / 4294967296;
281
- // This will generate a random int between 0 and MAX_REVALIDATE_CONCURRENCY
282
- // This means that we could have 1000 revalidate request at the same time
283
- const maxConcurrency = Number.parseInt(process.env.MAX_REVALIDATE_CONCURRENCY ?? "10");
284
- const randomInt = Math.floor(randomFloat * maxConcurrency);
285
- return `revalidate-${randomInt}`;
286
- }
287
- // Used to generate a hash int from a string
288
- function cyrb128(str) {
289
- let h1 = 1779033703;
290
- let h2 = 3144134277;
291
- let h3 = 1013904242;
292
- let h4 = 2773480762;
293
- for (let i = 0, k; i < str.length; i++) {
294
- k = str.charCodeAt(i);
295
- h1 = h2 ^ Math.imul(h1 ^ k, 597399067);
296
- h2 = h3 ^ Math.imul(h2 ^ k, 2869860233);
297
- h3 = h4 ^ Math.imul(h3 ^ k, 951274213);
298
- h4 = h1 ^ Math.imul(h4 ^ k, 2716044179);
299
- }
300
- h1 = Math.imul(h3 ^ (h1 >>> 18), 597399067);
301
- h2 = Math.imul(h4 ^ (h2 >>> 22), 2869860233);
302
- h3 = Math.imul(h1 ^ (h3 >>> 17), 951274213);
303
- h4 = Math.imul(h2 ^ (h4 >>> 19), 2716044179);
304
- // biome-ignore lint/style/noCommaOperator:
305
- (h1 ^= h2 ^ h3 ^ h4), (h2 ^= h1), (h3 ^= h1), (h4 ^= h1);
306
- return h1 >>> 0;
307
- }
308
282
  /**
309
283
  *
310
284
  * @__PURE__
@@ -315,15 +289,14 @@ export function fixISRHeaders(headers) {
315
289
  "private, no-cache, no-store, max-age=0, must-revalidate";
316
290
  return;
317
291
  }
318
- const requestId = globalThis.__openNextAls.getStore()?.requestId ?? "";
319
- const _lastModified = globalThis.lastModified[requestId] ?? 0;
292
+ const _lastModified = globalThis.__openNextAls.getStore()?.lastModified ?? 0;
320
293
  if (headers[CommonHeaders.NEXT_CACHE] === "HIT" && _lastModified > 0) {
321
294
  // calculate age
322
295
  const age = Math.round((Date.now() - _lastModified) / 1000);
323
296
  // extract s-maxage from cache-control
324
297
  const regex = /s-maxage=(\d+)/;
325
298
  const cacheControl = headers[CommonHeaders.CACHE_CONTROL];
326
- debug("cache-control", cacheControl, globalThis.lastModified, Date.now());
299
+ debug("cache-control", cacheControl, _lastModified, Date.now());
327
300
  if (typeof cacheControl !== "string")
328
301
  return;
329
302
  const match = cacheControl.match(regex);
@@ -366,7 +339,8 @@ export function createServerResponse(routingResult, headers, responseStream) {
366
339
  }
367
340
  // This function is used only for `res.revalidate()`
368
341
  export async function invalidateCDNOnRequest(params, headers) {
369
- const { internalEvent, initialPath, resolvedRoutes } = params;
342
+ const { internalEvent, resolvedRoutes, initialURL } = params;
343
+ const initialPath = new URL(initialURL).pathname;
370
344
  const isIsrRevalidation = internalEvent.headers["x-isr"] === "1";
371
345
  if (!isIsrRevalidation &&
372
346
  headers[CommonHeaders.NEXT_CACHE] === "REVALIDATED") {
@@ -2,6 +2,7 @@ import type { InternalEvent, InternalResult, RoutingResult } from "../types/open
2
2
  export declare const MIDDLEWARE_HEADER_PREFIX = "x-middleware-response-";
3
3
  export declare const MIDDLEWARE_HEADER_PREFIX_LEN: number;
4
4
  export declare const INTERNAL_HEADER_PREFIX = "x-opennext-";
5
- export declare const INTERNAL_HEADER_INITIAL_PATH = "x-opennext-initial-path";
5
+ export declare const INTERNAL_HEADER_INITIAL_URL = "x-opennext-initial-url";
6
+ export declare const INTERNAL_HEADER_LOCALE = "x-opennext-locale";
6
7
  export declare const INTERNAL_HEADER_RESOLVED_ROUTES = "x-opennext-resolved-routes";
7
8
  export default function routingHandler(event: InternalEvent): Promise<InternalResult | RoutingResult>;
@@ -1,13 +1,16 @@
1
- import { BuildId, ConfigHeaders, PrerenderManifest, RoutesManifest, } from "../adapters/config/index";
1
+ import { BuildId, ConfigHeaders, NextConfig, PrerenderManifest, RoutesManifest, } from "../adapters/config/index";
2
2
  import { debug } from "../adapters/logger";
3
3
  import { cacheInterceptor } from "./routing/cacheInterceptor";
4
+ import { detectLocale } from "./routing/i18n";
4
5
  import { fixDataPage, getNextConfigHeaders, handleFallbackFalse, handleRedirects, handleRewrites, } from "./routing/matcher";
5
6
  import { handleMiddleware } from "./routing/middleware";
6
7
  import { apiPrefix, dynamicRouteMatcher, staticRouteMatcher, } from "./routing/routeMatcher";
8
+ import { constructNextUrl } from "./routing/util";
7
9
  export const MIDDLEWARE_HEADER_PREFIX = "x-middleware-response-";
8
10
  export const MIDDLEWARE_HEADER_PREFIX_LEN = MIDDLEWARE_HEADER_PREFIX.length;
9
11
  export const INTERNAL_HEADER_PREFIX = "x-opennext-";
10
- export const INTERNAL_HEADER_INITIAL_PATH = `${INTERNAL_HEADER_PREFIX}initial-path`;
12
+ export const INTERNAL_HEADER_INITIAL_URL = `${INTERNAL_HEADER_PREFIX}initial-url`;
13
+ export const INTERNAL_HEADER_LOCALE = `${INTERNAL_HEADER_PREFIX}locale`;
11
14
  export const INTERNAL_HEADER_RESOLVED_ROUTES = `${INTERNAL_HEADER_PREFIX}resolved-routes`;
12
15
  // Geolocation headers starting from Nextjs 15
13
16
  // See https://github.com/vercel/vercel/blob/7714b1c/packages/functions/src/headers.ts
@@ -106,7 +109,7 @@ export default async function routingHandler(event) {
106
109
  internalEvent = {
107
110
  ...internalEvent,
108
111
  rawPath: "/404",
109
- url: "/404",
112
+ url: constructNextUrl(internalEvent.url, "/404"),
110
113
  headers: {
111
114
  ...internalEvent.headers,
112
115
  "x-middleware-response-cache-control": "private, no-cache, no-store, max-age=0, must-revalidate",
@@ -140,7 +143,10 @@ export default async function routingHandler(event) {
140
143
  isExternalRewrite,
141
144
  origin: false,
142
145
  isISR,
143
- initialPath: event.rawPath,
144
146
  resolvedRoutes,
147
+ initialURL: event.url,
148
+ locale: NextConfig.i18n
149
+ ? detectLocale(internalEvent, NextConfig.i18n)
150
+ : undefined,
145
151
  };
146
152
  }
@@ -1,5 +1,2 @@
1
- import type { MiddlewareManifest } from "../types/next-types.js";
2
- export declare const requestHandler: any;
3
- export declare function getMiddlewareMatch(middlewareManifest: MiddlewareManifest): RegExp[];
4
- export declare function loadMiddlewareManifest(nextDir: string): MiddlewareManifest;
1
+ export declare const requestHandler: (metadata: Record<string, any>) => any;
5
2
  export declare function setNextjsPrebundledReact(rawPath: string): void;
package/dist/core/util.js CHANGED
@@ -1,5 +1,3 @@
1
- import fs from "node:fs";
2
- import path from "node:path";
3
1
  import { AppPathsManifestKeys, NextConfig, RoutesManifest, } from "../adapters/config/index.js";
4
2
  // @ts-ignore
5
3
  import NextServer from "next/dist/server/next-server.js";
@@ -18,7 +16,7 @@ applyNextjsRequireHooksOverride();
18
16
  //#endOverride
19
17
  const cacheHandlerPath = require.resolve("./cache.cjs");
20
18
  // @ts-ignore
21
- export const requestHandler = new NextServer.default({
19
+ const nextServer = new NextServer.default({
22
20
  //#override requestHandlerHost
23
21
  hostname: "localhost",
24
22
  port: 3000,
@@ -49,18 +47,12 @@ export const requestHandler = new NextServer.default({
49
47
  customServer: false,
50
48
  dev: false,
51
49
  dir: __dirname,
52
- }).getRequestHandler();
53
- export function getMiddlewareMatch(middlewareManifest) {
54
- const rootMiddleware = middlewareManifest.middleware["/"];
55
- if (!rootMiddleware?.matchers)
56
- return [];
57
- return rootMiddleware.matchers.map(({ regexp }) => new RegExp(regexp));
58
- }
59
- export function loadMiddlewareManifest(nextDir) {
60
- const filePath = path.join(nextDir, "server", "middleware-manifest.json");
61
- const json = fs.readFileSync(filePath, "utf-8");
62
- return JSON.parse(json);
63
- }
50
+ });
51
+ // `getRequestHandlerWithMetadata` is not available in older versions of Next.js
52
+ // It is required to for next 15.2 to pass metadata for page router data route
53
+ export const requestHandler = (metadata) => "getRequestHandlerWithMetadata" in nextServer
54
+ ? nextServer.getRequestHandlerWithMetadata(metadata)
55
+ : nextServer.getRequestHandler();
64
56
  //#override setNextjsPrebundledReact
65
57
  export function setNextjsPrebundledReact(rawPath) {
66
58
  // WORKAROUND: Set `__NEXT_PRIVATE_PREBUNDLED_REACT` to use prebundled React
@@ -1,6 +1,6 @@
1
1
  import { fromReadableStream } from "../../utils/stream";
2
2
  import { debug } from "../../adapters/logger";
3
- import { removeUndefinedFromQuery } from "./utils";
3
+ import { extractHostFromHeaders, removeUndefinedFromQuery } from "./utils";
4
4
  function normalizeAPIGatewayProxyEventHeaders(event) {
5
5
  event.multiValueHeaders;
6
6
  const headers = {};
@@ -41,13 +41,14 @@ function normalizeAPIGatewayProxyEventQueryParams(event) {
41
41
  }
42
42
  async function convertFromAPIGatewayProxyEvent(event) {
43
43
  const { path, body, httpMethod, requestContext, isBase64Encoded } = event;
44
+ const headers = normalizeAPIGatewayProxyEventHeaders(event);
44
45
  return {
45
46
  type: "core",
46
47
  method: httpMethod,
47
48
  rawPath: path,
48
- url: path + normalizeAPIGatewayProxyEventQueryParams(event),
49
+ url: `https://${extractHostFromHeaders(headers)}${path}${normalizeAPIGatewayProxyEventQueryParams(event)}`,
49
50
  body: Buffer.from(body ?? "", isBase64Encoded ? "base64" : "utf8"),
50
- headers: normalizeAPIGatewayProxyEventHeaders(event),
51
+ headers,
51
52
  remoteAddress: requestContext.identity.sourceIp,
52
53
  query: removeUndefinedFromQuery(event.multiValueQueryStringParameters ?? {}),
53
54
  cookies: event.multiValueHeaders?.cookie?.reduce((acc, cur) => {
@@ -2,7 +2,7 @@ import { parseCookies } from "../../http/util";
2
2
  import { fromReadableStream } from "../../utils/stream";
3
3
  import { debug } from "../../adapters/logger";
4
4
  import { convertToQuery } from "../../core/routing/util";
5
- import { removeUndefinedFromQuery } from "./utils";
5
+ import { extractHostFromHeaders, removeUndefinedFromQuery } from "./utils";
6
6
  // Not sure which one is really needed as this is not documented anywhere but server actions redirect are not working without this,
7
7
  // it causes a 500 error from cloudfront itself with a 'x-amzErrortype: InternalFailure' header
8
8
  const CloudFrontBlacklistedHeaders = [
@@ -54,13 +54,14 @@ function normalizeAPIGatewayProxyEventV2Headers(event) {
54
54
  }
55
55
  async function convertFromAPIGatewayProxyEventV2(event) {
56
56
  const { rawPath, rawQueryString, requestContext } = event;
57
+ const headers = normalizeAPIGatewayProxyEventV2Headers(event);
57
58
  return {
58
59
  type: "core",
59
60
  method: requestContext.http.method,
60
61
  rawPath,
61
- url: rawPath + (rawQueryString ? `?${rawQueryString}` : ""),
62
+ url: `https://${extractHostFromHeaders(headers)}${rawPath}${rawQueryString ? `?${rawQueryString}` : ""}`,
62
63
  body: normalizeAPIGatewayProxyEventV2Body(event),
63
- headers: normalizeAPIGatewayProxyEventV2Headers(event),
64
+ headers,
64
65
  remoteAddress: requestContext.http.sourceIp,
65
66
  query: removeUndefinedFromQuery(convertToQuery(rawQueryString)),
66
67
  cookies: event.cookies?.reduce((acc, cur) => {
@@ -2,6 +2,7 @@ import { parseCookies } from "../../http/util";
2
2
  import { fromReadableStream } from "../../utils/stream";
3
3
  import { debug } from "../../adapters/logger";
4
4
  import { convertToQuery, convertToQueryString } from "../../core/routing/util";
5
+ import { extractHostFromHeaders } from "./utils";
5
6
  const cloudfrontBlacklistedHeaders = [
6
7
  // Disallowed headers, see: https://docs.aws.amazon.com/AmazonCloudFront/latest/DeveloperGuide/edge-function-restrictions-all.html#function-restrictions-disallowed-headers
7
8
  "connection",
@@ -56,17 +57,18 @@ function normalizeCloudFrontRequestEventHeaders(rawHeaders) {
56
57
  return headers;
57
58
  }
58
59
  async function convertFromCloudFrontRequestEvent(event) {
59
- const { method, uri, querystring, body, headers, clientIp } = event.Records[0].cf.request;
60
+ const { method, uri, querystring, body, headers: cfHeaders, clientIp, } = event.Records[0].cf.request;
61
+ const headers = normalizeCloudFrontRequestEventHeaders(cfHeaders);
60
62
  return {
61
63
  type: "core",
62
64
  method,
63
65
  rawPath: uri,
64
- url: uri + (querystring ? `?${querystring}` : ""),
66
+ url: `https://${extractHostFromHeaders(headers)}${uri}${querystring ? `?${querystring}` : ""}`,
65
67
  body: Buffer.from(body?.data ?? "", body?.encoding === "base64" ? "base64" : "utf8"),
66
- headers: normalizeCloudFrontRequestEventHeaders(headers),
68
+ headers,
67
69
  remoteAddress: clientIp,
68
70
  query: convertToQuery(querystring),
69
- cookies: headers.cookie?.reduce((acc, cur) => {
71
+ cookies: cfHeaders.cookie?.reduce((acc, cur) => {
70
72
  const { key = "", value } = cur;
71
73
  acc[key] = value;
72
74
  return acc;
@@ -42,16 +42,7 @@ const converter = {
42
42
  },
43
43
  convertTo: async (result) => {
44
44
  if ("internalEvent" in result) {
45
- let url = result.internalEvent.url;
46
- if (!result.isExternalRewrite) {
47
- if (result.origin) {
48
- url = `${result.origin.protocol}://${result.origin.host}${result.origin.port ? `:${result.origin.port}` : ""}${url}`;
49
- }
50
- else {
51
- url = `https://${result.internalEvent.headers.host}${url}`;
52
- }
53
- }
54
- const request = new Request(url, {
45
+ const request = new Request(result.internalEvent.url, {
55
46
  body: result.internalEvent.body,
56
47
  method: result.internalEvent.method,
57
48
  headers: {
@@ -1,4 +1,5 @@
1
1
  import { parseCookies } from "../../http/util";
2
+ import { extractHostFromHeaders } from "./utils";
2
3
  const converter = {
3
4
  convertFrom: async (req) => {
4
5
  const body = await new Promise((resolve) => {
@@ -10,20 +11,21 @@ const converter = {
10
11
  resolve(Buffer.concat(chunks));
11
12
  });
12
13
  });
13
- const url = new URL(req.url, `http://${req.headers.host}`);
14
+ const headers = Object.fromEntries(Object.entries(req.headers ?? {})
15
+ .map(([key, value]) => [
16
+ key.toLowerCase(),
17
+ Array.isArray(value) ? value.join(",") : value,
18
+ ])
19
+ .filter(([key]) => key));
20
+ const url = new URL(req.url, `http://${extractHostFromHeaders(headers)}`);
14
21
  const query = Object.fromEntries(url.searchParams.entries());
15
22
  return {
16
23
  type: "core",
17
24
  method: req.method ?? "GET",
18
25
  rawPath: url.pathname,
19
- url: url.pathname + url.search,
26
+ url: url.href,
20
27
  body,
21
- headers: Object.fromEntries(Object.entries(req.headers ?? {})
22
- .map(([key, value]) => [
23
- key.toLowerCase(),
24
- Array.isArray(value) ? value.join(",") : value,
25
- ])
26
- .filter(([key]) => key)),
28
+ headers,
27
29
  remoteAddress: req.headers["x-forwarded-for"] ??
28
30
  req.socket.remoteAddress ??
29
31
  "::1",