@opennextjs/aws 3.4.1 → 3.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (86) hide show
  1. package/dist/adapters/cache.d.ts +2 -61
  2. package/dist/adapters/cache.js +75 -37
  3. package/dist/adapters/config/index.d.ts +1 -0
  4. package/dist/adapters/config/index.js +3 -1
  5. package/dist/adapters/config/util.d.ts +2 -1
  6. package/dist/adapters/config/util.js +13 -3
  7. package/dist/adapters/dynamo-provider.js +8 -0
  8. package/dist/adapters/edge-adapter.js +4 -10
  9. package/dist/adapters/image-optimization-adapter.d.ts +3 -2
  10. package/dist/adapters/image-optimization-adapter.js +3 -3
  11. package/dist/adapters/middleware.js +12 -8
  12. package/dist/build/compileConfig.js +1 -1
  13. package/dist/build/compileTagCacheProvider.js +1 -0
  14. package/dist/build/constant.d.ts +1 -0
  15. package/dist/build/constant.js +2 -0
  16. package/dist/build/copyTracedFiles.d.ts +10 -1
  17. package/dist/build/copyTracedFiles.js +22 -12
  18. package/dist/build/createAssets.d.ts +3 -1
  19. package/dist/build/createAssets.js +3 -3
  20. package/dist/build/createMiddleware.js +18 -5
  21. package/dist/build/createServerBundle.js +7 -1
  22. package/dist/build/edge/createEdgeBundle.js +14 -12
  23. package/dist/build/generateOutput.js +5 -7
  24. package/dist/build/helper.d.ts +1 -1
  25. package/dist/build/helper.js +9 -2
  26. package/dist/build/middleware/buildNodeMiddleware.d.ts +3 -0
  27. package/dist/build/middleware/buildNodeMiddleware.js +97 -0
  28. package/dist/core/createMainHandler.js +0 -1
  29. package/dist/core/edgeFunctionHandler.js +3 -1
  30. package/dist/core/nodeMiddlewareHandler.d.ts +4 -0
  31. package/dist/core/nodeMiddlewareHandler.js +25 -0
  32. package/dist/core/requestHandler.d.ts +3 -2
  33. package/dist/core/requestHandler.js +58 -25
  34. package/dist/core/routing/cacheInterceptor.js +10 -6
  35. package/dist/core/routing/i18n/index.d.ts +2 -0
  36. package/dist/core/routing/i18n/index.js +1 -1
  37. package/dist/core/routing/matcher.js +17 -14
  38. package/dist/core/routing/middleware.js +14 -29
  39. package/dist/core/routing/queue.d.ts +1 -0
  40. package/dist/core/routing/queue.js +41 -0
  41. package/dist/core/routing/util.d.ts +12 -3
  42. package/dist/core/routing/util.js +24 -50
  43. package/dist/core/routingHandler.d.ts +2 -1
  44. package/dist/core/routingHandler.js +10 -4
  45. package/dist/core/util.d.ts +1 -4
  46. package/dist/core/util.js +7 -15
  47. package/dist/overrides/converters/aws-apigw-v1.js +4 -3
  48. package/dist/overrides/converters/aws-apigw-v2.js +4 -3
  49. package/dist/overrides/converters/aws-cloudfront.js +6 -4
  50. package/dist/overrides/converters/edge.js +1 -10
  51. package/dist/overrides/converters/node.js +10 -8
  52. package/dist/overrides/converters/utils.d.ts +7 -0
  53. package/dist/overrides/converters/utils.js +9 -0
  54. package/dist/overrides/imageLoader/s3-lite.d.ts +3 -0
  55. package/dist/overrides/imageLoader/s3-lite.js +61 -0
  56. package/dist/overrides/incrementalCache/multi-tier-ddb-s3.js +1 -1
  57. package/dist/overrides/proxyExternalRequest/fetch.js +2 -1
  58. package/dist/overrides/tagCache/dummy.js +1 -0
  59. package/dist/overrides/tagCache/dynamodb-lite.js +1 -0
  60. package/dist/overrides/tagCache/dynamodb-nextMode.d.ts +7 -0
  61. package/dist/overrides/tagCache/dynamodb-nextMode.js +119 -0
  62. package/dist/overrides/tagCache/dynamodb.js +1 -0
  63. package/dist/overrides/tagCache/fs-dev.js +1 -0
  64. package/dist/overrides/wrappers/aws-lambda-streaming.js +1 -1
  65. package/dist/overrides/wrappers/aws-lambda.js +3 -1
  66. package/dist/overrides/wrappers/cloudflare-edge.js +3 -2
  67. package/dist/overrides/wrappers/cloudflare-node.js +10 -8
  68. package/dist/overrides/wrappers/dummy.js +2 -2
  69. package/dist/overrides/wrappers/express-dev.js +4 -4
  70. package/dist/overrides/wrappers/node.js +2 -2
  71. package/dist/plugins/edge.d.ts +1 -3
  72. package/dist/plugins/edge.js +4 -11
  73. package/dist/plugins/externalMiddleware.d.ts +2 -0
  74. package/dist/plugins/externalMiddleware.js +10 -0
  75. package/dist/plugins/resolve.js +2 -2
  76. package/dist/types/cache.d.ts +34 -4
  77. package/dist/types/global.d.ts +19 -31
  78. package/dist/types/next-types.d.ts +13 -0
  79. package/dist/types/open-next.d.ts +12 -5
  80. package/dist/types/overrides.d.ts +73 -7
  81. package/dist/utils/cache.d.ts +3 -0
  82. package/dist/utils/cache.js +32 -0
  83. package/dist/utils/promise.d.ts +3 -1
  84. package/dist/utils/promise.js +18 -11
  85. package/dist/utils/regex.js +1 -1
  86. package/package.json +5 -4
@@ -1,9 +1,11 @@
1
1
  import fs from "node:fs";
2
2
  import path from "node:path";
3
+ import { loadFunctionsConfigManifest, loadMiddlewareManifest, } from "../adapters/config/util.js";
3
4
  import logger from "../logger.js";
4
5
  import { buildEdgeBundle } from "./edge/createEdgeBundle.js";
5
6
  import * as buildHelper from "./helper.js";
6
7
  import { installDependencies } from "./installDeps.js";
8
+ import { buildBundledNodeMiddleware, buildExternalNodeMiddleware, } from "./middleware/buildNodeMiddleware.js";
7
9
  /**
8
10
  * Compiles the middleware bundle.
9
11
  *
@@ -12,10 +14,21 @@ import { installDependencies } from "./installDeps.js";
12
14
  */
13
15
  export async function createMiddleware(options, { forceOnlyBuildOnce = false } = {}) {
14
16
  logger.info("Bundling middleware function...");
15
- const { appBuildOutputPath, config, outputDir } = options;
17
+ const { config, outputDir } = options;
18
+ const buildOutputDotNextDir = path.join(options.appBuildOutputPath, ".next");
16
19
  // Get middleware manifest
17
- const middlewareManifest = JSON.parse(fs.readFileSync(path.join(appBuildOutputPath, ".next/server/middleware-manifest.json"), "utf8"));
18
- const middlewareInfo = middlewareManifest.middleware["/"];
20
+ const middlewareManifest = loadMiddlewareManifest(buildOutputDotNextDir);
21
+ const edgeMiddlewareInfo = middlewareManifest.middleware["/"];
22
+ if (!edgeMiddlewareInfo) {
23
+ // If there is no middleware info, it might be a node middleware
24
+ const functionsConfigManifest = loadFunctionsConfigManifest(buildOutputDotNextDir);
25
+ if (functionsConfigManifest?.functions["/_middleware"]) {
26
+ await (config.middleware?.external
27
+ ? buildExternalNodeMiddleware(options)
28
+ : buildBundledNodeMiddleware(options));
29
+ return;
30
+ }
31
+ }
19
32
  if (config.middleware?.external) {
20
33
  const outputPath = path.join(outputDir, "middleware");
21
34
  fs.mkdirSync(outputPath, { recursive: true });
@@ -25,7 +38,7 @@ export async function createMiddleware(options, { forceOnlyBuildOnce = false } =
25
38
  await buildEdgeBundle({
26
39
  entrypoint: path.join(options.openNextDistDir, "adapters", "middleware.js"),
27
40
  outfile: path.join(outputPath, "handler.mjs"),
28
- middlewareInfo,
41
+ middlewareInfo: edgeMiddlewareInfo,
29
42
  options,
30
43
  overrides: {
31
44
  ...config.middleware.override,
@@ -43,7 +56,7 @@ export async function createMiddleware(options, { forceOnlyBuildOnce = false } =
43
56
  await buildEdgeBundle({
44
57
  entrypoint: path.join(options.openNextDistDir, "core", "edgeFunctionHandler.js"),
45
58
  outfile: path.join(options.buildDir, "middleware.mjs"),
46
- middlewareInfo,
59
+ middlewareInfo: edgeMiddlewareInfo,
47
60
  options,
48
61
  onlyBuildOnce: true,
49
62
  name: "middleware",
@@ -100,7 +100,13 @@ async function generateBundle(name, options, fnOptions) {
100
100
  // Copy env files
101
101
  buildHelper.copyEnvFile(appBuildOutputPath, packagePath, outputPath);
102
102
  // Copy all necessary traced files
103
- await copyTracedFiles(appBuildOutputPath, packagePath, outputPath, fnOptions.routes ?? ["app/page.tsx"], isBundled);
103
+ await copyTracedFiles({
104
+ buildOutputPath: appBuildOutputPath,
105
+ packagePath,
106
+ outputDir: outputPath,
107
+ routes: fnOptions.routes ?? ["app/page.tsx"],
108
+ bundledNextServer: isBundled,
109
+ });
104
110
  // Build Lambda code
105
111
  // note: bundle in OpenNext package b/c the adapter relies on the
106
112
  // "serverless-http" package which is not a dependency in user's
@@ -2,8 +2,10 @@ import { mkdirSync } from "node:fs";
2
2
  import fs from "node:fs";
3
3
  import path from "node:path";
4
4
  import { build } from "esbuild";
5
+ import { loadMiddlewareManifest } from "../../adapters/config/util.js";
5
6
  import logger from "../../logger.js";
6
7
  import { openNextEdgePlugins } from "../../plugins/edge.js";
8
+ import { openNextExternalMiddlewarePlugin } from "../../plugins/externalMiddleware.js";
7
9
  import { openNextReplacementPlugin } from "../../plugins/replacement.js";
8
10
  import { openNextResolvePlugin } from "../../plugins/resolve.js";
9
11
  import { getCrossPlatformPathRegex } from "../../utils/regex.js";
@@ -45,10 +47,10 @@ export async function buildEdgeBundle({ middlewareInfo, entrypoint, outfile, opt
45
47
  target: getCrossPlatformPathRegex("adapters/middleware.js"),
46
48
  deletes: includeCache ? [] : ["includeCacheInMiddleware"],
47
49
  }),
50
+ openNextExternalMiddlewarePlugin(path.join(options.openNextDistDir, "core/edgeFunctionHandler.js")),
48
51
  openNextEdgePlugins({
49
52
  middlewareInfo,
50
53
  nextDir: path.join(options.appBuildOutputPath, ".next"),
51
- edgeFunctionHandlerPath: path.join(options.openNextDistDir, "core", "edgeFunctionHandler.js"),
52
54
  isInCloudfare,
53
55
  }),
54
56
  ],
@@ -116,15 +118,15 @@ Object.defineProperty = function(o, p, a) {
116
118
  }
117
119
  }
118
120
  export async function generateEdgeBundle(name, options, fnOptions) {
119
- const { appBuildOutputPath, outputDir } = options;
120
121
  logger.info(`Generating edge bundle for: ${name}`);
122
+ const buildOutputDotNextDir = path.join(options.appBuildOutputPath, ".next");
121
123
  // Create output folder
122
- const outputPath = path.join(outputDir, "server-functions", name);
123
- fs.mkdirSync(outputPath, { recursive: true });
124
+ const outputDir = path.join(options.outputDir, "server-functions", name);
125
+ fs.mkdirSync(outputDir, { recursive: true });
124
126
  // Copy open-next.config.mjs
125
- copyOpenNextConfig(options.buildDir, outputPath, true);
127
+ copyOpenNextConfig(options.buildDir, outputDir, true);
126
128
  // Load middleware manifest
127
- const middlewareManifest = JSON.parse(fs.readFileSync(path.join(appBuildOutputPath, ".next/server/middleware-manifest.json"), "utf8"));
129
+ const middlewareManifest = loadMiddlewareManifest(buildOutputDotNextDir);
128
130
  // Find functions
129
131
  const functions = Object.values(middlewareManifest.functions).filter((fn) => fnOptions.routes.includes(fn.name));
130
132
  if (functions.length > 1) {
@@ -133,20 +135,20 @@ export async function generateEdgeBundle(name, options, fnOptions) {
133
135
  const middlewareInfo = functions[0];
134
136
  //Copy wasm files
135
137
  const wasmFiles = middlewareInfo.wasm;
136
- mkdirSync(path.join(outputPath, "wasm"), { recursive: true });
138
+ mkdirSync(path.join(outputDir, "wasm"), { recursive: true });
137
139
  for (const wasmFile of wasmFiles) {
138
- fs.copyFileSync(path.join(appBuildOutputPath, ".next", wasmFile.filePath), path.join(outputPath, `wasm/${wasmFile.name}.wasm`));
140
+ fs.copyFileSync(path.join(buildOutputDotNextDir, wasmFile.filePath), path.join(outputDir, `wasm/${wasmFile.name}.wasm`));
139
141
  }
140
142
  // Copy assets
141
143
  const assets = middlewareInfo.assets;
142
- mkdirSync(path.join(outputPath, "assets"), { recursive: true });
144
+ mkdirSync(path.join(outputDir, "assets"), { recursive: true });
143
145
  for (const asset of assets) {
144
- fs.copyFileSync(path.join(appBuildOutputPath, ".next", asset.filePath), path.join(outputPath, `assets/${asset.name}`));
146
+ fs.copyFileSync(path.join(buildOutputDotNextDir, asset.filePath), path.join(outputDir, `assets/${asset.name}`));
145
147
  }
146
148
  await buildEdgeBundle({
147
149
  middlewareInfo,
148
- entrypoint: path.join(options.openNextDistDir, "adapters", "edge-adapter.js"),
149
- outfile: path.join(outputPath, "index.mjs"),
150
+ entrypoint: path.join(options.openNextDistDir, "adapters/edge-adapter.js"),
151
+ outfile: path.join(outputDir, "index.mjs"),
150
152
  options,
151
153
  overrides: fnOptions.override,
152
154
  additionalExternals: options.config.edgeExternals,
@@ -1,5 +1,6 @@
1
1
  import * as fs from "node:fs";
2
2
  import path from "node:path";
3
+ import { loadConfig } from "../adapters/config/util.js";
3
4
  import { getBuildId } from "./helper.js";
4
5
  const indexHandler = "index.handler";
5
6
  async function canStream(opts) {
@@ -78,9 +79,8 @@ export async function generateOutput(options) {
78
79
  }
79
80
  });
80
81
  const defaultOriginCanstream = await canStream(config.default);
81
- //Load required-server-files.json
82
- const requiredServerFiles = JSON.parse(fs.readFileSync(path.join(appBuildOutputPath, ".next", "required-server-files.json"), "utf-8")).config;
83
- const prefixer = prefixPattern(requiredServerFiles.basePath ?? "");
82
+ const nextConfig = loadConfig(path.join(appBuildOutputPath, ".next"));
83
+ const prefixer = prefixPattern(nextConfig.basePath ?? "");
84
84
  // First add s3 origins and image optimization
85
85
  const defaultOrigins = {
86
86
  s3: {
@@ -89,9 +89,7 @@ export async function generateOutput(options) {
89
89
  copy: [
90
90
  {
91
91
  from: ".open-next/assets",
92
- to: requiredServerFiles.basePath
93
- ? `_assets${requiredServerFiles.basePath}`
94
- : "_assets",
92
+ to: nextConfig.basePath ? `_assets${nextConfig.basePath}` : "_assets",
95
93
  cached: true,
96
94
  versionedSubDir: prefixer("_next"),
97
95
  },
@@ -167,7 +165,7 @@ export async function generateOutput(options) {
167
165
  const patterns = "patterns" in value ? value.patterns : ["*"];
168
166
  patterns.forEach((pattern) => {
169
167
  behaviors.push({
170
- pattern: prefixer(pattern.replace(/BUILD_ID/, getBuildId(appBuildOutputPath))),
168
+ pattern: prefixer(pattern.replace(/BUILD_ID/, getBuildId(options))),
171
169
  origin: value.placement === "global" ? undefined : key,
172
170
  edgeFunction: value.placement === "global"
173
171
  ? key
@@ -48,7 +48,7 @@ export declare function traverseFiles(root: string, conditionFn: (paths: Travers
48
48
  */
49
49
  export declare function removeFiles(root: string, conditionFn: (paths: TraversePath) => boolean): void;
50
50
  export declare function getHtmlPages(dotNextPath: string): Set<string>;
51
- export declare function getBuildId(dotNextPath: string): string;
51
+ export declare function getBuildId(options: BuildOptions): string;
52
52
  export declare function getOpenNextVersion(): string;
53
53
  export declare function getNextVersion(appPath: string): string;
54
54
  /**
@@ -101,12 +101,15 @@ export function esbuildSync(esbuildOptions, options) {
101
101
  }
102
102
  export async function esbuildAsync(esbuildOptions, options) {
103
103
  const { openNextVersion, debug, minify } = options;
104
+ // Dump ESBuild build metadata to file in debug mode
105
+ const metafile = debug && esbuildOptions.outfile !== undefined;
104
106
  const result = await buildAsync({
105
107
  target: "esnext",
106
108
  format: "esm",
107
109
  platform: "node",
108
110
  bundle: true,
109
111
  minify,
112
+ metafile,
110
113
  mainFields: ["module", "main"],
111
114
  sourcemap: debug ? "inline" : false,
112
115
  sourcesContent: false,
@@ -129,6 +132,10 @@ export async function esbuildAsync(esbuildOptions, options) {
129
132
  result.errors.forEach((error) => logger.error(error));
130
133
  throw new Error(`There was a problem bundling ${esbuildOptions.entryPoints[0]}.`);
131
134
  }
135
+ if (result.metafile) {
136
+ const metaFile = `${esbuildOptions.outfile}.meta.json`;
137
+ fs.writeFileSync(metaFile, JSON.stringify(result.metafile, null, 2));
138
+ }
132
139
  }
133
140
  /**
134
141
  * Recursively traverse files in a directory and call `callbackFn` when `conditionFn` returns true
@@ -180,9 +187,9 @@ export function getHtmlPages(dotNextPath) {
180
187
  .map(([_, value]) => value.replace(/^pages\//, ""))
181
188
  .reduce((acc, page) => acc.add(page), new Set());
182
189
  }
183
- export function getBuildId(dotNextPath) {
190
+ export function getBuildId(options) {
184
191
  return fs
185
- .readFileSync(path.join(dotNextPath, ".next/BUILD_ID"), "utf-8")
192
+ .readFileSync(path.join(options.appBuildOutputPath, ".next/BUILD_ID"), "utf-8")
186
193
  .trim();
187
194
  }
188
195
  export function getOpenNextVersion() {
@@ -0,0 +1,3 @@
1
+ import * as buildHelper from "../helper.js";
2
+ export declare function buildExternalNodeMiddleware(options: buildHelper.BuildOptions): Promise<void>;
3
+ export declare function buildBundledNodeMiddleware(options: buildHelper.BuildOptions): Promise<void>;
@@ -0,0 +1,97 @@
1
+ import fs from "node:fs";
2
+ import path from "node:path";
3
+ import { getCrossPlatformPathRegex } from "../../utils/regex.js";
4
+ import { openNextExternalMiddlewarePlugin } from "../../plugins/externalMiddleware.js";
5
+ import { openNextReplacementPlugin } from "../../plugins/replacement.js";
6
+ import { openNextResolvePlugin } from "../../plugins/resolve.js";
7
+ import { copyTracedFiles } from "../copyTracedFiles.js";
8
+ import * as buildHelper from "../helper.js";
9
+ import { installDependencies } from "../installDeps.js";
10
+ export async function buildExternalNodeMiddleware(options) {
11
+ const { appBuildOutputPath, config, outputDir } = options;
12
+ if (!config.middleware?.external) {
13
+ throw new Error("This function should only be called for external middleware");
14
+ }
15
+ const outputPath = path.join(outputDir, "middleware");
16
+ fs.mkdirSync(outputPath, { recursive: true });
17
+ // Copy open-next.config.mjs
18
+ buildHelper.copyOpenNextConfig(options.buildDir, outputPath, await buildHelper.isEdgeRuntime(config.middleware.override));
19
+ const overrides = {
20
+ ...config.middleware.override,
21
+ originResolver: config.middleware.originResolver,
22
+ };
23
+ const includeCache = config.dangerous?.enableCacheInterception;
24
+ const packagePath = buildHelper.getPackagePath(options);
25
+ // TODO: change this so that we don't copy unnecessary files
26
+ await copyTracedFiles({
27
+ buildOutputPath: appBuildOutputPath,
28
+ packagePath,
29
+ outputDir: outputPath,
30
+ routes: [],
31
+ bundledNextServer: false,
32
+ skipServerFiles: true,
33
+ });
34
+ function override(target) {
35
+ return typeof overrides?.[target] === "string"
36
+ ? overrides[target]
37
+ : undefined;
38
+ }
39
+ // Bundle middleware
40
+ await buildHelper.esbuildAsync({
41
+ entryPoints: [
42
+ path.join(options.openNextDistDir, "adapters", "middleware.js"),
43
+ ],
44
+ outfile: path.join(outputPath, "handler.mjs"),
45
+ external: ["./.next/*"],
46
+ platform: "node",
47
+ plugins: [
48
+ openNextResolvePlugin({
49
+ overrides: {
50
+ wrapper: override("wrapper") ?? "aws-lambda",
51
+ converter: override("converter") ?? "aws-cloudfront",
52
+ ...(includeCache
53
+ ? {
54
+ tagCache: override("tagCache") ?? "dynamodb-lite",
55
+ incrementalCache: override("incrementalCache") ?? "s3-lite",
56
+ queue: override("queue") ?? "sqs-lite",
57
+ }
58
+ : {}),
59
+ originResolver: override("originResolver") ?? "pattern-env",
60
+ proxyExternalRequest: override("proxyExternalRequest") ?? "node",
61
+ },
62
+ fnName: "middleware",
63
+ }),
64
+ openNextReplacementPlugin({
65
+ name: "externalMiddlewareOverrides",
66
+ target: getCrossPlatformPathRegex("adapters/middleware.js"),
67
+ deletes: includeCache ? [] : ["includeCacheInMiddleware"],
68
+ }),
69
+ openNextExternalMiddlewarePlugin(path.join(options.openNextDistDir, "core", "nodeMiddlewareHandler.js")),
70
+ ],
71
+ banner: {
72
+ js: [
73
+ `globalThis.monorepoPackagePath = '${packagePath}';`,
74
+ "import process from 'node:process';",
75
+ "import { Buffer } from 'node:buffer';",
76
+ "import { AsyncLocalStorage } from 'node:async_hooks';",
77
+ "import { createRequire as topLevelCreateRequire } from 'module';",
78
+ "const require = topLevelCreateRequire(import.meta.url);",
79
+ "import bannerUrl from 'url';",
80
+ "const __dirname = bannerUrl.fileURLToPath(new URL('.', import.meta.url));",
81
+ ].join(""),
82
+ },
83
+ }, options);
84
+ // Do we need to copy or do something with env file here?
85
+ installDependencies(outputPath, config.middleware?.install);
86
+ }
87
+ export async function buildBundledNodeMiddleware(options) {
88
+ await buildHelper.esbuildAsync({
89
+ entryPoints: [
90
+ path.join(options.openNextDistDir, "core/nodeMiddlewareHandler.js"),
91
+ ],
92
+ external: ["./.next/*"],
93
+ outfile: path.join(options.buildDir, "middleware.mjs"),
94
+ bundle: true,
95
+ platform: "node",
96
+ }, options);
97
+ }
@@ -16,7 +16,6 @@ export async function createMainHandler() {
16
16
  globalThis.tagCache = await resolveTagCache(thisFunction.override?.tagCache);
17
17
  globalThis.proxyExternalRequest = await resolveProxyRequest(thisFunction.override?.proxyExternalRequest);
18
18
  globalThis.cdnInvalidationHandler = await resolveCdnInvalidation(thisFunction.override?.cdnInvalidation);
19
- globalThis.lastModified = {};
20
19
  // From the config, we create the converter
21
20
  const converter = await resolveConverter(thisFunction.override?.converter);
22
21
  // Then we create the handler
@@ -16,7 +16,9 @@ export default async function edgeFunctionHandler(request) {
16
16
  },
17
17
  },
18
18
  });
19
- await result.waitUntil;
19
+ globalThis.__openNextAls
20
+ .getStore()
21
+ ?.pendingPromiseRunner.add(result.waitUntil);
20
22
  const response = result.response;
21
23
  return response;
22
24
  }
@@ -0,0 +1,4 @@
1
+ import type { RequestData } from "../types/global";
2
+ type EdgeRequest = Omit<RequestData, "page">;
3
+ export default function middlewareHandler(request: EdgeRequest): Promise<Response>;
4
+ export {};
@@ -0,0 +1,25 @@
1
+ // Do we need Buffer here?
2
+ import { Buffer } from "node:buffer";
3
+ globalThis.Buffer = Buffer;
4
+ // AsyncLocalStorage is needed to be defined globally
5
+ import { AsyncLocalStorage } from "node:async_hooks";
6
+ globalThis.AsyncLocalStorage = AsyncLocalStorage;
7
+ let _module;
8
+ export default async function middlewareHandler(request) {
9
+ if (!_module) {
10
+ // We use await import here so that we are sure that it is loaded after AsyncLocalStorage is defined on globalThis
11
+ // We need both await here, same way as in https://github.com/opennextjs/opennextjs-aws/pull/704
12
+ //@ts-expect-error - This file should be bundled with esbuild
13
+ _module = await (await import("./.next/server/middleware.js")).default;
14
+ }
15
+ const adapterFn = _module.default || _module;
16
+ const result = await adapterFn({
17
+ handler: _module.middleware || _module,
18
+ request: request,
19
+ page: "middleware",
20
+ });
21
+ globalThis.__openNextAls
22
+ .getStore()
23
+ ?.pendingPromiseRunner.add(result.waitUntil);
24
+ return result.response;
25
+ }
@@ -1,2 +1,3 @@
1
- import type { InternalEvent, InternalResult, StreamCreator } from "../types/open-next";
2
- export declare function openNextHandler(internalEvent: InternalEvent, responseStreaming?: StreamCreator): Promise<InternalResult>;
1
+ import type { InternalEvent, InternalResult } from "../types/open-next";
2
+ import type { OpenNextHandlerOptions } from "../types/overrides";
3
+ export declare function openNextHandler(internalEvent: InternalEvent, options?: OpenNextHandlerOptions): Promise<InternalResult>;
@@ -1,25 +1,29 @@
1
1
  import { AsyncLocalStorage } from "node:async_hooks";
2
2
  import { IncomingMessage } from "../http/index.js";
3
3
  import { runWithOpenNextRequestContext } from "../utils/promise";
4
+ import { NextConfig } from "../adapters/config/index";
4
5
  import { debug, error, warn } from "../adapters/logger";
5
6
  import { patchAsyncStorage } from "./patchAsyncStorage";
6
- import { convertRes, createServerResponse } from "./routing/util";
7
- import routingHandler, { INTERNAL_HEADER_INITIAL_PATH, INTERNAL_HEADER_RESOLVED_ROUTES, MIDDLEWARE_HEADER_PREFIX, MIDDLEWARE_HEADER_PREFIX_LEN, } from "./routingHandler";
7
+ import { constructNextUrl, convertRes, convertToQuery, createServerResponse, } from "./routing/util";
8
+ import routingHandler, { INTERNAL_HEADER_INITIAL_URL, INTERNAL_HEADER_RESOLVED_ROUTES, MIDDLEWARE_HEADER_PREFIX, MIDDLEWARE_HEADER_PREFIX_LEN, } from "./routingHandler";
8
9
  import { requestHandler, setNextjsPrebundledReact } from "./util";
9
10
  // This is used to identify requests in the cache
10
11
  globalThis.__openNextAls = new AsyncLocalStorage();
11
12
  patchAsyncStorage();
12
- export async function openNextHandler(internalEvent, responseStreaming) {
13
+ export async function openNextHandler(internalEvent, options) {
13
14
  const initialHeaders = internalEvent.headers;
14
15
  // We run everything in the async local storage context so that it is available in the middleware as well as in NextServer
15
- return runWithOpenNextRequestContext({ isISRRevalidation: initialHeaders["x-isr"] === "1" }, async () => {
16
+ return runWithOpenNextRequestContext({
17
+ isISRRevalidation: initialHeaders["x-isr"] === "1",
18
+ waitUntil: options?.waitUntil,
19
+ }, async () => {
16
20
  if (initialHeaders["x-forwarded-host"]) {
17
21
  initialHeaders.host = initialHeaders["x-forwarded-host"];
18
22
  }
19
23
  debug("internalEvent", internalEvent);
20
24
  // These 2 will get overwritten by the routing handler if not using an external middleware
21
25
  const internalHeaders = {
22
- initialPath: initialHeaders[INTERNAL_HEADER_INITIAL_PATH] ?? internalEvent.rawPath,
26
+ initialPath: initialHeaders[INTERNAL_HEADER_INITIAL_URL] ?? internalEvent.rawPath,
23
27
  resolvedRoutes: initialHeaders[INTERNAL_HEADER_RESOLVED_ROUTES]
24
28
  ? JSON.parse(initialHeaders[INTERNAL_HEADER_RESOLVED_ROUTES])
25
29
  : [],
@@ -29,6 +33,7 @@ export async function openNextHandler(internalEvent, responseStreaming) {
29
33
  isExternalRewrite: false,
30
34
  origin: false,
31
35
  isISR: false,
36
+ initialURL: internalEvent.url,
32
37
  ...internalHeaders,
33
38
  };
34
39
  //#override withRouting
@@ -65,7 +70,7 @@ export async function openNextHandler(internalEvent, responseStreaming) {
65
70
  rawPath: "/500",
66
71
  method: "GET",
67
72
  headers: {},
68
- url: "/500",
73
+ url: constructNextUrl(internalEvent.url, "/500"),
69
74
  query: {},
70
75
  cookies: {},
71
76
  remoteAddress: "",
@@ -74,22 +79,22 @@ export async function openNextHandler(internalEvent, responseStreaming) {
74
79
  isExternalRewrite: false,
75
80
  isISR: false,
76
81
  origin: false,
77
- initialPath: internalEvent.rawPath,
82
+ initialURL: internalEvent.url,
78
83
  resolvedRoutes: [{ route: "/500", type: "page" }],
79
84
  };
80
85
  }
81
86
  }
82
87
  if ("type" in routingResult) {
83
88
  // response is used only in the streaming case
84
- if (responseStreaming) {
89
+ if (options?.streamCreator) {
85
90
  const response = createServerResponse({
86
91
  internalEvent,
87
92
  isExternalRewrite: false,
88
93
  isISR: false,
89
94
  resolvedRoutes: [],
90
95
  origin: false,
91
- initialPath: internalEvent.rawPath,
92
- }, headers, responseStreaming);
96
+ initialURL: internalEvent.url,
97
+ }, headers, options.streamCreator);
93
98
  response.statusCode = routingResult.statusCode;
94
99
  response.flushHeaders();
95
100
  const [bodyToConsume, bodyToReturn] = routingResult.body.tee();
@@ -103,12 +108,13 @@ export async function openNextHandler(internalEvent, responseStreaming) {
103
108
  }
104
109
  const preprocessedEvent = routingResult.internalEvent;
105
110
  debug("preprocessedEvent", preprocessedEvent);
111
+ const { search, pathname, hash } = new URL(preprocessedEvent.url);
106
112
  const reqProps = {
107
113
  method: preprocessedEvent.method,
108
- url: preprocessedEvent.url,
114
+ url: `${pathname}${search}${hash}`,
109
115
  //WORKAROUND: We pass this header to the serverless function to mimic a prefetch request which will not trigger revalidation since we handle revalidation differently
110
116
  // There is 3 way we can handle revalidation:
111
- // 1. We could just let the revalidation go as normal, but due to race condtions the revalidation will be unreliable
117
+ // 1. We could just let the revalidation go as normal, but due to race conditions the revalidation will be unreliable
112
118
  // 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
113
119
  // 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)
114
120
  headers: { ...headers, purpose: "prefetch" },
@@ -124,8 +130,8 @@ export async function openNextHandler(internalEvent, responseStreaming) {
124
130
  store.mergeHeadersPriority = mergeHeadersPriority;
125
131
  }
126
132
  const req = new IncomingMessage(reqProps);
127
- const res = createServerResponse(routingResult, overwrittenResponseHeaders, responseStreaming);
128
- await processRequest(req, res, preprocessedEvent);
133
+ const res = createServerResponse(routingResult, overwrittenResponseHeaders, options?.streamCreator);
134
+ await processRequest(req, res, routingResult);
129
135
  const { statusCode, headers: responseHeaders, isBase64Encoded, body, } = convertRes(res);
130
136
  const internalResult = {
131
137
  type: internalEvent.type,
@@ -134,36 +140,56 @@ export async function openNextHandler(internalEvent, responseStreaming) {
134
140
  body,
135
141
  isBase64Encoded,
136
142
  };
137
- const requestId = store?.requestId;
138
- if (requestId) {
139
- // reset lastModified. We need to do this to avoid memory leaks
140
- delete globalThis.lastModified[requestId];
141
- }
142
143
  return internalResult;
143
144
  });
144
145
  }
145
- async function processRequest(req, res, internalEvent) {
146
+ async function processRequest(req, res, routingResult) {
146
147
  // @ts-ignore
147
148
  // Next.js doesn't parse body if the property exists
148
149
  // https://github.com/dougmoscrop/serverless-http/issues/227
149
150
  delete req.body;
150
151
  try {
151
152
  //#override applyNextjsPrebundledReact
152
- setNextjsPrebundledReact(internalEvent.rawPath);
153
+ setNextjsPrebundledReact(routingResult.internalEvent.rawPath);
153
154
  //#endOverride
155
+ // Here we try to apply as much request metadata as possible
156
+ // We apply every metadata from `resolve-routes` https://github.com/vercel/next.js/blob/canary/packages/next/src/server/lib/router-utils/resolve-routes.ts
157
+ // and `router-server` https://github.com/vercel/next.js/blob/canary/packages/next/src/server/lib/router-server.ts
158
+ const initialURL = new URL(routingResult.initialURL);
159
+ let invokeStatus;
160
+ if (routingResult.internalEvent.rawPath === "/500") {
161
+ invokeStatus = 500;
162
+ }
163
+ else if (routingResult.internalEvent.rawPath === "/404") {
164
+ invokeStatus = 404;
165
+ }
166
+ const requestMetadata = {
167
+ isNextDataReq: routingResult.internalEvent.query.__nextDataReq === "1",
168
+ initURL: routingResult.initialURL,
169
+ initQuery: convertToQuery(initialURL.search),
170
+ initProtocol: initialURL.protocol,
171
+ defaultLocale: NextConfig.i18n?.defaultLocale,
172
+ locale: routingResult.locale,
173
+ middlewareInvoke: false,
174
+ // By setting invokePath and invokeQuery we can bypass some of the routing logic in Next.js
175
+ invokePath: routingResult.internalEvent.rawPath,
176
+ invokeQuery: routingResult.internalEvent.query,
177
+ // invokeStatus is only used for error pages
178
+ invokeStatus,
179
+ };
154
180
  // Next Server
155
- await requestHandler(req, res);
181
+ await requestHandler(requestMetadata)(req, res);
156
182
  }
157
183
  catch (e) {
158
184
  // This might fail when using bundled next, importing won't do the trick either
159
185
  if (e.constructor.name === "NoFallbackError") {
160
186
  // Do we need to handle _not-found
161
187
  // Ideally this should never get triggered and be intercepted by the routing handler
162
- await tryRenderError("404", res, internalEvent);
188
+ await tryRenderError("404", res, routingResult.internalEvent);
163
189
  }
164
190
  else {
165
191
  error("NextJS request failed.", e);
166
- await tryRenderError("500", res, internalEvent);
192
+ await tryRenderError("500", res, routingResult.internalEvent);
167
193
  }
168
194
  }
169
195
  }
@@ -176,7 +202,14 @@ async function tryRenderError(type, res, internalEvent) {
176
202
  body: internalEvent.body,
177
203
  remoteAddress: internalEvent.remoteAddress,
178
204
  });
179
- await requestHandler(_req, res);
205
+ // By setting this it will allow us to bypass and directly render the 404 or 500 page
206
+ const requestMetadata = {
207
+ // By setting invokePath and invokeQuery we can bypass some of the routing logic in Next.js
208
+ invokePath: type === "404" ? "/404" : "/500",
209
+ invokeStatus: type === "404" ? 404 : 500,
210
+ middlewareInvoke: false,
211
+ };
212
+ await requestHandler(requestMetadata)(_req, res);
180
213
  }
181
214
  catch (e) {
182
215
  error("NextJS request failed.", e);
@@ -1,9 +1,10 @@
1
1
  import { createHash } from "node:crypto";
2
2
  import { NextConfig, PrerenderManifest } from "../../adapters/config/index";
3
3
  import { emptyReadableStream, toReadableStream } from "../../utils/stream";
4
+ import { getTagsFromValue, hasBeenRevalidated } from "../../utils/cache";
4
5
  import { debug } from "../../adapters/logger";
5
6
  import { localizePath } from "./i18n";
6
- import { generateMessageGroupId } from "./util";
7
+ import { generateMessageGroupId } from "./queue";
7
8
  const CACHE_ONE_YEAR = 60 * 60 * 24 * 365;
8
9
  const CACHE_ONE_MONTH = 60 * 60 * 24 * 30;
9
10
  async function computeCacheControl(path, body, host, revalidate, lastModified) {
@@ -120,16 +121,19 @@ export async function cacheInterceptor(event) {
120
121
  try {
121
122
  const cachedData = await globalThis.incrementalCache.get(localizedPath);
122
123
  debug("cached data in interceptor", cachedData);
124
+ if (!cachedData?.value) {
125
+ return event;
126
+ }
127
+ // We need to check the tag cache now
123
128
  if (cachedData.value?.type === "app") {
124
- // We need to check the tag cache now
125
- const _lastModified = await globalThis.tagCache.getLastModified(localizedPath, cachedData.lastModified);
126
- if (_lastModified === -1) {
127
- // If some tags are stale we need to force revalidation
129
+ const tags = getTagsFromValue(cachedData.value);
130
+ const _hasBeenRevalidated = await hasBeenRevalidated(localizedPath, tags, cachedData);
131
+ if (_hasBeenRevalidated) {
128
132
  return event;
129
133
  }
130
134
  }
131
135
  const host = event.headers.host;
132
- switch (cachedData.value?.type) {
136
+ switch (cachedData?.value?.type) {
133
137
  case "app":
134
138
  case "page":
135
139
  return generateResult(event, localizedPath, cachedData.value, cachedData.lastModified);
@@ -1,2 +1,4 @@
1
+ import type { i18nConfig } from "../../../types/next-types";
1
2
  import type { InternalEvent } from "../../../types/open-next";
3
+ export declare function detectLocale(internalEvent: InternalEvent, i18n: i18nConfig): string;
2
4
  export declare function localizePath(internalEvent: InternalEvent): string;
@@ -12,7 +12,7 @@ function getLocaleFromCookie(cookies) {
12
12
  ? i18n?.locales.find((locale) => nextLocale === locale.toLowerCase())
13
13
  : undefined;
14
14
  }
15
- function detectLocale(internalEvent, i18n) {
15
+ export function detectLocale(internalEvent, i18n) {
16
16
  if (i18n.localeDetection === false) {
17
17
  return i18n.defaultLocale;
18
18
  }