@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
@@ -0,0 +1,120 @@
1
+ import { customFetchClient } from "../../utils/fetch";
2
+ import { LRUCache } from "../../utils/lru";
3
+ import { debug } from "../../adapters/logger";
4
+ import S3Cache, { getAwsClient } from "./s3-lite";
5
+ // TTL for the local cache in milliseconds
6
+ const localCacheTTL = process.env.OPEN_NEXT_LOCAL_CACHE_TTL_MS
7
+ ? Number.parseInt(process.env.OPEN_NEXT_LOCAL_CACHE_TTL_MS, 10)
8
+ : 0;
9
+ // Maximum size of the local cache in nb of entries
10
+ const maxCacheSize = process.env.OPEN_NEXT_LOCAL_CACHE_SIZE
11
+ ? Number.parseInt(process.env.OPEN_NEXT_LOCAL_CACHE_SIZE, 10)
12
+ : 1000;
13
+ const localCache = new LRUCache(maxCacheSize);
14
+ const awsFetch = (body, type = "get") => {
15
+ const { CACHE_BUCKET_REGION } = process.env;
16
+ const client = getAwsClient();
17
+ return customFetchClient(client)(`https://dynamodb.${CACHE_BUCKET_REGION}.amazonaws.com`, {
18
+ method: "POST",
19
+ headers: {
20
+ "Content-Type": "application/x-amz-json-1.0",
21
+ "X-Amz-Target": `DynamoDB_20120810.${type === "get" ? "GetItem" : "PutItem"}`,
22
+ },
23
+ body,
24
+ });
25
+ };
26
+ const buildDynamoKey = (key) => {
27
+ const { NEXT_BUILD_ID } = process.env;
28
+ return `__meta_${NEXT_BUILD_ID}_${key}`;
29
+ };
30
+ /**
31
+ * This cache implementation uses a multi-tier cache with a local cache, a DynamoDB metadata cache and an S3 cache.
32
+ * It uses the same DynamoDB table as the default tag cache and the same S3 bucket as the default incremental cache.
33
+ * It will first check the local cache.
34
+ * If the local cache is expired, it will check the DynamoDB metadata cache to see if the local cache is still valid.
35
+ * Lastly it will check the S3 cache.
36
+ */
37
+ const multiTierCache = {
38
+ name: "multi-tier-ddb-s3",
39
+ async get(key, isFetch) {
40
+ // First we check the local cache
41
+ const localCacheEntry = localCache.get(key);
42
+ if (localCacheEntry) {
43
+ if (Date.now() - localCacheEntry.lastModified < localCacheTTL) {
44
+ debug("Using local cache without checking ddb");
45
+ return localCacheEntry;
46
+ }
47
+ try {
48
+ // Here we'll check ddb metadata to see if the local cache is still valid
49
+ const { CACHE_DYNAMO_TABLE } = process.env;
50
+ const result = await awsFetch(JSON.stringify({
51
+ TableName: CACHE_DYNAMO_TABLE,
52
+ Key: {
53
+ path: { S: buildDynamoKey(key) },
54
+ tag: { S: buildDynamoKey(key) },
55
+ },
56
+ }));
57
+ if (result.status === 200) {
58
+ const data = await result.json();
59
+ const hasBeenDeleted = data.Item?.deleted?.BOOL;
60
+ if (hasBeenDeleted) {
61
+ localCache.delete(key);
62
+ return { value: undefined, lastModified: 0 };
63
+ }
64
+ // If the metadata is older than the local cache, we can use the local cache
65
+ // If it's not found we assume that no write has been done yet and we can use the local cache
66
+ const lastModified = data.Item?.revalidatedAt?.N
67
+ ? Number.parseInt(data.Item.revalidatedAt.N, 10)
68
+ : 0;
69
+ if (lastModified <= localCacheEntry.lastModified) {
70
+ debug("Using local cache after checking ddb");
71
+ return localCacheEntry;
72
+ }
73
+ }
74
+ }
75
+ catch (e) {
76
+ debug("Failed to get metadata from ddb", e);
77
+ }
78
+ }
79
+ const result = await S3Cache.get(key, isFetch);
80
+ if (result.value) {
81
+ localCache.set(key, {
82
+ value: result.value,
83
+ lastModified: result.lastModified ?? Date.now(),
84
+ });
85
+ }
86
+ return result;
87
+ },
88
+ // Both for set and delete we choose to do the write to S3 first and then to DynamoDB
89
+ // Which means that if it fails in DynamoDB, instance that don't have local cache will work as expected.
90
+ // But instance that have local cache will have a stale cache until the next working set or delete.
91
+ async set(key, value, isFetch) {
92
+ const revalidatedAt = Date.now();
93
+ await S3Cache.set(key, value, isFetch);
94
+ await awsFetch(JSON.stringify({
95
+ TableName: process.env.CACHE_DYNAMO_TABLE,
96
+ Item: {
97
+ tag: { S: buildDynamoKey(key) },
98
+ path: { S: buildDynamoKey(key) },
99
+ revalidatedAt: { N: String(revalidatedAt) },
100
+ },
101
+ }), "set");
102
+ localCache.set(key, {
103
+ value,
104
+ lastModified: revalidatedAt,
105
+ });
106
+ },
107
+ async delete(key) {
108
+ await S3Cache.delete(key);
109
+ await awsFetch(JSON.stringify({
110
+ TableName: process.env.CACHE_DYNAMO_TABLE,
111
+ Item: {
112
+ tag: { S: buildDynamoKey(key) },
113
+ path: { S: buildDynamoKey(key) },
114
+ deleted: { BOOL: true },
115
+ },
116
+ }), "set");
117
+ localCache.delete(key);
118
+ },
119
+ };
120
+ export default multiTierCache;
@@ -1,3 +1,5 @@
1
+ import { AwsClient } from "aws4fetch";
1
2
  import type { IncrementalCache } from "../../types/overrides";
3
+ export declare const getAwsClient: () => AwsClient;
2
4
  declare const incrementalCache: IncrementalCache;
3
5
  export default incrementalCache;
@@ -5,7 +5,7 @@ import { IgnorableError, RecoverableError } from "../../utils/error";
5
5
  import { customFetchClient } from "../../utils/fetch";
6
6
  import { parseNumberFromEnv } from "../../adapters/util";
7
7
  let awsClient = null;
8
- const getAwsClient = () => {
8
+ export const getAwsClient = () => {
9
9
  const { CACHE_BUCKET_REGION } = process.env;
10
10
  if (awsClient) {
11
11
  return awsClient;
@@ -36,16 +36,17 @@ const nodeProxy = {
36
36
  method,
37
37
  rejectUnauthorized: false,
38
38
  }, (_res) => {
39
- const nodeReadableStream = _res.headers["content-encoding"] === "br"
39
+ const resHeaders = _res.headers;
40
+ const nodeReadableStream = resHeaders["content-encoding"] === "br"
40
41
  ? _res.pipe(require("node:zlib").createBrotliDecompress())
41
- : _res.headers["content-encoding"] === "gzip"
42
+ : resHeaders["content-encoding"] === "gzip"
42
43
  ? _res.pipe(require("node:zlib").createGunzip())
43
44
  : _res;
44
- const isBase64Encoded = isBinaryContentType(headers["content-type"]) ||
45
- !!headers["content-encoding"];
45
+ const isBase64Encoded = isBinaryContentType(resHeaders["content-type"]) ||
46
+ !!resHeaders["content-encoding"];
46
47
  const result = {
47
48
  type: "core",
48
- headers: filterHeadersForProxy(_res.headers),
49
+ headers: filterHeadersForProxy(resHeaders),
49
50
  statusCode: _res.statusCode ?? 200,
50
51
  // TODO: check base64 encoding
51
52
  isBase64Encoded,
@@ -0,0 +1,3 @@
1
+ import type { Queue } from "../../types/overrides.js";
2
+ declare const queue: Queue;
3
+ export default queue;
@@ -0,0 +1,18 @@
1
+ const queue = {
2
+ name: "dev-queue",
3
+ send: async (message) => {
4
+ const prerenderManifest = (await import("../../adapters/config/index.js"))
5
+ .PrerenderManifest;
6
+ const { host, url } = message.MessageBody;
7
+ const protocol = host.includes("localhost") ? "http" : "https";
8
+ const revalidateId = prerenderManifest.preview.previewModeId;
9
+ await globalThis.internalFetch(`${protocol}://${host}${url}`, {
10
+ method: "HEAD",
11
+ headers: {
12
+ "x-prerender-revalidate": revalidateId,
13
+ "x-isr": "1",
14
+ },
15
+ });
16
+ },
17
+ };
18
+ export default queue;
@@ -49,8 +49,9 @@ function buildDynamoObject(path, tags, revalidatedAt) {
49
49
  const tagCache = {
50
50
  async getByPath(path) {
51
51
  try {
52
- if (globalThis.disableDynamoDBCache)
52
+ if (globalThis.openNextConfig.dangerous?.disableTagCache) {
53
53
  return [];
54
+ }
54
55
  const { CACHE_DYNAMO_TABLE, NEXT_BUILD_ID } = process.env;
55
56
  const result = await awsFetch(JSON.stringify({
56
57
  TableName: CACHE_DYNAMO_TABLE,
@@ -79,8 +80,9 @@ const tagCache = {
79
80
  },
80
81
  async getByTag(tag) {
81
82
  try {
82
- if (globalThis.disableDynamoDBCache)
83
+ if (globalThis.openNextConfig.dangerous?.disableTagCache) {
83
84
  return [];
85
+ }
84
86
  const { CACHE_DYNAMO_TABLE, NEXT_BUILD_ID } = process.env;
85
87
  const result = await awsFetch(JSON.stringify({
86
88
  TableName: CACHE_DYNAMO_TABLE,
@@ -107,8 +109,9 @@ const tagCache = {
107
109
  },
108
110
  async getLastModified(key, lastModified) {
109
111
  try {
110
- if (globalThis.disableDynamoDBCache)
112
+ if (globalThis.openNextConfig.dangerous?.disableTagCache) {
111
113
  return lastModified ?? Date.now();
114
+ }
112
115
  const { CACHE_DYNAMO_TABLE } = process.env;
113
116
  const result = await awsFetch(JSON.stringify({
114
117
  TableName: CACHE_DYNAMO_TABLE,
@@ -139,8 +142,9 @@ const tagCache = {
139
142
  async writeTags(tags) {
140
143
  try {
141
144
  const { CACHE_DYNAMO_TABLE } = process.env;
142
- if (globalThis.disableDynamoDBCache)
145
+ if (globalThis.openNextConfig.dangerous?.disableTagCache) {
143
146
  return;
147
+ }
144
148
  const dataChunks = chunk(tags, MAX_DYNAMO_BATCH_WRITE_ITEM_COUNT).map((Items) => ({
145
149
  RequestItems: {
146
150
  [CACHE_DYNAMO_TABLE ?? ""]: Items.map((Item) => ({
@@ -27,8 +27,9 @@ function buildDynamoObject(path, tags, revalidatedAt) {
27
27
  const tagCache = {
28
28
  async getByPath(path) {
29
29
  try {
30
- if (globalThis.disableDynamoDBCache)
30
+ if (globalThis.openNextConfig.dangerous?.disableTagCache) {
31
31
  return [];
32
+ }
32
33
  const result = await dynamoClient.send(new QueryCommand({
33
34
  TableName: CACHE_DYNAMO_TABLE,
34
35
  IndexName: "revalidate",
@@ -52,8 +53,9 @@ const tagCache = {
52
53
  },
53
54
  async getByTag(tag) {
54
55
  try {
55
- if (globalThis.disableDynamoDBCache)
56
+ if (globalThis.openNextConfig.dangerous?.disableTagCache) {
56
57
  return [];
58
+ }
57
59
  const { Items } = await dynamoClient.send(new QueryCommand({
58
60
  TableName: CACHE_DYNAMO_TABLE,
59
61
  KeyConditionExpression: "#tag = :tag",
@@ -75,8 +77,9 @@ const tagCache = {
75
77
  },
76
78
  async getLastModified(key, lastModified) {
77
79
  try {
78
- if (globalThis.disableDynamoDBCache)
80
+ if (globalThis.openNextConfig.dangerous?.disableTagCache) {
79
81
  return lastModified ?? Date.now();
82
+ }
80
83
  const result = await dynamoClient.send(new QueryCommand({
81
84
  TableName: CACHE_DYNAMO_TABLE,
82
85
  IndexName: "revalidate",
@@ -102,8 +105,9 @@ const tagCache = {
102
105
  },
103
106
  async writeTags(tags) {
104
107
  try {
105
- if (globalThis.disableDynamoDBCache)
108
+ if (globalThis.openNextConfig.dangerous?.disableTagCache) {
106
109
  return;
110
+ }
107
111
  const dataChunks = chunk(tags, MAX_DYNAMO_BATCH_WRITE_ITEM_COUNT).map((Items) => ({
108
112
  RequestItems: {
109
113
  [CACHE_DYNAMO_TABLE ?? ""]: Items.map((Item) => ({
@@ -0,0 +1,3 @@
1
+ import type { TagCache } from "../../types/overrides";
2
+ declare const tagCache: TagCache;
3
+ export default tagCache;
@@ -0,0 +1,32 @@
1
+ import fs from "node:fs";
2
+ const tagFile = "../../dynamodb-provider/dynamodb-cache.json";
3
+ const tagContent = fs.readFileSync(tagFile, "utf-8");
4
+ let tags = JSON.parse(tagContent);
5
+ const tagCache = {
6
+ name: "fs-dev",
7
+ getByPath: async (path) => {
8
+ return tags
9
+ .filter((tagPathMapping) => tagPathMapping.path.S === path)
10
+ .map((tag) => tag.tag.S);
11
+ },
12
+ getByTag: async (tag) => {
13
+ return tags
14
+ .filter((tagPathMapping) => tagPathMapping.tag.S === tag)
15
+ .map((tag) => tag.path.S);
16
+ },
17
+ getLastModified: async (path, lastModified) => {
18
+ const revalidatedTags = tags.filter((tagPathMapping) => tagPathMapping.path.S === path &&
19
+ Number.parseInt(tagPathMapping.revalidatedAt.N) > (lastModified ?? 0));
20
+ return revalidatedTags.length > 0 ? -1 : (lastModified ?? Date.now());
21
+ },
22
+ writeTags: async (newTags) => {
23
+ const newTagsSet = new Set(newTags.map(({ tag, path }) => `${tag}-${path}`));
24
+ const unchangedTags = tags.filter(({ tag, path }) => !newTagsSet.has(`${tag.S}-${path.S}`));
25
+ tags = unchangedTags.concat(newTags.map((tag) => ({
26
+ tag: { S: tag.tag },
27
+ path: { S: tag.path },
28
+ revalidatedAt: { N: String(tag.revalidatedAt) },
29
+ })));
30
+ },
31
+ };
32
+ export default tagCache;
@@ -1,5 +1,7 @@
1
- const cfPropNameToHeaderName = {
2
- city: "x-open-next-city",
1
+ const cfPropNameMapping = {
2
+ // The city name is percent-encoded.
3
+ // See https://github.com/vercel/vercel/blob/4cb6143/packages/functions/src/headers.ts#L94C19-L94C37
4
+ city: [encodeURIComponent, "x-open-next-city"],
3
5
  country: "x-open-next-country",
4
6
  regionCode: "x-open-next-region",
5
7
  latitude: "x-open-next-latitude",
@@ -20,10 +22,15 @@ const handler = async (handler, converter) => async (request, env, ctx) => {
20
22
  // See https://developers.cloudflare.com/workers/runtime-apis/request
21
23
  // Note: This code could be moved to a cloudflare specific converter when one is created.
22
24
  const cfProperties = request.cf;
23
- for (const [propName, headerName] of Object.entries(cfPropNameToHeaderName)) {
25
+ for (const [propName, mapping] of Object.entries(cfPropNameMapping)) {
24
26
  const propValue = cfProperties?.[propName];
25
27
  if (propValue != null) {
26
- internalEvent.headers[headerName] = propValue;
28
+ const [encode, headerName] = Array.isArray(mapping)
29
+ ? mapping
30
+ : [null, mapping];
31
+ internalEvent.headers[headerName] = encode
32
+ ? encode(propValue)
33
+ : propValue;
27
34
  }
28
35
  }
29
36
  const response = await handler(internalEvent);
@@ -2,6 +2,6 @@ import type { WrapperHandler } from "../../types/overrides";
2
2
  declare const _default: {
3
3
  name: string;
4
4
  wrapper: WrapperHandler;
5
- supportStreaming: false;
5
+ supportStreaming: true;
6
6
  };
7
7
  export default _default;
@@ -1,6 +1,10 @@
1
- const dummyWrapper = async () => async () => undefined;
1
+ const dummyWrapper = async (handler, converter) => {
2
+ return async (event, responseStream) => {
3
+ return await handler(event, responseStream);
4
+ };
5
+ };
2
6
  export default {
3
7
  name: "dummy",
4
8
  wrapper: dummyWrapper,
5
- supportStreaming: false,
9
+ supportStreaming: true,
6
10
  };
@@ -0,0 +1,7 @@
1
+ import type { WrapperHandler } from "../../types/overrides.js";
2
+ declare const _default: {
3
+ wrapper: WrapperHandler;
4
+ name: string;
5
+ supportStreaming: boolean;
6
+ };
7
+ export default _default;
@@ -0,0 +1,43 @@
1
+ import express from "express";
2
+ const wrapper = async (handler, converter) => {
3
+ const app = express();
4
+ // To serve static assets
5
+ app.use(express.static("../../assets"));
6
+ const imageHandlerPath = "../../image-optimization-function/index.mjs";
7
+ const imageHandler = await import(imageHandlerPath).then((m) => m.handler);
8
+ app.all("/_next/image", async (req, res) => {
9
+ const internalEvent = await converter.convertFrom(req);
10
+ const _res = {
11
+ writeHeaders: (prelude) => {
12
+ res.writeHead(prelude.statusCode, prelude.headers);
13
+ return res;
14
+ },
15
+ };
16
+ await imageHandler(internalEvent, _res);
17
+ });
18
+ app.all("*paths", async (req, res) => {
19
+ const internalEvent = await converter.convertFrom(req);
20
+ const _res = {
21
+ writeHeaders: (prelude) => {
22
+ res.writeHead(prelude.statusCode, prelude.headers);
23
+ return res;
24
+ },
25
+ onFinish: () => { },
26
+ };
27
+ await handler(internalEvent, _res);
28
+ });
29
+ const server = app.listen(Number.parseInt(process.env.PORT ?? "3000", 10), () => {
30
+ console.log(`Server running on port ${process.env.PORT ?? 3000}`);
31
+ });
32
+ app.on("error", (err) => {
33
+ console.error("error", err);
34
+ });
35
+ return () => {
36
+ server.close();
37
+ };
38
+ };
39
+ export default {
40
+ wrapper,
41
+ name: "expresss-dev",
42
+ supportStreaming: true,
43
+ };
@@ -3,6 +3,7 @@ import path from "node:path";
3
3
  import chalk from "chalk";
4
4
  import { loadAppPathRoutesManifest, loadAppPathsManifest, loadAppPathsManifestKeys, loadBuildId, loadConfig, loadConfigHeaders, loadHtmlPages, loadMiddlewareManifest, loadPrerenderManifest, loadRoutesManifest, } from "../adapters/config/util.js";
5
5
  import logger from "../logger.js";
6
+ import { getCrossPlatformPathRegex } from "../utils/regex.js";
6
7
  /**
7
8
  * @param opts.nextDir - The path to the .next directory
8
9
  * @param opts.edgeFunctionHandlerPath - The path to the edgeFunctionHandler.js file that we'll use to bundle the routing
@@ -28,7 +29,7 @@ export function openNextEdgePlugins({ nextDir, edgeFunctionHandlerPath, middlewa
28
29
  logger.debug(chalk.blue("OpenNext Edge plugin"));
29
30
  if (edgeFunctionHandlerPath) {
30
31
  // If we bundle the routing, we need to resolve the middleware
31
- build.onResolve({ filter: /\.\/middleware.mjs/g }, () => {
32
+ build.onResolve({ filter: getCrossPlatformPathRegex("./middleware.mjs") }, () => {
32
33
  return {
33
34
  path: edgeFunctionHandlerPath,
34
35
  };
@@ -55,7 +56,7 @@ export function openNextEdgePlugins({ nextDir, edgeFunctionHandlerPath, middlewa
55
56
  loader: "js",
56
57
  }));
57
58
  // We inject the entry files into the edgeFunctionHandler
58
- build.onLoad({ filter: /\/edgeFunctionHandler.js/g }, async (args) => {
59
+ build.onLoad({ filter: getCrossPlatformPathRegex("/edgeFunctionHandler.js") }, async (args) => {
59
60
  let contents = readFileSync(args.path, "utf-8");
60
61
  contents = `
61
62
  globalThis._ENTRIES = {};
@@ -117,7 +118,7 @@ ${contents}
117
118
  contents,
118
119
  };
119
120
  });
120
- build.onLoad({ filter: /adapters\/config\/index/g }, async () => {
121
+ build.onLoad({ filter: getCrossPlatformPathRegex("adapters/config/index") }, async () => {
121
122
  const NextConfig = loadConfig(nextDir);
122
123
  const BuildId = loadBuildId(nextDir);
123
124
  const HtmlPages = loadHtmlPages(nextDir);
@@ -8,7 +8,7 @@ export interface IPluginSettings {
8
8
  /**
9
9
  *
10
10
  * openNextPlugin({
11
- * target: /plugins\/default\.js/g,
11
+ * target: /plugins(\/|\\)default\.js/g,
12
12
  * replacements: [require.resolve("./plugins/default.js")],
13
13
  * deletes: ["id1"],
14
14
  * })
@@ -6,7 +6,7 @@ const importPattern = /\/\/#import([\s\S]*?)\n\/\/#endImport/gm;
6
6
  /**
7
7
  *
8
8
  * openNextPlugin({
9
- * target: /plugins\/default\.js/g,
9
+ * target: /plugins(\/|\\)default\.js/g,
10
10
  * replacements: [require.resolve("./plugins/default.js")],
11
11
  * deletes: ["id1"],
12
12
  * })
@@ -12,6 +12,7 @@ export interface IPluginSettings {
12
12
  originResolver?: LazyLoadedOverride<OriginResolver> | IncludedOriginResolver;
13
13
  warmer?: LazyLoadedOverride<Warmer> | IncludedWarmer;
14
14
  proxyExternalRequest?: OverrideOptions["proxyExternalRequest"];
15
+ cdnInvalidation?: OverrideOptions["cdnInvalidation"];
15
16
  };
16
17
  fnName?: string;
17
18
  }
@@ -1,6 +1,7 @@
1
1
  import { readFileSync } from "node:fs";
2
2
  import chalk from "chalk";
3
3
  import logger from "../logger.js";
4
+ import { getCrossPlatformPathRegex } from "../utils/regex.js";
4
5
  function getOverrideOrDummy(override) {
5
6
  if (typeof override === "string") {
6
7
  return override;
@@ -19,6 +20,7 @@ const nameToFolder = {
19
20
  originResolver: "originResolver",
20
21
  warmer: "warmer",
21
22
  proxyExternalRequest: "proxyExternalRequest",
23
+ cdnInvalidation: "cdnInvalidation",
22
24
  };
23
25
  const defaultOverrides = {
24
26
  wrapper: "aws-lambda",
@@ -30,6 +32,7 @@ const defaultOverrides = {
30
32
  originResolver: "pattern-env",
31
33
  warmer: "aws-lambda",
32
34
  proxyExternalRequest: "node",
35
+ cdnInvalidation: "dummy",
33
36
  };
34
37
  /**
35
38
  * @param opts.overrides - The name of the overrides to use
@@ -40,7 +43,7 @@ export function openNextResolvePlugin({ overrides, fnName, }) {
40
43
  name: "opennext-resolve",
41
44
  setup(build) {
42
45
  logger.debug(chalk.blue("OpenNext Resolve plugin"), fnName ? `for ${fnName}` : "");
43
- build.onLoad({ filter: /core(\/|\\)resolve\.js/g }, async (args) => {
46
+ build.onLoad({ filter: getCrossPlatformPathRegex("core/resolve.js") }, async (args) => {
44
47
  let contents = readFileSync(args.path, "utf-8");
45
48
  const overridesEntries = Object.entries(overrides ?? {});
46
49
  for (let [overrideName, overrideValue] of overridesEntries) {
@@ -1,6 +1,6 @@
1
1
  import type { AsyncLocalStorage } from "node:async_hooks";
2
2
  import type { OutgoingHttpHeaders } from "node:http";
3
- import type { IncrementalCache, ProxyExternalRequest, Queue, TagCache } from "./overrides";
3
+ import type { CDNInvalidationHandler, IncrementalCache, ProxyExternalRequest, Queue, TagCache } from "./overrides";
4
4
  import type { DetachedPromiseRunner } from "../utils/promise";
5
5
  import type { OpenNextConfig } from "./open-next";
6
6
  export interface RequestData {
@@ -29,16 +29,17 @@ export interface RequestData {
29
29
  body?: ReadableStream<Uint8Array>;
30
30
  signal: AbortSignal;
31
31
  }
32
+ interface Entry {
33
+ default: (props: {
34
+ page: string;
35
+ request: RequestData;
36
+ }) => Promise<{
37
+ response: Response;
38
+ waitUntil: Promise<void>;
39
+ }>;
40
+ }
32
41
  interface Entries {
33
- [k: string]: {
34
- default: (props: {
35
- page: string;
36
- request: RequestData;
37
- }) => Promise<{
38
- response: Response;
39
- waitUntil: Promise<void>;
40
- }>;
41
- };
42
+ [k: string]: Entry | Promise<Entry>;
42
43
  }
43
44
  export interface EdgeRoute {
44
45
  name: string;
@@ -66,13 +67,13 @@ declare global {
66
67
  var tagCache: TagCache;
67
68
  /**
68
69
  * A boolean that indicates if the DynamoDB cache is disabled.
69
- * TODO: Remove this, we already have access to the config file
70
+ * @deprecated This will be removed, use `globalThis.openNextConfig.dangerous?.disableTagCache` instead.
70
71
  * Defined in esbuild banner for the cache adapter.
71
72
  */
72
73
  var disableDynamoDBCache: boolean;
73
74
  /**
74
75
  * A boolean that indicates if the incremental cache is disabled.
75
- * TODO: Remove this, we already have access to the config file
76
+ * @deprecated This will be removed, use `globalThis.openNextConfig.dangerous?.disableIncrementalCache` instead.
76
77
  * Defined in esbuild banner for the cache adapter.
77
78
  */
78
79
  var disableIncrementalCache: boolean;
@@ -184,5 +185,11 @@ declare global {
184
185
  * Defined in `createMainHandler`.
185
186
  */
186
187
  var proxyExternalRequest: ProxyExternalRequest;
188
+ /**
189
+ * The function that will be called when the CDN needs invalidating (either from `revalidateTag` or from `res.revalidate`)
190
+ * Available in main functions
191
+ * Defined in `createMainHandler`
192
+ */
193
+ var cdnInvalidationHandler: CDNInvalidationHandler;
187
194
  }
188
195
  export {};
@@ -1,7 +1,7 @@
1
1
  import type { ReadableStream } from "node:stream/web";
2
2
  import type { Writable } from "node:stream";
3
3
  import type { WarmerEvent, WarmerResponse } from "../adapters/warmer-function";
4
- import type { Converter, ImageLoader, IncrementalCache, OriginResolver, ProxyExternalRequest, Queue, TagCache, Warmer, Wrapper } from "./overrides";
4
+ import type { CDNInvalidationHandler, Converter, ImageLoader, IncrementalCache, OriginResolver, ProxyExternalRequest, Queue, TagCache, Warmer, Wrapper } from "./overrides";
5
5
  export type BaseEventOrResult<T extends string = string> = {
6
6
  type: T;
7
7
  };
@@ -66,7 +66,7 @@ export interface Origin {
66
66
  port?: number;
67
67
  customHeaders?: Record<string, string>;
68
68
  }
69
- export type IncludedWrapper = "aws-lambda" | "aws-lambda-streaming" | "node" | "cloudflare" | "cloudflare-edge" | "cloudflare-node" | "dummy";
69
+ export type IncludedWrapper = "aws-lambda" | "aws-lambda-streaming" | "node" | "cloudflare" | "cloudflare-edge" | "cloudflare-node" | "express-dev" | "dummy";
70
70
  export type IncludedConverter = "aws-apigw-v2" | "aws-apigw-v1" | "aws-cloudfront" | "edge" | "node" | "sqs-revalidate" | "dummy";
71
71
  export type RouteType = "route" | "page" | "app";
72
72
  export interface ResolvedRoute {
@@ -83,13 +83,14 @@ export interface RoutingResult {
83
83
  }
84
84
  export interface MiddlewareResult extends RoutingResult, BaseEventOrResult<"middleware"> {
85
85
  }
86
- export type IncludedQueue = "sqs" | "sqs-lite" | "dummy";
87
- export type IncludedIncrementalCache = "s3" | "s3-lite" | "dummy";
88
- export type IncludedTagCache = "dynamodb" | "dynamodb-lite" | "dummy";
89
- export type IncludedImageLoader = "s3" | "host" | "dummy";
86
+ export type IncludedQueue = "sqs" | "sqs-lite" | "direct" | "dummy";
87
+ export type IncludedIncrementalCache = "s3" | "s3-lite" | "multi-tier-ddb-s3" | "fs-dev" | "dummy";
88
+ export type IncludedTagCache = "dynamodb" | "dynamodb-lite" | "fs-dev" | "dummy";
89
+ export type IncludedImageLoader = "s3" | "host" | "fs-dev" | "dummy";
90
90
  export type IncludedOriginResolver = "pattern-env" | "dummy";
91
91
  export type IncludedWarmer = "aws-lambda" | "dummy";
92
92
  export type IncludedProxyExternalRequest = "node" | "fetch" | "dummy";
93
+ export type IncludedCDNInvalidationHandler = "cloudfront" | "dummy";
93
94
  export interface DefaultOverrideOptions<E extends BaseEventOrResult = InternalEvent, R extends BaseEventOrResult = InternalResult> {
94
95
  /**
95
96
  * This is the main entrypoint of your app.
@@ -129,6 +130,11 @@ export interface OverrideOptions extends DefaultOverrideOptions {
129
130
  * @default "node"
130
131
  */
131
132
  proxyExternalRequest?: IncludedProxyExternalRequest | LazyLoadedOverride<ProxyExternalRequest>;
133
+ /**
134
+ * Add possibility to override the default cdn invalidation for On Demand Revalidation
135
+ * @default "dummy"
136
+ */
137
+ cdnInvalidation?: IncludedCDNInvalidationHandler | LazyLoadedOverride<CDNInvalidationHandler>;
132
138
  }
133
139
  export interface InstallOptions {
134
140
  /**
@@ -1,6 +1,6 @@
1
1
  import type { Readable } from "node:stream";
2
2
  import type { Meta } from "./cache";
3
- import type { BaseEventOrResult, BaseOverride, InternalEvent, InternalResult, Origin, StreamCreator } from "./open-next";
3
+ import type { BaseEventOrResult, BaseOverride, InternalEvent, InternalResult, Origin, ResolvedRoute, StreamCreator } from "./open-next";
4
4
  export interface QueueMessage {
5
5
  MessageDeduplicationId: string;
6
6
  MessageBody: {
@@ -84,3 +84,12 @@ export type OriginResolver = BaseOverride & {
84
84
  export type ProxyExternalRequest = BaseOverride & {
85
85
  proxy: (event: InternalEvent) => Promise<InternalResult>;
86
86
  };
87
+ type CDNPath = {
88
+ initialPath: string;
89
+ rawPath: string;
90
+ resolvedRoutes: ResolvedRoute[];
91
+ };
92
+ export type CDNInvalidationHandler = BaseOverride & {
93
+ invalidatePaths: (paths: CDNPath[]) => Promise<void>;
94
+ };
95
+ export {};
@@ -0,0 +1,8 @@
1
+ export declare class LRUCache<T> {
2
+ private maxSize;
3
+ private cache;
4
+ constructor(maxSize: number);
5
+ get(key: string): T | undefined;
6
+ set(key: string, value: any): void;
7
+ delete(key: string): void;
8
+ }