@opennextjs/aws 3.1.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 (175) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +94 -0
  3. package/dist/adapters/binary.d.ts +1 -0
  4. package/dist/adapters/binary.js +63 -0
  5. package/dist/adapters/cache.d.ts +96 -0
  6. package/dist/adapters/cache.js +278 -0
  7. package/dist/adapters/config/index.d.ts +27 -0
  8. package/dist/adapters/config/index.js +21 -0
  9. package/dist/adapters/config/util.d.ts +28 -0
  10. package/dist/adapters/config/util.js +84 -0
  11. package/dist/adapters/dynamo-provider.d.ts +8 -0
  12. package/dist/adapters/dynamo-provider.js +42 -0
  13. package/dist/adapters/edge-adapter.d.ts +8 -0
  14. package/dist/adapters/edge-adapter.js +54 -0
  15. package/dist/adapters/image-optimization-adapter.d.ts +4 -0
  16. package/dist/adapters/image-optimization-adapter.js +193 -0
  17. package/dist/adapters/logger.d.ts +13 -0
  18. package/dist/adapters/logger.js +51 -0
  19. package/dist/adapters/middleware.d.ts +5 -0
  20. package/dist/adapters/middleware.js +84 -0
  21. package/dist/adapters/plugins/14.1/util.d.ts +1 -0
  22. package/dist/adapters/plugins/14.1/util.js +29 -0
  23. package/dist/adapters/plugins/image-optimization/image-optimization.d.ts +11 -0
  24. package/dist/adapters/plugins/image-optimization/image-optimization.js +14 -0
  25. package/dist/adapters/plugins/image-optimization/image-optimization.replacement.d.ts +11 -0
  26. package/dist/adapters/plugins/image-optimization/image-optimization.replacement.js +22 -0
  27. package/dist/adapters/plugins/without-routing/requestHandler.d.ts +2 -0
  28. package/dist/adapters/plugins/without-routing/requestHandler.js +10 -0
  29. package/dist/adapters/revalidate.d.ts +9 -0
  30. package/dist/adapters/revalidate.js +71 -0
  31. package/dist/adapters/server-adapter.d.ts +4 -0
  32. package/dist/adapters/server-adapter.js +27 -0
  33. package/dist/adapters/util.d.ts +10 -0
  34. package/dist/adapters/util.js +28 -0
  35. package/dist/adapters/warmer-function.d.ts +12 -0
  36. package/dist/adapters/warmer-function.js +80 -0
  37. package/dist/build/bundleNextServer.d.ts +1 -0
  38. package/dist/build/bundleNextServer.js +87 -0
  39. package/dist/build/compileConfig.d.ts +3 -0
  40. package/dist/build/compileConfig.js +64 -0
  41. package/dist/build/copyTracedFiles.d.ts +1 -0
  42. package/dist/build/copyTracedFiles.js +207 -0
  43. package/dist/build/createServerBundle.d.ts +3 -0
  44. package/dist/build/createServerBundle.js +228 -0
  45. package/dist/build/edge/createEdgeBundle.d.ts +20 -0
  46. package/dist/build/edge/createEdgeBundle.js +149 -0
  47. package/dist/build/generateOutput.d.ts +2 -0
  48. package/dist/build/generateOutput.js +239 -0
  49. package/dist/build/helper.d.ts +33 -0
  50. package/dist/build/helper.js +193 -0
  51. package/dist/build/patch/patchedAsyncStorage.d.ts +5 -0
  52. package/dist/build/patch/patchedAsyncStorage.js +16 -0
  53. package/dist/build/validateConfig.d.ts +2 -0
  54. package/dist/build/validateConfig.js +76 -0
  55. package/dist/build.d.ts +7 -0
  56. package/dist/build.js +533 -0
  57. package/dist/cache/incremental/s3-lite.d.ts +3 -0
  58. package/dist/cache/incremental/s3-lite.js +72 -0
  59. package/dist/cache/incremental/s3.d.ts +3 -0
  60. package/dist/cache/incremental/s3.js +44 -0
  61. package/dist/cache/incremental/types.d.ts +34 -0
  62. package/dist/cache/incremental/types.js +1 -0
  63. package/dist/cache/next-types.d.ts +62 -0
  64. package/dist/cache/next-types.js +1 -0
  65. package/dist/cache/tag/constants.d.ts +2 -0
  66. package/dist/cache/tag/constants.js +17 -0
  67. package/dist/cache/tag/dynamodb-lite.d.ts +3 -0
  68. package/dist/cache/tag/dynamodb-lite.js +174 -0
  69. package/dist/cache/tag/dynamodb.d.ts +3 -0
  70. package/dist/cache/tag/dynamodb.js +129 -0
  71. package/dist/cache/tag/types.d.ts +11 -0
  72. package/dist/cache/tag/types.js +1 -0
  73. package/dist/converters/aws-apigw-v1.d.ts +3 -0
  74. package/dist/converters/aws-apigw-v1.js +89 -0
  75. package/dist/converters/aws-apigw-v2.d.ts +3 -0
  76. package/dist/converters/aws-apigw-v2.js +99 -0
  77. package/dist/converters/aws-cloudfront.d.ts +3 -0
  78. package/dist/converters/aws-cloudfront.js +170 -0
  79. package/dist/converters/dummy.d.ts +7 -0
  80. package/dist/converters/dummy.js +16 -0
  81. package/dist/converters/edge.d.ts +6 -0
  82. package/dist/converters/edge.js +88 -0
  83. package/dist/converters/node.d.ts +3 -0
  84. package/dist/converters/node.js +45 -0
  85. package/dist/converters/sqs-revalidate.d.ts +4 -0
  86. package/dist/converters/sqs-revalidate.js +22 -0
  87. package/dist/converters/utils.d.ts +1 -0
  88. package/dist/converters/utils.js +9 -0
  89. package/dist/core/createGenericHandler.d.ts +11 -0
  90. package/dist/core/createGenericHandler.js +16 -0
  91. package/dist/core/createMainHandler.d.ts +18 -0
  92. package/dist/core/createMainHandler.js +24 -0
  93. package/dist/core/edgeFunctionHandler.d.ts +54 -0
  94. package/dist/core/edgeFunctionHandler.js +21 -0
  95. package/dist/core/patchAsyncStorage.d.ts +1 -0
  96. package/dist/core/patchAsyncStorage.js +16 -0
  97. package/dist/core/requestHandler.d.ts +3 -0
  98. package/dist/core/requestHandler.js +162 -0
  99. package/dist/core/require-hooks.d.ts +3 -0
  100. package/dist/core/require-hooks.js +102 -0
  101. package/dist/core/resolve.d.ts +31 -0
  102. package/dist/core/resolve.js +81 -0
  103. package/dist/core/routing/cacheInterceptor.d.ts +2 -0
  104. package/dist/core/routing/cacheInterceptor.js +162 -0
  105. package/dist/core/routing/i18n/accept-header.d.ts +1 -0
  106. package/dist/core/routing/i18n/accept-header.js +101 -0
  107. package/dist/core/routing/i18n/index.d.ts +2 -0
  108. package/dist/core/routing/i18n/index.js +36 -0
  109. package/dist/core/routing/matcher.d.ts +25 -0
  110. package/dist/core/routing/matcher.js +294 -0
  111. package/dist/core/routing/middleware.d.ts +7 -0
  112. package/dist/core/routing/middleware.js +167 -0
  113. package/dist/core/routing/util.d.ts +105 -0
  114. package/dist/core/routing/util.js +415 -0
  115. package/dist/core/routingHandler.d.ts +8 -0
  116. package/dist/core/routingHandler.js +127 -0
  117. package/dist/core/util.d.ts +5 -0
  118. package/dist/core/util.js +84 -0
  119. package/dist/helpers/withCloudflare.d.ts +65 -0
  120. package/dist/helpers/withCloudflare.js +58 -0
  121. package/dist/helpers/withSST.d.ts +54 -0
  122. package/dist/helpers/withSST.js +28 -0
  123. package/dist/http/index.d.ts +2 -0
  124. package/dist/http/index.js +4 -0
  125. package/dist/http/openNextResponse.d.ts +78 -0
  126. package/dist/http/openNextResponse.js +281 -0
  127. package/dist/http/request.d.ts +12 -0
  128. package/dist/http/request.js +35 -0
  129. package/dist/http/util.d.ts +4 -0
  130. package/dist/http/util.js +34 -0
  131. package/dist/index.d.ts +2 -0
  132. package/dist/index.js +37 -0
  133. package/dist/logger.d.ts +12 -0
  134. package/dist/logger.js +13 -0
  135. package/dist/minimize-js.d.ts +1 -0
  136. package/dist/minimize-js.js +101 -0
  137. package/dist/overrides/imageLoader/host.d.ts +3 -0
  138. package/dist/overrides/imageLoader/host.js +29 -0
  139. package/dist/overrides/imageLoader/s3.d.ts +3 -0
  140. package/dist/overrides/imageLoader/s3.js +33 -0
  141. package/dist/plugins/edge.d.ts +15 -0
  142. package/dist/plugins/edge.js +157 -0
  143. package/dist/plugins/replacement.d.ts +42 -0
  144. package/dist/plugins/replacement.js +74 -0
  145. package/dist/plugins/resolve.d.ts +18 -0
  146. package/dist/plugins/resolve.js +45 -0
  147. package/dist/queue/sqs-lite.d.ts +3 -0
  148. package/dist/queue/sqs-lite.js +52 -0
  149. package/dist/queue/sqs.d.ts +3 -0
  150. package/dist/queue/sqs.js +20 -0
  151. package/dist/queue/types.d.ts +12 -0
  152. package/dist/queue/types.js +1 -0
  153. package/dist/types/aws-lambda.d.ts +22 -0
  154. package/dist/types/aws-lambda.js +1 -0
  155. package/dist/types/next-types.d.ts +155 -0
  156. package/dist/types/next-types.js +2 -0
  157. package/dist/types/open-next.d.ts +303 -0
  158. package/dist/types/open-next.js +1 -0
  159. package/dist/utils/error.d.ts +23 -0
  160. package/dist/utils/error.js +32 -0
  161. package/dist/utils/fetch.d.ts +2 -0
  162. package/dist/utils/fetch.js +23 -0
  163. package/dist/utils/promise.d.ts +18 -0
  164. package/dist/utils/promise.js +48 -0
  165. package/dist/utils/stream.d.ts +5 -0
  166. package/dist/utils/stream.js +30 -0
  167. package/dist/wrappers/aws-lambda-streaming.d.ts +7 -0
  168. package/dist/wrappers/aws-lambda-streaming.js +88 -0
  169. package/dist/wrappers/aws-lambda.d.ts +7 -0
  170. package/dist/wrappers/aws-lambda.js +39 -0
  171. package/dist/wrappers/cloudflare.d.ts +11 -0
  172. package/dist/wrappers/cloudflare.js +20 -0
  173. package/dist/wrappers/node.d.ts +7 -0
  174. package/dist/wrappers/node.js +57 -0
  175. package/package.json +66 -0
@@ -0,0 +1,415 @@
1
+ import crypto from "node:crypto";
2
+ import { Readable } from "node:stream";
3
+ import { BuildId, HtmlPages } from "../../adapters/config/index.js";
4
+ import { OpenNextNodeResponse } from "../../http/openNextResponse.js";
5
+ import { parseHeaders } from "../../http/util.js";
6
+ import { isBinaryContentType } from "../../adapters/binary.js";
7
+ import { debug, error } from "../../adapters/logger.js";
8
+ /**
9
+ *
10
+ * @__PURE__
11
+ */
12
+ export function isExternal(url, host) {
13
+ if (!url)
14
+ return false;
15
+ const pattern = /^https?:\/\//;
16
+ if (host) {
17
+ return pattern.test(url) && !url.includes(host);
18
+ }
19
+ return pattern.test(url);
20
+ }
21
+ export function convertFromQueryString(query) {
22
+ if (query === "")
23
+ return {};
24
+ const queryParts = query.split("&");
25
+ return queryParts.reduce((acc, part) => {
26
+ const [key, value] = part.split("=");
27
+ return { ...acc, [key]: value };
28
+ }, {});
29
+ }
30
+ /**
31
+ *
32
+ * @__PURE__
33
+ */
34
+ export function getUrlParts(url, isExternal) {
35
+ if (!isExternal) {
36
+ const regex = /\/([^?]*)\??(.*)/;
37
+ const match = url.match(regex);
38
+ return {
39
+ hostname: "",
40
+ pathname: match?.[1] ?? url,
41
+ protocol: "",
42
+ queryString: match?.[2] ?? "",
43
+ };
44
+ }
45
+ const regex = /^(https?:)\/\/?([^\/\s]+)(\/[^?]*)?(\?.*)?/;
46
+ const match = url.match(regex);
47
+ if (!match) {
48
+ throw new Error(`Invalid external URL: ${url}`);
49
+ }
50
+ return {
51
+ protocol: match[1] ?? "https:",
52
+ hostname: match[2],
53
+ pathname: match[3],
54
+ queryString: match[4]?.slice(1) ?? "",
55
+ };
56
+ }
57
+ /**
58
+ *
59
+ * @__PURE__
60
+ */
61
+ export function convertRes(res) {
62
+ // Format Next.js response to Lambda response
63
+ const statusCode = res.statusCode || 200;
64
+ // When using HEAD requests, it seems that flushHeaders is not called, not sure why
65
+ // Probably some kind of race condition
66
+ const headers = parseHeaders(res.getFixedHeaders());
67
+ const isBase64Encoded = isBinaryContentType(headers["content-type"]) ||
68
+ !!headers["content-encoding"];
69
+ // We cannot convert the OpenNextNodeResponse to a ReadableStream directly
70
+ // You can look in the `aws-lambda.ts` file for some context
71
+ const body = Readable.toWeb(Readable.from(res.getBody()));
72
+ return {
73
+ type: "core",
74
+ statusCode,
75
+ headers,
76
+ body,
77
+ isBase64Encoded,
78
+ };
79
+ }
80
+ /**
81
+ * Make sure that multi-value query parameters are transformed to
82
+ * ?key=value1&key=value2&... so that Next converts those parameters
83
+ * to an array when reading the query parameters
84
+ * @__PURE__
85
+ */
86
+ export function convertToQueryString(query) {
87
+ const urlQuery = new URLSearchParams();
88
+ Object.entries(query).forEach(([key, value]) => {
89
+ if (Array.isArray(value)) {
90
+ value.forEach((entry) => urlQuery.append(key, entry));
91
+ }
92
+ else {
93
+ urlQuery.append(key, value);
94
+ }
95
+ });
96
+ const queryString = urlQuery.toString();
97
+ return queryString ? `?${queryString}` : "";
98
+ }
99
+ /**
100
+ * Given a raw query string, returns a record with key value-array pairs
101
+ * similar to how multiValueQueryStringParameters are structured
102
+ * @__PURE__
103
+ */
104
+ export function convertToQuery(querystring) {
105
+ const query = new URLSearchParams(querystring);
106
+ const queryObject = {};
107
+ for (const key of query.keys()) {
108
+ const queries = query.getAll(key);
109
+ queryObject[key] = queries.length > 1 ? queries : queries[0];
110
+ }
111
+ return queryObject;
112
+ }
113
+ /**
114
+ *
115
+ * @__PURE__
116
+ */
117
+ export function getMiddlewareMatch(middlewareManifest) {
118
+ const rootMiddleware = middlewareManifest.middleware["/"];
119
+ if (!rootMiddleware?.matchers)
120
+ return [];
121
+ return rootMiddleware.matchers.map(({ regexp }) => new RegExp(regexp));
122
+ }
123
+ /**
124
+ *
125
+ * @__PURE__
126
+ */
127
+ export function escapeRegex(str) {
128
+ let path = str.replace(/\(\.\)/g, "_µ1_");
129
+ path = path.replace(/\(\.{2}\)/g, "_µ2_");
130
+ path = path.replace(/\(\.{3}\)/g, "_µ3_");
131
+ return path;
132
+ }
133
+ /**
134
+ *
135
+ * @__PURE__
136
+ */
137
+ export function unescapeRegex(str) {
138
+ let path = str.replace(/_µ1_/g, "(.)");
139
+ path = path.replace(/_µ2_/g, "(..)");
140
+ path = path.replace(/_µ3_/g, "(...)");
141
+ return path;
142
+ }
143
+ /**
144
+ *
145
+ * @__PURE__
146
+ */
147
+ function filterHeadersForProxy(headers) {
148
+ const filteredHeaders = {};
149
+ const disallowedHeaders = [
150
+ "host",
151
+ "connection",
152
+ "via",
153
+ "x-cache",
154
+ "transfer-encoding",
155
+ "content-encoding",
156
+ "content-length",
157
+ ];
158
+ Object.entries(headers).forEach(([key, value]) => {
159
+ const lowerKey = key.toLowerCase();
160
+ if (disallowedHeaders.includes(lowerKey) || lowerKey.startsWith("x-amz"))
161
+ return;
162
+ else {
163
+ filteredHeaders[key] = value?.toString() ?? "";
164
+ }
165
+ });
166
+ return filteredHeaders;
167
+ }
168
+ /**
169
+ * @__PURE__
170
+ */
171
+ export function convertBodyToReadableStream(method, body) {
172
+ if (method === "GET" || method === "HEAD")
173
+ return undefined;
174
+ if (!body)
175
+ return undefined;
176
+ const readable = new ReadableStream({
177
+ start(controller) {
178
+ controller.enqueue(body);
179
+ controller.close();
180
+ },
181
+ });
182
+ return readable;
183
+ }
184
+ /**
185
+ *
186
+ * @__PURE__
187
+ */
188
+ export async function proxyRequest(internalEvent, res) {
189
+ const { url, headers, method, body } = internalEvent;
190
+ const request = await import("node:https").then((m) => m.request);
191
+ debug("proxyRequest", url);
192
+ await new Promise((resolve, reject) => {
193
+ const filteredHeaders = filterHeadersForProxy(headers);
194
+ debug("filteredHeaders", filteredHeaders);
195
+ const req = request(url, {
196
+ headers: filteredHeaders,
197
+ method,
198
+ rejectUnauthorized: false,
199
+ }, (_res) => {
200
+ res.writeHead(_res.statusCode ?? 200, filterHeadersForProxy(_res.headers));
201
+ if (_res.headers["content-encoding"] === "br") {
202
+ _res.pipe(require("node:zlib").createBrotliDecompress()).pipe(res);
203
+ }
204
+ else if (_res.headers["content-encoding"] === "gzip") {
205
+ _res.pipe(require("node:zlib").createGunzip()).pipe(res);
206
+ }
207
+ else {
208
+ _res.pipe(res);
209
+ }
210
+ _res.on("error", (e) => {
211
+ error("proxyRequest error", e);
212
+ res.end();
213
+ reject(e);
214
+ });
215
+ _res.on("end", () => {
216
+ resolve();
217
+ });
218
+ });
219
+ if (body && method !== "GET" && method !== "HEAD") {
220
+ req.write(body);
221
+ }
222
+ req.end();
223
+ });
224
+ // console.log("result", result);
225
+ // res.writeHead(result.status, resHeaders);
226
+ // res.end(await result.text());
227
+ }
228
+ var CommonHeaders;
229
+ (function (CommonHeaders) {
230
+ CommonHeaders["CACHE_CONTROL"] = "cache-control";
231
+ CommonHeaders["NEXT_CACHE"] = "x-nextjs-cache";
232
+ })(CommonHeaders || (CommonHeaders = {}));
233
+ /**
234
+ *
235
+ * @__PURE__
236
+ */
237
+ export function fixCacheHeaderForHtmlPages(rawPath, headers) {
238
+ // We don't want to cache error pages
239
+ if (rawPath === "/404" || rawPath === "/500") {
240
+ headers[CommonHeaders.CACHE_CONTROL] =
241
+ "private, no-cache, no-store, max-age=0, must-revalidate";
242
+ return;
243
+ }
244
+ // WORKAROUND: `NextServer` does not set cache headers for HTML pages — https://github.com/serverless-stack/open-next#workaround-nextserver-does-not-set-cache-headers-for-html-pages
245
+ if (HtmlPages.includes(rawPath)) {
246
+ headers[CommonHeaders.CACHE_CONTROL] =
247
+ "public, max-age=0, s-maxage=31536000, must-revalidate";
248
+ }
249
+ }
250
+ /**
251
+ *
252
+ * @__PURE__
253
+ */
254
+ export function fixSWRCacheHeader(headers) {
255
+ // WORKAROUND: `NextServer` does not set correct SWR cache headers — https://github.com/sst/open-next#workaround-nextserver-does-not-set-correct-swr-cache-headers
256
+ let cacheControl = headers[CommonHeaders.CACHE_CONTROL];
257
+ if (!cacheControl)
258
+ return;
259
+ if (Array.isArray(cacheControl)) {
260
+ cacheControl = cacheControl.join(",");
261
+ }
262
+ if (typeof cacheControl !== "string")
263
+ return;
264
+ headers[CommonHeaders.CACHE_CONTROL] = cacheControl.replace(/\bstale-while-revalidate(?!=)/, "stale-while-revalidate=2592000");
265
+ }
266
+ /**
267
+ *
268
+ * @__PURE__
269
+ */
270
+ export function addOpenNextHeader(headers) {
271
+ headers["X-OpenNext"] = "1";
272
+ if (globalThis.openNextDebug) {
273
+ headers["X-OpenNext-Version"] = globalThis.openNextVersion;
274
+ headers["X-OpenNext-RequestId"] = globalThis.__als.getStore()?.requestId;
275
+ }
276
+ }
277
+ /**
278
+ *
279
+ * @__PURE__
280
+ */
281
+ export async function revalidateIfRequired(host, rawPath, headers, req) {
282
+ if (headers[CommonHeaders.NEXT_CACHE] === "STALE") {
283
+ // If the URL is rewritten, revalidation needs to be done on the rewritten URL.
284
+ // - Link to Next.js doc: https://nextjs.org/docs/pages/building-your-application/data-fetching/incremental-static-regeneration#on-demand-revalidation
285
+ // - Link to NextInternalRequestMeta: https://github.com/vercel/next.js/blob/57ab2818b93627e91c937a130fb56a36c41629c3/packages/next/src/server/request-meta.ts#L11
286
+ // @ts-ignore
287
+ const internalMeta = req?.[Symbol.for("NextInternalRequestMeta")];
288
+ // When using Pages Router, two requests will be received:
289
+ // 1. one for the page: /foo
290
+ // 2. one for the json data: /_next/data/BUILD_ID/foo.json
291
+ // The rewritten url is correct for 1, but that for the second request
292
+ // does not include the "/_next/data/" prefix. Need to add it.
293
+ const revalidateUrl = internalMeta?._nextDidRewrite
294
+ ? rawPath.startsWith("/_next/data/")
295
+ ? `/_next/data/${BuildId}${internalMeta?._nextRewroteUrl}.json`
296
+ : internalMeta?._nextRewroteUrl
297
+ : rawPath;
298
+ // We need to pass etag to the revalidation queue to try to bypass the default 5 min deduplication window.
299
+ // https://docs.aws.amazon.com/AWSSimpleQueueService/latest/SQSDeveloperGuide/using-messagededuplicationid-property.html
300
+ // If you need to have a revalidation happen more frequently than 5 minutes,
301
+ // your page will need to have a different etag to bypass the deduplication window.
302
+ // If data has the same etag during these 5 min dedup window, it will be deduplicated and not revalidated.
303
+ try {
304
+ const hash = (str) => crypto.createHash("md5").update(str).digest("hex");
305
+ const requestId = globalThis.__als.getStore()?.requestId ?? "";
306
+ const lastModified = globalThis.lastModified[requestId] > 0
307
+ ? globalThis.lastModified[requestId]
308
+ : "";
309
+ // For some weird cases, lastModified is not set, haven't been able to figure out yet why
310
+ // For those cases we add the etag to the deduplication id, it might help
311
+ const etag = headers["etag"] ?? headers["ETag"] ?? "";
312
+ await globalThis.queue.send({
313
+ MessageBody: { host, url: revalidateUrl },
314
+ MessageDeduplicationId: hash(`${rawPath}-${lastModified}-${etag}`),
315
+ MessageGroupId: generateMessageGroupId(rawPath),
316
+ });
317
+ }
318
+ catch (e) {
319
+ error(`Failed to revalidate stale page ${rawPath}`, e);
320
+ }
321
+ }
322
+ }
323
+ // Since we're using a FIFO queue, every messageGroupId is treated sequentially
324
+ // This could cause a backlog of messages in the queue if there is too much page to
325
+ // revalidate at once. To avoid this, we generate a random messageGroupId for each
326
+ // revalidation request.
327
+ // We can't just use a random string because we need to ensure that the same rawPath
328
+ // will always have the same messageGroupId.
329
+ // https://stackoverflow.com/questions/521295/seeding-the-random-number-generator-in-javascript#answer-47593316
330
+ export function generateMessageGroupId(rawPath) {
331
+ let a = cyrb128(rawPath);
332
+ // We use mulberry32 to generate a random int between 0 and MAX_REVALIDATE_CONCURRENCY
333
+ var t = (a += 0x6d2b79f5);
334
+ t = Math.imul(t ^ (t >>> 15), t | 1);
335
+ t ^= t + Math.imul(t ^ (t >>> 7), t | 61);
336
+ const randomFloat = ((t ^ (t >>> 14)) >>> 0) / 4294967296;
337
+ // This will generate a random int between 0 and MAX_REVALIDATE_CONCURRENCY
338
+ // This means that we could have 1000 revalidate request at the same time
339
+ const maxConcurrency = parseInt(process.env.MAX_REVALIDATE_CONCURRENCY ?? "10");
340
+ const randomInt = Math.floor(randomFloat * maxConcurrency);
341
+ return `revalidate-${randomInt}`;
342
+ }
343
+ // Used to generate a hash int from a string
344
+ function cyrb128(str) {
345
+ let h1 = 1779033703, h2 = 3144134277, h3 = 1013904242, h4 = 2773480762;
346
+ for (let i = 0, k; i < str.length; i++) {
347
+ k = str.charCodeAt(i);
348
+ h1 = h2 ^ Math.imul(h1 ^ k, 597399067);
349
+ h2 = h3 ^ Math.imul(h2 ^ k, 2869860233);
350
+ h3 = h4 ^ Math.imul(h3 ^ k, 951274213);
351
+ h4 = h1 ^ Math.imul(h4 ^ k, 2716044179);
352
+ }
353
+ h1 = Math.imul(h3 ^ (h1 >>> 18), 597399067);
354
+ h2 = Math.imul(h4 ^ (h2 >>> 22), 2869860233);
355
+ h3 = Math.imul(h1 ^ (h3 >>> 17), 951274213);
356
+ h4 = Math.imul(h2 ^ (h4 >>> 19), 2716044179);
357
+ (h1 ^= h2 ^ h3 ^ h4), (h2 ^= h1), (h3 ^= h1), (h4 ^= h1);
358
+ return h1 >>> 0;
359
+ }
360
+ /**
361
+ *
362
+ * @__PURE__
363
+ */
364
+ export function fixISRHeaders(headers) {
365
+ if (headers[CommonHeaders.NEXT_CACHE] === "REVALIDATED") {
366
+ headers[CommonHeaders.CACHE_CONTROL] =
367
+ "private, no-cache, no-store, max-age=0, must-revalidate";
368
+ return;
369
+ }
370
+ const requestId = globalThis.__als.getStore()?.requestId ?? "";
371
+ const _lastModified = globalThis.lastModified[requestId] ?? 0;
372
+ if (headers[CommonHeaders.NEXT_CACHE] === "HIT" && _lastModified > 0) {
373
+ // calculate age
374
+ const age = Math.round((Date.now() - _lastModified) / 1000);
375
+ // extract s-maxage from cache-control
376
+ const regex = /s-maxage=(\d+)/;
377
+ const cacheControl = headers[CommonHeaders.CACHE_CONTROL];
378
+ debug("cache-control", cacheControl, globalThis.lastModified, Date.now());
379
+ if (typeof cacheControl !== "string")
380
+ return;
381
+ const match = cacheControl.match(regex);
382
+ const sMaxAge = match ? parseInt(match[1]) : undefined;
383
+ // 31536000 is the default s-maxage value for SSG pages
384
+ if (sMaxAge && sMaxAge !== 31536000) {
385
+ const remainingTtl = Math.max(sMaxAge - age, 1);
386
+ headers[CommonHeaders.CACHE_CONTROL] = `s-maxage=${remainingTtl}, stale-while-revalidate=2592000`;
387
+ }
388
+ }
389
+ if (headers[CommonHeaders.NEXT_CACHE] !== "STALE")
390
+ return;
391
+ // If the cache is stale, we revalidate in the background
392
+ // In order for CloudFront SWR to work, we set the stale-while-revalidate value to 2 seconds
393
+ // This will cause CloudFront to cache the stale data for a short period of time while we revalidate in the background
394
+ // Once the revalidation is complete, CloudFront will serve the fresh data
395
+ headers[CommonHeaders.CACHE_CONTROL] =
396
+ "s-maxage=2, stale-while-revalidate=2592000";
397
+ }
398
+ /**
399
+ *
400
+ * @param internalEvent
401
+ * @param headers
402
+ * @param responseStream
403
+ * @returns
404
+ * @__PURE__
405
+ */
406
+ export function createServerResponse(internalEvent, headers, responseStream) {
407
+ return new OpenNextNodeResponse((_headers) => {
408
+ fixCacheHeaderForHtmlPages(internalEvent.rawPath, _headers);
409
+ fixSWRCacheHeader(_headers);
410
+ addOpenNextHeader(_headers);
411
+ fixISRHeaders(_headers);
412
+ }, async (_headers) => {
413
+ await revalidateIfRequired(internalEvent.headers.host, internalEvent.rawPath, _headers);
414
+ }, responseStream, headers);
415
+ }
@@ -0,0 +1,8 @@
1
+ import { InternalEvent, InternalResult, Origin } from "../types/open-next";
2
+ export interface MiddlewareOutputEvent {
3
+ internalEvent: InternalEvent;
4
+ isExternalRewrite: boolean;
5
+ origin: Origin | false;
6
+ isISR: boolean;
7
+ }
8
+ export default function routingHandler(event: InternalEvent): Promise<InternalResult | MiddlewareOutputEvent>;
@@ -0,0 +1,127 @@
1
+ import { BuildId, ConfigHeaders, PrerenderManifest, RoutesManifest, } from "../adapters/config/index";
2
+ import { debug } from "../adapters/logger";
3
+ import { cacheInterceptor } from "./routing/cacheInterceptor";
4
+ import { addNextConfigHeaders, fixDataPage, handleFallbackFalse, handleRedirects, handleRewrites, } from "./routing/matcher";
5
+ import { handleMiddleware } from "./routing/middleware";
6
+ // Add the locale prefix to the regex so we correctly match the rawPath
7
+ const optionalLocalePrefixRegex = !!RoutesManifest.locales.length
8
+ ? `^/(?:${RoutesManifest.locales.map((locale) => `${locale}/?`).join("|")})?`
9
+ : "^/";
10
+ // Add the basepath prefix to the regex so we correctly match the rawPath
11
+ const optionalBasepathPrefixRegex = !!RoutesManifest.basePath
12
+ ? `^${RoutesManifest.basePath}/?`
13
+ : "^/";
14
+ // Add the basePath prefix to the api routes
15
+ const apiPrefix = !!RoutesManifest.basePath
16
+ ? `${RoutesManifest.basePath}/api`
17
+ : "/api";
18
+ const staticRegexp = RoutesManifest.routes.static.map((route) => new RegExp(route.regex
19
+ .replace("^/", optionalLocalePrefixRegex)
20
+ .replace("^/", optionalBasepathPrefixRegex)));
21
+ const dynamicRegexp = RoutesManifest.routes.dynamic.map((route) => new RegExp(route.regex
22
+ .replace("^/", optionalLocalePrefixRegex)
23
+ .replace("^/", optionalBasepathPrefixRegex)));
24
+ function applyMiddlewareHeaders(eventHeaders, middlewareHeaders, setPrefix = true) {
25
+ Object.entries(middlewareHeaders).forEach(([key, value]) => {
26
+ if (value) {
27
+ eventHeaders[`${setPrefix ? "x-middleware-response-" : ""}${key}`] =
28
+ Array.isArray(value) ? value.join(",") : value;
29
+ }
30
+ });
31
+ }
32
+ export default async function routingHandler(event) {
33
+ const nextHeaders = addNextConfigHeaders(event, ConfigHeaders) ?? {};
34
+ let internalEvent = fixDataPage(event, BuildId);
35
+ if ("statusCode" in internalEvent) {
36
+ return internalEvent;
37
+ }
38
+ const redirect = handleRedirects(internalEvent, RoutesManifest.redirects);
39
+ if (redirect) {
40
+ debug("redirect", redirect);
41
+ return redirect;
42
+ }
43
+ const middleware = await handleMiddleware(internalEvent);
44
+ let middlewareResponseHeaders = {};
45
+ if ("statusCode" in middleware) {
46
+ return middleware;
47
+ }
48
+ else {
49
+ middlewareResponseHeaders = middleware.responseHeaders || {};
50
+ internalEvent = middleware;
51
+ }
52
+ // At this point internalEvent is an InternalEvent or a MiddlewareOutputEvent
53
+ let isExternalRewrite = middleware.externalRewrite ?? false;
54
+ if (!isExternalRewrite) {
55
+ // First rewrite to be applied
56
+ const beforeRewrites = handleRewrites(internalEvent, RoutesManifest.rewrites.beforeFiles);
57
+ internalEvent = beforeRewrites.internalEvent;
58
+ isExternalRewrite = beforeRewrites.isExternalRewrite;
59
+ }
60
+ const isStaticRoute = !isExternalRewrite &&
61
+ staticRegexp.some((route) => route.test(internalEvent.rawPath));
62
+ if (!isStaticRoute && !isExternalRewrite) {
63
+ // Second rewrite to be applied
64
+ const afterRewrites = handleRewrites(internalEvent, RoutesManifest.rewrites.afterFiles);
65
+ internalEvent = afterRewrites.internalEvent;
66
+ isExternalRewrite = afterRewrites.isExternalRewrite;
67
+ }
68
+ // We want to run this just before the dynamic route check
69
+ const { event: fallbackEvent, isISR } = handleFallbackFalse(internalEvent, PrerenderManifest);
70
+ internalEvent = fallbackEvent;
71
+ const isDynamicRoute = !isExternalRewrite &&
72
+ dynamicRegexp.some((route) => route.test(internalEvent.rawPath));
73
+ if (!isDynamicRoute && !isStaticRoute && !isExternalRewrite) {
74
+ // Fallback rewrite to be applied
75
+ const fallbackRewrites = handleRewrites(internalEvent, RoutesManifest.rewrites.fallback);
76
+ internalEvent = fallbackRewrites.internalEvent;
77
+ isExternalRewrite = fallbackRewrites.isExternalRewrite;
78
+ }
79
+ // Api routes are not present in the routes manifest except if they're not behind /api
80
+ // /api even if it's a page route doesn't get generated in the manifest
81
+ // Ideally we would need to properly check api routes here
82
+ const isApiRoute = internalEvent.rawPath === apiPrefix ||
83
+ internalEvent.rawPath.startsWith(`${apiPrefix}/`);
84
+ const isNextImageRoute = internalEvent.rawPath.startsWith("/_next/image");
85
+ const isRouteFoundBeforeAllRewrites = isStaticRoute || isDynamicRoute || isExternalRewrite;
86
+ // If we still haven't found a route, we show the 404 page
87
+ // We need to ensure that rewrites are applied before showing the 404 page
88
+ if (!isRouteFoundBeforeAllRewrites &&
89
+ !isApiRoute &&
90
+ !isNextImageRoute &&
91
+ // We need to check again once all rewrites have been applied
92
+ !staticRegexp.some((route) => route.test(internalEvent.rawPath)) &&
93
+ !dynamicRegexp.some((route) => route.test(internalEvent.rawPath))) {
94
+ internalEvent = {
95
+ ...internalEvent,
96
+ rawPath: "/404",
97
+ url: "/404",
98
+ headers: {
99
+ ...internalEvent.headers,
100
+ "x-middleware-response-cache-control": "private, no-cache, no-store, max-age=0, must-revalidate",
101
+ },
102
+ };
103
+ }
104
+ if (globalThis.openNextConfig.dangerous?.enableCacheInterception &&
105
+ !("statusCode" in internalEvent)) {
106
+ debug("Cache interception enabled");
107
+ internalEvent = await cacheInterceptor(internalEvent);
108
+ if ("statusCode" in internalEvent) {
109
+ applyMiddlewareHeaders(internalEvent.headers, {
110
+ ...middlewareResponseHeaders,
111
+ ...nextHeaders,
112
+ }, false);
113
+ return internalEvent;
114
+ }
115
+ }
116
+ // We apply the headers from the middleware response last
117
+ applyMiddlewareHeaders(internalEvent.headers, {
118
+ ...middlewareResponseHeaders,
119
+ ...nextHeaders,
120
+ });
121
+ return {
122
+ internalEvent,
123
+ isExternalRewrite,
124
+ origin: false,
125
+ isISR,
126
+ };
127
+ }
@@ -0,0 +1,5 @@
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;
5
+ export declare function setNextjsPrebundledReact(rawPath: string): void;
@@ -0,0 +1,84 @@
1
+ import fs from "node:fs";
2
+ import path from "node:path";
3
+ import { AppPathsManifestKeys, NextConfig, RoutesManifest, } from "../adapters/config/index.js";
4
+ // @ts-ignore
5
+ import NextServer from "next/dist/server/next-server.js";
6
+ import { debug } from "../adapters/logger.js";
7
+ import { applyOverride as applyNextjsRequireHooksOverride, overrideHooks as overrideNextjsRequireHooks, } from "./require-hooks.js";
8
+ // WORKAROUND: Set `__NEXT_PRIVATE_PREBUNDLED_REACT` to use prebundled React — https://github.com/serverless-stack/open-next#workaround-set-__next_private_prebundled_react-to-use-prebundled-react
9
+ // Step 1: Need to override the require hooks for React before Next.js server
10
+ // overrides them with prebundled ones in the case of app dir
11
+ // Step 2: Import Next.js server
12
+ // Step 3: Apply the override after Next.js server is imported since the
13
+ // override that Next.js does is done at import time
14
+ //#override requireHooks
15
+ overrideNextjsRequireHooks(NextConfig);
16
+ applyNextjsRequireHooksOverride();
17
+ //#endOverride
18
+ const cacheHandlerPath = require.resolve("./cache.cjs");
19
+ // @ts-ignore
20
+ export const requestHandler = new NextServer.default({
21
+ //#override requestHandlerHost
22
+ hostname: "localhost",
23
+ port: 3000,
24
+ //#endOverride
25
+ conf: {
26
+ ...NextConfig,
27
+ // Next.js compression should be disabled because of a bug in the bundled
28
+ // `compression` package — https://github.com/vercel/next.js/issues/11669
29
+ compress: false,
30
+ // By default, Next.js uses local disk to store ISR cache. We will use
31
+ // our own cache handler to store the cache on S3.
32
+ //#override stableIncrementalCache
33
+ cacheHandler: cacheHandlerPath,
34
+ cacheMaxMemorySize: 0,
35
+ //#endOverride
36
+ experimental: {
37
+ ...NextConfig.experimental,
38
+ // This uses the request.headers.host as the URL
39
+ // https://github.com/vercel/next.js/blob/canary/packages/next/src/server/next-server.ts#L1749-L1754
40
+ //#override trustHostHeader
41
+ trustHostHeader: true,
42
+ //#endOverride
43
+ //#override experimentalIncrementalCacheHandler
44
+ incrementalCacheHandlerPath: cacheHandlerPath,
45
+ //#endOverride
46
+ },
47
+ },
48
+ customServer: false,
49
+ dev: false,
50
+ dir: __dirname,
51
+ }).getRequestHandler();
52
+ export function getMiddlewareMatch(middlewareManifest) {
53
+ const rootMiddleware = middlewareManifest.middleware["/"];
54
+ if (!rootMiddleware?.matchers)
55
+ return [];
56
+ return rootMiddleware.matchers.map(({ regexp }) => new RegExp(regexp));
57
+ }
58
+ export function loadMiddlewareManifest(nextDir) {
59
+ const filePath = path.join(nextDir, "server", "middleware-manifest.json");
60
+ const json = fs.readFileSync(filePath, "utf-8");
61
+ return JSON.parse(json);
62
+ }
63
+ //#override setNextjsPrebundledReact
64
+ export function setNextjsPrebundledReact(rawPath) {
65
+ // WORKAROUND: Set `__NEXT_PRIVATE_PREBUNDLED_REACT` to use prebundled React — https://github.com/serverless-stack/open-next#workaround-set-__next_private_prebundled_react-to-use-prebundled-react
66
+ const routes = [
67
+ ...RoutesManifest.routes.static,
68
+ ...RoutesManifest.routes.dynamic,
69
+ ];
70
+ const route = routes.find((route) => new RegExp(route.regex).test(rawPath ?? ""));
71
+ const isApp = AppPathsManifestKeys.includes(route?.page ?? "");
72
+ debug("setNextjsPrebundledReact", { url: rawPath, isApp, route });
73
+ // app routes => use prebundled React
74
+ if (isApp) {
75
+ process.env.__NEXT_PRIVATE_PREBUNDLED_REACT = NextConfig.experimental
76
+ .serverActions
77
+ ? "experimental"
78
+ : "next";
79
+ return;
80
+ }
81
+ // page routes => use node_modules React
82
+ process.env.__NEXT_PRIVATE_PREBUNDLED_REACT = undefined;
83
+ }
84
+ //#endOverride