@opennextjs/aws 3.5.5 → 3.5.7

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 (40) hide show
  1. package/dist/build/createAssets.d.ts +11 -1
  2. package/dist/build/createAssets.js +18 -3
  3. package/dist/build/createServerBundle.js +6 -2
  4. package/dist/build/patch/patches/index.d.ts +4 -0
  5. package/dist/build/patch/patches/index.js +4 -0
  6. package/dist/build/patch/patches/patchBackgroundRevalidation.d.ts +11 -0
  7. package/dist/build/patch/patches/patchBackgroundRevalidation.js +27 -0
  8. package/dist/build/patch/patches/patchEnvVar.d.ts +10 -0
  9. package/dist/build/patch/patches/patchEnvVar.js +52 -0
  10. package/dist/build/patch/{patchFetchCacheISR.d.ts → patches/patchFetchCacheISR.d.ts} +1 -1
  11. package/dist/build/patch/{patchFetchCacheISR.js → patches/patchFetchCacheISR.js} +2 -2
  12. package/dist/build/patch/{patchFetchCacheWaitUntil.d.ts → patches/patchFetchCacheWaitUntil.d.ts} +1 -1
  13. package/dist/build/patch/{patchFetchCacheWaitUntil.js → patches/patchFetchCacheWaitUntil.js} +2 -2
  14. package/dist/build/patch/patches/patchNextServer.d.ts +5 -0
  15. package/dist/build/patch/patches/patchNextServer.js +91 -0
  16. package/dist/build/validateConfig.js +1 -0
  17. package/dist/core/createMainHandler.js +2 -0
  18. package/dist/core/requestHandler.js +7 -1
  19. package/dist/core/routing/matcher.js +4 -10
  20. package/dist/core/routing/middleware.d.ts +8 -1
  21. package/dist/core/routing/middleware.js +25 -40
  22. package/dist/core/routing/util.d.ts +5 -2
  23. package/dist/core/routing/util.js +15 -17
  24. package/dist/core/routingHandler.js +5 -1
  25. package/dist/core/util.js +41 -1
  26. package/dist/http/util.d.ts +7 -0
  27. package/dist/http/util.js +23 -0
  28. package/dist/overrides/converters/edge.js +2 -14
  29. package/dist/overrides/converters/node.js +2 -2
  30. package/dist/overrides/converters/utils.d.ts +7 -0
  31. package/dist/overrides/converters/utils.js +10 -0
  32. package/dist/overrides/wrappers/aws-lambda-compressed.d.ts +7 -0
  33. package/dist/overrides/wrappers/aws-lambda-compressed.js +96 -0
  34. package/dist/overrides/wrappers/aws-lambda-streaming.js +1 -0
  35. package/dist/overrides/wrappers/aws-lambda.d.ts +2 -0
  36. package/dist/overrides/wrappers/aws-lambda.js +1 -1
  37. package/dist/types/aws-lambda.d.ts +4 -1
  38. package/dist/types/global.d.ts +6 -0
  39. package/dist/types/open-next.d.ts +12 -1
  40. package/package.json +1 -1
@@ -1,6 +1,16 @@
1
1
  import type { TagCacheMetaFile } from "../types/cache.js";
2
2
  import * as buildHelper from "./helper.js";
3
- export declare function createStaticAssets(options: buildHelper.BuildOptions): void;
3
+ /**
4
+ * Copy the static assets to the output folder
5
+ *
6
+ * WARNING: `useBasePath` should be set to `false` when the output file is used.
7
+ *
8
+ * @param options OpenNext build options
9
+ * @param useBasePath whether to copy files into the to Next.js configured basePath
10
+ */
11
+ export declare function createStaticAssets(options: buildHelper.BuildOptions, { useBasePath }?: {
12
+ useBasePath?: boolean | undefined;
13
+ }): void;
4
14
  /**
5
15
  * Create the cache assets.
6
16
  *
@@ -1,16 +1,31 @@
1
1
  import fs from "node:fs";
2
2
  import path from "node:path";
3
+ import { loadConfig } from "../adapters/config/util.js";
3
4
  import logger from "../logger.js";
4
5
  import { isBinaryContentType } from "../utils/binary.js";
5
6
  import * as buildHelper from "./helper.js";
6
- export function createStaticAssets(options) {
7
+ /**
8
+ * Copy the static assets to the output folder
9
+ *
10
+ * WARNING: `useBasePath` should be set to `false` when the output file is used.
11
+ *
12
+ * @param options OpenNext build options
13
+ * @param useBasePath whether to copy files into the to Next.js configured basePath
14
+ */
15
+ export function createStaticAssets(options, { useBasePath = false } = {}) {
7
16
  logger.info("Bundling static assets...");
8
17
  const { appBuildOutputPath, appPublicPath, outputDir, appPath } = options;
18
+ const NextConfig = loadConfig(path.join(appBuildOutputPath, ".next"));
19
+ const basePath = useBasePath ? (NextConfig.basePath ?? "") : "";
9
20
  // Create output folder
10
- const outputPath = path.join(outputDir, "assets");
21
+ const outputPath = path.join(outputDir, "assets", basePath);
11
22
  fs.mkdirSync(outputPath, { recursive: true });
12
23
  /**
13
- * Next.js outputs assets into multiple files. Copy into the same directory.
24
+ * Next.js outputs assets into multiple files.
25
+ *
26
+ * Copy into the same directory:
27
+ * - `.open-next/assets` when `useBasePath` is `false`
28
+ * - `.open-next/assets/basePath` when `useBasePath` is `true`
14
29
  *
15
30
  * Copy over:
16
31
  * - .next/BUILD_ID => BUILD_ID
@@ -14,8 +14,8 @@ import { copyMiddlewareResources, generateEdgeBundle, } from "./edge/createEdgeB
14
14
  import * as buildHelper from "./helper.js";
15
15
  import { installDependencies } from "./installDeps.js";
16
16
  import { applyCodePatches } from "./patch/codePatcher.js";
17
- import { patchFetchCacheForISR, patchUnstableCacheForISR, } from "./patch/patchFetchCacheISR.js";
18
- import { patchFetchCacheSetMissingWaitUntil } from "./patch/patchFetchCacheWaitUntil.js";
17
+ import { patchEnvVars, patchFetchCacheForISR, patchFetchCacheSetMissingWaitUntil, patchNextServer, patchUnstableCacheForISR, } from "./patch/patches/index.js";
18
+ import { patchBackgroundRevalidation } from "./patch/patches/patchBackgroundRevalidation.js";
19
19
  export async function createServerBundle(options, codeCustomization) {
20
20
  const { config } = options;
21
21
  const foundRoutes = new Set();
@@ -120,6 +120,9 @@ async function generateBundle(name, options, fnOptions, codeCustomization) {
120
120
  patchFetchCacheSetMissingWaitUntil,
121
121
  patchFetchCacheForISR,
122
122
  patchUnstableCacheForISR,
123
+ patchNextServer,
124
+ patchEnvVars,
125
+ patchBackgroundRevalidation,
123
126
  ...additionalCodePatches,
124
127
  ]);
125
128
  // Build Lambda code
@@ -145,6 +148,7 @@ async function generateBundle(name, options, fnOptions, codeCustomization) {
145
148
  ...(disableNextPrebundledReact ? ["applyNextjsPrebundledReact"] : []),
146
149
  ...(disableRouting ? ["withRouting"] : []),
147
150
  ...(isAfter142 ? ["patchAsyncStorage"] : []),
151
+ ...(isAfter141 ? ["appendPrefetch"] : []),
148
152
  ],
149
153
  }),
150
154
  openNextReplacementPlugin({
@@ -0,0 +1,4 @@
1
+ export { envVarRuleCreator, patchEnvVars } from "./patchEnvVar.js";
2
+ export { patchNextServer } from "./patchNextServer.js";
3
+ export { patchFetchCacheForISR, patchUnstableCacheForISR, } from "./patchFetchCacheISR.js";
4
+ export { patchFetchCacheSetMissingWaitUntil } from "./patchFetchCacheWaitUntil.js";
@@ -0,0 +1,4 @@
1
+ export { envVarRuleCreator, patchEnvVars } from "./patchEnvVar.js";
2
+ export { patchNextServer } from "./patchNextServer.js";
3
+ export { patchFetchCacheForISR, patchUnstableCacheForISR, } from "./patchFetchCacheISR.js";
4
+ export { patchFetchCacheSetMissingWaitUntil } from "./patchFetchCacheWaitUntil.js";
@@ -0,0 +1,11 @@
1
+ export declare const rule = "\nrule:\n kind: binary_expression\n all:\n - has:\n kind: unary_expression\n regex: \"!cachedResponse.isStale\"\n - has:\n kind: member_expression\n regex: \"context.isPrefetch\"\nfix:\n 'true'";
2
+ export declare const patchBackgroundRevalidation: {
3
+ name: string;
4
+ patches: {
5
+ versions: ">=14.1.0";
6
+ field: {
7
+ pathFilter: RegExp;
8
+ patchCode: import("../codePatcher.js").PatchCodeFn;
9
+ };
10
+ }[];
11
+ };
@@ -0,0 +1,27 @@
1
+ import { getCrossPlatformPathRegex } from "../../../utils/regex.js";
2
+ import { createPatchCode } from "../astCodePatcher.js";
3
+ export const rule = `
4
+ rule:
5
+ kind: binary_expression
6
+ all:
7
+ - has:
8
+ kind: unary_expression
9
+ regex: "!cachedResponse.isStale"
10
+ - has:
11
+ kind: member_expression
12
+ regex: "context.isPrefetch"
13
+ fix:
14
+ 'true'`;
15
+ export const patchBackgroundRevalidation = {
16
+ name: "patchBackgroundRevalidation",
17
+ patches: [
18
+ {
19
+ // TODO: test for earlier versions of Next
20
+ versions: ">=14.1.0",
21
+ field: {
22
+ pathFilter: getCrossPlatformPathRegex("server/response-cache/index.js"),
23
+ patchCode: createPatchCode(rule),
24
+ },
25
+ },
26
+ ],
27
+ };
@@ -0,0 +1,10 @@
1
+ import type { CodePatcher } from "../codePatcher";
2
+ /**
3
+ * Creates a rule to replace `process.env.${envVar}` by `value` in the condition of if statements
4
+ * This is used to avoid loading unnecessary deps at runtime
5
+ * @param envVar The env var that we want to replace
6
+ * @param value The value that we want to replace it with
7
+ * @returns
8
+ */
9
+ export declare const envVarRuleCreator: (envVar: string, value: string) => string;
10
+ export declare const patchEnvVars: CodePatcher;
@@ -0,0 +1,52 @@
1
+ import { createPatchCode } from "../astCodePatcher.js";
2
+ /**
3
+ * Creates a rule to replace `process.env.${envVar}` by `value` in the condition of if statements
4
+ * This is used to avoid loading unnecessary deps at runtime
5
+ * @param envVar The env var that we want to replace
6
+ * @param value The value that we want to replace it with
7
+ * @returns
8
+ */
9
+ export const envVarRuleCreator = (envVar, value) => `
10
+ rule:
11
+ kind: member_expression
12
+ pattern: process.env.${envVar}
13
+ inside:
14
+ kind: parenthesized_expression
15
+ stopBy: end
16
+ inside:
17
+ kind: if_statement
18
+ fix:
19
+ '${value}'
20
+ `;
21
+ export const patchEnvVars = {
22
+ name: "patch-env-vars",
23
+ patches: [
24
+ // This patch will set the `NEXT_RUNTIME` env var to "node" to avoid loading unnecessary edge deps at runtime
25
+ {
26
+ versions: ">=15.0.0",
27
+ field: {
28
+ pathFilter: /module\.compiled\.js$/,
29
+ contentFilter: /process\.env\.NEXT_RUNTIME/,
30
+ patchCode: createPatchCode(envVarRuleCreator("NEXT_RUNTIME", '"node"')),
31
+ },
32
+ },
33
+ // This patch will set `NODE_ENV` to production to avoid loading unnecessary dev deps at runtime
34
+ {
35
+ versions: ">=15.0.0",
36
+ field: {
37
+ pathFilter: /(module\.compiled|react\/index|react\/jsx-runtime|react-dom\/index)\.js$/,
38
+ contentFilter: /process\.env\.NODE_ENV/,
39
+ patchCode: createPatchCode(envVarRuleCreator("NODE_ENV", '"production"')),
40
+ },
41
+ },
42
+ // This patch will set `TURBOPACK` env to false to avoid loading turbopack related deps at runtime
43
+ {
44
+ versions: ">=15.0.0",
45
+ field: {
46
+ pathFilter: /module\.compiled\.js$/,
47
+ contentFilter: /process\.env\.TURBOPACK/,
48
+ patchCode: createPatchCode(envVarRuleCreator("TURBOPACK", "false")),
49
+ },
50
+ },
51
+ ],
52
+ };
@@ -1,4 +1,4 @@
1
- import type { CodePatcher } from "./codePatcher";
1
+ import type { CodePatcher } from "../codePatcher.js";
2
2
  export declare const fetchRule = "\nrule:\n kind: member_expression\n pattern: $WORK_STORE.isOnDemandRevalidate\n inside:\n kind: ternary_expression\n all:\n - has: {kind: 'null'}\n - has: \n kind: await_expression\n has:\n kind: call_expression\n all:\n - has:\n kind: member_expression\n has:\n kind: property_identifier\n field: property\n regex: get\n - has:\n kind: arguments\n has:\n kind: object\n has:\n kind: pair\n all:\n - has:\n kind: property_identifier\n field: key\n regex: softTags\n inside:\n kind: variable_declarator\n\nfix:\n ($WORK_STORE.isOnDemandRevalidate && !globalThis.__openNextAls?.getStore()?.isISRRevalidation)\n";
3
3
  export declare const unstable_cacheRule = "\nrule:\n kind: member_expression\n pattern: $STORE_OR_CACHE.isOnDemandRevalidate\n inside:\n kind: if_statement\n stopBy: end\n has:\n kind: statement_block\n has:\n kind: variable_declarator\n has: \n kind: await_expression\n has:\n kind: call_expression\n all:\n - has:\n kind: member_expression\n has:\n kind: property_identifier\n field: property\n regex: get\n - has:\n kind: arguments\n has:\n kind: object\n has:\n kind: pair\n all:\n - has:\n kind: property_identifier\n field: key\n regex: softTags\n stopBy: end\nfix:\n ($STORE_OR_CACHE.isOnDemandRevalidate && !globalThis.__openNextAls?.getStore()?.isISRRevalidation)\n";
4
4
  export declare const patchFetchCacheForISR: CodePatcher;
@@ -1,6 +1,6 @@
1
1
  import { Lang } from "@ast-grep/napi";
2
- import { getCrossPlatformPathRegex } from "../../utils/regex.js";
3
- import { createPatchCode } from "./astCodePatcher.js";
2
+ import { getCrossPlatformPathRegex } from "../../../utils/regex.js";
3
+ import { createPatchCode } from "../astCodePatcher.js";
4
4
  export const fetchRule = `
5
5
  rule:
6
6
  kind: member_expression
@@ -1,3 +1,3 @@
1
- import type { CodePatcher } from "./codePatcher";
1
+ import type { CodePatcher } from "../codePatcher.js";
2
2
  export declare const rule = "\nrule:\n kind: call_expression\n pattern: $PROMISE\n all:\n - has: { pattern: $_.arrayBuffer().then, stopBy: end }\n - has: { pattern: \"Buffer.from\", stopBy: end }\n - any:\n - inside:\n kind: sequence_expression\n inside:\n kind: return_statement\n - inside:\n kind: expression_statement\n precedes:\n kind: return_statement\n - has: { pattern: $_.FETCH, stopBy: end }\n\nfix: |\n globalThis.__openNextAls?.getStore()?.pendingPromiseRunner.add($PROMISE)\n";
3
3
  export declare const patchFetchCacheSetMissingWaitUntil: CodePatcher;
@@ -1,5 +1,5 @@
1
- import { getCrossPlatformPathRegex } from "../../utils/regex.js";
2
- import { createPatchCode } from "./astCodePatcher.js";
1
+ import { getCrossPlatformPathRegex } from "../../../utils/regex.js";
2
+ import { createPatchCode } from "../astCodePatcher.js";
3
3
  export const rule = `
4
4
  rule:
5
5
  kind: call_expression
@@ -0,0 +1,5 @@
1
+ import type { CodePatcher } from "../codePatcher.js";
2
+ export declare const minimalRule = "\nrule:\n kind: member_expression\n pattern: process.env.NEXT_MINIMAL\n any:\n - inside:\n kind: parenthesized_expression\n stopBy: end\n inside:\n kind: if_statement\n any:\n - inside:\n kind: statement_block\n inside:\n kind: method_definition\n any:\n - has: {kind: property_identifier, field: name, regex: runEdgeFunction}\n - has: {kind: property_identifier, field: name, regex: runMiddleware}\n - has: {kind: property_identifier, field: name, regex: imageOptimizer}\n - has:\n kind: statement_block\n has:\n kind: expression_statement\n pattern: res.statusCode = 400;\nfix:\n 'true'\n";
3
+ export declare const disablePreloadingRule = "\nrule:\n kind: statement_block\n inside:\n kind: if_statement\n any: \n - has: \n kind: member_expression\n pattern: this.nextConfig.experimental.preloadEntriesOnStart\n stopBy: end\n - has: \n kind: binary_expression\n pattern: appDocumentPreloading === true\n stopBy: end\nfix:\n '{}'\n";
4
+ export declare const removeMiddlewareManifestRule = "\nrule:\n kind: statement_block\n inside:\n kind: method_definition\n has:\n kind: property_identifier\n regex: getMiddlewareManifest\nfix:\n '{return null;}'\n";
5
+ export declare const patchNextServer: CodePatcher;
@@ -0,0 +1,91 @@
1
+ import { createPatchCode } from "../astCodePatcher.js";
2
+ // This rule will replace the `NEXT_MINIMAL` env variable with true in multiple places to avoid executing unwanted path (i.e. next middleware, edge functions and image optimization)
3
+ export const minimalRule = `
4
+ rule:
5
+ kind: member_expression
6
+ pattern: process.env.NEXT_MINIMAL
7
+ any:
8
+ - inside:
9
+ kind: parenthesized_expression
10
+ stopBy: end
11
+ inside:
12
+ kind: if_statement
13
+ any:
14
+ - inside:
15
+ kind: statement_block
16
+ inside:
17
+ kind: method_definition
18
+ any:
19
+ - has: {kind: property_identifier, field: name, regex: runEdgeFunction}
20
+ - has: {kind: property_identifier, field: name, regex: runMiddleware}
21
+ - has: {kind: property_identifier, field: name, regex: imageOptimizer}
22
+ - has:
23
+ kind: statement_block
24
+ has:
25
+ kind: expression_statement
26
+ pattern: res.statusCode = 400;
27
+ fix:
28
+ 'true'
29
+ `;
30
+ // This rule will disable the background preloading of route done by NextServer by default during the creation of NextServer
31
+ export const disablePreloadingRule = `
32
+ rule:
33
+ kind: statement_block
34
+ inside:
35
+ kind: if_statement
36
+ any:
37
+ - has:
38
+ kind: member_expression
39
+ pattern: this.nextConfig.experimental.preloadEntriesOnStart
40
+ stopBy: end
41
+ - has:
42
+ kind: binary_expression
43
+ pattern: appDocumentPreloading === true
44
+ stopBy: end
45
+ fix:
46
+ '{}'
47
+ `;
48
+ // This rule is mostly for splitted edge functions so that we don't try to match them on the other non edge functions
49
+ export const removeMiddlewareManifestRule = `
50
+ rule:
51
+ kind: statement_block
52
+ inside:
53
+ kind: method_definition
54
+ has:
55
+ kind: property_identifier
56
+ regex: getMiddlewareManifest
57
+ fix:
58
+ '{return null;}'
59
+ `;
60
+ export const patchNextServer = {
61
+ name: "patch-next-server",
62
+ patches: [
63
+ // Skip executing next middleware, edge functions and image optimization inside NextServer
64
+ {
65
+ versions: ">=15.0.0",
66
+ field: {
67
+ pathFilter: /next-server\.(js)$/,
68
+ contentFilter: /process\.env\.NEXT_MINIMAL/,
69
+ patchCode: createPatchCode(minimalRule),
70
+ },
71
+ },
72
+ // Disable Next background preloading done at creation of `NetxServer`
73
+ {
74
+ versions: ">=15.0.0",
75
+ field: {
76
+ pathFilter: /next-server\.(js)$/,
77
+ contentFilter: /this\.nextConfig\.experimental\.preloadEntriesOnStart/,
78
+ patchCode: createPatchCode(disablePreloadingRule),
79
+ },
80
+ },
81
+ // Don't match edge functions in `NextServer`
82
+ {
83
+ versions: ">=15.0.0",
84
+ field: {
85
+ pathFilter: /next-server\.(js)$/,
86
+ contentFilter: /getMiddlewareManifest/,
87
+ patchCode: createPatchCode(removeMiddlewareManifestRule),
88
+ },
89
+ },
90
+ ],
91
+ };
@@ -6,6 +6,7 @@ const compatibilityMatrix = {
6
6
  "aws-cloudfront",
7
7
  "sqs-revalidate",
8
8
  ],
9
+ "aws-lambda-compressed": ["aws-apigw-v2"],
9
10
  "aws-lambda-streaming": ["aws-apigw-v2"],
10
11
  cloudflare: ["edge"],
11
12
  "cloudflare-edge": ["edge"],
@@ -10,6 +10,8 @@ export async function createMainHandler() {
10
10
  : config.default;
11
11
  globalThis.serverId = generateUniqueId();
12
12
  globalThis.openNextConfig = config;
13
+ // If route preloading behavior is set to start, it will wait for every single route to be preloaded before even creating the main handler.
14
+ await globalThis.__next_route_preloader("start");
13
15
  // Default queue
14
16
  globalThis.queue = await resolveQueue(thisFunction.override?.queue);
15
17
  globalThis.incrementalCache = await resolveIncrementalCache(thisFunction.override?.incrementalCache);
@@ -19,6 +19,7 @@ export async function openNextHandler(internalEvent, options) {
19
19
  isISRRevalidation: initialHeaders["x-isr"] === "1",
20
20
  waitUntil: options?.waitUntil,
21
21
  }, async () => {
22
+ await globalThis.__next_route_preloader("waitUntil");
22
23
  if (initialHeaders["x-forwarded-host"]) {
23
24
  initialHeaders.host = initialHeaders["x-forwarded-host"];
24
25
  }
@@ -114,7 +115,12 @@ export async function openNextHandler(internalEvent, options) {
114
115
  // 1. We could just let the revalidation go as normal, but due to race conditions the revalidation will be unreliable
115
116
  // 2. We could alter the lastModified time of our cache to make next believe that the cache is fresh, but this could cause issues with stale data since the cdn will cache the stale data as if it was fresh
116
117
  // 3. OUR CHOICE: We could pass a purpose prefetch header to the serverless function to make next believe that the request is a prefetch request and not trigger revalidation (This could potentially break in the future if next changes the behavior of prefetch requests)
117
- headers: { ...headers, purpose: "prefetch" },
118
+ headers: {
119
+ ...headers,
120
+ //#override appendPrefetch
121
+ purpose: "prefetch",
122
+ //#endOverride
123
+ },
118
124
  body: preprocessedEvent.body,
119
125
  remoteAddress: preprocessedEvent.remoteAddress,
120
126
  };
@@ -114,19 +114,13 @@ export function handleRewrites(event, rewrites) {
114
114
  const { pathname, protocol, hostname, queryString } = getUrlParts(rewrite.destination, isExternalRewrite);
115
115
  // We need to use a localized path if the rewrite is not locale specific
116
116
  const pathToUse = rewrite.locale === false ? rawPath : localizedRawPath;
117
- // We need to encode the "+" character with its UTF-8 equivalent "%20" to avoid 2 issues:
118
- // 1. The compile function from path-to-regexp will throw an error if it finds a "+" character.
119
- // https://github.com/pillarjs/path-to-regexp?tab=readme-ov-file#unexpected--or-
120
- // 2. The convertToQueryString function will replace the "+" character with %2B instead of %20.
121
- // %2B does not get interpreted as a space in the URL thus breaking the query string.
122
- const encodePlusQueryString = queryString.replaceAll("+", "%20");
123
117
  debug("urlParts", { pathname, protocol, hostname, queryString });
124
- const toDestinationPath = compile(escapeRegex(pathname));
118
+ const toDestinationPath = compile(escapeRegex(pathname, { isPath: true }));
125
119
  const toDestinationHost = compile(escapeRegex(hostname));
126
- const toDestinationQuery = compile(escapeRegex(encodePlusQueryString));
120
+ const toDestinationQuery = compile(escapeRegex(queryString));
127
121
  const params = {
128
122
  // params for the source
129
- ...getParamsFromSource(match(escapeRegex(rewrite.source)))(pathToUse),
123
+ ...getParamsFromSource(match(escapeRegex(rewrite.source, { isPath: true })))(pathToUse),
130
124
  // params for the has
131
125
  ...rewrite.has?.reduce((acc, cur) => {
132
126
  return Object.assign(acc, computeHas(cur));
@@ -137,7 +131,7 @@ export function handleRewrites(event, rewrites) {
137
131
  }, {}),
138
132
  };
139
133
  const isUsingParams = Object.keys(params).length > 0;
140
- let rewrittenQuery = encodePlusQueryString;
134
+ let rewrittenQuery = queryString;
141
135
  let rewrittenHost = hostname;
142
136
  let rewrittenPath = pathname;
143
137
  if (isUsingParams) {
@@ -7,5 +7,12 @@ type Middleware = (request: Request) => Response | Promise<Response>;
7
7
  type MiddlewareLoader = () => Promise<{
8
8
  default: Middleware;
9
9
  }>;
10
- export declare function handleMiddleware(internalEvent: InternalEvent, middlewareLoader?: MiddlewareLoader): Promise<MiddlewareEvent | InternalResult>;
10
+ /**
11
+ *
12
+ * @param internalEvent the internal event
13
+ * @param initialSearch the initial query string as it was received in the handler
14
+ * @param middlewareLoader Only used for unit test
15
+ * @returns `Promise<MiddlewareEvent | InternalResult>`
16
+ */
17
+ export declare function handleMiddleware(internalEvent: InternalEvent, initialSearch: string, middlewareLoader?: MiddlewareLoader): Promise<MiddlewareEvent | InternalResult>;
11
18
  export {};
@@ -1,14 +1,7 @@
1
1
  import { FunctionsConfigManifest, MiddlewareManifest, NextConfig, PrerenderManifest, } from "../../adapters/config/index.js";
2
2
  import { emptyReadableStream } from "../../utils/stream.js";
3
3
  import { localizePath } from "./i18n/index.js";
4
- //NOTE: we should try to avoid importing stuff from next as much as possible
5
- // every release of next could break this
6
- // const { run } = require("next/dist/server/web/sandbox");
7
- // const { getCloneableBody } = require("next/dist/server/body-streams");
8
- // const {
9
- // signalFromNodeResponse,
10
- // } = require("next/dist/server/web/spec-extension/adapters/next-request");
11
- import { convertBodyToReadableStream, convertToQueryString, getMiddlewareMatch, isExternal, } from "./util.js";
4
+ import { convertBodyToReadableStream, getMiddlewareMatch, isExternal, } from "./util.js";
12
5
  const middlewareManifest = MiddlewareManifest;
13
6
  const functionsConfigManifest = FunctionsConfigManifest;
14
7
  const middleMatch = getMiddlewareMatch(middlewareManifest, functionsConfigManifest);
@@ -16,12 +9,14 @@ function defaultMiddlewareLoader() {
16
9
  // @ts-expect-error - This is bundled
17
10
  return import("./middleware.mjs");
18
11
  }
19
- // NOTE: As of Nextjs 13.4.13+, the middleware is handled outside the next-server.
20
- // OpenNext will run the middleware in a sandbox and set the appropriate req headers
21
- // and res.body prior to processing the next-server.
22
- // @returns undefined | res.end()
23
- // if res.end() is return, the parent needs to return and not process next server
24
- export async function handleMiddleware(internalEvent, middlewareLoader = defaultMiddlewareLoader) {
12
+ /**
13
+ *
14
+ * @param internalEvent the internal event
15
+ * @param initialSearch the initial query string as it was received in the handler
16
+ * @param middlewareLoader Only used for unit test
17
+ * @returns `Promise<MiddlewareEvent | InternalResult>`
18
+ */
19
+ export async function handleMiddleware(internalEvent, initialSearch, middlewareLoader = defaultMiddlewareLoader) {
25
20
  const headers = internalEvent.headers;
26
21
  // We bypass the middleware if the request is internal
27
22
  // We should only do that if the request has the correct `x-prerender-revalidate` header
@@ -36,7 +31,7 @@ export async function handleMiddleware(internalEvent, middlewareLoader = default
36
31
  if (!hasMatch)
37
32
  return internalEvent;
38
33
  const initialUrl = new URL(normalizedPath, internalEvent.url);
39
- initialUrl.search = convertToQueryString(internalEvent.query);
34
+ initialUrl.search = initialSearch;
40
35
  const url = initialUrl.href;
41
36
  const middleware = await middlewareLoader();
42
37
  const result = await middleware.default({
@@ -76,13 +71,15 @@ export async function handleMiddleware(internalEvent, middlewareLoader = default
76
71
  const responseHeaders = result.headers;
77
72
  const reqHeaders = {};
78
73
  const resHeaders = {};
79
- responseHeaders.delete("x-middleware-override-headers");
80
- /* Next will set the header `x-middleware-set-cookie` when you `set-cookie` in the middleware.
81
- * We can delete it here since it will be set in `set-cookie` aswell. Next removes this header in the response themselves.
82
- * `x-middleware-next` is set when you invoke `NextResponse.next()`. We can delete it here aswell.
83
- */
84
- responseHeaders.delete("x-middleware-set-cookie");
85
- responseHeaders.delete("x-middleware-next");
74
+ // These are internal headers used by Next.js, we don't want to expose them to the client
75
+ const filteredHeaders = [
76
+ "x-middleware-override-headers",
77
+ "x-middleware-set-cookie",
78
+ "x-middleware-next",
79
+ "x-middleware-rewrite",
80
+ // We need to drop `content-encoding` because it will be decoded
81
+ "content-encoding",
82
+ ];
86
83
  const xMiddlewareKey = "x-middleware-request-";
87
84
  responseHeaders.forEach((value, key) => {
88
85
  if (key.startsWith(xMiddlewareKey)) {
@@ -90,6 +87,8 @@ export async function handleMiddleware(internalEvent, middlewareLoader = default
90
87
  reqHeaders[k] = value;
91
88
  }
92
89
  else {
90
+ if (filteredHeaders.includes(key.toLowerCase()))
91
+ return;
93
92
  if (key.toLowerCase() === "set-cookie") {
94
93
  resHeaders[key] = resHeaders[key]
95
94
  ? [...resHeaders[key], value]
@@ -100,20 +99,6 @@ export async function handleMiddleware(internalEvent, middlewareLoader = default
100
99
  }
101
100
  }
102
101
  });
103
- // If the middleware returned a Redirect, we set the `Location` header with
104
- // the redirected url and end the response.
105
- if (statusCode >= 300 && statusCode < 400) {
106
- resHeaders.location =
107
- responseHeaders.get("location") ?? resHeaders.location;
108
- // res.setHeader("Location", location);
109
- return {
110
- body: emptyReadableStream(),
111
- type: internalEvent.type,
112
- statusCode: statusCode,
113
- headers: resHeaders,
114
- isBase64Encoded: false,
115
- };
116
- }
117
102
  // If the middleware returned a Rewrite, set the `url` to the pathname of the rewrite
118
103
  // NOTE: the header was added to `req` from above
119
104
  const rewriteUrl = responseHeaders.get("x-middleware-rewrite");
@@ -139,11 +124,11 @@ export async function handleMiddleware(internalEvent, middlewareLoader = default
139
124
  });
140
125
  }
141
126
  }
142
- // If the middleware returned a `NextResponse`, pipe the body to res. This will return
143
- // the body immediately to the client.
144
- if (result.body) {
127
+ // If the middleware wants to directly return a response (i.e. not using `NextResponse.next()` or `NextResponse.rewrite()`)
128
+ // we return the response directly
129
+ if (!rewriteUrl && !responseHeaders.get("x-middleware-next")) {
145
130
  // transfer response body to res
146
- const body = result.body;
131
+ const body = result.body ?? emptyReadableStream();
147
132
  return {
148
133
  type: internalEvent.type,
149
134
  statusCode: statusCode,
@@ -8,7 +8,7 @@ import type { InternalEvent, InternalResult, RoutingResult, StreamCreator } from
8
8
  * @__PURE__
9
9
  */
10
10
  export declare function isExternal(url?: string, host?: string): boolean;
11
- export declare function convertFromQueryString(query: string): Record<string, string>;
11
+ export declare function convertFromQueryString(query: string): Record<string, string | string[]>;
12
12
  /**
13
13
  *
14
14
  * @__PURE__
@@ -38,6 +38,7 @@ export declare function convertRes(res: OpenNextNodeResponse): InternalResult;
38
38
  * Make sure that multi-value query parameters are transformed to
39
39
  * ?key=value1&key=value2&... so that Next converts those parameters
40
40
  * to an array when reading the query parameters
41
+ * query should be properly encoded before using this function
41
42
  * @__PURE__
42
43
  */
43
44
  export declare function convertToQueryString(query: Record<string, string | string[]>): string;
@@ -56,7 +57,9 @@ export declare function getMiddlewareMatch(middlewareManifest: MiddlewareManifes
56
57
  *
57
58
  * @__PURE__
58
59
  */
59
- export declare function escapeRegex(str: string): string;
60
+ export declare function escapeRegex(str: string, { isPath }?: {
61
+ isPath?: boolean;
62
+ }): string;
60
63
  /**
61
64
  *
62
65
  * @__PURE__
@@ -2,7 +2,7 @@ import crypto from "node:crypto";
2
2
  import { Readable } from "node:stream";
3
3
  import { BuildId, HtmlPages, NextConfig } from "../../adapters/config/index.js";
4
4
  import { OpenNextNodeResponse } from "../../http/openNextResponse.js";
5
- import { parseHeaders } from "../../http/util.js";
5
+ import { getQueryFromIterator, 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";
@@ -24,11 +24,10 @@ export function convertFromQueryString(query) {
24
24
  if (query === "")
25
25
  return {};
26
26
  const queryParts = query.split("&");
27
- return queryParts.reduce((acc, part) => {
28
- const [key, value] = part.split("=");
29
- acc[key] = value;
30
- return acc;
31
- }, {});
27
+ return getQueryFromIterator(queryParts.map((p) => {
28
+ const [key, value] = p.split("=");
29
+ return [key, value];
30
+ }));
32
31
  }
33
32
  /**
34
33
  *
@@ -99,23 +98,20 @@ export function convertRes(res) {
99
98
  * Make sure that multi-value query parameters are transformed to
100
99
  * ?key=value1&key=value2&... so that Next converts those parameters
101
100
  * to an array when reading the query parameters
101
+ * query should be properly encoded before using this function
102
102
  * @__PURE__
103
103
  */
104
104
  export function convertToQueryString(query) {
105
- // URLSearchParams is a representation of the PARSED query.
106
- // So we must decode the value before appending it to the URLSearchParams.
107
- // https://stackoverflow.com/a/45516812
108
- const urlQuery = new URLSearchParams();
105
+ const queryStrings = [];
109
106
  Object.entries(query).forEach(([key, value]) => {
110
107
  if (Array.isArray(value)) {
111
- value.forEach((entry) => urlQuery.append(key, decodeURIComponent(entry)));
108
+ value.forEach((entry) => queryStrings.push(`${key}=${entry}`));
112
109
  }
113
110
  else {
114
- urlQuery.append(key, decodeURIComponent(value));
111
+ queryStrings.push(`${key}=${value}`);
115
112
  }
116
113
  });
117
- const queryString = urlQuery.toString();
118
- return queryString ? `?${queryString}` : "";
114
+ return queryStrings.length > 0 ? `?${queryStrings.join("&")}` : "";
119
115
  }
120
116
  /**
121
117
  * Given a raw query string, returns a record with key value-array pairs
@@ -148,11 +144,12 @@ export function getMiddlewareMatch(middlewareManifest, functionsManifest) {
148
144
  *
149
145
  * @__PURE__
150
146
  */
151
- export function escapeRegex(str) {
152
- return str
147
+ export function escapeRegex(str, { isPath } = {}) {
148
+ const result = str
153
149
  .replaceAll("(.)", "_µ1_")
154
150
  .replaceAll("(..)", "_µ2_")
155
151
  .replaceAll("(...)", "_µ3_");
152
+ return isPath ? result : result.replaceAll("+", "_µ4_");
156
153
  }
157
154
  /**
158
155
  *
@@ -162,7 +159,8 @@ export function unescapeRegex(str) {
162
159
  return str
163
160
  .replaceAll("_µ1_", "(.)")
164
161
  .replaceAll("_µ2_", "(..)")
165
- .replaceAll("_µ3_", "(...)");
162
+ .replaceAll("_µ3_", "(...)")
163
+ .replaceAll("_µ4_", "+");
166
164
  }
167
165
  /**
168
166
  * @__PURE__
@@ -58,7 +58,11 @@ export default async function routingHandler(event) {
58
58
  debug("redirect", redirect);
59
59
  return redirect;
60
60
  }
61
- const eventOrResult = await handleMiddleware(internalEvent);
61
+ const eventOrResult = await handleMiddleware(internalEvent,
62
+ // We need to pass the initial search without any decoding
63
+ // TODO: we'd need to refactor InternalEvent to include the initial querystring directly
64
+ // Should be done in another PR because it is a breaking change
65
+ new URL(event.url).search);
62
66
  const isResult = "statusCode" in eventOrResult;
63
67
  if (isResult) {
64
68
  return eventOrResult;
package/dist/core/util.js CHANGED
@@ -1,7 +1,7 @@
1
1
  import { AppPathsManifestKeys, NextConfig, RoutesManifest, } from "../adapters/config/index.js";
2
2
  // @ts-ignore
3
3
  import NextServer from "next/dist/server/next-server.js";
4
- import { debug } from "../adapters/logger.js";
4
+ import { debug, error } from "../adapters/logger.js";
5
5
  import { applyOverride as applyNextjsRequireHooksOverride, overrideHooks as overrideNextjsRequireHooks, } from "./require-hooks.js";
6
6
  // WORKAROUND: Set `__NEXT_PRIVATE_PREBUNDLED_REACT` to use prebundled Reac
7
7
  // See https://opennext.js.org/aws/v2/advanced/workaround#workaround-set-__next_private_prebundled_react-to-use-prebundled-react
@@ -48,6 +48,46 @@ const nextServer = new NextServer.default({
48
48
  dev: false,
49
49
  dir: __dirname,
50
50
  });
51
+ let routesLoaded = false;
52
+ globalThis.__next_route_preloader = async (stage) => {
53
+ if (routesLoaded) {
54
+ return;
55
+ }
56
+ const thisFunction = globalThis.fnName
57
+ ? globalThis.openNextConfig.functions[globalThis.fnName]
58
+ : globalThis.openNextConfig.default;
59
+ const routePreloadingBehavior = thisFunction?.routePreloadingBehavior ?? "none";
60
+ if (routePreloadingBehavior === "none") {
61
+ routesLoaded = true;
62
+ return;
63
+ }
64
+ if (!("unstable_preloadEntries" in nextServer)) {
65
+ debug("The current version of Next.js does not support route preloading. Skipping route preloading.");
66
+ routesLoaded = true;
67
+ return;
68
+ }
69
+ if (stage === "waitUntil" && routePreloadingBehavior === "withWaitUntil") {
70
+ // We need to access the waitUntil
71
+ const waitUntil = globalThis.__openNextAls.getStore()?.waitUntil;
72
+ if (!waitUntil) {
73
+ error("You've tried to use the 'withWaitUntil' route preloading behavior, but the 'waitUntil' function is not available.");
74
+ routesLoaded = true;
75
+ return;
76
+ }
77
+ debug("Preloading entries with waitUntil");
78
+ waitUntil?.(nextServer.unstable_preloadEntries());
79
+ routesLoaded = true;
80
+ }
81
+ else if ((stage === "start" && routePreloadingBehavior === "onStart") ||
82
+ (stage === "warmerEvent" && routePreloadingBehavior === "onWarmerEvent") ||
83
+ stage === "onDemand") {
84
+ const startTimestamp = Date.now();
85
+ debug("Preloading entries");
86
+ await nextServer.unstable_preloadEntries();
87
+ debug("Preloading entries took", Date.now() - startTimestamp, "ms");
88
+ routesLoaded = true;
89
+ }
90
+ };
51
91
  // `getRequestHandlerWithMetadata` is not available in older versions of Next.js
52
92
  // It is required to for next 15.2 to pass metadata for page router data route
53
93
  export const requestHandler = (metadata) => "getRequestHandlerWithMetadata" in nextServer
@@ -2,3 +2,10 @@ import type http from "node:http";
2
2
  export declare const parseHeaders: (headers?: http.OutgoingHttpHeader[] | http.OutgoingHttpHeaders) => Record<string, string>;
3
3
  export declare const convertHeader: (header: http.OutgoingHttpHeader) => string;
4
4
  export declare function parseCookies(cookies: string | string[] | null | undefined): string[];
5
+ /**
6
+ *
7
+ * Get the query object from an iterable of [key, value] pairs
8
+ * @param it - The iterable of [key, value] pairs
9
+ * @returns The query object
10
+ */
11
+ export declare function getQueryFromIterator(it: Iterable<[string, string]>): Record<string, string | string[]>;
package/dist/http/util.js CHANGED
@@ -28,3 +28,26 @@ export function parseCookies(cookies) {
28
28
  ? cookies.split(/(?<!Expires=\w+),/i).map((c) => c.trim())
29
29
  : cookies;
30
30
  }
31
+ /**
32
+ *
33
+ * Get the query object from an iterable of [key, value] pairs
34
+ * @param it - The iterable of [key, value] pairs
35
+ * @returns The query object
36
+ */
37
+ export function getQueryFromIterator(it) {
38
+ const query = {};
39
+ for (const [key, value] of it) {
40
+ if (key in query) {
41
+ if (Array.isArray(query[key])) {
42
+ query[key].push(value);
43
+ }
44
+ else {
45
+ query[key] = [query[key], value];
46
+ }
47
+ }
48
+ else {
49
+ query[key] = value;
50
+ }
51
+ }
52
+ return query;
53
+ }
@@ -1,23 +1,11 @@
1
1
  import { Buffer } from "node:buffer";
2
2
  import { parseCookies } from "../../http/util";
3
+ import { getQueryFromSearchParams } from "./utils.js";
3
4
  const converter = {
4
5
  convertFrom: async (event) => {
5
6
  const url = new URL(event.url);
6
7
  const searchParams = url.searchParams;
7
- const query = {};
8
- for (const [key, value] of searchParams.entries()) {
9
- if (key in query) {
10
- if (Array.isArray(query[key])) {
11
- query[key].push(value);
12
- }
13
- else {
14
- query[key] = [query[key], value];
15
- }
16
- }
17
- else {
18
- query[key] = value;
19
- }
20
- }
8
+ const query = getQueryFromSearchParams(searchParams);
21
9
  // Transform body into Buffer
22
10
  const body = await event.arrayBuffer();
23
11
  const headers = {};
@@ -1,5 +1,5 @@
1
1
  import { parseCookies } from "../../http/util";
2
- import { extractHostFromHeaders } from "./utils";
2
+ import { extractHostFromHeaders, getQueryFromSearchParams } from "./utils.js";
3
3
  const converter = {
4
4
  convertFrom: async (req) => {
5
5
  const body = await new Promise((resolve) => {
@@ -18,7 +18,7 @@ const converter = {
18
18
  ])
19
19
  .filter(([key]) => key));
20
20
  const url = new URL(req.url, `http://${extractHostFromHeaders(headers)}`);
21
- const query = Object.fromEntries(url.searchParams.entries());
21
+ const query = getQueryFromSearchParams(url.searchParams);
22
22
  return {
23
23
  type: "core",
24
24
  method: req.method ?? "GET",
@@ -6,3 +6,10 @@ export declare function removeUndefinedFromQuery(query: Record<string, string |
6
6
  * @returns The host
7
7
  */
8
8
  export declare function extractHostFromHeaders(headers: Record<string, string>): string;
9
+ /**
10
+ * Get the query object from an URLSearchParams
11
+ *
12
+ * @param searchParams
13
+ * @returns
14
+ */
15
+ export declare function getQueryFromSearchParams(searchParams: URLSearchParams): Record<string, string | string[]>;
@@ -1,3 +1,4 @@
1
+ import { getQueryFromIterator } from "../../http/util.js";
1
2
  export function removeUndefinedFromQuery(query) {
2
3
  const newQuery = {};
3
4
  for (const [key, value] of Object.entries(query)) {
@@ -16,3 +17,12 @@ export function removeUndefinedFromQuery(query) {
16
17
  export function extractHostFromHeaders(headers) {
17
18
  return headers["x-forwarded-host"] ?? headers.host ?? "on";
18
19
  }
20
+ /**
21
+ * Get the query object from an URLSearchParams
22
+ *
23
+ * @param searchParams
24
+ * @returns
25
+ */
26
+ export function getQueryFromSearchParams(searchParams) {
27
+ return getQueryFromIterator(searchParams.entries());
28
+ }
@@ -0,0 +1,7 @@
1
+ import type { WrapperHandler } from "../../types/overrides";
2
+ declare const _default: {
3
+ wrapper: WrapperHandler;
4
+ name: string;
5
+ supportStreaming: boolean;
6
+ };
7
+ export default _default;
@@ -0,0 +1,96 @@
1
+ import { Readable, Writable } from "node:stream";
2
+ import zlib from "node:zlib";
3
+ import { error } from "../../adapters/logger";
4
+ import { formatWarmerResponse } from "./aws-lambda";
5
+ const handler = async (handler, converter) => async (event) => {
6
+ // Handle warmer event
7
+ if ("type" in event) {
8
+ return formatWarmerResponse(event);
9
+ }
10
+ const internalEvent = await converter.convertFrom(event);
11
+ // This is a workaround
12
+ // https://github.com/opennextjs/opennextjs-aws/blob/e9b37fd44eb856eb8ae73168bf455ff85dd8b285/packages/open-next/src/overrides/wrappers/aws-lambda.ts#L49-L53
13
+ const fakeStream = {
14
+ writeHeaders: () => {
15
+ return new Writable({
16
+ write: (_chunk, _encoding, callback) => {
17
+ callback();
18
+ },
19
+ });
20
+ },
21
+ };
22
+ const handlerResponse = await handler(internalEvent, {
23
+ streamCreator: fakeStream,
24
+ });
25
+ // Check if response is already compressed
26
+ // The handlers response headers are lowercase
27
+ const alreadyEncoded = handlerResponse.headers["content-encoding"] ?? "";
28
+ // Return early here if the response is already compressed
29
+ if (alreadyEncoded) {
30
+ return converter.convertTo(handlerResponse, event);
31
+ }
32
+ // We compress the body if the client accepts it
33
+ const acceptEncoding = internalEvent.headers["accept-encoding"] ??
34
+ internalEvent.headers["Accept-Encoding"] ??
35
+ "";
36
+ let contentEncoding = null;
37
+ if (acceptEncoding?.includes("br")) {
38
+ contentEncoding = "br";
39
+ }
40
+ else if (acceptEncoding?.includes("gzip")) {
41
+ contentEncoding = "gzip";
42
+ }
43
+ else if (acceptEncoding?.includes("deflate")) {
44
+ contentEncoding = "deflate";
45
+ }
46
+ const response = {
47
+ ...handlerResponse,
48
+ body: compressBody(handlerResponse.body, contentEncoding),
49
+ headers: {
50
+ ...handlerResponse.headers,
51
+ ...(contentEncoding ? { "content-encoding": contentEncoding } : {}),
52
+ },
53
+ isBase64Encoded: !!contentEncoding || handlerResponse.isBase64Encoded,
54
+ };
55
+ return converter.convertTo(response, event);
56
+ };
57
+ export default {
58
+ wrapper: handler,
59
+ name: "aws-lambda-compressed",
60
+ supportStreaming: false,
61
+ };
62
+ function compressBody(body, encoding) {
63
+ // If no encoding is specified, return original body
64
+ if (!encoding)
65
+ return body;
66
+ try {
67
+ const readable = Readable.fromWeb(body);
68
+ let transform;
69
+ switch (encoding) {
70
+ case "br":
71
+ transform = zlib.createBrotliCompress({
72
+ params: {
73
+ // This is a compromise between speed and compression ratio.
74
+ // The default one will most likely timeout an AWS Lambda with default configuration on large bodies (>6mb).
75
+ // Therefore we set it to 6, which is a good compromise.
76
+ [zlib.constants.BROTLI_PARAM_QUALITY]: Number(process.env.BROTLI_QUALITY) ?? 6,
77
+ },
78
+ });
79
+ break;
80
+ case "gzip":
81
+ transform = zlib.createGzip();
82
+ break;
83
+ case "deflate":
84
+ transform = zlib.createDeflate();
85
+ break;
86
+ default:
87
+ return body;
88
+ }
89
+ return Readable.toWeb(readable.pipe(transform));
90
+ }
91
+ catch (e) {
92
+ error("Error compressing body:", e);
93
+ // Fall back to no compression on error
94
+ return body;
95
+ }
96
+ }
@@ -14,6 +14,7 @@ const handler = async (handler, converter) => awslambda.streamifyResponse(async
14
14
  if ("type" in event) {
15
15
  const result = await formatWarmerResponse(event);
16
16
  responseStream.end(Buffer.from(JSON.stringify(result)), "utf-8");
17
+ await globalThis.__next_route_preloader("warmerEvent");
17
18
  return;
18
19
  }
19
20
  const internalEvent = await converter.convertFrom(event);
@@ -1,4 +1,6 @@
1
1
  import type { WrapperHandler } from "../../types/overrides";
2
+ import type { WarmerEvent, WarmerResponse } from "../../adapters/warmer-function";
3
+ export declare function formatWarmerResponse(event: WarmerEvent): Promise<WarmerResponse>;
2
4
  declare const _default: {
3
5
  wrapper: WrapperHandler;
4
6
  name: string;
@@ -1,5 +1,5 @@
1
1
  import { Writable } from "node:stream";
2
- function formatWarmerResponse(event) {
2
+ export function formatWarmerResponse(event) {
3
3
  return new Promise((resolve) => {
4
4
  setTimeout(() => {
5
5
  resolve({ serverId, type: "warmer" });
@@ -1,5 +1,6 @@
1
1
  import type { Writable } from "node:stream";
2
- import type { APIGatewayProxyEventV2, Context } from "aws-lambda";
2
+ import type { APIGatewayProxyEvent, APIGatewayProxyEventV2, APIGatewayProxyResult, APIGatewayProxyResultV2, CloudFrontRequestEvent, CloudFrontRequestResult, Context } from "aws-lambda";
3
+ import type { WarmerEvent, WarmerResponse } from "../adapters/warmer-function";
3
4
  export interface ResponseStream extends Writable {
4
5
  getBufferedData(): Buffer;
5
6
  setContentType(contentType: string): void;
@@ -17,4 +18,6 @@ declare global {
17
18
  }
18
19
  }
19
20
  }
21
+ export type AwsLambdaEvent = APIGatewayProxyEventV2 | CloudFrontRequestEvent | APIGatewayProxyEvent | WarmerEvent;
22
+ export type AwsLambdaReturn = APIGatewayProxyResultV2 | APIGatewayProxyResult | CloudFrontRequestResult | WarmerResponse;
20
23
  export {};
@@ -179,5 +179,11 @@ declare global {
179
179
  * Defined in `createMainHandler`
180
180
  */
181
181
  var cdnInvalidationHandler: CDNInvalidationHandler;
182
+ /**
183
+ * A function to preload the routes.
184
+ * This needs to be defined on globalThis because it can be used by custom overrides.
185
+ * Only available in main functions.
186
+ */
187
+ var __next_route_preloader: (stage: "waitUntil" | "start" | "warmerEvent" | "onDemand") => Promise<void>;
182
188
  }
183
189
  export {};
@@ -67,13 +67,23 @@ export interface Origin {
67
67
  port?: number;
68
68
  customHeaders?: Record<string, string>;
69
69
  }
70
- export type IncludedWrapper = "aws-lambda" | "aws-lambda-streaming" | "node" | "cloudflare" | "cloudflare-edge" | "cloudflare-node" | "express-dev" | "dummy";
70
+ export type IncludedWrapper = "aws-lambda" | "aws-lambda-streaming" | "aws-lambda-compressed" | "node" | "cloudflare" | "cloudflare-edge" | "cloudflare-node" | "express-dev" | "dummy";
71
71
  export type IncludedConverter = "aws-apigw-v2" | "aws-apigw-v1" | "aws-cloudfront" | "edge" | "node" | "sqs-revalidate" | "dummy";
72
72
  export type RouteType = "route" | "page" | "app";
73
73
  export interface ResolvedRoute {
74
74
  route: string;
75
75
  type: RouteType;
76
76
  }
77
+ /**
78
+ * The route preloading behavior. Only supported in Next 15+.
79
+ * Default behavior of Next is disabled. You should do your own testing to choose which one suits you best
80
+ * - "none" - No preloading of the route at all. This is the default
81
+ * - "withWaitUntil" - Preload the route using the waitUntil provided by the wrapper - If not supported, it will fallback to "none". At the moment only cloudflare wrappers provide a `waitUntil`
82
+ * - "onWarmerEvent" - Preload the route on the warmer event - Needs to be implemented by the wrapper. Only supported in `aws-lambda-streaming` wrapper for now
83
+ * - "onStart" - Preload the route before even invoking the wrapper - This is a blocking operation. The handler will only be created after all the routes have been loaded, it may increase the cold start time by a lot in some cases. Useful for long running server or in serverless with some careful testing
84
+ * @default "none"
85
+ */
86
+ export type RoutePreloadingBehavior = "none" | "withWaitUntil" | "onWarmerEvent" | "onStart";
77
87
  export interface RoutingResult {
78
88
  internalEvent: InternalEvent;
79
89
  isExternalRewrite: boolean;
@@ -212,6 +222,7 @@ export interface FunctionOptions extends DefaultFunctionOptions {
212
222
  * @deprecated This is not supported in 14.2+
213
223
  */
214
224
  experimentalBundledNextServer?: boolean;
225
+ routePreloadingBehavior?: RoutePreloadingBehavior;
215
226
  }
216
227
  export type RouteTemplate = `app/${string}/route` | `app/${string}/page` | `app/page` | `app/route` | `pages/${string}`;
217
228
  export interface SplittedFunctionOptions extends FunctionOptions {
package/package.json CHANGED
@@ -3,7 +3,7 @@
3
3
  "access": "public"
4
4
  },
5
5
  "name": "@opennextjs/aws",
6
- "version": "3.5.5",
6
+ "version": "3.5.7",
7
7
  "bin": {
8
8
  "open-next": "./dist/index.js"
9
9
  },