@opennextjs/aws 4.1.8 → 4.1.9
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 +1 -1
- package/dist/adapters/cache.js +7 -6
- package/dist/adapters/composable-cache.d.ts +1 -1
- package/dist/adapters/composable-cache.js +2 -2
- package/dist/adapters/config/util.d.ts +10 -10
- package/dist/adapters/edge-adapter.js +2 -2
- package/dist/adapters/middleware.js +2 -2
- package/dist/build/copyTracedFiles.js +6 -2
- package/dist/build/edge/createEdgeBundle.d.ts +2 -2
- package/dist/build/installDeps.d.ts +1 -1
- package/dist/build/validateConfig.d.ts +1 -1
- package/dist/core/createGenericHandler.d.ts +2 -2
- package/dist/core/edgeFunctionHandler.d.ts +1 -1
- package/dist/core/nodeMiddlewareHandler.d.ts +1 -1
- package/dist/core/requestHandler.d.ts +2 -2
- package/dist/core/requestHandler.js +15 -7
- package/dist/core/resolve.d.ts +10 -10
- package/dist/core/routing/cacheInterceptor.d.ts +1 -1
- package/dist/core/routing/cacheInterceptor.js +6 -6
- package/dist/core/routing/i18n/index.d.ts +2 -2
- package/dist/core/routing/matcher.d.ts +61 -6
- package/dist/core/routing/matcher.js +283 -55
- package/dist/core/routing/routeMatcher.d.ts +1 -1
- package/dist/core/routing/routeMatcher.js +1 -1
- package/dist/core/routing/util.d.ts +20 -4
- package/dist/core/routing/util.js +32 -15
- package/dist/core/routingHandler.d.ts +2 -2
- package/dist/core/routingHandler.js +3 -3
- package/dist/helpers/withCloudflare.d.ts +1 -1
- package/dist/helpers/withSST.d.ts +2 -2
- package/dist/http/openNextResponse.d.ts +1 -1
- package/dist/overrides/assetResolver/dummy.d.ts +1 -1
- package/dist/overrides/converters/aws-apigw-v1.d.ts +1 -1
- package/dist/overrides/converters/aws-apigw-v1.js +1 -1
- package/dist/overrides/converters/aws-apigw-v2.d.ts +1 -1
- package/dist/overrides/converters/aws-apigw-v2.js +2 -2
- package/dist/overrides/converters/aws-cloudfront.d.ts +1 -1
- package/dist/overrides/converters/aws-cloudfront.js +2 -2
- package/dist/overrides/converters/dummy.d.ts +1 -1
- package/dist/overrides/converters/edge.d.ts +2 -2
- package/dist/overrides/converters/edge.js +1 -1
- package/dist/overrides/converters/node.d.ts +1 -1
- package/dist/overrides/converters/sqs-revalidate.d.ts +1 -1
- package/dist/overrides/imageLoader/dummy.d.ts +1 -1
- package/dist/overrides/imageLoader/dummy.js +1 -1
- package/dist/overrides/imageLoader/fs-dev.js +2 -2
- package/dist/overrides/imageLoader/host.d.ts +1 -1
- package/dist/overrides/imageLoader/host.js +1 -1
- package/dist/overrides/imageLoader/s3-lite.d.ts +1 -1
- package/dist/overrides/imageLoader/s3-lite.js +1 -1
- package/dist/overrides/imageLoader/s3.d.ts +1 -1
- package/dist/overrides/imageLoader/s3.js +1 -1
- package/dist/overrides/incrementalCache/dummy.d.ts +1 -1
- package/dist/overrides/incrementalCache/dummy.js +1 -1
- package/dist/overrides/incrementalCache/fs-dev.js +1 -1
- package/dist/overrides/incrementalCache/multi-tier-ddb-s3.d.ts +1 -1
- package/dist/overrides/incrementalCache/multi-tier-ddb-s3.js +2 -2
- package/dist/overrides/incrementalCache/s3-lite.d.ts +1 -1
- package/dist/overrides/incrementalCache/s3-lite.js +2 -2
- package/dist/overrides/incrementalCache/s3.d.ts +1 -1
- package/dist/overrides/originResolver/dummy.d.ts +1 -1
- package/dist/overrides/originResolver/pattern-env.d.ts +1 -1
- package/dist/overrides/proxyExternalRequest/dummy.d.ts +1 -1
- package/dist/overrides/proxyExternalRequest/dummy.js +1 -1
- package/dist/overrides/proxyExternalRequest/fetch.d.ts +1 -1
- package/dist/overrides/proxyExternalRequest/fetch.js +1 -1
- package/dist/overrides/proxyExternalRequest/node.d.ts +1 -1
- package/dist/overrides/queue/dummy.d.ts +1 -1
- package/dist/overrides/queue/dummy.js +1 -1
- package/dist/overrides/queue/sqs-lite.d.ts +1 -1
- package/dist/overrides/queue/sqs-lite.js +2 -2
- package/dist/overrides/queue/sqs.d.ts +1 -1
- package/dist/overrides/tagCache/dummy.d.ts +1 -1
- package/dist/overrides/tagCache/dynamodb-lite.d.ts +1 -1
- package/dist/overrides/tagCache/dynamodb-lite.js +2 -2
- package/dist/overrides/tagCache/dynamodb-nextMode.d.ts +1 -1
- package/dist/overrides/tagCache/dynamodb-nextMode.js +2 -2
- package/dist/overrides/tagCache/dynamodb.d.ts +1 -1
- package/dist/overrides/tagCache/fs-dev-nextMode.d.ts +1 -1
- package/dist/overrides/tagCache/fs-dev.d.ts +1 -1
- package/dist/overrides/tagCache/fs-dev.js +1 -1
- package/dist/overrides/warmer/aws-lambda.d.ts +1 -1
- package/dist/overrides/warmer/dummy.d.ts +1 -1
- package/dist/overrides/warmer/dummy.js +1 -1
- package/dist/overrides/wrappers/aws-apigw-streaming.d.ts +1 -1
- package/dist/overrides/wrappers/aws-lambda-compressed.d.ts +1 -1
- package/dist/overrides/wrappers/aws-lambda-streaming.d.ts +1 -1
- package/dist/overrides/wrappers/aws-lambda.d.ts +1 -1
- package/dist/overrides/wrappers/cloudflare-edge.d.ts +2 -2
- package/dist/overrides/wrappers/cloudflare-node.d.ts +2 -2
- package/dist/overrides/wrappers/dummy.d.ts +1 -1
- package/dist/overrides/wrappers/express-dev.js +2 -2
- package/dist/overrides/wrappers/node.d.ts +1 -1
- package/dist/plugins/resolve.d.ts +2 -2
- package/dist/types/global.d.ts +1 -1
- package/dist/types/overrides.d.ts +1 -1
- package/dist/utils/cache.d.ts +1 -1
- package/dist/utils/promise.d.ts +1 -1
- package/package.json +12 -12
|
@@ -1,26 +1,90 @@
|
|
|
1
|
-
import { NextConfig } from "../../adapters/config/index";
|
|
2
|
-
import { compile, match } from "path-to-regexp";
|
|
3
|
-
import { normalizeRepeatedSlashes } from "../../utils/normalize-path";
|
|
4
|
-
import { emptyReadableStream, toReadableStream } from "../../utils/stream";
|
|
1
|
+
import { NextConfig } from "../../adapters/config/index.js";
|
|
2
|
+
import { compile, match, parse } from "path-to-regexp";
|
|
3
|
+
import { normalizeRepeatedSlashes } from "../../utils/normalize-path.js";
|
|
4
|
+
import { emptyReadableStream, toReadableStream } from "../../utils/stream.js";
|
|
5
5
|
import { debug } from "../../adapters/logger";
|
|
6
6
|
import { handleLocaleRedirect, localizePath } from "./i18n";
|
|
7
7
|
import { dynamicRouteMatcher, staticRouteMatcher } from "./routeMatcher";
|
|
8
8
|
import { constructNextUrl, convertFromQueryString, convertToQueryString, escapeRegex, getUrlParts, isExternal, unescapeRegex, } from "./util";
|
|
9
|
+
const compiledRegExps = new Map();
|
|
10
|
+
/**
|
|
11
|
+
* Returns the compiled regular expression for a configured pattern.
|
|
12
|
+
*
|
|
13
|
+
* Patterns come from the Next.js config and manifests, so each one is compiled
|
|
14
|
+
* on first use and reused by later requests. `lastIndex` is reset on every call
|
|
15
|
+
* so a shared global or sticky expression always starts from the beginning.
|
|
16
|
+
*
|
|
17
|
+
* @param pattern The regular expression source
|
|
18
|
+
* @param flags The optional regular expression flags
|
|
19
|
+
* @returns The compiled regular expression
|
|
20
|
+
* @throws {SyntaxError} When the pattern is invalid
|
|
21
|
+
*/
|
|
22
|
+
export function getCompiledRegExp(pattern, flags = "") {
|
|
23
|
+
const key = `/${pattern}/${flags}`;
|
|
24
|
+
let regExp = compiledRegExps.get(key);
|
|
25
|
+
if (!regExp) {
|
|
26
|
+
regExp = new RegExp(pattern, flags);
|
|
27
|
+
compiledRegExps.set(key, regExp);
|
|
28
|
+
}
|
|
29
|
+
regExp.lastIndex = 0;
|
|
30
|
+
return regExp;
|
|
31
|
+
}
|
|
32
|
+
/**
|
|
33
|
+
* Tests a request value against a route condition.
|
|
34
|
+
*
|
|
35
|
+
* Repeated query parameters are present when their array exists, but configured
|
|
36
|
+
* patterns match only the final entry, including an empty final entry. See
|
|
37
|
+
* https://github.com/vercel/next.js/blob/ae745ba/packages/next/src/shared/lib/router/utils/prepare-destination.ts#L92-L115
|
|
38
|
+
*
|
|
39
|
+
* @param value The scalar or repeated request value
|
|
40
|
+
* @param pattern The optional configured condition pattern
|
|
41
|
+
* @returns Whether the request value satisfies the condition
|
|
42
|
+
* @throws {SyntaxError} When the configured pattern is invalid
|
|
43
|
+
*/
|
|
44
|
+
function matchHasValue(value, pattern) {
|
|
45
|
+
if (!value)
|
|
46
|
+
return false;
|
|
47
|
+
if (!pattern)
|
|
48
|
+
return true;
|
|
49
|
+
const candidate = Array.isArray(value) ? value.at(-1) : value;
|
|
50
|
+
return (candidate !== undefined && getCompiledRegExp(`^${pattern}$`).test(candidate));
|
|
51
|
+
}
|
|
52
|
+
/**
|
|
53
|
+
* Normalizes the request host for route-condition matching and captures.
|
|
54
|
+
*
|
|
55
|
+
* Next.js removes the port and lowercases the hostname before applying host
|
|
56
|
+
* patterns. See
|
|
57
|
+
* https://github.com/vercel/next.js/blob/ae745ba/packages/next/src/shared/lib/router/utils/prepare-destination.ts#L82-L88
|
|
58
|
+
*
|
|
59
|
+
* @param headers The request headers
|
|
60
|
+
* @returns The normalized hostname, or undefined when no host is present
|
|
61
|
+
*/
|
|
62
|
+
function getHostname(headers) {
|
|
63
|
+
return headers.host?.split(":", 1)[0].toLowerCase();
|
|
64
|
+
}
|
|
65
|
+
/**
|
|
66
|
+
* Removes characters unsupported by path-to-regexp parameter names.
|
|
67
|
+
*
|
|
68
|
+
* Next.js exposes value-less conditions under a name containing only ASCII
|
|
69
|
+
* letters. See
|
|
70
|
+
* https://github.com/vercel/next.js/blob/ae745ba/packages/next/src/shared/lib/router/utils/prepare-destination.ts#L21-L36
|
|
71
|
+
*
|
|
72
|
+
* @param name The route-condition key
|
|
73
|
+
* @returns The sanitized destination parameter name
|
|
74
|
+
*/
|
|
75
|
+
function getSafeParamName(name) {
|
|
76
|
+
return name.replaceAll(/[^a-zA-Z]/g, "");
|
|
77
|
+
}
|
|
9
78
|
const routeHasMatcher = (headers, cookies, query) => (redirect) => {
|
|
10
79
|
switch (redirect.type) {
|
|
11
80
|
case "header":
|
|
12
|
-
return (
|
|
13
|
-
new RegExp(redirect.value ?? "").test(headers[redirect.key.toLowerCase()] ?? ""));
|
|
81
|
+
return matchHasValue(headers[redirect.key.toLowerCase()], redirect.value);
|
|
14
82
|
case "cookie":
|
|
15
|
-
return (
|
|
16
|
-
new RegExp(redirect.value ?? "").test(cookies[redirect.key] ?? ""));
|
|
83
|
+
return matchHasValue(cookies[redirect.key], redirect.value);
|
|
17
84
|
case "query":
|
|
18
|
-
return query[redirect.key]
|
|
19
|
-
? redirect.value.reduce((prev, current) => prev || new RegExp(current).test(query[redirect.key]), false)
|
|
20
|
-
: new RegExp(redirect.value ?? "").test(query[redirect.key] ?? "");
|
|
85
|
+
return matchHasValue(query[redirect.key], redirect.value);
|
|
21
86
|
case "host":
|
|
22
|
-
return (headers
|
|
23
|
-
new RegExp(redirect.value ?? "").test(headers.host));
|
|
87
|
+
return matchHasValue(getHostname(headers), redirect.value);
|
|
24
88
|
default:
|
|
25
89
|
return false;
|
|
26
90
|
}
|
|
@@ -39,13 +103,48 @@ const getParamsFromSource = (source) => (value) => {
|
|
|
39
103
|
const _match = source(value);
|
|
40
104
|
return _match ? _match.params : {};
|
|
41
105
|
};
|
|
106
|
+
/**
|
|
107
|
+
* Creates an extractor for route-condition captures.
|
|
108
|
+
*
|
|
109
|
+
* Value-less conditions expose their present value under a safe parameter
|
|
110
|
+
* name. Patterned repeated query parameters capture their final value, matching
|
|
111
|
+
* the value used to decide the condition. See
|
|
112
|
+
* https://github.com/vercel/next.js/blob/ae745ba/packages/next/src/shared/lib/router/utils/prepare-destination.ts#L92-L107
|
|
113
|
+
*
|
|
114
|
+
* @param headers The request headers
|
|
115
|
+
* @param cookies The parsed request cookies
|
|
116
|
+
* @param query The request query parameters
|
|
117
|
+
* @returns A function that extracts named captures for one condition
|
|
118
|
+
* @throws {SyntaxError} When the configured pattern is invalid
|
|
119
|
+
*/
|
|
42
120
|
const computeParamHas = (headers, cookies, query) => (has) => {
|
|
43
|
-
if (!has.value)
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
121
|
+
if (!has.value) {
|
|
122
|
+
let key;
|
|
123
|
+
let value;
|
|
124
|
+
switch (has.type) {
|
|
125
|
+
case "header":
|
|
126
|
+
key = has.key.toLowerCase();
|
|
127
|
+
value = headers[key];
|
|
128
|
+
break;
|
|
129
|
+
case "cookie":
|
|
130
|
+
key = has.key;
|
|
131
|
+
value = cookies[key];
|
|
132
|
+
break;
|
|
133
|
+
case "query":
|
|
134
|
+
key = has.key;
|
|
135
|
+
value = query[key];
|
|
136
|
+
break;
|
|
137
|
+
case "host":
|
|
138
|
+
return {};
|
|
139
|
+
}
|
|
140
|
+
return value ? { [getSafeParamName(key)]: value } : {};
|
|
141
|
+
}
|
|
142
|
+
const matcher = getCompiledRegExp(`^${has.value}$`);
|
|
143
|
+
const fromSource = (value, implicitKey) => {
|
|
47
144
|
const matches = value.match(matcher);
|
|
48
|
-
|
|
145
|
+
if (matches?.groups)
|
|
146
|
+
return matches.groups;
|
|
147
|
+
return implicitKey && matches?.[0] ? { [implicitKey]: matches[0] } : {};
|
|
49
148
|
};
|
|
50
149
|
switch (has.type) {
|
|
51
150
|
case "header":
|
|
@@ -53,39 +152,108 @@ const computeParamHas = (headers, cookies, query) => (has) => {
|
|
|
53
152
|
case "cookie":
|
|
54
153
|
return fromSource(cookies[has.key] ?? "");
|
|
55
154
|
case "query":
|
|
56
|
-
return Array.isArray(query[has.key])
|
|
57
|
-
?
|
|
58
|
-
:
|
|
155
|
+
return fromSource(Array.isArray(query[has.key])
|
|
156
|
+
? (query[has.key].at(-1) ?? "")
|
|
157
|
+
: (query[has.key] ?? ""));
|
|
59
158
|
case "host":
|
|
60
|
-
return fromSource(headers
|
|
159
|
+
return fromSource(getHostname(headers) ?? "", "host");
|
|
61
160
|
}
|
|
62
161
|
};
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
162
|
+
/**
|
|
163
|
+
* Compiles parameters in a non-path value while preserving literal syntax.
|
|
164
|
+
*
|
|
165
|
+
* Header keys and values can contain URL schemes and characters that
|
|
166
|
+
* path-to-regexp otherwise treats as patterns. See
|
|
167
|
+
* https://github.com/vercel/next.js/blob/ae745ba/packages/next/src/shared/lib/router/utils/prepare-destination.ts#L127-L160
|
|
168
|
+
*
|
|
169
|
+
* @param value The configured header key or value
|
|
170
|
+
* @param params The source and route-condition parameters
|
|
171
|
+
* @returns The interpolated value
|
|
172
|
+
* @throws {TypeError} When a referenced parameter cannot be compiled
|
|
173
|
+
*/
|
|
174
|
+
function compileNonPath(value, params) {
|
|
175
|
+
if (!value.includes(":"))
|
|
176
|
+
return value;
|
|
177
|
+
let compiledValue = value;
|
|
178
|
+
for (const key of Object.keys(params)) {
|
|
179
|
+
if (!compiledValue.includes(`:${key}`))
|
|
180
|
+
continue;
|
|
181
|
+
const escapedKey = key.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
182
|
+
compiledValue = compiledValue
|
|
183
|
+
.replace(getCompiledRegExp(`:${escapedKey}\\*`, "g"), `:${key}--ESCAPED_PARAM_ASTERISKS`)
|
|
184
|
+
.replace(getCompiledRegExp(`:${escapedKey}\\?`, "g"), `:${key}--ESCAPED_PARAM_QUESTION`)
|
|
185
|
+
.replace(getCompiledRegExp(`:${escapedKey}\\+`, "g"), `:${key}--ESCAPED_PARAM_PLUS`)
|
|
186
|
+
.replace(getCompiledRegExp(`:${escapedKey}(?!\\w)`, "g"), `--ESCAPED_PARAM_COLON${key}`);
|
|
66
187
|
}
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
188
|
+
compiledValue = compiledValue
|
|
189
|
+
.replace(/(:|\*|\?|\+|\(|\)|\{|\})/g, "\\$1")
|
|
190
|
+
.replaceAll("--ESCAPED_PARAM_PLUS", "+")
|
|
191
|
+
.replaceAll("--ESCAPED_PARAM_COLON", ":")
|
|
192
|
+
.replaceAll("--ESCAPED_PARAM_QUESTION", "?")
|
|
193
|
+
.replaceAll("--ESCAPED_PARAM_ASTERISKS", "*");
|
|
194
|
+
return compile(`/${compiledValue}`, { validate: false })(params).slice(1);
|
|
70
195
|
}
|
|
196
|
+
/**
|
|
197
|
+
* Compiles each destination query value independently.
|
|
198
|
+
*
|
|
199
|
+
* Independent compilation gives repeat parameters the same slash separator as
|
|
200
|
+
* Next.js while preserving configured query keys and delimiters.
|
|
201
|
+
*
|
|
202
|
+
* @param queryString The destination query string without a leading question mark
|
|
203
|
+
* @param params The source and route-condition parameters
|
|
204
|
+
* @returns The interpolated destination query string
|
|
205
|
+
* @throws {TypeError} When a referenced parameter cannot be compiled
|
|
206
|
+
*/
|
|
207
|
+
function compileQueryString(queryString, params) {
|
|
208
|
+
return queryString
|
|
209
|
+
.split("&")
|
|
210
|
+
.map((part) => {
|
|
211
|
+
const separator = part.indexOf("=");
|
|
212
|
+
if (separator === -1)
|
|
213
|
+
return part;
|
|
214
|
+
const key = part.slice(0, separator + 1);
|
|
215
|
+
return `${key}${compileNonPath(part.slice(separator + 1), params)}`;
|
|
216
|
+
})
|
|
217
|
+
.join("&");
|
|
218
|
+
}
|
|
219
|
+
/**
|
|
220
|
+
* Resolves configured response headers for a request.
|
|
221
|
+
*
|
|
222
|
+
* Source parameters are merged with successful condition parameters before
|
|
223
|
+
* interpolation, with condition parameters taking precedence. See
|
|
224
|
+
* https://github.com/vercel/next.js/blob/ae745ba/packages/next/src/server/lib/router-utils/resolve-routes.ts#L400-L414
|
|
225
|
+
*
|
|
226
|
+
* @param event The request used to match configured headers
|
|
227
|
+
* @param configHeaders The configured header routes
|
|
228
|
+
* @returns The response headers produced by every matching route
|
|
229
|
+
* @throws {SyntaxError} When a configured route pattern is invalid
|
|
230
|
+
*/
|
|
71
231
|
export function getNextConfigHeaders(event, configHeaders) {
|
|
72
232
|
if (!configHeaders) {
|
|
73
233
|
return {};
|
|
74
234
|
}
|
|
75
235
|
const matcher = routeHasMatcher(event.headers, event.cookies, event.query);
|
|
236
|
+
const computeHas = computeParamHas(event.headers, event.cookies, event.query);
|
|
76
237
|
const requestHeaders = {};
|
|
77
238
|
const localizedRawPath = localizePath(event);
|
|
78
239
|
for (const { headers, has, missing, regex, source, locale, } of configHeaders) {
|
|
79
240
|
const path = locale === false ? event.rawPath : localizedRawPath;
|
|
80
|
-
if (
|
|
241
|
+
if (getCompiledRegExp(regex).test(path) &&
|
|
81
242
|
checkHas(matcher, has) &&
|
|
82
243
|
checkHas(matcher, missing, true)) {
|
|
83
244
|
const fromSource = match(source);
|
|
84
245
|
const _match = fromSource(path);
|
|
246
|
+
const params = {
|
|
247
|
+
...(_match ? _match.params : {}),
|
|
248
|
+
...has?.reduce((acc, cur) => {
|
|
249
|
+
return Object.assign(acc, computeHas(cur));
|
|
250
|
+
}, {}),
|
|
251
|
+
};
|
|
252
|
+
const hasParams = Object.keys(params).length > 0;
|
|
85
253
|
headers.forEach((h) => {
|
|
86
254
|
try {
|
|
87
|
-
const key =
|
|
88
|
-
const value =
|
|
255
|
+
const key = hasParams ? compileNonPath(h.key, params) : h.key;
|
|
256
|
+
const value = hasParams ? compileNonPath(h.value, params) : h.value;
|
|
89
257
|
requestHeaders[key] = value;
|
|
90
258
|
}
|
|
91
259
|
catch {
|
|
@@ -98,18 +266,34 @@ export function getNextConfigHeaders(event, configHeaders) {
|
|
|
98
266
|
return requestHeaders;
|
|
99
267
|
}
|
|
100
268
|
/**
|
|
101
|
-
*
|
|
102
|
-
*
|
|
103
|
-
*
|
|
269
|
+
* Applies the first matching rewrite to an internal request.
|
|
270
|
+
*
|
|
271
|
+
* Only successful `has` predicates supply destination parameters. Nonmatching
|
|
272
|
+
* `missing` predicates must not manufacture captures from absent values. See
|
|
273
|
+
* https://github.com/vercel/next.js/blob/ae745ba/packages/next/src/shared/lib/router/utils/prepare-destination.ts#L113-L125
|
|
274
|
+
*
|
|
275
|
+
* Destination queries use non-path compilation so repeated parameters retain
|
|
276
|
+
* their separators. See
|
|
277
|
+
* https://github.com/vercel/next.js/blob/ae745ba/packages/next/src/shared/lib/router/utils/prepare-destination.ts#L292-L302
|
|
278
|
+
*
|
|
279
|
+
* TODO: This method currently only checks the first match. It should check all
|
|
280
|
+
* matches for `beforeFiles` and `afterFiles` rewrites. See
|
|
281
|
+
* https://nextjs.org/docs/app/api-reference/config/next-config-js/rewrites
|
|
282
|
+
*
|
|
283
|
+
* @param event The request to rewrite
|
|
284
|
+
* @param rewrites The configured rewrites to evaluate
|
|
285
|
+
* @param appendParamsToQuery Whether unused match parameters are added to the query
|
|
286
|
+
* @returns The rewritten request and matched rewrite metadata
|
|
287
|
+
* @throws {TypeError} When a matched destination cannot be compiled
|
|
104
288
|
*/
|
|
105
|
-
export function handleRewrites(event, rewrites) {
|
|
289
|
+
export function handleRewrites(event, rewrites, appendParamsToQuery = true) {
|
|
106
290
|
const { rawPath, headers, query, cookies, url } = event;
|
|
107
291
|
const localizedRawPath = localizePath(event);
|
|
108
292
|
const matcher = routeHasMatcher(headers, cookies, query);
|
|
109
293
|
const computeHas = computeParamHas(headers, cookies, query);
|
|
110
294
|
const rewrite = rewrites.find((route) => {
|
|
111
295
|
const path = route.locale === false ? rawPath : localizedRawPath;
|
|
112
|
-
return (
|
|
296
|
+
return (getCompiledRegExp(route.regex).test(path) &&
|
|
113
297
|
checkHas(matcher, route.has) &&
|
|
114
298
|
checkHas(matcher, route.missing, true));
|
|
115
299
|
});
|
|
@@ -118,14 +302,16 @@ export function handleRewrites(event, rewrites) {
|
|
|
118
302
|
const isExternalRewrite = isExternal(rewrite?.destination);
|
|
119
303
|
debug("isExternalRewrite", isExternalRewrite);
|
|
120
304
|
if (rewrite) {
|
|
121
|
-
const { pathname, protocol, hostname, queryString } = getUrlParts(rewrite.destination, isExternalRewrite);
|
|
305
|
+
const { pathname, protocol, hostname, queryString, hash } = getUrlParts(rewrite.destination, isExternalRewrite);
|
|
122
306
|
// We need to use a localized path if the rewrite is not locale specific
|
|
123
307
|
const pathToUse = rewrite.locale === false ? rawPath : localizedRawPath;
|
|
124
|
-
debug("urlParts", { pathname, protocol, hostname, queryString });
|
|
125
|
-
|
|
308
|
+
debug("urlParts", { pathname, protocol, hostname, queryString, hash });
|
|
309
|
+
// Values were validated while matching; Next.js does not revalidate them
|
|
310
|
+
// as single path segments. https://github.com/vercel/next.js/blob/ae745ba/packages/next/src/shared/lib/router/utils/prepare-destination.ts#L254-L268
|
|
311
|
+
const compileOptions = { validate: false };
|
|
312
|
+
const toDestinationPath = compile(escapeRegex(pathname, { isPath: true }), compileOptions);
|
|
126
313
|
// A literal numeric port is URL syntax, not a path-to-regexp parameter.
|
|
127
|
-
const toDestinationHost = compile(escapeRegex(hostname).replace(/:(\d+)$/, "\\:$1"));
|
|
128
|
-
const toDestinationQuery = compile(escapeRegex(queryString));
|
|
314
|
+
const toDestinationHost = compile(escapeRegex(hostname).replace(/:(\d+)$/, "\\:$1"), { ...compileOptions, encode: encodeURIComponent });
|
|
129
315
|
const params = {
|
|
130
316
|
// params for the source
|
|
131
317
|
...getParamsFromSource(match(escapeRegex(rewrite.source, { isPath: true })))(pathToUse),
|
|
@@ -133,23 +319,33 @@ export function handleRewrites(event, rewrites) {
|
|
|
133
319
|
...rewrite.has?.reduce((acc, cur) => {
|
|
134
320
|
return Object.assign(acc, computeHas(cur));
|
|
135
321
|
}, {}),
|
|
136
|
-
// params for the missing
|
|
137
|
-
...rewrite.missing?.reduce((acc, cur) => {
|
|
138
|
-
return Object.assign(acc, computeHas(cur));
|
|
139
|
-
}, {}),
|
|
140
322
|
};
|
|
141
323
|
const isUsingParams = Object.keys(params).length > 0;
|
|
324
|
+
const queryParams = Object.fromEntries(Object.entries(params).filter(([key]) => key !== "nextInternalLocale"));
|
|
325
|
+
// Next.js appends match parameters only when none are consumed by the
|
|
326
|
+
// destination path or hostname. See
|
|
327
|
+
// https://github.com/vercel/next.js/blob/ae745ba/packages/next/src/shared/lib/router/utils/prepare-destination.ts#L286-L302
|
|
328
|
+
const destinationParams = [
|
|
329
|
+
...parse(escapeRegex(`${pathname}${hash}`, { isPath: true })),
|
|
330
|
+
...(hostname
|
|
331
|
+
? parse(escapeRegex(hostname).replace(/:(\d+)$/, "\\:$1"))
|
|
332
|
+
: []),
|
|
333
|
+
].flatMap((token) => typeof token === "string" ? [] : [String(token.name)]);
|
|
142
334
|
let rewrittenQuery = queryString;
|
|
143
335
|
let rewrittenHost = hostname;
|
|
144
|
-
let
|
|
336
|
+
let rewrittenHash = hash;
|
|
337
|
+
let rewrittenPath = unescapeRegex(toDestinationPath(params));
|
|
338
|
+
if (pathname.startsWith("/") && !rewrittenPath.startsWith("/")) {
|
|
339
|
+
rewrittenPath = `/${rewrittenPath}`;
|
|
340
|
+
}
|
|
145
341
|
if (isUsingParams) {
|
|
146
|
-
rewrittenPath = unescapeRegex(toDestinationPath(params));
|
|
147
342
|
rewrittenHost = unescapeRegex(toDestinationHost(params));
|
|
148
|
-
rewrittenQuery =
|
|
343
|
+
rewrittenQuery = compileQueryString(queryString, params);
|
|
344
|
+
rewrittenHash = compileNonPath(hash, params);
|
|
149
345
|
}
|
|
150
346
|
// We need to strip the locale from the path if it's a local api route
|
|
151
347
|
if (NextConfig.i18n && !isExternalRewrite) {
|
|
152
|
-
const strippedPathLocale = rewrittenPath.replace(
|
|
348
|
+
const strippedPathLocale = rewrittenPath.replace(getCompiledRegExp(`^/(${NextConfig.i18n.locales.join("|")})`), "");
|
|
153
349
|
if (strippedPathLocale.startsWith("/api/")) {
|
|
154
350
|
rewrittenPath = strippedPathLocale;
|
|
155
351
|
}
|
|
@@ -158,11 +354,17 @@ export function handleRewrites(event, rewrites) {
|
|
|
158
354
|
? `${protocol}//${rewrittenHost}${rewrittenPath}`
|
|
159
355
|
: new URL(rewrittenPath, event.url).href;
|
|
160
356
|
// We merge query params from the source and the destination
|
|
357
|
+
const destinationQuery = convertFromQueryString(rewrittenQuery);
|
|
161
358
|
finalQuery = {
|
|
162
359
|
...query,
|
|
163
|
-
...
|
|
360
|
+
...(!appendParamsToQuery ||
|
|
361
|
+
Object.keys(queryParams).some((key) => destinationParams.includes(key))
|
|
362
|
+
? {}
|
|
363
|
+
: queryParams),
|
|
364
|
+
...destinationQuery,
|
|
164
365
|
};
|
|
165
366
|
rewrittenUrl += convertToQueryString(finalQuery);
|
|
367
|
+
rewrittenUrl += rewrittenHash;
|
|
166
368
|
debug("rewrittenUrl", { rewrittenUrl, finalQuery, isUsingParams });
|
|
167
369
|
}
|
|
168
370
|
return {
|
|
@@ -176,6 +378,35 @@ export function handleRewrites(event, rewrites) {
|
|
|
176
378
|
isExternalRewrite,
|
|
177
379
|
};
|
|
178
380
|
}
|
|
381
|
+
/**
|
|
382
|
+
* Applies every matching `beforeFiles` rewrite, in order.
|
|
383
|
+
*
|
|
384
|
+
* Next.js keeps evaluating `beforeFiles` rewrites after a match, each one
|
|
385
|
+
* against the URL produced by the previous match. An external rewrite ends the
|
|
386
|
+
* evaluation. See
|
|
387
|
+
* https://github.com/vercel/next.js/blob/ae745ba/packages/next/src/server/lib/router-utils/resolve-routes.ts#L863-L932
|
|
388
|
+
*
|
|
389
|
+
* @param event The request to rewrite
|
|
390
|
+
* @param rewrites The configured `beforeFiles` rewrites
|
|
391
|
+
* @returns The rewritten request and the last matched rewrite metadata
|
|
392
|
+
* @throws {TypeError} When a matched destination cannot be compiled
|
|
393
|
+
*/
|
|
394
|
+
export function handleBeforeFilesRewrites(event, rewrites) {
|
|
395
|
+
let result = {
|
|
396
|
+
internalEvent: event,
|
|
397
|
+
__rewrite: undefined,
|
|
398
|
+
isExternalRewrite: false,
|
|
399
|
+
};
|
|
400
|
+
for (const rewrite of rewrites) {
|
|
401
|
+
const rewriteResult = handleRewrites(result.internalEvent, [rewrite]);
|
|
402
|
+
if (!rewriteResult.__rewrite)
|
|
403
|
+
continue;
|
|
404
|
+
result = rewriteResult;
|
|
405
|
+
if (result.isExternalRewrite)
|
|
406
|
+
break;
|
|
407
|
+
}
|
|
408
|
+
return result;
|
|
409
|
+
}
|
|
179
410
|
// Normalizes repeated slashes in the path e.g. hello//world -> hello/world
|
|
180
411
|
// or backslashes to forward slashes. This prevents requests such as //domain
|
|
181
412
|
// from invoking the middleware with `request.url === "domain"`.
|
|
@@ -250,7 +481,7 @@ export function handleRedirects(event, redirects) {
|
|
|
250
481
|
const localeRedirect = handleLocaleRedirect(event);
|
|
251
482
|
if (localeRedirect)
|
|
252
483
|
return localeRedirect;
|
|
253
|
-
const { internalEvent, __rewrite } = handleRewrites(event, redirects.filter((r) => !r.internal));
|
|
484
|
+
const { internalEvent, __rewrite } = handleRewrites(event, redirects.filter((r) => !r.internal), false);
|
|
254
485
|
if (__rewrite && !__rewrite.internal) {
|
|
255
486
|
return {
|
|
256
487
|
type: event.type,
|
|
@@ -301,10 +532,7 @@ export function handleFallbackFalse(internalEvent, prerenderManifest) {
|
|
|
301
532
|
const { rawPath } = internalEvent;
|
|
302
533
|
const { dynamicRoutes = {}, routes = {} } = prerenderManifest ?? {};
|
|
303
534
|
const prerenderedFallbackRoutes = Object.entries(dynamicRoutes).filter(([, { fallback }]) => fallback === false);
|
|
304
|
-
const routeFallback = prerenderedFallbackRoutes.some(([, { routeRegex }]) =>
|
|
305
|
-
const routeRegexExp = new RegExp(routeRegex);
|
|
306
|
-
return routeRegexExp.test(rawPath);
|
|
307
|
-
});
|
|
535
|
+
const routeFallback = prerenderedFallbackRoutes.some(([, { routeRegex }]) => getCompiledRegExp(routeRegex).test(rawPath));
|
|
308
536
|
const locales = NextConfig.i18n?.locales;
|
|
309
537
|
const routesAlreadyHaveLocale = locales?.includes(rawPath.split("/")[1]) ||
|
|
310
538
|
// If we don't use locales, we don't need to add the default locale
|
|
@@ -1,3 +1,3 @@
|
|
|
1
|
-
import type { ResolvedRoute } from "../../types/open-next";
|
|
1
|
+
import type { ResolvedRoute } from "../../types/open-next.js";
|
|
2
2
|
export declare const staticRouteMatcher: (path: string) => ResolvedRoute[];
|
|
3
3
|
export declare const dynamicRouteMatcher: (path: string) => ResolvedRoute[];
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { AppPathRoutesManifest, PagesManifest, RoutesManifest, } from "../../adapters/config/index";
|
|
1
|
+
import { AppPathRoutesManifest, PagesManifest, RoutesManifest, } from "../../adapters/config/index.js";
|
|
2
2
|
// Add the locale prefix to the regex so we correctly match the rawPath
|
|
3
3
|
const optionalLocalePrefixRegex = `^/(?:${RoutesManifest.locales.map((locale) => `${locale}/?`).join("|")})?`;
|
|
4
4
|
// Add the basepath prefix to the regex so we correctly match the rawPath
|
|
@@ -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 { FunctionsConfigManifest, MiddlewareManifest } from "../../types/next-types";
|
|
4
|
+
import type { FunctionsConfigManifest, MiddlewareManifest } from "../../types/next-types.js";
|
|
5
5
|
import type { InternalEvent, InternalResult, RoutingResult, StreamCreator } from "../../types/open-next.js";
|
|
6
6
|
import { ReadableStream } from "node:stream/web";
|
|
7
7
|
/**
|
|
@@ -9,17 +9,31 @@ import { ReadableStream } from "node:stream/web";
|
|
|
9
9
|
* @__PURE__
|
|
10
10
|
*/
|
|
11
11
|
export declare function isExternal(url?: string, host?: string): boolean;
|
|
12
|
+
/**
|
|
13
|
+
* Parses a raw query string into decoded scalar or repeated values.
|
|
14
|
+
*
|
|
15
|
+
* The result has the same shape the converters produce from `URLSearchParams`
|
|
16
|
+
* and follows Next.js's decoded query representation.
|
|
17
|
+
* https://github.com/vercel/next.js/blob/3439bde/packages/next/src/shared/lib/router/utils/querystring.ts#L34-L48
|
|
18
|
+
*
|
|
19
|
+
* @param query The query string without a leading question mark
|
|
20
|
+
* @returns The decoded scalar and repeated query values
|
|
21
|
+
*
|
|
22
|
+
* @__PURE__
|
|
23
|
+
*/
|
|
12
24
|
export declare function convertFromQueryString(query: string): Record<string, string | string[]>;
|
|
13
25
|
/**
|
|
14
26
|
* Splits an URL into its parts
|
|
15
27
|
*
|
|
16
28
|
* The URL is a route destination, i.e. a `path-to-regexp` pattern rather than a
|
|
17
29
|
* well formed URL, so it can not be parsed with `new URL()` which would percent
|
|
18
|
-
* encode some of the pattern characters.
|
|
30
|
+
* encode some of the pattern characters. Like Next.js, the fragment is kept
|
|
31
|
+
* separate from the query while destination parameters are interpolated.
|
|
32
|
+
* https://github.com/vercel/next.js/blob/3439bde/packages/next/src/shared/lib/router/utils/prepare-destination.ts#L221-L299
|
|
19
33
|
*
|
|
20
34
|
* @param url The URL to split
|
|
21
35
|
* @param isExternal Whether the URL points to an external host
|
|
22
|
-
* @returns The protocol, hostname, pathname
|
|
36
|
+
* @returns The protocol, hostname, pathname, query string and fragment
|
|
23
37
|
* @throws When `isExternal` is true and the URL is not an absolute HTTP(S) URL
|
|
24
38
|
*
|
|
25
39
|
* @__PURE__
|
|
@@ -29,6 +43,7 @@ export declare function getUrlParts(url: string, isExternal: boolean): {
|
|
|
29
43
|
pathname: string;
|
|
30
44
|
protocol: string;
|
|
31
45
|
queryString: string;
|
|
46
|
+
hash: string;
|
|
32
47
|
};
|
|
33
48
|
/**
|
|
34
49
|
* Creates an URL to a Next page
|
|
@@ -49,7 +64,8 @@ export declare function convertRes(res: OpenNextNodeResponse): InternalResult;
|
|
|
49
64
|
* Make sure that multi-value query parameters are transformed to
|
|
50
65
|
* ?key=value1&key=value2&... so that Next converts those parameters
|
|
51
66
|
* to an array when reading the query parameters
|
|
52
|
-
*
|
|
67
|
+
* Keys and values are expected decoded (as produced by the converters) and are
|
|
68
|
+
* percent-encoded here, so a value such as "h&m" survives as "h%26m"
|
|
53
69
|
* @__PURE__
|
|
54
70
|
*/
|
|
55
71
|
export declare function convertToQueryString(query: Record<string, string | string[]>): string;
|
|
@@ -31,42 +31,56 @@ export function isExternal(url, host) {
|
|
|
31
31
|
}
|
|
32
32
|
return true;
|
|
33
33
|
}
|
|
34
|
+
/**
|
|
35
|
+
* Parses a raw query string into decoded scalar or repeated values.
|
|
36
|
+
*
|
|
37
|
+
* The result has the same shape the converters produce from `URLSearchParams`
|
|
38
|
+
* and follows Next.js's decoded query representation.
|
|
39
|
+
* https://github.com/vercel/next.js/blob/3439bde/packages/next/src/shared/lib/router/utils/querystring.ts#L34-L48
|
|
40
|
+
*
|
|
41
|
+
* @param query The query string without a leading question mark
|
|
42
|
+
* @returns The decoded scalar and repeated query values
|
|
43
|
+
*
|
|
44
|
+
* @__PURE__
|
|
45
|
+
*/
|
|
34
46
|
export function convertFromQueryString(query) {
|
|
35
47
|
if (query === "")
|
|
36
48
|
return {};
|
|
37
|
-
|
|
38
|
-
return getQueryFromIterator(queryParts.map((p) => {
|
|
39
|
-
const [key, value] = p.split("=");
|
|
40
|
-
return [key, value];
|
|
41
|
-
}));
|
|
49
|
+
return getQueryFromIterator(new URLSearchParams(query).entries());
|
|
42
50
|
}
|
|
43
51
|
/**
|
|
44
52
|
* Splits an URL into its parts
|
|
45
53
|
*
|
|
46
54
|
* The URL is a route destination, i.e. a `path-to-regexp` pattern rather than a
|
|
47
55
|
* well formed URL, so it can not be parsed with `new URL()` which would percent
|
|
48
|
-
* encode some of the pattern characters.
|
|
56
|
+
* encode some of the pattern characters. Like Next.js, the fragment is kept
|
|
57
|
+
* separate from the query while destination parameters are interpolated.
|
|
58
|
+
* https://github.com/vercel/next.js/blob/3439bde/packages/next/src/shared/lib/router/utils/prepare-destination.ts#L221-L299
|
|
49
59
|
*
|
|
50
60
|
* @param url The URL to split
|
|
51
61
|
* @param isExternal Whether the URL points to an external host
|
|
52
|
-
* @returns The protocol, hostname, pathname
|
|
62
|
+
* @returns The protocol, hostname, pathname, query string and fragment
|
|
53
63
|
* @throws When `isExternal` is true and the URL is not an absolute HTTP(S) URL
|
|
54
64
|
*
|
|
55
65
|
* @__PURE__
|
|
56
66
|
*/
|
|
57
67
|
export function getUrlParts(url, isExternal) {
|
|
68
|
+
const hashIndex = url.indexOf("#");
|
|
69
|
+
const hash = hashIndex === -1 ? "" : url.slice(hashIndex);
|
|
70
|
+
const urlWithoutHash = hashIndex === -1 ? url : url.slice(0, hashIndex);
|
|
58
71
|
if (!isExternal) {
|
|
59
72
|
const regex = /\/([^?]*)\??(.*)/;
|
|
60
|
-
const match =
|
|
73
|
+
const match = urlWithoutHash.match(regex);
|
|
61
74
|
return {
|
|
62
75
|
hostname: "",
|
|
63
|
-
pathname:
|
|
76
|
+
pathname: urlWithoutHash.startsWith("/") ? `/${match?.[1] ?? ""}` : "",
|
|
64
77
|
protocol: "",
|
|
65
78
|
queryString: match?.[2] ?? "",
|
|
79
|
+
hash,
|
|
66
80
|
};
|
|
67
81
|
}
|
|
68
82
|
const regex = /^(https?:)\/\/?([^\/\s?]+)(\/[^?]*)?(\?.*)?/;
|
|
69
|
-
const match =
|
|
83
|
+
const match = urlWithoutHash.match(regex);
|
|
70
84
|
if (!match) {
|
|
71
85
|
throw new Error(`Invalid external URL: ${url}`);
|
|
72
86
|
}
|
|
@@ -75,6 +89,7 @@ export function getUrlParts(url, isExternal) {
|
|
|
75
89
|
hostname: match[2],
|
|
76
90
|
pathname: match[3] ?? "",
|
|
77
91
|
queryString: match[4]?.slice(1) ?? "",
|
|
92
|
+
hash,
|
|
78
93
|
};
|
|
79
94
|
}
|
|
80
95
|
/**
|
|
@@ -125,20 +140,22 @@ export function convertRes(res) {
|
|
|
125
140
|
* Make sure that multi-value query parameters are transformed to
|
|
126
141
|
* ?key=value1&key=value2&... so that Next converts those parameters
|
|
127
142
|
* to an array when reading the query parameters
|
|
128
|
-
*
|
|
143
|
+
* Keys and values are expected decoded (as produced by the converters) and are
|
|
144
|
+
* percent-encoded here, so a value such as "h&m" survives as "h%26m"
|
|
129
145
|
* @__PURE__
|
|
130
146
|
*/
|
|
131
147
|
export function convertToQueryString(query) {
|
|
132
|
-
const
|
|
148
|
+
const searchParams = new URLSearchParams();
|
|
133
149
|
Object.entries(query).forEach(([key, value]) => {
|
|
134
150
|
if (Array.isArray(value)) {
|
|
135
|
-
value.forEach((entry) =>
|
|
151
|
+
value.forEach((entry) => searchParams.append(key, entry));
|
|
136
152
|
}
|
|
137
153
|
else {
|
|
138
|
-
|
|
154
|
+
searchParams.append(key, value);
|
|
139
155
|
}
|
|
140
156
|
});
|
|
141
|
-
|
|
157
|
+
const queryString = searchParams.toString();
|
|
158
|
+
return queryString ? `?${queryString}` : "";
|
|
142
159
|
}
|
|
143
160
|
/**
|
|
144
161
|
* Given a raw query string, returns a record with key value-array pairs
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import type { InternalEvent, InternalResult, RoutingResult } from "../types/open-next";
|
|
2
|
-
import type { AssetResolver } from "../types/overrides";
|
|
1
|
+
import type { InternalEvent, InternalResult, RoutingResult } from "../types/open-next.js";
|
|
2
|
+
import type { AssetResolver } from "../types/overrides.js";
|
|
3
3
|
export declare const MIDDLEWARE_HEADER_PREFIX = "x-middleware-response-";
|
|
4
4
|
export declare const MIDDLEWARE_HEADER_PREFIX_LEN: number;
|
|
5
5
|
export declare const INTERNAL_HEADER_PREFIX = "x-opennext-";
|