@opennextjs/aws 3.3.0 → 3.4.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 (61) hide show
  1. package/dist/adapters/cache.d.ts +1 -2
  2. package/dist/adapters/cache.js +20 -11
  3. package/dist/adapters/config/index.d.ts +2 -0
  4. package/dist/adapters/config/index.js +4 -3
  5. package/dist/adapters/config/util.d.ts +2 -0
  6. package/dist/adapters/config/util.js +12 -2
  7. package/dist/adapters/middleware.js +13 -2
  8. package/dist/adapters/util.js +5 -1
  9. package/dist/build/createAssets.js +22 -23
  10. package/dist/build/createImageOptimizationBundle.js +2 -1
  11. package/dist/build/createServerBundle.js +4 -4
  12. package/dist/build/edge/createEdgeBundle.d.ts +4 -3
  13. package/dist/build/edge/createEdgeBundle.js +14 -22
  14. package/dist/build/validateConfig.js +2 -1
  15. package/dist/core/createMainHandler.js +2 -1
  16. package/dist/core/requestHandler.js +24 -6
  17. package/dist/core/resolve.d.ts +4 -0
  18. package/dist/core/resolve.js +10 -0
  19. package/dist/core/routing/middleware.js +3 -1
  20. package/dist/core/routing/routeMatcher.d.ts +10 -0
  21. package/dist/core/routing/routeMatcher.js +45 -0
  22. package/dist/core/routing/util.d.ts +4 -3
  23. package/dist/core/routing/util.js +24 -5
  24. package/dist/core/routingHandler.d.ts +3 -0
  25. package/dist/core/routingHandler.js +30 -29
  26. package/dist/overrides/cdnInvalidation/cloudfront.d.ts +9 -0
  27. package/dist/overrides/cdnInvalidation/cloudfront.js +26 -0
  28. package/dist/overrides/cdnInvalidation/dummy.d.ts +9 -0
  29. package/dist/overrides/cdnInvalidation/dummy.js +6 -0
  30. package/dist/overrides/imageLoader/fs-dev.d.ts +10 -0
  31. package/dist/overrides/imageLoader/fs-dev.js +14 -0
  32. package/dist/overrides/incrementalCache/fs-dev.d.ts +3 -0
  33. package/dist/overrides/incrementalCache/fs-dev.js +30 -0
  34. package/dist/overrides/incrementalCache/multi-tier-ddb-s3.d.ts +10 -0
  35. package/dist/overrides/incrementalCache/multi-tier-ddb-s3.js +120 -0
  36. package/dist/overrides/incrementalCache/s3-lite.d.ts +2 -0
  37. package/dist/overrides/incrementalCache/s3-lite.js +1 -1
  38. package/dist/overrides/proxyExternalRequest/node.js +6 -5
  39. package/dist/overrides/queue/direct.d.ts +3 -0
  40. package/dist/overrides/queue/direct.js +18 -0
  41. package/dist/overrides/tagCache/fs-dev.d.ts +3 -0
  42. package/dist/overrides/tagCache/fs-dev.js +32 -0
  43. package/dist/overrides/wrappers/cloudflare-edge.js +11 -4
  44. package/dist/overrides/wrappers/cloudflare-node.js +4 -1
  45. package/dist/overrides/wrappers/dummy.d.ts +1 -1
  46. package/dist/overrides/wrappers/dummy.js +6 -2
  47. package/dist/overrides/wrappers/express-dev.d.ts +7 -0
  48. package/dist/overrides/wrappers/express-dev.js +43 -0
  49. package/dist/plugins/edge.js +10 -4
  50. package/dist/plugins/replacement.d.ts +1 -1
  51. package/dist/plugins/replacement.js +1 -1
  52. package/dist/plugins/resolve.d.ts +1 -0
  53. package/dist/plugins/resolve.js +4 -1
  54. package/dist/types/global.d.ts +7 -1
  55. package/dist/types/open-next.d.ts +19 -6
  56. package/dist/types/overrides.d.ts +13 -4
  57. package/dist/utils/lru.d.ts +8 -0
  58. package/dist/utils/lru.js +29 -0
  59. package/dist/utils/regex.d.ts +18 -0
  60. package/dist/utils/regex.js +16 -0
  61. package/package.json +4 -1
@@ -58,8 +58,7 @@ interface CacheHandlerValue {
58
58
  cacheState?: string;
59
59
  value: IncrementalCacheValue | null;
60
60
  }
61
- export declare function hasCacheExtension(key: string): boolean;
62
- export default class S3Cache {
61
+ export default class Cache {
63
62
  get(key: string, options?: boolean | {
64
63
  fetchCache?: boolean;
65
64
  kindHint?: "app" | "pages" | "fetch";
@@ -1,10 +1,5 @@
1
1
  import { isBinaryContentType } from "../utils/binary";
2
2
  import { debug, error, warn } from "./logger";
3
- /** Beginning single backslash is intentional, to look for the dot + the extension. Do not escape it again. */
4
- const CACHE_EXTENSION_REGEX = /\.(cache|fetch)$/;
5
- export function hasCacheExtension(key) {
6
- return CACHE_EXTENSION_REGEX.test(key);
7
- }
8
3
  function isFetchCache(options) {
9
4
  if (typeof options === "boolean") {
10
5
  return options;
@@ -17,7 +12,7 @@ function isFetchCache(options) {
17
12
  return false;
18
13
  }
19
14
  // We need to use globalThis client here as this class can be defined at load time in next 12 but client is not available at load time
20
- export default class S3Cache {
15
+ export default class Cache {
21
16
  async get(key,
22
17
  // fetchCache is for next 13.5 and above, kindHint is for next 14 and above and boolean is for earlier versions
23
18
  options) {
@@ -34,7 +29,6 @@ export default class S3Cache {
34
29
  debug("get fetch cache", { key, softTags, tags });
35
30
  try {
36
31
  const { value, lastModified } = await globalThis.incrementalCache.get(key, true);
37
- // const { Body, LastModified } = await this.getS3Object(key, "fetch");
38
32
  const _lastModified = await globalThis.tagCache.getLastModified(key, lastModified);
39
33
  if (_lastModified === -1) {
40
34
  // If some tags are stale we need to force revalidation
@@ -71,10 +65,6 @@ export default class S3Cache {
71
65
  async getIncrementalCache(key) {
72
66
  try {
73
67
  const { value: cacheData, lastModified } = await globalThis.incrementalCache.get(key, false);
74
- // const { Body, LastModified } = await this.getS3Object(key, "cache");
75
- // const cacheData = JSON.parse(
76
- // (await Body?.transformToString()) ?? "{}",
77
- // ) as S3CachedFile;
78
68
  const meta = cacheData?.meta;
79
69
  const _lastModified = await globalThis.tagCache.getLastModified(key, lastModified);
80
70
  if (_lastModified === -1) {
@@ -285,6 +275,25 @@ export default class S3Cache {
285
275
  }
286
276
  // Update all keys with the given tag with revalidatedAt set to now
287
277
  await globalThis.tagCache.writeTags(toInsert);
278
+ // We can now invalidate all paths in the CDN
279
+ // This only applies to `revalidateTag`, not to `res.revalidate()`
280
+ const uniquePaths = Array.from(new Set(toInsert
281
+ // We need to filter fetch cache key as they are not in the CDN
282
+ .filter((t) => t.tag.startsWith("_N_T_/"))
283
+ .map((t) => `/${t.path}`)));
284
+ if (uniquePaths.length > 0) {
285
+ await globalThis.cdnInvalidationHandler.invalidatePaths(uniquePaths.map((path) => ({
286
+ initialPath: path,
287
+ rawPath: path,
288
+ resolvedRoutes: [
289
+ {
290
+ route: path,
291
+ // TODO: ideally here we should check if it's an app router page or route
292
+ type: "app",
293
+ },
294
+ ],
295
+ })));
296
+ }
288
297
  }
289
298
  }
290
299
  catch (e) {
@@ -25,3 +25,5 @@ export declare const ConfigHeaders: import("../../types/next-types").Header[] |
25
25
  export declare const PrerenderManifest: import("../../types/next-types").PrerenderManifest;
26
26
  export declare const AppPathsManifestKeys: string[];
27
27
  export declare const MiddlewareManifest: import("../../types/next-types").MiddlewareManifest;
28
+ export declare const AppPathsManifest: Record<string, string>;
29
+ export declare const AppPathRoutesManifest: Record<string, string>;
@@ -1,8 +1,6 @@
1
1
  import path from "node:path";
2
2
  import { debug } from "../logger";
3
- import { loadAppPathsManifestKeys, loadBuildId, loadConfig, loadConfigHeaders, loadHtmlPages, loadMiddlewareManifest, loadPrerenderManifest,
4
- // loadPublicAssets,
5
- loadRoutesManifest, } from "./util.js";
3
+ import { loadAppPathRoutesManifest, loadAppPathsManifest, loadAppPathsManifestKeys, loadBuildId, loadConfig, loadConfigHeaders, loadHtmlPages, loadMiddlewareManifest, loadPrerenderManifest, loadRoutesManifest, } from "./util.js";
6
4
  export const NEXT_DIR = path.join(__dirname, ".next");
7
5
  export const OPEN_NEXT_DIR = path.join(__dirname, ".open-next");
8
6
  debug({ NEXT_DIR, OPEN_NEXT_DIR });
@@ -19,3 +17,6 @@ export const AppPathsManifestKeys =
19
17
  /* @__PURE__ */ loadAppPathsManifestKeys(NEXT_DIR);
20
18
  export const MiddlewareManifest =
21
19
  /* @__PURE__ */ loadMiddlewareManifest(NEXT_DIR);
20
+ export const AppPathsManifest = /* @__PURE__ */ loadAppPathsManifest(NEXT_DIR);
21
+ export const AppPathRoutesManifest =
22
+ /* @__PURE__ */ loadAppPathRoutesManifest(NEXT_DIR);
@@ -24,5 +24,7 @@ export declare function loadRoutesManifest(nextDir: string): {
24
24
  };
25
25
  export declare function loadConfigHeaders(nextDir: string): import("../../types/next-types").Header[] | undefined;
26
26
  export declare function loadPrerenderManifest(nextDir: string): PrerenderManifest;
27
+ export declare function loadAppPathsManifest(nextDir: string): Record<string, string>;
28
+ export declare function loadAppPathRoutesManifest(nextDir: string): Record<string, string>;
27
29
  export declare function loadAppPathsManifestKeys(nextDir: string): string[];
28
30
  export declare function loadMiddlewareManifest(nextDir: string): MiddlewareManifest;
@@ -60,12 +60,22 @@ export function loadPrerenderManifest(nextDir) {
60
60
  const json = fs.readFileSync(filePath, "utf-8");
61
61
  return JSON.parse(json);
62
62
  }
63
- export function loadAppPathsManifestKeys(nextDir) {
63
+ export function loadAppPathsManifest(nextDir) {
64
64
  const appPathsManifestPath = path.join(nextDir, "server", "app-paths-manifest.json");
65
65
  const appPathsManifestJson = fs.existsSync(appPathsManifestPath)
66
66
  ? fs.readFileSync(appPathsManifestPath, "utf-8")
67
67
  : "{}";
68
- const appPathsManifest = JSON.parse(appPathsManifestJson);
68
+ return JSON.parse(appPathsManifestJson);
69
+ }
70
+ export function loadAppPathRoutesManifest(nextDir) {
71
+ const appPathRoutesManifestPath = path.join(nextDir, "app-path-routes-manifest.json");
72
+ if (fs.existsSync(appPathRoutesManifestPath)) {
73
+ return JSON.parse(fs.readFileSync(appPathRoutesManifestPath, "utf-8"));
74
+ }
75
+ return {};
76
+ }
77
+ export function loadAppPathsManifestKeys(nextDir) {
78
+ const appPathsManifest = loadAppPathsManifest(nextDir);
69
79
  return Object.keys(appPathsManifest).map((key) => {
70
80
  // Remove parallel route
71
81
  let cleanedKey = key.replace(/\/@[^\/]+/g, "");
@@ -2,7 +2,7 @@ import { runWithOpenNextRequestContext } from "../utils/promise";
2
2
  import { debug, error } from "../adapters/logger";
3
3
  import { createGenericHandler } from "../core/createGenericHandler";
4
4
  import { resolveIncrementalCache, resolveOriginResolver, resolveProxyRequest, resolveQueue, resolveTagCache, } from "../core/resolve";
5
- import routingHandler from "../core/routingHandler";
5
+ import routingHandler, { INTERNAL_HEADER_INITIAL_PATH, INTERNAL_HEADER_RESOLVED_ROUTES, } from "../core/routingHandler";
6
6
  globalThis.internalFetch = fetch;
7
7
  globalThis.__openNextAls = new AsyncLocalStorage();
8
8
  const defaultHandler = async (internalEvent) => {
@@ -22,10 +22,19 @@ const defaultHandler = async (internalEvent) => {
22
22
  const origin = await originResolver.resolve(result.internalEvent.rawPath);
23
23
  return {
24
24
  type: "middleware",
25
- internalEvent: result.internalEvent,
25
+ internalEvent: {
26
+ ...result.internalEvent,
27
+ headers: {
28
+ ...result.internalEvent.headers,
29
+ [INTERNAL_HEADER_INITIAL_PATH]: internalEvent.rawPath,
30
+ [INTERNAL_HEADER_RESOLVED_ROUTES]: JSON.stringify(result.resolvedRoutes) ?? "[]",
31
+ },
32
+ },
26
33
  isExternalRewrite: result.isExternalRewrite,
27
34
  origin,
28
35
  isISR: result.isISR,
36
+ initialPath: result.initialPath,
37
+ resolvedRoutes: result.resolvedRoutes,
29
38
  };
30
39
  }
31
40
  try {
@@ -45,6 +54,8 @@ const defaultHandler = async (internalEvent) => {
45
54
  isExternalRewrite: false,
46
55
  origin: false,
47
56
  isISR: result.isISR,
57
+ initialPath: result.internalEvent.rawPath,
58
+ resolvedRoutes: [{ route: "/500", type: "page" }],
48
59
  };
49
60
  }
50
61
  }
@@ -1,6 +1,10 @@
1
1
  //TODO: We should probably move all the utils to a separate location
2
2
  export function setNodeEnv() {
3
- process.env.NODE_ENV = process.env.NODE_ENV ?? "production";
3
+ // Note: we create a `processEnv` variable instead of just using `process.env` directly
4
+ // because build tools can substitute `process.env.NODE_ENV` on build making
5
+ // assignments such as `process.env.NODE_ENV = ...` problematic
6
+ const processEnv = process.env;
7
+ processEnv.NODE_ENV = process.env.NODE_ENV ?? "production";
4
8
  }
5
9
  export function generateUniqueId() {
6
10
  return Math.random().toString(36).slice(2, 8);
@@ -109,10 +109,29 @@ export function createCacheAssets(options) {
109
109
  };
110
110
  fs.writeFileSync(cacheFilePath, JSON.stringify(cacheFileContent));
111
111
  });
112
+ // We need to traverse the cache to find every .meta file
113
+ const metaFiles = [];
114
+ // Copy fetch-cache to cache folder
115
+ const fetchCachePath = path.join(appBuildOutputPath, ".next/cache/fetch-cache");
116
+ if (fs.existsSync(fetchCachePath)) {
117
+ const fetchOutputPath = path.join(outputDir, "cache", "__fetch", buildId);
118
+ fs.mkdirSync(fetchOutputPath, { recursive: true });
119
+ fs.cpSync(fetchCachePath, fetchOutputPath, { recursive: true });
120
+ buildHelper.traverseFiles(fetchCachePath, () => true, ({ absolutePath, relativePath }) => {
121
+ const fileContent = fs.readFileSync(absolutePath, "utf8");
122
+ const fileData = JSON.parse(fileContent);
123
+ fileData?.tags?.forEach((tag) => {
124
+ metaFiles.push({
125
+ tag: { S: path.posix.join(buildId, tag) },
126
+ path: {
127
+ S: path.posix.join(buildId, relativePath),
128
+ },
129
+ revalidatedAt: { N: "1" },
130
+ });
131
+ });
132
+ });
133
+ }
112
134
  if (!options.config.dangerous?.disableTagCache) {
113
- // Generate dynamodb data
114
- // We need to traverse the cache to find every .meta file
115
- const metaFiles = [];
116
135
  // Compute dynamodb cache data
117
136
  // Traverse files inside cache to find all meta files and cache tags associated with them
118
137
  buildHelper.traverseFiles(outputPath, ({ absolutePath }) => absolutePath.endsWith(".meta"), ({ absolutePath, relativePath }) => {
@@ -134,26 +153,6 @@ export function createCacheAssets(options) {
134
153
  });
135
154
  }
136
155
  });
137
- // Copy fetch-cache to cache folder
138
- const fetchCachePath = path.join(appBuildOutputPath, ".next/cache/fetch-cache");
139
- if (fs.existsSync(fetchCachePath)) {
140
- const fetchOutputPath = path.join(outputDir, "cache", "__fetch", buildId);
141
- fs.mkdirSync(fetchOutputPath, { recursive: true });
142
- fs.cpSync(fetchCachePath, fetchOutputPath, { recursive: true });
143
- buildHelper.traverseFiles(fetchCachePath, () => true, ({ absolutePath, relativePath }) => {
144
- const fileContent = fs.readFileSync(absolutePath, "utf8");
145
- const fileData = JSON.parse(fileContent);
146
- fileData?.tags?.forEach((tag) => {
147
- metaFiles.push({
148
- tag: { S: path.posix.join(buildId, tag) },
149
- path: {
150
- S: path.posix.join(buildId, relativePath),
151
- },
152
- revalidatedAt: { N: "1" },
153
- });
154
- });
155
- });
156
- }
157
156
  if (metaFiles.length > 0) {
158
157
  useTagCache = true;
159
158
  const providerPath = path.join(outputDir, "dynamodb-provider");
@@ -4,6 +4,7 @@ import path from "node:path";
4
4
  import logger from "../logger.js";
5
5
  import { openNextReplacementPlugin } from "../plugins/replacement.js";
6
6
  import { openNextResolvePlugin } from "../plugins/resolve.js";
7
+ import { getCrossPlatformPathRegex } from "../utils/regex.js";
7
8
  import * as buildHelper from "./helper.js";
8
9
  import { installDependencies } from "./installDeps.js";
9
10
  const require = createRequire(import.meta.url);
@@ -28,7 +29,7 @@ export async function createImageOptimizationBundle(options) {
28
29
  if (buildHelper.compareSemver(options.nextVersion, "14.1.1") >= 0) {
29
30
  plugins.push(openNextReplacementPlugin({
30
31
  name: "opennext-14.1.1-image-optimization",
31
- target: /plugins(\/|\\)image-optimization(\/|\\)image-optimization\.js/g,
32
+ target: getCrossPlatformPathRegex("plugins/image-optimization/image-optimization.js"),
32
33
  replacements: [
33
34
  require.resolve("../adapters/plugins/image-optimization/image-optimization.replacement.js"),
34
35
  ],
@@ -4,6 +4,7 @@ import logger from "../logger.js";
4
4
  import { minifyAll } from "../minimize-js.js";
5
5
  import { openNextReplacementPlugin } from "../plugins/replacement.js";
6
6
  import { openNextResolvePlugin } from "../plugins/resolve.js";
7
+ import { getCrossPlatformPathRegex } from "../utils/regex.js";
7
8
  import { bundleNextServer } from "./bundleNextServer.js";
8
9
  import { compileCache } from "./compileCache.js";
9
10
  import { copyTracedFiles } from "./copyTracedFiles.js";
@@ -114,7 +115,7 @@ async function generateBundle(name, options, fnOptions) {
114
115
  const plugins = [
115
116
  openNextReplacementPlugin({
116
117
  name: `requestHandlerOverride ${name}`,
117
- target: /core(\/|\\)requestHandler\.js/g,
118
+ target: getCrossPlatformPathRegex("core/requestHandler.js"),
118
119
  deletes: [
119
120
  ...(disableNextPrebundledReact ? ["applyNextjsPrebundledReact"] : []),
120
121
  ...(disableRouting ? ["withRouting"] : []),
@@ -122,11 +123,10 @@ async function generateBundle(name, options, fnOptions) {
122
123
  }),
123
124
  openNextReplacementPlugin({
124
125
  name: `utilOverride ${name}`,
125
- target: /core(\/|\\)util\.js/g,
126
+ target: getCrossPlatformPathRegex("core/util.js"),
126
127
  deletes: [
127
128
  ...(disableNextPrebundledReact ? ["requireHooks"] : []),
128
- ...(disableRouting ? ["trustHostHeader"] : []),
129
- ...(!isBefore13413 ? ["requestHandlerHost"] : []),
129
+ ...(isBefore13413 ? ["trustHostHeader"] : ["requestHandlerHost"]),
130
130
  ...(isAfter141
131
131
  ? ["experimentalIncrementalCacheHandler"]
132
132
  : ["stableIncrementalCache"]),
@@ -2,14 +2,15 @@ import type { MiddlewareInfo } from "../../types/next-types";
2
2
  import type { IncludedConverter, IncludedOriginResolver, LazyLoadedOverride, OverrideOptions, SplittedFunctionOptions } from "../../types/open-next";
3
3
  import type { OriginResolver } from "../../types/overrides.js";
4
4
  import { type BuildOptions } from "../helper.js";
5
+ type Override = OverrideOptions & {
6
+ originResolver?: LazyLoadedOverride<OriginResolver> | IncludedOriginResolver;
7
+ };
5
8
  interface BuildEdgeBundleOptions {
6
9
  middlewareInfo?: MiddlewareInfo;
7
10
  entrypoint: string;
8
11
  outfile: string;
9
12
  options: BuildOptions;
10
- overrides?: OverrideOptions & {
11
- originResolver?: LazyLoadedOverride<OriginResolver> | IncludedOriginResolver;
12
- };
13
+ overrides?: Override;
13
14
  defaultConverter?: IncludedConverter;
14
15
  additionalInject?: string;
15
16
  includeCache?: boolean;
@@ -6,10 +6,16 @@ import logger from "../../logger.js";
6
6
  import { openNextEdgePlugins } from "../../plugins/edge.js";
7
7
  import { openNextReplacementPlugin } from "../../plugins/replacement.js";
8
8
  import { openNextResolvePlugin } from "../../plugins/resolve.js";
9
+ import { getCrossPlatformPathRegex } from "../../utils/regex.js";
9
10
  import { isEdgeRuntime } from "../helper.js";
10
11
  import { copyOpenNextConfig, esbuildAsync } from "../helper.js";
11
12
  export async function buildEdgeBundle({ middlewareInfo, entrypoint, outfile, options, defaultConverter, overrides, additionalInject, includeCache, additionalExternals, onlyBuildOnce, name, }) {
12
13
  const isInCloudfare = await isEdgeRuntime(overrides);
14
+ function override(target) {
15
+ return typeof overrides?.[target] === "string"
16
+ ? overrides[target]
17
+ : undefined;
18
+ }
13
19
  await esbuildAsync({
14
20
  entryPoints: [entrypoint],
15
21
  bundle: true,
@@ -20,37 +26,23 @@ export async function buildEdgeBundle({ middlewareInfo, entrypoint, outfile, opt
20
26
  plugins: [
21
27
  openNextResolvePlugin({
22
28
  overrides: {
23
- wrapper: typeof overrides?.wrapper === "string"
24
- ? overrides.wrapper
25
- : "aws-lambda",
26
- converter: typeof overrides?.converter === "string"
27
- ? overrides.converter
28
- : defaultConverter,
29
+ wrapper: override("wrapper") ?? "aws-lambda",
30
+ converter: override("converter") ?? defaultConverter,
29
31
  ...(includeCache
30
32
  ? {
31
- tagCache: typeof overrides?.tagCache === "string"
32
- ? overrides.tagCache
33
- : "dynamodb-lite",
34
- incrementalCache: typeof overrides?.incrementalCache === "string"
35
- ? overrides.incrementalCache
36
- : "s3-lite",
37
- queue: typeof overrides?.queue === "string"
38
- ? overrides.queue
39
- : "sqs-lite",
33
+ tagCache: override("tagCache") ?? "dynamodb-lite",
34
+ incrementalCache: override("incrementalCache") ?? "s3-lite",
35
+ queue: override("queue") ?? "sqs-lite",
40
36
  }
41
37
  : {}),
42
- originResolver: typeof overrides?.originResolver === "string"
43
- ? overrides.originResolver
44
- : "pattern-env",
45
- proxyExternalRequest: typeof overrides?.proxyExternalRequest === "string"
46
- ? overrides.proxyExternalRequest
47
- : "node",
38
+ originResolver: override("originResolver") ?? "pattern-env",
39
+ proxyExternalRequest: override("proxyExternalRequest") ?? "node",
48
40
  },
49
41
  fnName: name,
50
42
  }),
51
43
  openNextReplacementPlugin({
52
44
  name: "externalMiddlewareOverrides",
53
- target: /adapters(\/|\\)middleware\.js/g,
45
+ target: getCrossPlatformPathRegex("adapters/middleware.js"),
54
46
  deletes: includeCache ? [] : ["includeCacheInMiddleware"],
55
47
  }),
56
48
  openNextEdgePlugins({
@@ -11,7 +11,8 @@ const compatibilityMatrix = {
11
11
  "cloudflare-edge": ["edge"],
12
12
  "cloudflare-node": ["edge"],
13
13
  node: ["node"],
14
- dummy: [],
14
+ "express-dev": ["node"],
15
+ dummy: ["dummy"],
15
16
  };
16
17
  function validateFunctionOptions(fnOptions) {
17
18
  if (fnOptions.runtime === "edge" && fnOptions.experimentalBundledNextServer) {
@@ -1,7 +1,7 @@
1
1
  import { debug } from "../adapters/logger";
2
2
  import { generateUniqueId } from "../adapters/util";
3
3
  import { openNextHandler } from "./requestHandler";
4
- import { resolveConverter, resolveIncrementalCache, resolveProxyRequest, resolveQueue, resolveTagCache, resolveWrapper, } from "./resolve";
4
+ import { resolveCdnInvalidation, resolveConverter, resolveIncrementalCache, resolveProxyRequest, resolveQueue, resolveTagCache, resolveWrapper, } from "./resolve";
5
5
  export async function createMainHandler() {
6
6
  // @ts-expect-error `./open-next.config.mjs` exists only in the build output
7
7
  const config = await import("./open-next.config.mjs").then((m) => m.default);
@@ -15,6 +15,7 @@ export async function createMainHandler() {
15
15
  globalThis.incrementalCache = await resolveIncrementalCache(thisFunction.override?.incrementalCache);
16
16
  globalThis.tagCache = await resolveTagCache(thisFunction.override?.tagCache);
17
17
  globalThis.proxyExternalRequest = await resolveProxyRequest(thisFunction.override?.proxyExternalRequest);
18
+ globalThis.cdnInvalidationHandler = await resolveCdnInvalidation(thisFunction.override?.cdnInvalidation);
18
19
  globalThis.lastModified = {};
19
20
  // From the config, we create the converter
20
21
  const converter = await resolveConverter(thisFunction.override?.converter);
@@ -4,23 +4,32 @@ import { runWithOpenNextRequestContext } from "../utils/promise";
4
4
  import { debug, error, warn } from "../adapters/logger";
5
5
  import { patchAsyncStorage } from "./patchAsyncStorage";
6
6
  import { convertRes, createServerResponse } from "./routing/util";
7
- import routingHandler, { MIDDLEWARE_HEADER_PREFIX, MIDDLEWARE_HEADER_PREFIX_LEN, } from "./routingHandler";
7
+ import routingHandler, { INTERNAL_HEADER_INITIAL_PATH, INTERNAL_HEADER_RESOLVED_ROUTES, MIDDLEWARE_HEADER_PREFIX, MIDDLEWARE_HEADER_PREFIX_LEN, } from "./routingHandler";
8
8
  import { requestHandler, setNextjsPrebundledReact } from "./util";
9
9
  // This is used to identify requests in the cache
10
10
  globalThis.__openNextAls = new AsyncLocalStorage();
11
11
  patchAsyncStorage();
12
12
  export async function openNextHandler(internalEvent, responseStreaming) {
13
+ const initialHeaders = internalEvent.headers;
13
14
  // We run everything in the async local storage context so that it is available in the middleware as well as in NextServer
14
- return runWithOpenNextRequestContext({ isISRRevalidation: internalEvent.headers["x-isr"] === "1" }, async () => {
15
- if (internalEvent.headers["x-forwarded-host"]) {
16
- internalEvent.headers.host = internalEvent.headers["x-forwarded-host"];
15
+ return runWithOpenNextRequestContext({ isISRRevalidation: initialHeaders["x-isr"] === "1" }, async () => {
16
+ if (initialHeaders["x-forwarded-host"]) {
17
+ initialHeaders.host = initialHeaders["x-forwarded-host"];
17
18
  }
18
19
  debug("internalEvent", internalEvent);
20
+ // These 2 will get overwritten by the routing handler if not using an external middleware
21
+ const internalHeaders = {
22
+ initialPath: initialHeaders[INTERNAL_HEADER_INITIAL_PATH] ?? internalEvent.rawPath,
23
+ resolvedRoutes: initialHeaders[INTERNAL_HEADER_RESOLVED_ROUTES]
24
+ ? JSON.parse(initialHeaders[INTERNAL_HEADER_RESOLVED_ROUTES])
25
+ : [],
26
+ };
19
27
  let routingResult = {
20
28
  internalEvent,
21
29
  isExternalRewrite: false,
22
30
  origin: false,
23
31
  isISR: false,
32
+ ...internalHeaders,
24
33
  };
25
34
  //#override withRouting
26
35
  try {
@@ -65,13 +74,22 @@ export async function openNextHandler(internalEvent, responseStreaming) {
65
74
  isExternalRewrite: false,
66
75
  isISR: false,
67
76
  origin: false,
77
+ initialPath: internalEvent.rawPath,
78
+ resolvedRoutes: [{ route: "/500", type: "page" }],
68
79
  };
69
80
  }
70
81
  }
71
82
  if ("type" in routingResult) {
72
83
  // response is used only in the streaming case
73
84
  if (responseStreaming) {
74
- const response = createServerResponse(internalEvent, headers, responseStreaming);
85
+ const response = createServerResponse({
86
+ internalEvent,
87
+ isExternalRewrite: false,
88
+ isISR: false,
89
+ resolvedRoutes: [],
90
+ origin: false,
91
+ initialPath: internalEvent.rawPath,
92
+ }, headers, responseStreaming);
75
93
  response.statusCode = routingResult.statusCode;
76
94
  response.flushHeaders();
77
95
  const [bodyToConsume, bodyToReturn] = routingResult.body.tee();
@@ -106,7 +124,7 @@ export async function openNextHandler(internalEvent, responseStreaming) {
106
124
  store.mergeHeadersPriority = mergeHeadersPriority;
107
125
  }
108
126
  const req = new IncomingMessage(reqProps);
109
- const res = createServerResponse(preprocessedEvent, overwrittenResponseHeaders, responseStreaming);
127
+ const res = createServerResponse(routingResult, overwrittenResponseHeaders, responseStreaming);
110
128
  await processRequest(req, res, preprocessedEvent);
111
129
  const { statusCode, headers: responseHeaders, isBase64Encoded, body, } = convertRes(res);
112
130
  const internalResult = {
@@ -43,4 +43,8 @@ export declare function resolveWarmerInvoke(warmer: RemoveUndefined<OpenNextConf
43
43
  * @__PURE__
44
44
  */
45
45
  export declare function resolveProxyRequest(proxyRequest: OverrideOptions["proxyExternalRequest"]): Promise<import("../types/overrides").ProxyExternalRequest>;
46
+ /**
47
+ * @__PURE__
48
+ */
49
+ export declare function resolveCdnInvalidation(cdnInvalidation: OverrideOptions["cdnInvalidation"]): Promise<import("../types/overrides").CDNInvalidationHandler>;
46
50
  export {};
@@ -98,3 +98,13 @@ export async function resolveProxyRequest(proxyRequest) {
98
98
  const m_1 = await import("../overrides/proxyExternalRequest/node.js");
99
99
  return m_1.default;
100
100
  }
101
+ /**
102
+ * @__PURE__
103
+ */
104
+ export async function resolveCdnInvalidation(cdnInvalidation) {
105
+ if (typeof cdnInvalidation === "function") {
106
+ return cdnInvalidation();
107
+ }
108
+ const m_1 = await import("../overrides/cdnInvalidation/dummy.js");
109
+ return m_1.default;
110
+ }
@@ -44,7 +44,9 @@ export async function handleMiddleware(internalEvent, middlewareLoader = default
44
44
  const result = await middleware.default({
45
45
  // `geo` is pre Next 15.
46
46
  geo: {
47
- city: headers["x-open-next-city"],
47
+ // The city name is percent-encoded.
48
+ // See https://github.com/vercel/vercel/blob/4cb6143/packages/functions/src/headers.ts#L94C19-L94C37
49
+ city: decodeURIComponent(headers["x-open-next-city"]),
48
50
  country: headers["x-open-next-country"],
49
51
  region: headers["x-open-next-region"],
50
52
  latitude: headers["x-open-next-latitude"],
@@ -0,0 +1,10 @@
1
+ import type { RouteType } from "../../types/open-next";
2
+ export declare const apiPrefix: string;
3
+ export declare const staticRouteMatcher: (path: string) => {
4
+ route: string;
5
+ type: RouteType;
6
+ }[];
7
+ export declare const dynamicRouteMatcher: (path: string) => {
8
+ route: string;
9
+ type: RouteType;
10
+ }[];
@@ -0,0 +1,45 @@
1
+ import { AppPathRoutesManifest, RoutesManifest } from "../../adapters/config/index";
2
+ // Add the locale prefix to the regex so we correctly match the rawPath
3
+ const optionalLocalePrefixRegex = `^/(?:${RoutesManifest.locales.map((locale) => `${locale}/?`).join("|")})?`;
4
+ // Add the basepath prefix to the regex so we correctly match the rawPath
5
+ const optionalBasepathPrefixRegex = RoutesManifest.basePath
6
+ ? `^${RoutesManifest.basePath}/?`
7
+ : "^/";
8
+ // Add the basePath prefix to the api routes
9
+ export const apiPrefix = `${RoutesManifest.basePath ?? ""}/api`;
10
+ const optionalPrefix = optionalLocalePrefixRegex.replace("^/", optionalBasepathPrefixRegex);
11
+ function routeMatcher(routeDefinitions) {
12
+ const regexp = routeDefinitions.map((route) => ({
13
+ page: route.page,
14
+ regexp: new RegExp(route.regex.replace("^/", optionalPrefix)),
15
+ }));
16
+ const appPathsSet = new Set();
17
+ const routePathsSet = new Set();
18
+ // We need to use AppPathRoutesManifest here
19
+ for (const [k, v] of Object.entries(AppPathRoutesManifest)) {
20
+ if (k.endsWith("page")) {
21
+ appPathsSet.add(v);
22
+ }
23
+ else if (k.endsWith("route")) {
24
+ routePathsSet.add(v);
25
+ }
26
+ }
27
+ return function matchRoute(path) {
28
+ const foundRoutes = regexp.filter((route) => route.regexp.test(path));
29
+ return foundRoutes.map((foundRoute) => {
30
+ let routeType = "page";
31
+ if (appPathsSet.has(foundRoute.page)) {
32
+ routeType = "app";
33
+ }
34
+ else if (routePathsSet.has(foundRoute.page)) {
35
+ routeType = "route";
36
+ }
37
+ return {
38
+ route: foundRoute.page,
39
+ type: routeType,
40
+ };
41
+ });
42
+ };
43
+ }
44
+ export const staticRouteMatcher = routeMatcher(RoutesManifest.routes.static);
45
+ export const dynamicRouteMatcher = routeMatcher(RoutesManifest.routes.dynamic);
@@ -2,7 +2,7 @@ import type { OutgoingHttpHeaders } from "node:http";
2
2
  import type { IncomingMessage } from "../../http/index.js";
3
3
  import { OpenNextNodeResponse } from "../../http/openNextResponse.js";
4
4
  import type { MiddlewareManifest } from "../../types/next-types";
5
- import type { InternalEvent, InternalResult, StreamCreator } from "../../types/open-next.js";
5
+ import type { InternalEvent, InternalResult, RoutingResult, StreamCreator } from "../../types/open-next.js";
6
6
  /**
7
7
  *
8
8
  * @__PURE__
@@ -60,7 +60,7 @@ export declare function convertBodyToReadableStream(method: string, body?: strin
60
60
  *
61
61
  * @__PURE__
62
62
  */
63
- export declare function fixCacheHeaderForHtmlPages(rawPath: string, headers: OutgoingHttpHeaders): void;
63
+ export declare function fixCacheHeaderForHtmlPages(internalEvent: InternalEvent, headers: OutgoingHttpHeaders): void;
64
64
  /**
65
65
  *
66
66
  * @__PURE__
@@ -90,4 +90,5 @@ export declare function fixISRHeaders(headers: OutgoingHttpHeaders): void;
90
90
  * @returns
91
91
  * @__PURE__
92
92
  */
93
- export declare function createServerResponse(internalEvent: InternalEvent, headers: Record<string, string | string[] | undefined>, responseStream?: StreamCreator): OpenNextNodeResponse;
93
+ export declare function createServerResponse(routingResult: RoutingResult, headers: Record<string, string | string[] | undefined>, responseStream?: StreamCreator): OpenNextNodeResponse;
94
+ export declare function invalidateCDNOnRequest(params: RoutingResult, headers: OutgoingHttpHeaders): Promise<void>;