@opennextjs/aws 3.4.2 → 3.5.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/adapters/cache.d.ts +2 -61
- package/dist/adapters/cache.js +68 -34
- package/dist/adapters/config/index.d.ts +1 -0
- package/dist/adapters/config/index.js +3 -1
- package/dist/adapters/config/util.d.ts +2 -1
- package/dist/adapters/config/util.js +13 -3
- package/dist/adapters/dynamo-provider.js +8 -0
- package/dist/adapters/edge-adapter.js +4 -10
- package/dist/adapters/image-optimization-adapter.d.ts +3 -2
- package/dist/adapters/image-optimization-adapter.js +3 -3
- package/dist/adapters/middleware.js +12 -8
- package/dist/build/compileConfig.js +1 -1
- package/dist/build/compileTagCacheProvider.js +1 -0
- package/dist/build/constant.d.ts +1 -0
- package/dist/build/constant.js +2 -0
- package/dist/build/copyTracedFiles.d.ts +10 -1
- package/dist/build/copyTracedFiles.js +22 -12
- package/dist/build/createAssets.d.ts +3 -1
- package/dist/build/createAssets.js +2 -2
- package/dist/build/createMiddleware.js +18 -5
- package/dist/build/createServerBundle.js +7 -1
- package/dist/build/edge/createEdgeBundle.js +14 -12
- package/dist/build/generateOutput.js +4 -6
- package/dist/build/middleware/buildNodeMiddleware.d.ts +3 -0
- package/dist/build/middleware/buildNodeMiddleware.js +97 -0
- package/dist/core/createMainHandler.js +0 -1
- package/dist/core/edgeFunctionHandler.js +3 -1
- package/dist/core/nodeMiddlewareHandler.d.ts +4 -0
- package/dist/core/nodeMiddlewareHandler.js +25 -0
- package/dist/core/requestHandler.d.ts +3 -2
- package/dist/core/requestHandler.js +58 -25
- package/dist/core/routing/cacheInterceptor.js +7 -6
- package/dist/core/routing/i18n/index.d.ts +29 -1
- package/dist/core/routing/i18n/index.js +98 -4
- package/dist/core/routing/matcher.js +17 -13
- package/dist/core/routing/middleware.js +14 -29
- package/dist/core/routing/queue.d.ts +1 -0
- package/dist/core/routing/queue.js +41 -0
- package/dist/core/routing/util.d.ts +12 -3
- package/dist/core/routing/util.js +25 -50
- package/dist/core/routingHandler.d.ts +2 -1
- package/dist/core/routingHandler.js +10 -4
- package/dist/core/util.d.ts +1 -4
- package/dist/core/util.js +7 -15
- package/dist/overrides/converters/aws-apigw-v1.js +4 -3
- package/dist/overrides/converters/aws-apigw-v2.js +4 -3
- package/dist/overrides/converters/aws-cloudfront.js +6 -4
- package/dist/overrides/converters/edge.js +1 -10
- package/dist/overrides/converters/node.js +10 -8
- package/dist/overrides/converters/utils.d.ts +7 -0
- package/dist/overrides/converters/utils.js +9 -0
- package/dist/overrides/imageLoader/s3-lite.d.ts +3 -0
- package/dist/overrides/imageLoader/s3-lite.js +61 -0
- package/dist/overrides/tagCache/dummy.js +1 -0
- package/dist/overrides/tagCache/dynamodb-lite.js +1 -0
- package/dist/overrides/tagCache/dynamodb-nextMode.d.ts +7 -0
- package/dist/overrides/tagCache/dynamodb-nextMode.js +119 -0
- package/dist/overrides/tagCache/dynamodb.js +1 -0
- package/dist/overrides/tagCache/fs-dev.js +1 -0
- package/dist/overrides/wrappers/aws-lambda-streaming.js +1 -1
- package/dist/overrides/wrappers/aws-lambda.js +3 -1
- package/dist/overrides/wrappers/cloudflare-edge.js +3 -2
- package/dist/overrides/wrappers/cloudflare-node.js +5 -8
- package/dist/overrides/wrappers/dummy.js +2 -2
- package/dist/overrides/wrappers/express-dev.js +4 -4
- package/dist/overrides/wrappers/node.js +2 -2
- package/dist/plugins/edge.d.ts +1 -3
- package/dist/plugins/edge.js +4 -11
- package/dist/plugins/externalMiddleware.d.ts +2 -0
- package/dist/plugins/externalMiddleware.js +10 -0
- package/dist/plugins/resolve.js +2 -2
- package/dist/types/cache.d.ts +34 -4
- package/dist/types/global.d.ts +19 -31
- package/dist/types/next-types.d.ts +20 -0
- package/dist/types/open-next.d.ts +12 -5
- package/dist/types/overrides.d.ts +72 -6
- package/dist/utils/cache.d.ts +3 -0
- package/dist/utils/cache.js +32 -0
- package/dist/utils/promise.d.ts +3 -1
- package/dist/utils/promise.js +18 -11
- package/dist/utils/regex.js +1 -1
- package/package.json +5 -4
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
import { NextConfig } from "../../../adapters/config/index.js";
|
|
2
|
+
import { emptyReadableStream } from "../../../utils/stream.js";
|
|
2
3
|
import { debug } from "../../../adapters/logger.js";
|
|
4
|
+
import { constructNextUrl } from "../util.js";
|
|
3
5
|
import { acceptLanguage } from "./accept-header";
|
|
4
6
|
function isLocalizedPath(path) {
|
|
5
7
|
return (NextConfig.i18n?.locales.includes(path.split("/")[1].toLowerCase()) ?? false);
|
|
@@ -12,9 +14,40 @@ function getLocaleFromCookie(cookies) {
|
|
|
12
14
|
? i18n?.locales.find((locale) => nextLocale === locale.toLowerCase())
|
|
13
15
|
: undefined;
|
|
14
16
|
}
|
|
15
|
-
|
|
17
|
+
// Inspired by https://github.com/vercel/next.js/blob/6d93d652e0e7ba72d9a3b66e78746dce2069db03/packages/next/src/shared/lib/i18n/detect-domain-locale.ts#L3-L25
|
|
18
|
+
/**
|
|
19
|
+
* @param arg an object containing the hostname and detectedLocale
|
|
20
|
+
* @returns The `DomainLocale` object if a domain is detected, `undefined` otherwise
|
|
21
|
+
*/
|
|
22
|
+
export function detectDomainLocale({ hostname, detectedLocale, }) {
|
|
23
|
+
const i18n = NextConfig.i18n;
|
|
24
|
+
const domains = i18n?.domains;
|
|
25
|
+
if (!domains) {
|
|
26
|
+
return;
|
|
27
|
+
}
|
|
28
|
+
const lowercasedLocale = detectedLocale?.toLowerCase();
|
|
29
|
+
for (const domain of domains) {
|
|
30
|
+
// We remove the port if present
|
|
31
|
+
const domainHostname = domain.domain.split(":", 1)[0].toLowerCase();
|
|
32
|
+
if (hostname === domainHostname ||
|
|
33
|
+
lowercasedLocale === domain.defaultLocale.toLowerCase() ||
|
|
34
|
+
domain.locales?.some((locale) => lowercasedLocale === locale.toLowerCase())) {
|
|
35
|
+
return domain;
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
/**
|
|
40
|
+
*
|
|
41
|
+
* @param internalEvent
|
|
42
|
+
* @param i18n
|
|
43
|
+
* @returns The detected locale, if `localeDetection` is set to `false` it will return the default locale **or** the domain default locale if a domain is detected.
|
|
44
|
+
*/
|
|
45
|
+
export function detectLocale(internalEvent, i18n) {
|
|
46
|
+
const domainLocale = detectDomainLocale({
|
|
47
|
+
hostname: internalEvent.headers.host,
|
|
48
|
+
});
|
|
16
49
|
if (i18n.localeDetection === false) {
|
|
17
|
-
return i18n.defaultLocale;
|
|
50
|
+
return domainLocale?.defaultLocale ?? i18n.defaultLocale;
|
|
18
51
|
}
|
|
19
52
|
const cookiesLocale = getLocaleFromCookie(internalEvent.cookies);
|
|
20
53
|
const preferredLocale = acceptLanguage(internalEvent.headers["accept-language"], i18n?.locales);
|
|
@@ -22,18 +55,79 @@ function detectLocale(internalEvent, i18n) {
|
|
|
22
55
|
cookiesLocale,
|
|
23
56
|
preferredLocale,
|
|
24
57
|
defaultLocale: i18n.defaultLocale,
|
|
58
|
+
domainLocale,
|
|
25
59
|
});
|
|
26
|
-
return
|
|
27
|
-
|
|
60
|
+
return (domainLocale?.defaultLocale ??
|
|
61
|
+
cookiesLocale ??
|
|
62
|
+
preferredLocale ??
|
|
63
|
+
i18n.defaultLocale);
|
|
28
64
|
}
|
|
65
|
+
/**
|
|
66
|
+
* This function is used for OpenNext internal routing to localize the path for next config rewrite/redirects/headers and the middleware
|
|
67
|
+
* @param internalEvent
|
|
68
|
+
* @returns The localized path
|
|
69
|
+
*/
|
|
29
70
|
export function localizePath(internalEvent) {
|
|
30
71
|
const i18n = NextConfig.i18n;
|
|
31
72
|
if (!i18n) {
|
|
32
73
|
return internalEvent.rawPath;
|
|
33
74
|
}
|
|
75
|
+
// When the path is already localized we don't need to do anything
|
|
34
76
|
if (isLocalizedPath(internalEvent.rawPath)) {
|
|
35
77
|
return internalEvent.rawPath;
|
|
36
78
|
}
|
|
37
79
|
const detectedLocale = detectLocale(internalEvent, i18n);
|
|
38
80
|
return `/${detectedLocale}${internalEvent.rawPath}`;
|
|
39
81
|
}
|
|
82
|
+
/**
|
|
83
|
+
*
|
|
84
|
+
* @param internalEvent
|
|
85
|
+
* In this function, for domain locale redirect we need to rely on the host to be present and correct
|
|
86
|
+
* @returns `false` if no redirect is needed, `InternalResult` if a redirect is needed
|
|
87
|
+
*/
|
|
88
|
+
export function handleLocaleRedirect(internalEvent) {
|
|
89
|
+
const i18n = NextConfig.i18n;
|
|
90
|
+
if (!i18n ||
|
|
91
|
+
i18n.localeDetection === false ||
|
|
92
|
+
internalEvent.rawPath !== "/") {
|
|
93
|
+
return false;
|
|
94
|
+
}
|
|
95
|
+
const preferredLocale = acceptLanguage(internalEvent.headers["accept-language"], i18n?.locales);
|
|
96
|
+
const detectedLocale = detectLocale(internalEvent, i18n);
|
|
97
|
+
const domainLocale = detectDomainLocale({
|
|
98
|
+
hostname: internalEvent.headers.host,
|
|
99
|
+
});
|
|
100
|
+
const preferredDomain = detectDomainLocale({
|
|
101
|
+
detectedLocale: preferredLocale,
|
|
102
|
+
});
|
|
103
|
+
if (domainLocale && preferredDomain) {
|
|
104
|
+
const isPDomain = preferredDomain.domain === domainLocale.domain;
|
|
105
|
+
const isPLocale = preferredDomain.defaultLocale === preferredLocale;
|
|
106
|
+
if (!isPDomain || !isPLocale) {
|
|
107
|
+
const scheme = `http${preferredDomain.http ? "" : "s"}`;
|
|
108
|
+
const rlocale = isPLocale ? "" : preferredLocale;
|
|
109
|
+
return {
|
|
110
|
+
type: "core",
|
|
111
|
+
statusCode: 307,
|
|
112
|
+
headers: {
|
|
113
|
+
Location: `${scheme}://${preferredDomain.domain}/${rlocale}`,
|
|
114
|
+
},
|
|
115
|
+
body: emptyReadableStream(),
|
|
116
|
+
isBase64Encoded: false,
|
|
117
|
+
};
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
const defaultLocale = domainLocale?.defaultLocale ?? i18n.defaultLocale;
|
|
121
|
+
if (detectedLocale.toLowerCase() !== defaultLocale.toLowerCase()) {
|
|
122
|
+
return {
|
|
123
|
+
type: "core",
|
|
124
|
+
statusCode: 307,
|
|
125
|
+
headers: {
|
|
126
|
+
Location: constructNextUrl(internalEvent.url, `/${detectedLocale}`),
|
|
127
|
+
},
|
|
128
|
+
body: emptyReadableStream(),
|
|
129
|
+
isBase64Encoded: false,
|
|
130
|
+
};
|
|
131
|
+
}
|
|
132
|
+
return false;
|
|
133
|
+
}
|
|
@@ -2,8 +2,8 @@ import { NextConfig } from "../../adapters/config/index";
|
|
|
2
2
|
import { compile, match } from "path-to-regexp";
|
|
3
3
|
import { emptyReadableStream, toReadableStream } from "../../utils/stream";
|
|
4
4
|
import { debug } from "../../adapters/logger";
|
|
5
|
-
import { localizePath } from "./i18n";
|
|
6
|
-
import { convertFromQueryString, convertToQueryString, escapeRegex, getUrlParts, isExternal, unescapeRegex, } from "./util";
|
|
5
|
+
import { handleLocaleRedirect, localizePath } from "./i18n";
|
|
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 =
|
|
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
|
|
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,19 +147,20 @@ export function handleRewrites(event, rewrites) {
|
|
|
147
147
|
}
|
|
148
148
|
rewrittenUrl = isExternalRewrite
|
|
149
149
|
? `${protocol}//${rewrittenHost}${rewrittenPath}`
|
|
150
|
-
:
|
|
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:
|
|
162
|
+
rawPath: new URL(rewrittenUrl).pathname,
|
|
163
|
+
url: rewrittenUrl,
|
|
163
164
|
},
|
|
164
165
|
__rewrite: rewrite,
|
|
165
166
|
isExternalRewrite,
|
|
@@ -214,6 +215,9 @@ export function handleRedirects(event, redirects) {
|
|
|
214
215
|
const trailingSlashRedirect = handleTrailingSlashRedirect(event);
|
|
215
216
|
if (trailingSlashRedirect)
|
|
216
217
|
return trailingSlashRedirect;
|
|
218
|
+
const localeRedirect = handleLocaleRedirect(event);
|
|
219
|
+
if (localeRedirect)
|
|
220
|
+
return localeRedirect;
|
|
217
221
|
const { internalEvent, __rewrite } = handleRewrites(event, redirects.filter((r) => !r.internal));
|
|
218
222
|
if (__rewrite && !__rewrite.internal) {
|
|
219
223
|
return {
|
|
@@ -251,7 +255,7 @@ export function fixDataPage(internalEvent, buildId) {
|
|
|
251
255
|
...internalEvent,
|
|
252
256
|
rawPath: newPath,
|
|
253
257
|
query,
|
|
254
|
-
url: `${newPath}${convertToQueryString(query)}`,
|
|
258
|
+
url: new URL(`${newPath}${convertToQueryString(query)}`, internalEvent.url).href,
|
|
255
259
|
};
|
|
256
260
|
}
|
|
257
261
|
return internalEvent;
|
|
@@ -278,7 +282,7 @@ export function handleFallbackFalse(internalEvent, prerenderManifest) {
|
|
|
278
282
|
event: {
|
|
279
283
|
...internalEvent,
|
|
280
284
|
rawPath: "/404",
|
|
281
|
-
url: "/404",
|
|
285
|
+
url: constructNextUrl(internalEvent.url, "/404"),
|
|
282
286
|
headers: {
|
|
283
287
|
...internalEvent.headers,
|
|
284
288
|
"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
|
|
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
|
-
|
|
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.
|
|
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(
|
|
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
|
-
|
|
134
|
-
|
|
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:
|
|
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,21 @@ 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
|
+
// basePath is generated as "" if not provided on Next.js 15 (not sure about older versions)
|
|
71
|
+
const nextBasePath = NextConfig.basePath ?? "";
|
|
72
|
+
const url = new URL(`${nextBasePath}${path}`, baseUrl);
|
|
73
|
+
return url.href;
|
|
74
|
+
}
|
|
59
75
|
/**
|
|
60
76
|
*
|
|
61
77
|
* @__PURE__
|
|
@@ -119,7 +135,10 @@ export function convertToQuery(querystring) {
|
|
|
119
135
|
*
|
|
120
136
|
* @__PURE__
|
|
121
137
|
*/
|
|
122
|
-
export function getMiddlewareMatch(middlewareManifest) {
|
|
138
|
+
export function getMiddlewareMatch(middlewareManifest, functionsManifest) {
|
|
139
|
+
if (functionsManifest?.functions?.["/_middleware"]) {
|
|
140
|
+
return (functionsManifest.functions["/_middleware"].matchers?.map(({ regexp }) => new RegExp(regexp)) ?? [/.*/]);
|
|
141
|
+
}
|
|
123
142
|
const rootMiddleware = middlewareManifest.middleware["/"];
|
|
124
143
|
if (!rootMiddleware?.matchers)
|
|
125
144
|
return [];
|
|
@@ -246,10 +265,7 @@ export async function revalidateIfRequired(host, rawPath, headers, req) {
|
|
|
246
265
|
// If data has the same etag during these 5 min dedup window, it will be deduplicated and not revalidated.
|
|
247
266
|
try {
|
|
248
267
|
const hash = (str) => crypto.createHash("md5").update(str).digest("hex");
|
|
249
|
-
const
|
|
250
|
-
const lastModified = globalThis.lastModified[requestId] > 0
|
|
251
|
-
? globalThis.lastModified[requestId]
|
|
252
|
-
: "";
|
|
268
|
+
const lastModified = globalThis.__openNextAls.getStore()?.lastModified ?? 0;
|
|
253
269
|
// For some weird cases, lastModified is not set, haven't been able to figure out yet why
|
|
254
270
|
// For those cases we add the etag to the deduplication id, it might help
|
|
255
271
|
const etag = headers.etag ?? headers.ETag ?? "";
|
|
@@ -264,47 +280,6 @@ export async function revalidateIfRequired(host, rawPath, headers, req) {
|
|
|
264
280
|
}
|
|
265
281
|
}
|
|
266
282
|
}
|
|
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
283
|
/**
|
|
309
284
|
*
|
|
310
285
|
* @__PURE__
|
|
@@ -315,15 +290,14 @@ export function fixISRHeaders(headers) {
|
|
|
315
290
|
"private, no-cache, no-store, max-age=0, must-revalidate";
|
|
316
291
|
return;
|
|
317
292
|
}
|
|
318
|
-
const
|
|
319
|
-
const _lastModified = globalThis.lastModified[requestId] ?? 0;
|
|
293
|
+
const _lastModified = globalThis.__openNextAls.getStore()?.lastModified ?? 0;
|
|
320
294
|
if (headers[CommonHeaders.NEXT_CACHE] === "HIT" && _lastModified > 0) {
|
|
321
295
|
// calculate age
|
|
322
296
|
const age = Math.round((Date.now() - _lastModified) / 1000);
|
|
323
297
|
// extract s-maxage from cache-control
|
|
324
298
|
const regex = /s-maxage=(\d+)/;
|
|
325
299
|
const cacheControl = headers[CommonHeaders.CACHE_CONTROL];
|
|
326
|
-
debug("cache-control", cacheControl,
|
|
300
|
+
debug("cache-control", cacheControl, _lastModified, Date.now());
|
|
327
301
|
if (typeof cacheControl !== "string")
|
|
328
302
|
return;
|
|
329
303
|
const match = cacheControl.match(regex);
|
|
@@ -366,7 +340,8 @@ export function createServerResponse(routingResult, headers, responseStream) {
|
|
|
366
340
|
}
|
|
367
341
|
// This function is used only for `res.revalidate()`
|
|
368
342
|
export async function invalidateCDNOnRequest(params, headers) {
|
|
369
|
-
const { internalEvent,
|
|
343
|
+
const { internalEvent, resolvedRoutes, initialURL } = params;
|
|
344
|
+
const initialPath = new URL(initialURL).pathname;
|
|
370
345
|
const isIsrRevalidation = internalEvent.headers["x-isr"] === "1";
|
|
371
346
|
if (!isIsrRevalidation &&
|
|
372
347
|
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
|
|
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
|
|
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
|
}
|
package/dist/core/util.d.ts
CHANGED
|
@@ -1,5 +1,2 @@
|
|
|
1
|
-
|
|
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
|
-
|
|
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
|
-
})
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
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
|
|
49
|
+
url: `https://${extractHostFromHeaders(headers)}${path}${normalizeAPIGatewayProxyEventQueryParams(event)}`,
|
|
49
50
|
body: Buffer.from(body ?? "", isBase64Encoded ? "base64" : "utf8"),
|
|
50
|
-
headers
|
|
51
|
+
headers,
|
|
51
52
|
remoteAddress: requestContext.identity.sourceIp,
|
|
52
53
|
query: removeUndefinedFromQuery(event.multiValueQueryStringParameters ?? {}),
|
|
53
54
|
cookies: event.multiValueHeaders?.cookie?.reduce((acc, cur) => {
|