@opennextjs/aws 3.4.2 → 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 (82) hide show
  1. package/dist/adapters/cache.d.ts +2 -61
  2. package/dist/adapters/cache.js +68 -34
  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 +2 -2
  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 +4 -6
  24. package/dist/build/middleware/buildNodeMiddleware.d.ts +3 -0
  25. package/dist/build/middleware/buildNodeMiddleware.js +97 -0
  26. package/dist/core/createMainHandler.js +0 -1
  27. package/dist/core/edgeFunctionHandler.js +3 -1
  28. package/dist/core/nodeMiddlewareHandler.d.ts +4 -0
  29. package/dist/core/nodeMiddlewareHandler.js +25 -0
  30. package/dist/core/requestHandler.d.ts +3 -2
  31. package/dist/core/requestHandler.js +58 -25
  32. package/dist/core/routing/cacheInterceptor.js +7 -6
  33. package/dist/core/routing/i18n/index.d.ts +2 -0
  34. package/dist/core/routing/i18n/index.js +1 -1
  35. package/dist/core/routing/matcher.js +13 -12
  36. package/dist/core/routing/middleware.js +14 -29
  37. package/dist/core/routing/queue.d.ts +1 -0
  38. package/dist/core/routing/queue.js +41 -0
  39. package/dist/core/routing/util.d.ts +12 -3
  40. package/dist/core/routing/util.js +24 -50
  41. package/dist/core/routingHandler.d.ts +2 -1
  42. package/dist/core/routingHandler.js +10 -4
  43. package/dist/core/util.d.ts +1 -4
  44. package/dist/core/util.js +7 -15
  45. package/dist/overrides/converters/aws-apigw-v1.js +4 -3
  46. package/dist/overrides/converters/aws-apigw-v2.js +4 -3
  47. package/dist/overrides/converters/aws-cloudfront.js +6 -4
  48. package/dist/overrides/converters/edge.js +1 -10
  49. package/dist/overrides/converters/node.js +10 -8
  50. package/dist/overrides/converters/utils.d.ts +7 -0
  51. package/dist/overrides/converters/utils.js +9 -0
  52. package/dist/overrides/imageLoader/s3-lite.d.ts +3 -0
  53. package/dist/overrides/imageLoader/s3-lite.js +61 -0
  54. package/dist/overrides/tagCache/dummy.js +1 -0
  55. package/dist/overrides/tagCache/dynamodb-lite.js +1 -0
  56. package/dist/overrides/tagCache/dynamodb-nextMode.d.ts +7 -0
  57. package/dist/overrides/tagCache/dynamodb-nextMode.js +119 -0
  58. package/dist/overrides/tagCache/dynamodb.js +1 -0
  59. package/dist/overrides/tagCache/fs-dev.js +1 -0
  60. package/dist/overrides/wrappers/aws-lambda-streaming.js +1 -1
  61. package/dist/overrides/wrappers/aws-lambda.js +3 -1
  62. package/dist/overrides/wrappers/cloudflare-edge.js +3 -2
  63. package/dist/overrides/wrappers/cloudflare-node.js +5 -8
  64. package/dist/overrides/wrappers/dummy.js +2 -2
  65. package/dist/overrides/wrappers/express-dev.js +4 -4
  66. package/dist/overrides/wrappers/node.js +2 -2
  67. package/dist/plugins/edge.d.ts +1 -3
  68. package/dist/plugins/edge.js +4 -11
  69. package/dist/plugins/externalMiddleware.d.ts +2 -0
  70. package/dist/plugins/externalMiddleware.js +10 -0
  71. package/dist/plugins/resolve.js +2 -2
  72. package/dist/types/cache.d.ts +34 -4
  73. package/dist/types/global.d.ts +19 -31
  74. package/dist/types/next-types.d.ts +13 -0
  75. package/dist/types/open-next.d.ts +12 -5
  76. package/dist/types/overrides.d.ts +72 -6
  77. package/dist/utils/cache.d.ts +3 -0
  78. package/dist/utils/cache.js +32 -0
  79. package/dist/utils/promise.d.ts +3 -1
  80. package/dist/utils/promise.js +18 -11
  81. package/dist/utils/regex.js +1 -1
  82. package/package.json +5 -4
@@ -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
  },
@@ -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) {
@@ -123,11 +124,11 @@ export async function cacheInterceptor(event) {
123
124
  if (!cachedData?.value) {
124
125
  return event;
125
126
  }
126
- if (cachedData?.value?.type === "app") {
127
- // We need to check the tag cache now
128
- const _lastModified = await globalThis.tagCache.getLastModified(localizedPath, cachedData.lastModified);
129
- if (_lastModified === -1) {
130
- // If some tags are stale we need to force revalidation
127
+ // We need to check the tag cache now
128
+ if (cachedData.value?.type === "app") {
129
+ const tags = getTagsFromValue(cachedData.value);
130
+ const _hasBeenRevalidated = await hasBeenRevalidated(localizedPath, tags, cachedData);
131
+ if (_hasBeenRevalidated) {
131
132
  return event;
132
133
  }
133
134
  }
@@ -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
  }
@@ -3,7 +3,7 @@ import { compile, match } from "path-to-regexp";
3
3
  import { emptyReadableStream, toReadableStream } from "../../utils/stream";
4
4
  import { debug } from "../../adapters/logger";
5
5
  import { localizePath } from "./i18n";
6
- import { convertFromQueryString, convertToQueryString, escapeRegex, getUrlParts, isExternal, unescapeRegex, } from "./util";
6
+ import { constructNextUrl, convertFromQueryString, convertToQueryString, escapeRegex, getUrlParts, isExternal, unescapeRegex, } from "./util";
7
7
  const routeHasMatcher = (headers, cookies, query) => (redirect) => {
8
8
  switch (redirect.type) {
9
9
  case "header":
@@ -96,7 +96,7 @@ export function getNextConfigHeaders(event, configHeaders) {
96
96
  return requestHeaders;
97
97
  }
98
98
  export function handleRewrites(event, rewrites) {
99
- const { rawPath, headers, query, cookies } = event;
99
+ const { rawPath, headers, query, cookies, url } = event;
100
100
  const localizedRawPath = localizePath(event);
101
101
  const matcher = routeHasMatcher(headers, cookies, query);
102
102
  const computeHas = computeParamHas(headers, cookies, query);
@@ -107,7 +107,7 @@ export function handleRewrites(event, rewrites) {
107
107
  checkHas(matcher, route.missing, true));
108
108
  });
109
109
  let finalQuery = query;
110
- let rewrittenUrl = rawPath;
110
+ let rewrittenUrl = url;
111
111
  const isExternalRewrite = isExternal(rewrite?.destination);
112
112
  debug("isExternalRewrite", isExternalRewrite);
113
113
  if (rewrite) {
@@ -121,12 +121,12 @@ export function handleRewrites(event, rewrites) {
121
121
  // %2B does not get interpreted as a space in the URL thus breaking the query string.
122
122
  const encodePlusQueryString = queryString.replaceAll("+", "%20");
123
123
  debug("urlParts", { pathname, protocol, hostname, queryString });
124
- const toDestinationPath = compile(escapeRegex(pathname ?? "") ?? "");
125
- const toDestinationHost = compile(escapeRegex(hostname ?? "") ?? "");
126
- const toDestinationQuery = compile(escapeRegex(encodePlusQueryString ?? "") ?? "");
124
+ const toDestinationPath = compile(escapeRegex(pathname));
125
+ const toDestinationHost = compile(escapeRegex(hostname));
126
+ const toDestinationQuery = compile(escapeRegex(encodePlusQueryString));
127
127
  const params = {
128
128
  // params for the source
129
- ...getParamsFromSource(match(escapeRegex(rewrite?.source) ?? ""))(pathToUse),
129
+ ...getParamsFromSource(match(escapeRegex(rewrite.source)))(pathToUse),
130
130
  // params for the has
131
131
  ...rewrite.has?.reduce((acc, cur) => {
132
132
  return Object.assign(acc, computeHas(cur));
@@ -147,19 +147,20 @@ export function handleRewrites(event, rewrites) {
147
147
  }
148
148
  rewrittenUrl = isExternalRewrite
149
149
  ? `${protocol}//${rewrittenHost}${rewrittenPath}`
150
- : `${rewrittenPath}`;
150
+ : new URL(rewrittenPath, event.url).href;
151
151
  // Should we merge the query params or use only the ones from the rewrite?
152
152
  finalQuery = {
153
153
  ...query,
154
154
  ...convertFromQueryString(rewrittenQuery),
155
155
  };
156
+ rewrittenUrl += convertToQueryString(finalQuery);
156
157
  debug("rewrittenUrl", { rewrittenUrl, finalQuery, isUsingParams });
157
158
  }
158
159
  return {
159
160
  internalEvent: {
160
161
  ...event,
161
- rawPath: rewrittenUrl,
162
- url: `${rewrittenUrl}${convertToQueryString(finalQuery)}`,
162
+ rawPath: new URL(rewrittenUrl).pathname,
163
+ url: rewrittenUrl,
163
164
  },
164
165
  __rewrite: rewrite,
165
166
  isExternalRewrite,
@@ -251,7 +252,7 @@ export function fixDataPage(internalEvent, buildId) {
251
252
  ...internalEvent,
252
253
  rawPath: newPath,
253
254
  query,
254
- url: `${newPath}${convertToQueryString(query)}`,
255
+ url: new URL(`${newPath}${convertToQueryString(query)}`, internalEvent.url).href,
255
256
  };
256
257
  }
257
258
  return internalEvent;
@@ -278,7 +279,7 @@ export function handleFallbackFalse(internalEvent, prerenderManifest) {
278
279
  event: {
279
280
  ...internalEvent,
280
281
  rawPath: "/404",
281
- url: "/404",
282
+ url: constructNextUrl(internalEvent.url, "/404"),
282
283
  headers: {
283
284
  ...internalEvent.headers,
284
285
  "x-invoke-status": "404",