@opennextjs/aws 3.3.1 → 3.4.1

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 (54) hide show
  1. package/dist/adapters/cache.js +23 -3
  2. package/dist/adapters/logger.js +2 -2
  3. package/dist/build/createAssets.js +1 -1
  4. package/dist/build/createImageOptimizationBundle.js +2 -1
  5. package/dist/build/createServerBundle.js +8 -9
  6. package/dist/build/edge/createEdgeBundle.js +2 -1
  7. package/dist/build/helper.d.ts +10 -1
  8. package/dist/build/helper.js +19 -3
  9. package/dist/build/validateConfig.js +2 -1
  10. package/dist/core/createMainHandler.js +2 -1
  11. package/dist/core/edgeFunctionHandler.js +2 -1
  12. package/dist/core/requestHandler.js +9 -2
  13. package/dist/core/resolve.d.ts +4 -0
  14. package/dist/core/resolve.js +10 -0
  15. package/dist/core/routing/middleware.js +3 -1
  16. package/dist/core/routing/util.d.ts +3 -2
  17. package/dist/core/routing/util.js +18 -1
  18. package/dist/overrides/cdnInvalidation/cloudfront.d.ts +9 -0
  19. package/dist/overrides/cdnInvalidation/cloudfront.js +26 -0
  20. package/dist/overrides/cdnInvalidation/dummy.d.ts +9 -0
  21. package/dist/overrides/cdnInvalidation/dummy.js +6 -0
  22. package/dist/overrides/imageLoader/fs-dev.d.ts +10 -0
  23. package/dist/overrides/imageLoader/fs-dev.js +14 -0
  24. package/dist/overrides/incrementalCache/fs-dev.d.ts +3 -0
  25. package/dist/overrides/incrementalCache/fs-dev.js +30 -0
  26. package/dist/overrides/incrementalCache/multi-tier-ddb-s3.d.ts +10 -0
  27. package/dist/overrides/incrementalCache/multi-tier-ddb-s3.js +120 -0
  28. package/dist/overrides/incrementalCache/s3-lite.d.ts +2 -0
  29. package/dist/overrides/incrementalCache/s3-lite.js +1 -1
  30. package/dist/overrides/proxyExternalRequest/node.js +6 -5
  31. package/dist/overrides/queue/direct.d.ts +3 -0
  32. package/dist/overrides/queue/direct.js +18 -0
  33. package/dist/overrides/tagCache/dynamodb-lite.js +8 -4
  34. package/dist/overrides/tagCache/dynamodb.js +8 -4
  35. package/dist/overrides/tagCache/fs-dev.d.ts +3 -0
  36. package/dist/overrides/tagCache/fs-dev.js +32 -0
  37. package/dist/overrides/wrappers/cloudflare-edge.js +11 -4
  38. package/dist/overrides/wrappers/dummy.d.ts +1 -1
  39. package/dist/overrides/wrappers/dummy.js +6 -2
  40. package/dist/overrides/wrappers/express-dev.d.ts +7 -0
  41. package/dist/overrides/wrappers/express-dev.js +43 -0
  42. package/dist/plugins/edge.js +4 -3
  43. package/dist/plugins/replacement.d.ts +1 -1
  44. package/dist/plugins/replacement.js +1 -1
  45. package/dist/plugins/resolve.d.ts +1 -0
  46. package/dist/plugins/resolve.js +4 -1
  47. package/dist/types/global.d.ts +19 -12
  48. package/dist/types/open-next.d.ts +12 -6
  49. package/dist/types/overrides.d.ts +10 -1
  50. package/dist/utils/lru.d.ts +8 -0
  51. package/dist/utils/lru.js +29 -0
  52. package/dist/utils/regex.d.ts +18 -0
  53. package/dist/utils/regex.js +16 -0
  54. package/package.json +4 -1
@@ -16,7 +16,7 @@ export default class Cache {
16
16
  async get(key,
17
17
  // fetchCache is for next 13.5 and above, kindHint is for next 14 and above and boolean is for earlier versions
18
18
  options) {
19
- if (globalThis.disableIncrementalCache) {
19
+ if (globalThis.openNextConfig.dangerous?.disableIncrementalCache) {
20
20
  return null;
21
21
  }
22
22
  const softTags = typeof options === "object" ? options.softTags : [];
@@ -130,7 +130,7 @@ export default class Cache {
130
130
  }
131
131
  }
132
132
  async set(key, data, ctx) {
133
- if (globalThis.disableIncrementalCache) {
133
+ if (globalThis.openNextConfig.dangerous?.disableIncrementalCache) {
134
134
  return;
135
135
  }
136
136
  // This one might not even be necessary anymore
@@ -242,7 +242,8 @@ export default class Cache {
242
242
  }
243
243
  }
244
244
  async revalidateTag(tags) {
245
- if (globalThis.disableDynamoDBCache || globalThis.disableIncrementalCache) {
245
+ const config = globalThis.openNextConfig.dangerous;
246
+ if (config?.disableTagCache || config?.disableIncrementalCache) {
246
247
  return;
247
248
  }
248
249
  try {
@@ -275,6 +276,25 @@ export default class Cache {
275
276
  }
276
277
  // Update all keys with the given tag with revalidatedAt set to now
277
278
  await globalThis.tagCache.writeTags(toInsert);
279
+ // We can now invalidate all paths in the CDN
280
+ // This only applies to `revalidateTag`, not to `res.revalidate()`
281
+ const uniquePaths = Array.from(new Set(toInsert
282
+ // We need to filter fetch cache key as they are not in the CDN
283
+ .filter((t) => t.tag.startsWith("_N_T_/"))
284
+ .map((t) => `/${t.path}`)));
285
+ if (uniquePaths.length > 0) {
286
+ await globalThis.cdnInvalidationHandler.invalidatePaths(uniquePaths.map((path) => ({
287
+ initialPath: path,
288
+ rawPath: path,
289
+ resolvedRoutes: [
290
+ {
291
+ route: path,
292
+ // TODO: ideally here we should check if it's an app router page or route
293
+ type: "app",
294
+ },
295
+ ],
296
+ })));
297
+ }
278
298
  }
279
299
  }
280
300
  catch (e) {
@@ -21,9 +21,9 @@ const isDownplayedErrorLog = (errorLog) => DOWNPLAYED_ERROR_LOGS.some((downplaye
21
21
  export function error(...args) {
22
22
  // we try to catch errors from the aws-sdk client and downplay some of them
23
23
  if (args.some((arg) => isDownplayedErrorLog(arg))) {
24
- debug(...args);
24
+ return debug(...args);
25
25
  }
26
- else if (args.some((arg) => isOpenNextError(arg))) {
26
+ if (args.some((arg) => isOpenNextError(arg))) {
27
27
  // In case of an internal error, we log it with the appropriate log level
28
28
  const error = args.find((arg) => isOpenNextError(arg));
29
29
  if (error.logLevel < getOpenNextErrorLogLevel()) {
@@ -44,7 +44,7 @@ export function createStaticAssets(options) {
44
44
  export function createCacheAssets(options) {
45
45
  logger.info("Bundling cache assets...");
46
46
  const { appBuildOutputPath, outputDir } = options;
47
- const packagePath = path.relative(options.monorepoRoot, appBuildOutputPath);
47
+ const packagePath = buildHelper.getPackagePath(options);
48
48
  const buildId = buildHelper.getBuildId(appBuildOutputPath);
49
49
  let useTagCache = false;
50
50
  // Copy pages to cache folder
@@ -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";
@@ -35,10 +36,9 @@ export async function createServerBundle(options) {
35
36
  // We build every other function than default before so we know which route there is left
36
37
  await Promise.all(promises);
37
38
  const remainingRoutes = new Set();
38
- const { appBuildOutputPath, monorepoRoot } = options;
39
- const packagePath = path.relative(monorepoRoot, appBuildOutputPath);
39
+ const { appBuildOutputPath } = options;
40
40
  // Find remaining routes
41
- const serverPath = path.join(appBuildOutputPath, ".next", "standalone", packagePath, ".next", "server");
41
+ const serverPath = path.join(appBuildOutputPath, ".next/standalone", buildHelper.getPackagePath(options), ".next/server");
42
42
  // Find app dir routes
43
43
  if (fs.existsSync(path.join(serverPath, "app"))) {
44
44
  const appPath = path.join(serverPath, "app");
@@ -77,7 +77,7 @@ async function generateBundle(name, options, fnOptions) {
77
77
  // `node_modules` inside `.next/standalone`, and others inside
78
78
  // `.next/standalone/package/path` (ie. `.next`, `server.js`).
79
79
  // We need to output the handler file inside the package path.
80
- const packagePath = path.relative(monorepoRoot, appBuildOutputPath);
80
+ const packagePath = buildHelper.getPackagePath(options);
81
81
  fs.mkdirSync(path.join(outputPath, packagePath), { recursive: true });
82
82
  const ext = fnOptions.runtime === "deno" ? "mjs" : "cjs";
83
83
  fs.copyFileSync(path.join(options.buildDir, `cache.${ext}`), path.join(outputPath, packagePath, "cache.cjs"));
@@ -109,12 +109,12 @@ async function generateBundle(name, options, fnOptions) {
109
109
  buildHelper.compareSemver(options.nextVersion, "13.4.1") <= 0;
110
110
  const overrides = fnOptions.override ?? {};
111
111
  const isBefore13413 = buildHelper.compareSemver(options.nextVersion, "13.4.13") <= 0;
112
- const isAfter141 = buildHelper.compareSemver(options.nextVersion, "14.0.4") >= 0;
112
+ const isAfter141 = buildHelper.compareSemver(options.nextVersion, "14.1") >= 0;
113
113
  const disableRouting = isBefore13413 || config.middleware?.external;
114
114
  const plugins = [
115
115
  openNextReplacementPlugin({
116
116
  name: `requestHandlerOverride ${name}`,
117
- target: /core(\/|\\)requestHandler\.js/g,
117
+ target: getCrossPlatformPathRegex("core/requestHandler.js"),
118
118
  deletes: [
119
119
  ...(disableNextPrebundledReact ? ["applyNextjsPrebundledReact"] : []),
120
120
  ...(disableRouting ? ["withRouting"] : []),
@@ -122,11 +122,10 @@ async function generateBundle(name, options, fnOptions) {
122
122
  }),
123
123
  openNextReplacementPlugin({
124
124
  name: `utilOverride ${name}`,
125
- target: /core(\/|\\)util\.js/g,
125
+ target: getCrossPlatformPathRegex("core/util.js"),
126
126
  deletes: [
127
127
  ...(disableNextPrebundledReact ? ["requireHooks"] : []),
128
- ...(disableRouting ? ["trustHostHeader"] : []),
129
- ...(!isBefore13413 ? ["requestHandlerHost"] : []),
128
+ ...(isBefore13413 ? ["trustHostHeader"] : ["requestHandlerHost"]),
130
129
  ...(isAfter141
131
130
  ? ["experimentalIncrementalCacheHandler"]
132
131
  : ["stableIncrementalCache"]),
@@ -6,6 +6,7 @@ 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, }) {
@@ -41,7 +42,7 @@ export async function buildEdgeBundle({ middlewareInfo, entrypoint, outfile, opt
41
42
  }),
42
43
  openNextReplacementPlugin({
43
44
  name: "externalMiddlewareOverrides",
44
- target: /adapters(\/|\\)middleware\.js/g,
45
+ target: getCrossPlatformPathRegex("adapters/middleware.js"),
45
46
  deletes: includeCache ? [] : ["includeCacheInMiddleware"],
46
47
  }),
47
48
  openNextEdgePlugins({
@@ -15,7 +15,7 @@ export declare function normalizeOptions(config: OpenNextConfig, distDir: string
15
15
  openNextVersion: string;
16
16
  openNextDistDir: string;
17
17
  outputDir: string;
18
- packager: "npm" | "yarn" | "pnpm" | "bun";
18
+ packager: "bun" | "npm" | "yarn" | "pnpm";
19
19
  tempBuildDir: string;
20
20
  };
21
21
  export declare function esbuildSync(esbuildOptions: ESBuildOptions, options: BuildOptions): void;
@@ -51,6 +51,14 @@ export declare function getHtmlPages(dotNextPath: string): Set<string>;
51
51
  export declare function getBuildId(dotNextPath: string): string;
52
52
  export declare function getOpenNextVersion(): string;
53
53
  export declare function getNextVersion(appPath: string): string;
54
+ /**
55
+ * Compare two semver versions.
56
+ *
57
+ * @param v1 - First version. Can be "latest", otherwise it should be a valid semver version in the format of `major.minor.patch`. Usually is the next version from the package.json without canary suffix. If minor or patch are missing, they are considered 0.
58
+ * @param v2 - Second version. Should not be "latest", it should be a valid semver version in the format of `major.minor.patch`. If minor or patch are missing, they are considered 0.
59
+ * @example
60
+ * compareSemver("1.0.0", "1.0.0") // 0
61
+ */
54
62
  export declare function compareSemver(v1: string, v2: string): number;
55
63
  export declare function copyOpenNextConfig(inputDir: string, outputDir: string, isEdge?: boolean): void;
56
64
  export declare function copyEnvFile(appPath: string, packagePath: string, outputPath: string): void;
@@ -73,3 +81,4 @@ export declare function initOutputDir(options: BuildOptions): void;
73
81
  * @returns Whether the edge runtime is used
74
82
  */
75
83
  export declare function isEdgeRuntime(overrides: DefaultOverrideOptions | undefined): Promise<boolean | undefined>;
84
+ export declare function getPackagePath(options: BuildOptions): string;
@@ -45,10 +45,12 @@ function findMonorepoRoot(appPath) {
45
45
  let currentPath = appPath;
46
46
  while (currentPath !== "/") {
47
47
  const found = [
48
+ // bun can generate yaml lock files (`bun install --yarn`) so bun should be before yarn
49
+ { file: "bun.lockb", packager: "bun" },
50
+ { file: "bun.lock", packager: "bun" },
48
51
  { file: "package-lock.json", packager: "npm" },
49
52
  { file: "yarn.lock", packager: "yarn" },
50
53
  { file: "pnpm-lock.yaml", packager: "pnpm" },
51
- { file: "bun.lockb", packager: "bun" },
52
54
  ].find((f) => fs.existsSync(path.join(currentPath, f.file)));
53
55
  if (found) {
54
56
  if (currentPath !== appPath) {
@@ -198,6 +200,14 @@ export function getNextVersion(appPath) {
198
200
  // Drop the -canary.n suffix
199
201
  return version.split("-")[0];
200
202
  }
203
+ /**
204
+ * Compare two semver versions.
205
+ *
206
+ * @param v1 - First version. Can be "latest", otherwise it should be a valid semver version in the format of `major.minor.patch`. Usually is the next version from the package.json without canary suffix. If minor or patch are missing, they are considered 0.
207
+ * @param v2 - Second version. Should not be "latest", it should be a valid semver version in the format of `major.minor.patch`. If minor or patch are missing, they are considered 0.
208
+ * @example
209
+ * compareSemver("1.0.0", "1.0.0") // 0
210
+ */
201
211
  export function compareSemver(v1, v2) {
202
212
  if (v1 === "latest")
203
213
  return 1;
@@ -209,8 +219,11 @@ export function compareSemver(v1, v2) {
209
219
  // biome-ignore lint/style/noParameterAssign:
210
220
  v2 = v2.substring(1);
211
221
  }
212
- const [major1, minor1, patch1] = v1.split(".").map(Number);
213
- const [major2, minor2, patch2] = v2.split(".").map(Number);
222
+ const [major1, minor1 = 0, patch1 = 0] = v1.split(".").map(Number);
223
+ const [major2, minor2 = 0, patch2 = 0] = v2.split(".").map(Number);
224
+ if (Number.isNaN(major1) || Number.isNaN(major2)) {
225
+ throw new Error("The major version is required.");
226
+ }
214
227
  if (major1 !== major2)
215
228
  return major1 - major2;
216
229
  if (minor1 !== minor2)
@@ -276,3 +289,6 @@ export async function isEdgeRuntime(overrides) {
276
289
  }
277
290
  return (await overrides?.wrapper?.())?.edgeRuntime;
278
291
  }
292
+ export function getPackagePath(options) {
293
+ return path.relative(options.monorepoRoot, options.appBuildOutputPath);
294
+ }
@@ -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);
@@ -6,7 +6,8 @@ export default async function edgeFunctionHandler(request) {
6
6
  if (!correspondingRoute) {
7
7
  throw new Error(`No route found for ${request.url}`);
8
8
  }
9
- const result = await self._ENTRIES[`middleware_${correspondingRoute.name}`].default({
9
+ const entry = await self._ENTRIES[`middleware_${correspondingRoute.name}`];
10
+ const result = await entry.default({
10
11
  page: correspondingRoute.page,
11
12
  request: {
12
13
  ...request,
@@ -82,7 +82,14 @@ export async function openNextHandler(internalEvent, responseStreaming) {
82
82
  if ("type" in routingResult) {
83
83
  // response is used only in the streaming case
84
84
  if (responseStreaming) {
85
- 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);
86
93
  response.statusCode = routingResult.statusCode;
87
94
  response.flushHeaders();
88
95
  const [bodyToConsume, bodyToReturn] = routingResult.body.tee();
@@ -117,7 +124,7 @@ export async function openNextHandler(internalEvent, responseStreaming) {
117
124
  store.mergeHeadersPriority = mergeHeadersPriority;
118
125
  }
119
126
  const req = new IncomingMessage(reqProps);
120
- const res = createServerResponse(preprocessedEvent, overwrittenResponseHeaders, responseStreaming);
127
+ const res = createServerResponse(routingResult, overwrittenResponseHeaders, responseStreaming);
121
128
  await processRequest(req, res, preprocessedEvent);
122
129
  const { statusCode, headers: responseHeaders, isBase64Encoded, body, } = convertRes(res);
123
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"],
@@ -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__
@@ -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>;
@@ -352,7 +352,8 @@ export function fixISRHeaders(headers) {
352
352
  * @returns
353
353
  * @__PURE__
354
354
  */
355
- export function createServerResponse(internalEvent, headers, responseStream) {
355
+ export function createServerResponse(routingResult, headers, responseStream) {
356
+ const internalEvent = routingResult.internalEvent;
356
357
  return new OpenNextNodeResponse((_headers) => {
357
358
  fixCacheHeaderForHtmlPages(internalEvent, _headers);
358
359
  fixSWRCacheHeader(_headers);
@@ -360,5 +361,21 @@ export function createServerResponse(internalEvent, headers, responseStream) {
360
361
  fixISRHeaders(_headers);
361
362
  }, async (_headers) => {
362
363
  await revalidateIfRequired(internalEvent.headers.host, internalEvent.rawPath, _headers);
364
+ await invalidateCDNOnRequest(routingResult, _headers);
363
365
  }, responseStream, headers);
364
366
  }
367
+ // This function is used only for `res.revalidate()`
368
+ export async function invalidateCDNOnRequest(params, headers) {
369
+ const { internalEvent, initialPath, resolvedRoutes } = params;
370
+ const isIsrRevalidation = internalEvent.headers["x-isr"] === "1";
371
+ if (!isIsrRevalidation &&
372
+ headers[CommonHeaders.NEXT_CACHE] === "REVALIDATED") {
373
+ await globalThis.cdnInvalidationHandler.invalidatePaths([
374
+ {
375
+ initialPath,
376
+ rawPath: internalEvent.rawPath,
377
+ resolvedRoutes,
378
+ },
379
+ ]);
380
+ }
381
+ }
@@ -0,0 +1,9 @@
1
+ declare const _default: {
2
+ name: string;
3
+ invalidatePaths: (paths: {
4
+ initialPath: string;
5
+ rawPath: string;
6
+ resolvedRoutes: import("../../types/open-next").ResolvedRoute[];
7
+ }[]) => Promise<void>;
8
+ };
9
+ export default _default;
@@ -0,0 +1,26 @@
1
+ import { CloudFrontClient, CreateInvalidationCommand, } from "@aws-sdk/client-cloudfront";
2
+ const cloudfront = new CloudFrontClient({});
3
+ export default {
4
+ name: "cloudfront",
5
+ invalidatePaths: async (paths) => {
6
+ const constructedPaths = paths.flatMap(({ initialPath, resolvedRoutes }) => {
7
+ const isAppRouter = resolvedRoutes.some((route) => route.type === "app");
8
+ // revalidateTag doesn't have any leading slash, remove it just to be sure
9
+ const path = initialPath.replace(/^\//, "");
10
+ return isAppRouter
11
+ ? [`/${path}`, `/${path}?_rsc=*`]
12
+ : [
13
+ `/${path}`,
14
+ `/_next/data/${process.env.NEXT_BUILD_ID}${path === "/" ? "/index" : `/${path}`}.json*`,
15
+ ];
16
+ });
17
+ await cloudfront.send(new CreateInvalidationCommand({
18
+ DistributionId: process.env.CLOUDFRONT_DISTRIBUTION_ID,
19
+ InvalidationBatch: {
20
+ // Do we need to limit the number of paths? Or batch them into multiple commands?
21
+ Paths: { Quantity: constructedPaths.length, Items: constructedPaths },
22
+ CallerReference: `${Date.now()}`,
23
+ },
24
+ }));
25
+ },
26
+ };
@@ -0,0 +1,9 @@
1
+ declare const _default: {
2
+ name: string;
3
+ invalidatePaths: (_: {
4
+ initialPath: string;
5
+ rawPath: string;
6
+ resolvedRoutes: import("../../types/open-next").ResolvedRoute[];
7
+ }[]) => Promise<void>;
8
+ };
9
+ export default _default;
@@ -0,0 +1,6 @@
1
+ export default {
2
+ name: "dummy",
3
+ invalidatePaths: (_) => {
4
+ return Promise.resolve();
5
+ },
6
+ };
@@ -0,0 +1,10 @@
1
+ import fs from "node:fs";
2
+ declare const _default: {
3
+ name: string;
4
+ load: (url: string) => Promise<{
5
+ body: fs.ReadStream;
6
+ contentType: string;
7
+ cacheControl: string;
8
+ }>;
9
+ };
10
+ export default _default;
@@ -0,0 +1,14 @@
1
+ import fs from "node:fs";
2
+ export default {
3
+ name: "fs-dev",
4
+ load: async (url) => {
5
+ const basePath = "../../assets";
6
+ const body = fs.createReadStream(`${basePath}/${url}`);
7
+ const contentType = url.endsWith(".png") ? "image/png" : "image/jpeg";
8
+ return {
9
+ body,
10
+ contentType,
11
+ cacheControl: "public, max-age=31536000, immutable",
12
+ };
13
+ },
14
+ };
@@ -0,0 +1,3 @@
1
+ import type { IncrementalCache } from "../../types/overrides.js";
2
+ declare const cache: IncrementalCache;
3
+ export default cache;
@@ -0,0 +1,30 @@
1
+ import fs from "node:fs/promises";
2
+ import path from "node:path";
3
+ const buildId = process.env.NEXT_BUILD_ID;
4
+ const basePath = path.resolve(process.cwd(), `../../cache/${buildId}`);
5
+ const getCacheKey = (key) => {
6
+ return path.join(basePath, `${key}.cache`);
7
+ };
8
+ const cache = {
9
+ name: "fs-dev",
10
+ get: async (key) => {
11
+ const fileData = await fs.readFile(getCacheKey(key), "utf-8");
12
+ const data = JSON.parse(fileData);
13
+ const { mtime } = await fs.stat(getCacheKey(key));
14
+ return {
15
+ value: data,
16
+ lastModified: mtime.getTime(),
17
+ };
18
+ },
19
+ set: async (key, value, isFetch) => {
20
+ const data = JSON.stringify(value);
21
+ const cacheKey = getCacheKey(key);
22
+ // We need to create the directory before writing the file
23
+ await fs.mkdir(path.dirname(cacheKey), { recursive: true });
24
+ await fs.writeFile(cacheKey, data);
25
+ },
26
+ delete: async (key) => {
27
+ await fs.rm(getCacheKey(key));
28
+ },
29
+ };
30
+ export default cache;
@@ -0,0 +1,10 @@
1
+ import type { IncrementalCache } from "../../types/overrides";
2
+ /**
3
+ * This cache implementation uses a multi-tier cache with a local cache, a DynamoDB metadata cache and an S3 cache.
4
+ * It uses the same DynamoDB table as the default tag cache and the same S3 bucket as the default incremental cache.
5
+ * It will first check the local cache.
6
+ * If the local cache is expired, it will check the DynamoDB metadata cache to see if the local cache is still valid.
7
+ * Lastly it will check the S3 cache.
8
+ */
9
+ declare const multiTierCache: IncrementalCache;
10
+ export default multiTierCache;