@opennextjs/aws 3.5.8 → 3.6.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.
- package/dist/adapters/cache.d.ts +1 -0
- package/dist/adapters/cache.js +31 -9
- package/dist/adapters/composable-cache.d.ts +17 -0
- package/dist/adapters/composable-cache.js +89 -0
- package/dist/build/compileCache.d.ts +5 -2
- package/dist/build/compileCache.js +26 -4
- package/dist/build/createServerBundle.js +7 -1
- package/dist/build/patch/patches/index.d.ts +1 -1
- package/dist/build/patch/patches/index.js +1 -1
- package/dist/build/patch/patches/patchFetchCacheISR.d.ts +2 -0
- package/dist/build/patch/patches/patchFetchCacheISR.js +35 -0
- package/dist/core/requestHandler.js +57 -29
- package/dist/core/routing/cacheInterceptor.js +2 -6
- package/dist/core/routing/matcher.js +15 -5
- package/dist/core/routing/middleware.js +9 -12
- package/dist/core/routing/routeMatcher.d.ts +3 -9
- package/dist/core/util.js +6 -0
- package/dist/overrides/converters/edge.js +17 -1
- package/dist/overrides/imageLoader/fs-dev.js +4 -2
- package/dist/overrides/incrementalCache/fs-dev.js +2 -1
- package/dist/overrides/incrementalCache/s3-lite.js +4 -4
- package/dist/overrides/incrementalCache/s3.js +4 -4
- package/dist/overrides/tagCache/dynamodb-nextMode.d.ts +1 -0
- package/dist/overrides/tagCache/dynamodb-nextMode.js +4 -0
- package/dist/overrides/tagCache/fs-dev.js +4 -2
- package/dist/overrides/wrappers/express-dev.js +4 -2
- package/dist/plugins/edge.js +2 -1
- package/dist/types/cache.d.ts +51 -7
- package/dist/types/global.d.ts +5 -0
- package/dist/types/overrides.d.ts +9 -4
- package/dist/utils/cache.d.ts +1 -1
- package/dist/utils/normalize-path.d.ts +2 -0
- package/dist/utils/normalize-path.js +11 -0
- package/package.json +1 -1
package/dist/adapters/cache.d.ts
CHANGED
package/dist/adapters/cache.js
CHANGED
|
@@ -29,7 +29,7 @@ export default class Cache {
|
|
|
29
29
|
async getFetchCache(key, softTags, tags) {
|
|
30
30
|
debug("get fetch cache", { key, softTags, tags });
|
|
31
31
|
try {
|
|
32
|
-
const cachedEntry = await globalThis.incrementalCache.get(key,
|
|
32
|
+
const cachedEntry = await globalThis.incrementalCache.get(key, "fetch");
|
|
33
33
|
if (cachedEntry?.value === undefined)
|
|
34
34
|
return null;
|
|
35
35
|
const _tags = [...(tags ?? []), ...(softTags ?? [])];
|
|
@@ -65,7 +65,7 @@ export default class Cache {
|
|
|
65
65
|
}
|
|
66
66
|
async getIncrementalCache(key) {
|
|
67
67
|
try {
|
|
68
|
-
const cachedEntry = await globalThis.incrementalCache.get(key,
|
|
68
|
+
const cachedEntry = await globalThis.incrementalCache.get(key, "cache");
|
|
69
69
|
if (!cachedEntry?.value) {
|
|
70
70
|
return null;
|
|
71
71
|
}
|
|
@@ -150,6 +150,7 @@ export default class Cache {
|
|
|
150
150
|
await globalThis.incrementalCache.delete(key);
|
|
151
151
|
}
|
|
152
152
|
else {
|
|
153
|
+
const revalidate = this.extractRevalidateForSet(ctx);
|
|
153
154
|
switch (data.kind) {
|
|
154
155
|
case "ROUTE":
|
|
155
156
|
case "APP_ROUTE": {
|
|
@@ -163,7 +164,8 @@ export default class Cache {
|
|
|
163
164
|
status,
|
|
164
165
|
headers,
|
|
165
166
|
},
|
|
166
|
-
|
|
167
|
+
revalidate,
|
|
168
|
+
}, "cache");
|
|
167
169
|
break;
|
|
168
170
|
}
|
|
169
171
|
case "PAGE":
|
|
@@ -179,14 +181,16 @@ export default class Cache {
|
|
|
179
181
|
status,
|
|
180
182
|
headers,
|
|
181
183
|
},
|
|
182
|
-
|
|
184
|
+
revalidate,
|
|
185
|
+
}, "cache");
|
|
183
186
|
}
|
|
184
187
|
else {
|
|
185
188
|
await globalThis.incrementalCache.set(key, {
|
|
186
189
|
type: "page",
|
|
187
190
|
html,
|
|
188
191
|
json: pageData,
|
|
189
|
-
|
|
192
|
+
revalidate,
|
|
193
|
+
}, "cache");
|
|
190
194
|
}
|
|
191
195
|
break;
|
|
192
196
|
}
|
|
@@ -200,17 +204,19 @@ export default class Cache {
|
|
|
200
204
|
status,
|
|
201
205
|
headers,
|
|
202
206
|
},
|
|
203
|
-
|
|
207
|
+
revalidate,
|
|
208
|
+
}, "cache");
|
|
204
209
|
break;
|
|
205
210
|
}
|
|
206
211
|
case "FETCH":
|
|
207
|
-
await globalThis.incrementalCache.set(key, data,
|
|
212
|
+
await globalThis.incrementalCache.set(key, data, "fetch");
|
|
208
213
|
break;
|
|
209
214
|
case "REDIRECT":
|
|
210
215
|
await globalThis.incrementalCache.set(key, {
|
|
211
216
|
type: "redirect",
|
|
212
217
|
props: data.props,
|
|
213
|
-
|
|
218
|
+
revalidate,
|
|
219
|
+
}, "cache");
|
|
214
220
|
break;
|
|
215
221
|
case "IMAGE":
|
|
216
222
|
// Not implemented
|
|
@@ -323,7 +329,8 @@ export default class Cache {
|
|
|
323
329
|
// Write derivedTags to the tag cache
|
|
324
330
|
// If we use an in house version of getDerivedTags in build we should use it here instead of next's one
|
|
325
331
|
const derivedTags = data?.kind === "FETCH"
|
|
326
|
-
?
|
|
332
|
+
? //@ts-expect-error - On older versions of next, ctx was a number, but for these cases we use data?.data?.tags
|
|
333
|
+
(ctx?.tags ?? data?.data?.tags ?? []) // before version 14 next.js used data?.data?.tags so we keep it for backward compatibility
|
|
327
334
|
: data?.kind === "PAGE"
|
|
328
335
|
? (data.headers?.["x-next-cache-tags"]?.split(",") ?? [])
|
|
329
336
|
: [];
|
|
@@ -342,4 +349,19 @@ export default class Cache {
|
|
|
342
349
|
})));
|
|
343
350
|
}
|
|
344
351
|
}
|
|
352
|
+
extractRevalidateForSet(ctx) {
|
|
353
|
+
if (ctx === undefined) {
|
|
354
|
+
return undefined;
|
|
355
|
+
}
|
|
356
|
+
if (typeof ctx === "number" || ctx === false) {
|
|
357
|
+
return ctx;
|
|
358
|
+
}
|
|
359
|
+
if ("revalidate" in ctx) {
|
|
360
|
+
return ctx.revalidate;
|
|
361
|
+
}
|
|
362
|
+
if ("cacheControl" in ctx) {
|
|
363
|
+
return ctx.cacheControl?.revalidate;
|
|
364
|
+
}
|
|
365
|
+
return undefined;
|
|
366
|
+
}
|
|
345
367
|
}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import type { ComposableCacheEntry } from "../types/cache";
|
|
2
|
+
declare const _default: {
|
|
3
|
+
get(cacheKey: string): Promise<{
|
|
4
|
+
value: import("stream/web").ReadableStream<any>;
|
|
5
|
+
tags: string[];
|
|
6
|
+
stale: number;
|
|
7
|
+
timestamp: number;
|
|
8
|
+
expire: number;
|
|
9
|
+
revalidate: number;
|
|
10
|
+
} | undefined>;
|
|
11
|
+
set(cacheKey: string, pendingEntry: Promise<ComposableCacheEntry>): Promise<void>;
|
|
12
|
+
refreshTags(): Promise<void>;
|
|
13
|
+
getExpiration(...tags: string[]): Promise<number>;
|
|
14
|
+
expireTags(...tags: string[]): Promise<void>;
|
|
15
|
+
receiveExpiredTags(...tags: string[]): Promise<void>;
|
|
16
|
+
};
|
|
17
|
+
export default _default;
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
import { fromReadableStream, toReadableStream } from "../utils/stream";
|
|
2
|
+
import { debug } from "./logger";
|
|
3
|
+
export default {
|
|
4
|
+
async get(cacheKey) {
|
|
5
|
+
try {
|
|
6
|
+
const result = await globalThis.incrementalCache.get(cacheKey, "composable");
|
|
7
|
+
if (!result?.value?.value) {
|
|
8
|
+
return undefined;
|
|
9
|
+
}
|
|
10
|
+
debug("composable cache result", result);
|
|
11
|
+
// We need to check if the tags associated with this entry has been revalidated
|
|
12
|
+
if (globalThis.tagCache.mode === "nextMode" &&
|
|
13
|
+
result.value.tags.length > 0) {
|
|
14
|
+
const hasBeenRevalidated = await globalThis.tagCache.hasBeenRevalidated(result.value.tags, result.lastModified);
|
|
15
|
+
if (hasBeenRevalidated)
|
|
16
|
+
return undefined;
|
|
17
|
+
}
|
|
18
|
+
else if (globalThis.tagCache.mode === "original" ||
|
|
19
|
+
globalThis.tagCache.mode === undefined) {
|
|
20
|
+
const hasBeenRevalidated = (await globalThis.tagCache.getLastModified(cacheKey, result.lastModified)) === -1;
|
|
21
|
+
if (hasBeenRevalidated)
|
|
22
|
+
return undefined;
|
|
23
|
+
}
|
|
24
|
+
return {
|
|
25
|
+
...result.value,
|
|
26
|
+
value: toReadableStream(result.value.value),
|
|
27
|
+
};
|
|
28
|
+
}
|
|
29
|
+
catch (e) {
|
|
30
|
+
debug("Cannot read composable cache entry");
|
|
31
|
+
return undefined;
|
|
32
|
+
}
|
|
33
|
+
},
|
|
34
|
+
async set(cacheKey, pendingEntry) {
|
|
35
|
+
const entry = await pendingEntry;
|
|
36
|
+
const valueToStore = await fromReadableStream(entry.value);
|
|
37
|
+
await globalThis.incrementalCache.set(cacheKey, {
|
|
38
|
+
...entry,
|
|
39
|
+
value: valueToStore,
|
|
40
|
+
}, "composable");
|
|
41
|
+
if (globalThis.tagCache.mode === "original") {
|
|
42
|
+
const storedTags = await globalThis.tagCache.getByPath(cacheKey);
|
|
43
|
+
const tagsToWrite = entry.tags.filter((tag) => !storedTags.includes(tag));
|
|
44
|
+
if (tagsToWrite.length > 0) {
|
|
45
|
+
await globalThis.tagCache.writeTags(tagsToWrite.map((tag) => ({ tag, path: cacheKey })));
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
},
|
|
49
|
+
async refreshTags() {
|
|
50
|
+
// We don't do anything for now, do we want to do something here ???
|
|
51
|
+
return;
|
|
52
|
+
},
|
|
53
|
+
async getExpiration(...tags) {
|
|
54
|
+
if (globalThis.tagCache.mode === "nextMode") {
|
|
55
|
+
return globalThis.tagCache.getLastRevalidated(tags);
|
|
56
|
+
}
|
|
57
|
+
// We always return 0 here, original tag cache are handled directly in the get part
|
|
58
|
+
// TODO: We need to test this more, i'm not entirely sure that this is working as expected
|
|
59
|
+
return 0;
|
|
60
|
+
},
|
|
61
|
+
async expireTags(...tags) {
|
|
62
|
+
if (globalThis.tagCache.mode === "nextMode") {
|
|
63
|
+
return globalThis.tagCache.writeTags(tags);
|
|
64
|
+
}
|
|
65
|
+
const tagCache = globalThis.tagCache;
|
|
66
|
+
const revalidatedAt = Date.now();
|
|
67
|
+
// For the original mode, we have more work to do here.
|
|
68
|
+
// We need to find all paths linked to to these tags
|
|
69
|
+
const pathsToUpdate = await Promise.all(tags.map(async (tag) => {
|
|
70
|
+
const paths = await tagCache.getByTag(tag);
|
|
71
|
+
return paths.map((path) => ({
|
|
72
|
+
path,
|
|
73
|
+
tag,
|
|
74
|
+
revalidatedAt,
|
|
75
|
+
}));
|
|
76
|
+
}));
|
|
77
|
+
// We need to deduplicate paths, we use a set for that
|
|
78
|
+
const setToWrite = new Set();
|
|
79
|
+
for (const entry of pathsToUpdate.flat()) {
|
|
80
|
+
setToWrite.add(entry);
|
|
81
|
+
}
|
|
82
|
+
await globalThis.tagCache.writeTags(Array.from(setToWrite));
|
|
83
|
+
},
|
|
84
|
+
// This one is necessary for older versions of next
|
|
85
|
+
async receiveExpiredTags(...tags) {
|
|
86
|
+
// This function does absolutely nothing
|
|
87
|
+
return;
|
|
88
|
+
},
|
|
89
|
+
};
|
|
@@ -4,6 +4,9 @@ import * as buildHelper from "./helper.js";
|
|
|
4
4
|
*
|
|
5
5
|
* @param options Build options.
|
|
6
6
|
* @param format Output format.
|
|
7
|
-
* @returns
|
|
7
|
+
* @returns An object containing the paths to the compiled cache and composable cache files.
|
|
8
8
|
*/
|
|
9
|
-
export declare function compileCache(options: buildHelper.BuildOptions, format?: "cjs" | "esm"):
|
|
9
|
+
export declare function compileCache(options: buildHelper.BuildOptions, format?: "cjs" | "esm"): {
|
|
10
|
+
cache: string;
|
|
11
|
+
composableCache: string;
|
|
12
|
+
};
|
|
@@ -5,17 +5,18 @@ import * as buildHelper from "./helper.js";
|
|
|
5
5
|
*
|
|
6
6
|
* @param options Build options.
|
|
7
7
|
* @param format Output format.
|
|
8
|
-
* @returns
|
|
8
|
+
* @returns An object containing the paths to the compiled cache and composable cache files.
|
|
9
9
|
*/
|
|
10
10
|
export function compileCache(options, format = "cjs") {
|
|
11
11
|
const { config } = options;
|
|
12
12
|
const ext = format === "cjs" ? "cjs" : "mjs";
|
|
13
|
-
const
|
|
13
|
+
const compiledCacheFile = path.join(options.buildDir, `cache.${ext}`);
|
|
14
14
|
const isAfter15 = buildHelper.compareSemver(options.nextVersion, ">=", "15.0.0");
|
|
15
|
+
// Normal cache
|
|
15
16
|
buildHelper.esbuildSync({
|
|
16
17
|
external: ["next", "styled-jsx", "react", "@aws-sdk/*"],
|
|
17
18
|
entryPoints: [path.join(options.openNextDistDir, "adapters", "cache.js")],
|
|
18
|
-
outfile:
|
|
19
|
+
outfile: compiledCacheFile,
|
|
19
20
|
target: ["node18"],
|
|
20
21
|
format,
|
|
21
22
|
banner: {
|
|
@@ -26,5 +27,26 @@ export function compileCache(options, format = "cjs") {
|
|
|
26
27
|
].join(""),
|
|
27
28
|
},
|
|
28
29
|
}, options);
|
|
29
|
-
|
|
30
|
+
const compiledComposableCacheFile = path.join(options.buildDir, `composable-cache.${ext}`);
|
|
31
|
+
// Composable cache
|
|
32
|
+
buildHelper.esbuildSync({
|
|
33
|
+
external: ["next", "styled-jsx", "react", "@aws-sdk/*"],
|
|
34
|
+
entryPoints: [
|
|
35
|
+
path.join(options.openNextDistDir, "adapters", "composable-cache.js"),
|
|
36
|
+
],
|
|
37
|
+
outfile: compiledComposableCacheFile,
|
|
38
|
+
target: ["node18"],
|
|
39
|
+
format,
|
|
40
|
+
banner: {
|
|
41
|
+
js: [
|
|
42
|
+
`globalThis.disableIncrementalCache = ${config.dangerous?.disableIncrementalCache ?? false};`,
|
|
43
|
+
`globalThis.disableDynamoDBCache = ${config.dangerous?.disableTagCache ?? false};`,
|
|
44
|
+
`globalThis.isNextAfter15 = ${isAfter15};`,
|
|
45
|
+
].join(""),
|
|
46
|
+
},
|
|
47
|
+
}, options);
|
|
48
|
+
return {
|
|
49
|
+
cache: compiledCacheFile,
|
|
50
|
+
composableCache: compiledComposableCacheFile,
|
|
51
|
+
};
|
|
30
52
|
}
|
|
@@ -14,7 +14,7 @@ import { copyMiddlewareResources, generateEdgeBundle, } from "./edge/createEdgeB
|
|
|
14
14
|
import * as buildHelper from "./helper.js";
|
|
15
15
|
import { installDependencies } from "./installDeps.js";
|
|
16
16
|
import { applyCodePatches } from "./patch/codePatcher.js";
|
|
17
|
-
import { patchBackgroundRevalidation, patchEnvVars, patchFetchCacheForISR, patchFetchCacheSetMissingWaitUntil, patchNextServer, patchUnstableCacheForISR, } from "./patch/patches/index.js";
|
|
17
|
+
import { patchBackgroundRevalidation, patchEnvVars, patchFetchCacheForISR, patchFetchCacheSetMissingWaitUntil, patchNextServer, patchUnstableCacheForISR, patchUseCacheForISR, } from "./patch/patches/index.js";
|
|
18
18
|
export async function createServerBundle(options, codeCustomization) {
|
|
19
19
|
const { config } = options;
|
|
20
20
|
const foundRoutes = new Set();
|
|
@@ -85,7 +85,10 @@ async function generateBundle(name, options, fnOptions, codeCustomization) {
|
|
|
85
85
|
const outPackagePath = path.join(outputPath, packagePath);
|
|
86
86
|
fs.mkdirSync(outPackagePath, { recursive: true });
|
|
87
87
|
const ext = fnOptions.runtime === "deno" ? "mjs" : "cjs";
|
|
88
|
+
// Normal cache
|
|
88
89
|
fs.copyFileSync(path.join(options.buildDir, `cache.${ext}`), path.join(outPackagePath, "cache.cjs"));
|
|
90
|
+
// Composable cache
|
|
91
|
+
fs.copyFileSync(path.join(options.buildDir, `composable-cache.${ext}`), path.join(outPackagePath, "composable-cache.cjs"));
|
|
89
92
|
if (fnOptions.runtime === "deno") {
|
|
90
93
|
addDenoJson(outputPath, packagePath);
|
|
91
94
|
}
|
|
@@ -122,6 +125,7 @@ async function generateBundle(name, options, fnOptions, codeCustomization) {
|
|
|
122
125
|
patchNextServer,
|
|
123
126
|
patchEnvVars,
|
|
124
127
|
patchBackgroundRevalidation,
|
|
128
|
+
patchUseCacheForISR,
|
|
125
129
|
...additionalCodePatches,
|
|
126
130
|
]);
|
|
127
131
|
// Build Lambda code
|
|
@@ -134,6 +138,7 @@ async function generateBundle(name, options, fnOptions, codeCustomization) {
|
|
|
134
138
|
const isBefore13413 = buildHelper.compareSemver(options.nextVersion, "<=", "13.4.13");
|
|
135
139
|
const isAfter141 = buildHelper.compareSemver(options.nextVersion, ">=", "14.1");
|
|
136
140
|
const isAfter142 = buildHelper.compareSemver(options.nextVersion, ">=", "14.2");
|
|
141
|
+
const isAfter152 = buildHelper.compareSemver(options.nextVersion, ">=", "15.2.0");
|
|
137
142
|
const disableRouting = isBefore13413 || config.middleware?.external;
|
|
138
143
|
const updater = new ContentUpdater(options);
|
|
139
144
|
const additionalPlugins = codeCustomization?.additionalPlugins
|
|
@@ -159,6 +164,7 @@ async function generateBundle(name, options, fnOptions, codeCustomization) {
|
|
|
159
164
|
...(isAfter141
|
|
160
165
|
? ["experimentalIncrementalCacheHandler"]
|
|
161
166
|
: ["stableIncrementalCache"]),
|
|
167
|
+
...(isAfter152 ? [] : ["composableCache"]),
|
|
162
168
|
],
|
|
163
169
|
}),
|
|
164
170
|
openNextResolvePlugin({
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
export { envVarRuleCreator, patchEnvVars } from "./patchEnvVar.js";
|
|
2
2
|
export { patchNextServer } from "./patchNextServer.js";
|
|
3
|
-
export { patchFetchCacheForISR, patchUnstableCacheForISR, } from "./patchFetchCacheISR.js";
|
|
3
|
+
export { patchFetchCacheForISR, patchUnstableCacheForISR, patchUseCacheForISR, } from "./patchFetchCacheISR.js";
|
|
4
4
|
export { patchFetchCacheSetMissingWaitUntil } from "./patchFetchCacheWaitUntil.js";
|
|
5
5
|
export { patchBackgroundRevalidation } from "./patchBackgroundRevalidation.js";
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
export { envVarRuleCreator, patchEnvVars } from "./patchEnvVar.js";
|
|
2
2
|
export { patchNextServer } from "./patchNextServer.js";
|
|
3
|
-
export { patchFetchCacheForISR, patchUnstableCacheForISR, } from "./patchFetchCacheISR.js";
|
|
3
|
+
export { patchFetchCacheForISR, patchUnstableCacheForISR, patchUseCacheForISR, } from "./patchFetchCacheISR.js";
|
|
4
4
|
export { patchFetchCacheSetMissingWaitUntil } from "./patchFetchCacheWaitUntil.js";
|
|
5
5
|
export { patchBackgroundRevalidation } from "./patchBackgroundRevalidation.js";
|
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
import type { CodePatcher } from "../codePatcher.js";
|
|
2
2
|
export declare const fetchRule = "\nrule:\n kind: member_expression\n pattern: $WORK_STORE.isOnDemandRevalidate\n inside:\n kind: ternary_expression\n all:\n - has: {kind: 'null'}\n - has: \n kind: await_expression\n has:\n kind: call_expression\n all:\n - has:\n kind: member_expression\n has:\n kind: property_identifier\n field: property\n regex: get\n - has:\n kind: arguments\n has:\n kind: object\n has:\n kind: pair\n all:\n - has:\n kind: property_identifier\n field: key\n regex: softTags\n inside:\n kind: variable_declarator\n\nfix:\n ($WORK_STORE.isOnDemandRevalidate && !globalThis.__openNextAls?.getStore()?.isISRRevalidation)\n";
|
|
3
3
|
export declare const unstable_cacheRule = "\nrule:\n kind: member_expression\n pattern: $STORE_OR_CACHE.isOnDemandRevalidate\n inside:\n kind: if_statement\n stopBy: end\n has:\n kind: statement_block\n has:\n kind: variable_declarator\n has: \n kind: await_expression\n has:\n kind: call_expression\n all:\n - has:\n kind: member_expression\n has:\n kind: property_identifier\n field: property\n regex: get\n - has:\n kind: arguments\n has:\n kind: object\n has:\n kind: pair\n all:\n - has:\n kind: property_identifier\n field: key\n regex: softTags\n stopBy: end\nfix:\n ($STORE_OR_CACHE.isOnDemandRevalidate && !globalThis.__openNextAls?.getStore()?.isISRRevalidation)\n";
|
|
4
|
+
export declare const useCacheRule = "\nrule:\n kind: member_expression\n pattern: $STORE_OR_CACHE.isOnDemandRevalidate\n inside:\n kind: binary_expression\n has: \n kind: member_expression\n pattern: $STORE_OR_CACHE.isDraftMode\n inside:\n kind: if_statement\n stopBy: end\n has: \n kind: return_statement\n any:\n - has:\n kind: 'true'\n - has:\n regex: '!0'\n stopBy: end\nfix:\n '($STORE_OR_CACHE.isOnDemandRevalidate && !globalThis.__openNextAls?.getStore()?.isISRRevalidation)'";
|
|
4
5
|
export declare const patchFetchCacheForISR: CodePatcher;
|
|
5
6
|
export declare const patchUnstableCacheForISR: CodePatcher;
|
|
7
|
+
export declare const patchUseCacheForISR: CodePatcher;
|
|
@@ -74,6 +74,28 @@ rule:
|
|
|
74
74
|
fix:
|
|
75
75
|
($STORE_OR_CACHE.isOnDemandRevalidate && !globalThis.__openNextAls?.getStore()?.isISRRevalidation)
|
|
76
76
|
`;
|
|
77
|
+
export const useCacheRule = `
|
|
78
|
+
rule:
|
|
79
|
+
kind: member_expression
|
|
80
|
+
pattern: $STORE_OR_CACHE.isOnDemandRevalidate
|
|
81
|
+
inside:
|
|
82
|
+
kind: binary_expression
|
|
83
|
+
has:
|
|
84
|
+
kind: member_expression
|
|
85
|
+
pattern: $STORE_OR_CACHE.isDraftMode
|
|
86
|
+
inside:
|
|
87
|
+
kind: if_statement
|
|
88
|
+
stopBy: end
|
|
89
|
+
has:
|
|
90
|
+
kind: return_statement
|
|
91
|
+
any:
|
|
92
|
+
- has:
|
|
93
|
+
kind: 'true'
|
|
94
|
+
- has:
|
|
95
|
+
regex: '!0'
|
|
96
|
+
stopBy: end
|
|
97
|
+
fix:
|
|
98
|
+
'($STORE_OR_CACHE.isOnDemandRevalidate && !globalThis.__openNextAls?.getStore()?.isISRRevalidation)'`;
|
|
77
99
|
export const patchFetchCacheForISR = {
|
|
78
100
|
name: "patch-fetch-cache-for-isr",
|
|
79
101
|
patches: [
|
|
@@ -100,3 +122,16 @@ export const patchUnstableCacheForISR = {
|
|
|
100
122
|
},
|
|
101
123
|
],
|
|
102
124
|
};
|
|
125
|
+
export const patchUseCacheForISR = {
|
|
126
|
+
name: "patch-use-cache-for-isr",
|
|
127
|
+
patches: [
|
|
128
|
+
{
|
|
129
|
+
versions: ">=15.3.0",
|
|
130
|
+
field: {
|
|
131
|
+
pathFilter: getCrossPlatformPathRegex(String.raw `(server/chunks/.*\.js|\.runtime\..*\.js|use-cache/use-cache-wrapper\.js)$`, { escape: false }),
|
|
132
|
+
contentFilter: /\.isOnDemandRevalidate/,
|
|
133
|
+
patchCode: createPatchCode(useCacheRule, Lang.JavaScript),
|
|
134
|
+
},
|
|
135
|
+
},
|
|
136
|
+
],
|
|
137
|
+
};
|
|
@@ -51,7 +51,11 @@ export async function openNextHandler(internalEvent, options) {
|
|
|
51
51
|
continue;
|
|
52
52
|
}
|
|
53
53
|
const key = rawKey.slice(MIDDLEWARE_HEADER_PREFIX_LEN);
|
|
54
|
-
|
|
54
|
+
// We skip this header here since it is used by Next internally and we don't want it on the response headers.
|
|
55
|
+
// This header needs to be present in the request headers for processRequest, so cookies().get() from Next will work on initial render.
|
|
56
|
+
if (key !== "x-middleware-set-cookie") {
|
|
57
|
+
overwrittenResponseHeaders[key] = value;
|
|
58
|
+
}
|
|
55
59
|
headers[key] = value;
|
|
56
60
|
delete headers[rawKey];
|
|
57
61
|
}
|
|
@@ -151,44 +155,68 @@ async function processRequest(req, res, routingResult) {
|
|
|
151
155
|
// Next.js doesn't parse body if the property exists
|
|
152
156
|
// https://github.com/dougmoscrop/serverless-http/issues/227
|
|
153
157
|
delete req.body;
|
|
158
|
+
// Here we try to apply as much request metadata as possible
|
|
159
|
+
// We apply every metadata from `resolve-routes` https://github.com/vercel/next.js/blob/916f105b97211de50f8580f0b39c9e7c60de4886/packages/next/src/server/lib/router-utils/resolve-routes.ts
|
|
160
|
+
// and `router-server` https://github.com/vercel/next.js/blob/916f105b97211de50f8580f0b39c9e7c60de4886/packages/next/src/server/lib/router-server.ts
|
|
161
|
+
const initialURL = new URL(routingResult.initialURL);
|
|
162
|
+
let invokeStatus;
|
|
163
|
+
if (routingResult.internalEvent.rawPath === "/500") {
|
|
164
|
+
invokeStatus = 500;
|
|
165
|
+
}
|
|
166
|
+
else if (routingResult.internalEvent.rawPath === "/404") {
|
|
167
|
+
invokeStatus = 404;
|
|
168
|
+
}
|
|
169
|
+
const requestMetadata = {
|
|
170
|
+
isNextDataReq: routingResult.internalEvent.query.__nextDataReq === "1",
|
|
171
|
+
initURL: routingResult.initialURL,
|
|
172
|
+
initQuery: convertToQuery(initialURL.search),
|
|
173
|
+
initProtocol: initialURL.protocol,
|
|
174
|
+
defaultLocale: NextConfig.i18n?.defaultLocale,
|
|
175
|
+
locale: routingResult.locale,
|
|
176
|
+
middlewareInvoke: false,
|
|
177
|
+
// By setting invokePath and invokeQuery we can bypass some of the routing logic in Next.js
|
|
178
|
+
invokePath: routingResult.internalEvent.rawPath,
|
|
179
|
+
invokeQuery: routingResult.internalEvent.query,
|
|
180
|
+
// invokeStatus is only used for error pages
|
|
181
|
+
invokeStatus,
|
|
182
|
+
};
|
|
154
183
|
try {
|
|
155
184
|
//#override applyNextjsPrebundledReact
|
|
156
185
|
setNextjsPrebundledReact(routingResult.internalEvent.rawPath);
|
|
157
186
|
//#endOverride
|
|
158
|
-
// Here we try to apply as much request metadata as possible
|
|
159
|
-
// 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
|
|
160
|
-
// and `router-server` https://github.com/vercel/next.js/blob/canary/packages/next/src/server/lib/router-server.ts
|
|
161
|
-
const initialURL = new URL(routingResult.initialURL);
|
|
162
|
-
let invokeStatus;
|
|
163
|
-
if (routingResult.internalEvent.rawPath === "/500") {
|
|
164
|
-
invokeStatus = 500;
|
|
165
|
-
}
|
|
166
|
-
else if (routingResult.internalEvent.rawPath === "/404") {
|
|
167
|
-
invokeStatus = 404;
|
|
168
|
-
}
|
|
169
|
-
const requestMetadata = {
|
|
170
|
-
isNextDataReq: routingResult.internalEvent.query.__nextDataReq === "1",
|
|
171
|
-
initURL: routingResult.initialURL,
|
|
172
|
-
initQuery: convertToQuery(initialURL.search),
|
|
173
|
-
initProtocol: initialURL.protocol,
|
|
174
|
-
defaultLocale: NextConfig.i18n?.defaultLocale,
|
|
175
|
-
locale: routingResult.locale,
|
|
176
|
-
middlewareInvoke: false,
|
|
177
|
-
// By setting invokePath and invokeQuery we can bypass some of the routing logic in Next.js
|
|
178
|
-
invokePath: routingResult.internalEvent.rawPath,
|
|
179
|
-
invokeQuery: routingResult.internalEvent.query,
|
|
180
|
-
// invokeStatus is only used for error pages
|
|
181
|
-
invokeStatus,
|
|
182
|
-
};
|
|
183
187
|
// Next Server
|
|
184
188
|
await requestHandler(requestMetadata)(req, res);
|
|
185
189
|
}
|
|
186
190
|
catch (e) {
|
|
187
191
|
// This might fail when using bundled next, importing won't do the trick either
|
|
188
192
|
if (e.constructor.name === "NoFallbackError") {
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
193
|
+
await handleNoFallbackError(req, res, routingResult, requestMetadata);
|
|
194
|
+
}
|
|
195
|
+
else {
|
|
196
|
+
error("NextJS request failed.", e);
|
|
197
|
+
await tryRenderError("500", res, routingResult.internalEvent);
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
async function handleNoFallbackError(req, res, routingResult, metadata, index = 1) {
|
|
202
|
+
if (index >= 5) {
|
|
203
|
+
await tryRenderError("500", res, routingResult.internalEvent);
|
|
204
|
+
return;
|
|
205
|
+
}
|
|
206
|
+
if (index >= routingResult.resolvedRoutes.length) {
|
|
207
|
+
await tryRenderError("404", res, routingResult.internalEvent);
|
|
208
|
+
return;
|
|
209
|
+
}
|
|
210
|
+
try {
|
|
211
|
+
await requestHandler({
|
|
212
|
+
...routingResult,
|
|
213
|
+
invokeOutput: routingResult.resolvedRoutes[index].route,
|
|
214
|
+
...metadata,
|
|
215
|
+
})(req, res);
|
|
216
|
+
}
|
|
217
|
+
catch (e) {
|
|
218
|
+
if (e.constructor.name === "NoFallbackError") {
|
|
219
|
+
await handleNoFallbackError(req, res, routingResult, metadata, index + 1);
|
|
192
220
|
}
|
|
193
221
|
else {
|
|
194
222
|
error("NextJS request failed.", e);
|
|
@@ -114,17 +114,13 @@ export async function cacheInterceptor(event) {
|
|
|
114
114
|
}
|
|
115
115
|
// We also need to remove trailing slash
|
|
116
116
|
localizedPath = localizedPath.replace(/\/$/, "");
|
|
117
|
-
// If empty path, it means we want index
|
|
118
|
-
if (localizedPath === "") {
|
|
119
|
-
localizedPath = "index";
|
|
120
|
-
}
|
|
121
117
|
debug("Checking cache for", localizedPath, PrerenderManifest);
|
|
122
|
-
const isISR = Object.keys(PrerenderManifest.routes).includes(localizedPath) ||
|
|
118
|
+
const isISR = Object.keys(PrerenderManifest.routes).includes(localizedPath ?? "/") ||
|
|
123
119
|
Object.values(PrerenderManifest.dynamicRoutes).some((dr) => new RegExp(dr.routeRegex).test(localizedPath));
|
|
124
120
|
debug("isISR", isISR);
|
|
125
121
|
if (isISR) {
|
|
126
122
|
try {
|
|
127
|
-
const cachedData = await globalThis.incrementalCache.get(localizedPath);
|
|
123
|
+
const cachedData = await globalThis.incrementalCache.get(localizedPath ?? "/index");
|
|
128
124
|
debug("cached data in interceptor", cachedData);
|
|
129
125
|
if (!cachedData?.value) {
|
|
130
126
|
return event;
|
|
@@ -3,6 +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 { handleLocaleRedirect, localizePath } from "./i18n";
|
|
6
|
+
import { dynamicRouteMatcher, staticRouteMatcher } from "./routeMatcher";
|
|
6
7
|
import { constructNextUrl, convertFromQueryString, convertToQueryString, escapeRegex, getUrlParts, isExternal, unescapeRegex, } from "./util";
|
|
7
8
|
const routeHasMatcher = (headers, cookies, query) => (redirect) => {
|
|
8
9
|
switch (redirect.type) {
|
|
@@ -265,9 +266,8 @@ export function fixDataPage(internalEvent, buildId) {
|
|
|
265
266
|
export function handleFallbackFalse(internalEvent, prerenderManifest) {
|
|
266
267
|
const { rawPath } = internalEvent;
|
|
267
268
|
const { dynamicRoutes, routes } = prerenderManifest;
|
|
268
|
-
const
|
|
269
|
-
|
|
270
|
-
.some(([, { routeRegex }]) => {
|
|
269
|
+
const prerenderedFallbackRoutes = Object.entries(dynamicRoutes).filter(([, { fallback }]) => fallback === false);
|
|
270
|
+
const routeFallback = prerenderedFallbackRoutes.some(([, { routeRegex }]) => {
|
|
271
271
|
const routeRegexExp = new RegExp(routeRegex);
|
|
272
272
|
return routeRegexExp.test(rawPath);
|
|
273
273
|
});
|
|
@@ -275,11 +275,21 @@ export function handleFallbackFalse(internalEvent, prerenderManifest) {
|
|
|
275
275
|
const routesAlreadyHaveLocale = locales?.includes(rawPath.split("/")[1]) ||
|
|
276
276
|
// If we don't use locales, we don't need to add the default locale
|
|
277
277
|
locales === undefined;
|
|
278
|
-
|
|
278
|
+
let localizedPath = routesAlreadyHaveLocale
|
|
279
279
|
? rawPath
|
|
280
280
|
: `/${NextConfig.i18n?.defaultLocale}${rawPath}`;
|
|
281
|
+
// We need to remove the trailing slash if it exists
|
|
282
|
+
if (NextConfig.trailingSlash && localizedPath.endsWith("/")) {
|
|
283
|
+
localizedPath = localizedPath.slice(0, -1);
|
|
284
|
+
}
|
|
285
|
+
const matchedStaticRoute = staticRouteMatcher(localizedPath);
|
|
286
|
+
const prerenderedFallbackRoutesName = prerenderedFallbackRoutes.map(([name]) => name);
|
|
287
|
+
const matchedDynamicRoute = dynamicRouteMatcher(localizedPath).filter(({ route }) => !prerenderedFallbackRoutesName.includes(route));
|
|
281
288
|
const isPregenerated = Object.keys(routes).includes(localizedPath);
|
|
282
|
-
if (routeFallback &&
|
|
289
|
+
if (routeFallback &&
|
|
290
|
+
!isPregenerated &&
|
|
291
|
+
matchedStaticRoute.length === 0 &&
|
|
292
|
+
matchedDynamicRoute.length === 0) {
|
|
283
293
|
return {
|
|
284
294
|
event: {
|
|
285
295
|
...internalEvent,
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { FunctionsConfigManifest, MiddlewareManifest, NextConfig, PrerenderManifest, } from "../../adapters/config/index.js";
|
|
2
2
|
import { emptyReadableStream } from "../../utils/stream.js";
|
|
3
|
+
import { getQueryFromSearchParams } from "../../overrides/converters/utils.js";
|
|
3
4
|
import { localizePath } from "./i18n/index.js";
|
|
4
5
|
import { convertBodyToReadableStream, getMiddlewareMatch, isExternal, } from "./util.js";
|
|
5
6
|
const middlewareManifest = MiddlewareManifest;
|
|
@@ -74,7 +75,6 @@ export async function handleMiddleware(internalEvent, initialSearch, middlewareL
|
|
|
74
75
|
// These are internal headers used by Next.js, we don't want to expose them to the client
|
|
75
76
|
const filteredHeaders = [
|
|
76
77
|
"x-middleware-override-headers",
|
|
77
|
-
"x-middleware-set-cookie",
|
|
78
78
|
"x-middleware-next",
|
|
79
79
|
"x-middleware-rewrite",
|
|
80
80
|
// We need to drop `content-encoding` because it will be decoded
|
|
@@ -103,7 +103,7 @@ export async function handleMiddleware(internalEvent, initialSearch, middlewareL
|
|
|
103
103
|
// NOTE: the header was added to `req` from above
|
|
104
104
|
const rewriteUrl = responseHeaders.get("x-middleware-rewrite");
|
|
105
105
|
let isExternalRewrite = false;
|
|
106
|
-
let
|
|
106
|
+
let middlewareQuery = internalEvent.query;
|
|
107
107
|
let newUrl = internalEvent.url;
|
|
108
108
|
if (rewriteUrl) {
|
|
109
109
|
newUrl = rewriteUrl;
|
|
@@ -113,15 +113,12 @@ export async function handleMiddleware(internalEvent, initialSearch, middlewareL
|
|
|
113
113
|
}
|
|
114
114
|
else {
|
|
115
115
|
const rewriteUrlObject = new URL(rewriteUrl);
|
|
116
|
-
//
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
rewriteUrlObject.searchParams.forEach((v, k) => {
|
|
123
|
-
middlewareQueryString[k] = v;
|
|
124
|
-
});
|
|
116
|
+
// Search params from the rewritten URL override the original search params
|
|
117
|
+
middlewareQuery = getQueryFromSearchParams(rewriteUrlObject.searchParams);
|
|
118
|
+
// We still need to add internal search params to the query string for pages router on older versions of Next.js
|
|
119
|
+
if ("__nextDataReq" in internalEvent.query) {
|
|
120
|
+
middlewareQuery.__nextDataReq = internalEvent.query.__nextDataReq;
|
|
121
|
+
}
|
|
125
122
|
}
|
|
126
123
|
}
|
|
127
124
|
// If the middleware wants to directly return a response (i.e. not using `NextResponse.next()` or `NextResponse.rewrite()`)
|
|
@@ -145,7 +142,7 @@ export async function handleMiddleware(internalEvent, initialSearch, middlewareL
|
|
|
145
142
|
headers: { ...internalEvent.headers, ...reqHeaders },
|
|
146
143
|
body: internalEvent.body,
|
|
147
144
|
method: internalEvent.method,
|
|
148
|
-
query:
|
|
145
|
+
query: middlewareQuery,
|
|
149
146
|
cookies: internalEvent.cookies,
|
|
150
147
|
remoteAddress: internalEvent.remoteAddress,
|
|
151
148
|
isExternalRewrite,
|
|
@@ -1,10 +1,4 @@
|
|
|
1
|
-
import type {
|
|
1
|
+
import type { ResolvedRoute } from "../../types/open-next";
|
|
2
2
|
export declare const apiPrefix: string;
|
|
3
|
-
export declare const staticRouteMatcher: (path: string) =>
|
|
4
|
-
|
|
5
|
-
type: RouteType;
|
|
6
|
-
}[];
|
|
7
|
-
export declare const dynamicRouteMatcher: (path: string) => {
|
|
8
|
-
route: string;
|
|
9
|
-
type: RouteType;
|
|
10
|
-
}[];
|
|
3
|
+
export declare const staticRouteMatcher: (path: string) => ResolvedRoute[];
|
|
4
|
+
export declare const dynamicRouteMatcher: (path: string) => ResolvedRoute[];
|
package/dist/core/util.js
CHANGED
|
@@ -15,6 +15,7 @@ overrideNextjsRequireHooks(NextConfig);
|
|
|
15
15
|
applyNextjsRequireHooksOverride();
|
|
16
16
|
//#endOverride
|
|
17
17
|
const cacheHandlerPath = require.resolve("./cache.cjs");
|
|
18
|
+
const composableCacheHandlerPath = require.resolve("./composable-cache.cjs");
|
|
18
19
|
// @ts-ignore
|
|
19
20
|
const nextServer = new NextServer.default({
|
|
20
21
|
//#override requestHandlerHost
|
|
@@ -42,6 +43,11 @@ const nextServer = new NextServer.default({
|
|
|
42
43
|
//#override experimentalIncrementalCacheHandler
|
|
43
44
|
incrementalCacheHandlerPath: cacheHandlerPath,
|
|
44
45
|
//#endOverride
|
|
46
|
+
//#override composableCache
|
|
47
|
+
cacheHandlers: {
|
|
48
|
+
default: composableCacheHandlerPath,
|
|
49
|
+
},
|
|
50
|
+
//#endOverride
|
|
45
51
|
},
|
|
46
52
|
},
|
|
47
53
|
customServer: false,
|
|
@@ -55,7 +55,23 @@ const converter = {
|
|
|
55
55
|
}
|
|
56
56
|
const headers = new Headers();
|
|
57
57
|
for (const [key, value] of Object.entries(result.headers)) {
|
|
58
|
-
|
|
58
|
+
if (key === "set-cookie" && typeof value === "string") {
|
|
59
|
+
// If the value is a string, we need to parse it into an array
|
|
60
|
+
// This is the case for middleware direct result
|
|
61
|
+
const cookies = parseCookies(value);
|
|
62
|
+
for (const cookie of cookies) {
|
|
63
|
+
headers.append(key, cookie);
|
|
64
|
+
}
|
|
65
|
+
continue;
|
|
66
|
+
}
|
|
67
|
+
if (Array.isArray(value)) {
|
|
68
|
+
for (const v of value) {
|
|
69
|
+
headers.append(key, v);
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
else {
|
|
73
|
+
headers.set(key, value);
|
|
74
|
+
}
|
|
59
75
|
}
|
|
60
76
|
return new Response(result.body, {
|
|
61
77
|
status: result.statusCode,
|
|
@@ -1,9 +1,11 @@
|
|
|
1
1
|
import fs from "node:fs";
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
import { getMonorepoRelativePath } from "../../utils/normalize-path";
|
|
2
4
|
export default {
|
|
3
5
|
name: "fs-dev",
|
|
4
6
|
load: async (url) => {
|
|
5
|
-
const
|
|
6
|
-
const body = fs.createReadStream(
|
|
7
|
+
const imagePath = path.join(getMonorepoRelativePath(), "assets", url);
|
|
8
|
+
const body = fs.createReadStream(imagePath);
|
|
7
9
|
const contentType = url.endsWith(".png") ? "image/png" : "image/jpeg";
|
|
8
10
|
return {
|
|
9
11
|
body,
|
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
import fs from "node:fs/promises";
|
|
2
2
|
import path from "node:path";
|
|
3
|
+
import { getMonorepoRelativePath } from "../../utils/normalize-path";
|
|
3
4
|
const buildId = process.env.NEXT_BUILD_ID;
|
|
4
|
-
const basePath = path.
|
|
5
|
+
const basePath = path.join(getMonorepoRelativePath(), `cache/${buildId}`);
|
|
5
6
|
const getCacheKey = (key) => {
|
|
6
7
|
return path.join(basePath, `${key}.cache`);
|
|
7
8
|
};
|
|
@@ -30,8 +30,8 @@ function buildS3Key(key, extension) {
|
|
|
30
30
|
return path.posix.join(CACHE_BUCKET_KEY_PREFIX ?? "", extension === "fetch" ? "__fetch" : "", NEXT_BUILD_ID ?? "", extension === "fetch" ? key : `${key}.${extension}`);
|
|
31
31
|
}
|
|
32
32
|
const incrementalCache = {
|
|
33
|
-
async get(key,
|
|
34
|
-
const result = await awsFetch(buildS3Key(key,
|
|
33
|
+
async get(key, cacheType) {
|
|
34
|
+
const result = await awsFetch(buildS3Key(key, cacheType ?? "cache"), {
|
|
35
35
|
method: "GET",
|
|
36
36
|
});
|
|
37
37
|
if (result.status === 404) {
|
|
@@ -46,8 +46,8 @@ const incrementalCache = {
|
|
|
46
46
|
lastModified: new Date(result.headers.get("last-modified") ?? "").getTime(),
|
|
47
47
|
};
|
|
48
48
|
},
|
|
49
|
-
async set(key, value,
|
|
50
|
-
const response = await awsFetch(buildS3Key(key,
|
|
49
|
+
async set(key, value, cacheType) {
|
|
50
|
+
const response = await awsFetch(buildS3Key(key, cacheType ?? "cache"), {
|
|
51
51
|
method: "PUT",
|
|
52
52
|
body: JSON.stringify(value),
|
|
53
53
|
});
|
|
@@ -15,10 +15,10 @@ function buildS3Key(key, extension) {
|
|
|
15
15
|
return path.posix.join(CACHE_BUCKET_KEY_PREFIX ?? "", extension === "fetch" ? "__fetch" : "", NEXT_BUILD_ID ?? "", extension === "fetch" ? key : `${key}.${extension}`);
|
|
16
16
|
}
|
|
17
17
|
const incrementalCache = {
|
|
18
|
-
async get(key,
|
|
18
|
+
async get(key, cacheType) {
|
|
19
19
|
const result = await s3Client.send(new GetObjectCommand({
|
|
20
20
|
Bucket: CACHE_BUCKET_NAME,
|
|
21
|
-
Key: buildS3Key(key,
|
|
21
|
+
Key: buildS3Key(key, cacheType ?? "cache"),
|
|
22
22
|
}));
|
|
23
23
|
const cacheData = JSON.parse((await result.Body?.transformToString()) ?? "{}");
|
|
24
24
|
return {
|
|
@@ -26,10 +26,10 @@ const incrementalCache = {
|
|
|
26
26
|
lastModified: result.LastModified?.getTime(),
|
|
27
27
|
};
|
|
28
28
|
},
|
|
29
|
-
async set(key, value,
|
|
29
|
+
async set(key, value, cacheType) {
|
|
30
30
|
await s3Client.send(new PutObjectCommand({
|
|
31
31
|
Bucket: CACHE_BUCKET_NAME,
|
|
32
|
-
Key: buildS3Key(key,
|
|
32
|
+
Key: buildS3Key(key, cacheType ?? "cache"),
|
|
33
33
|
Body: JSON.stringify(value),
|
|
34
34
|
}));
|
|
35
35
|
},
|
|
@@ -52,6 +52,10 @@ function buildDynamoObject(tag, revalidatedAt) {
|
|
|
52
52
|
export default {
|
|
53
53
|
name: "ddb-nextMode",
|
|
54
54
|
mode: "nextMode",
|
|
55
|
+
getLastRevalidated: async (tags) => {
|
|
56
|
+
// Not supported for now
|
|
57
|
+
return 0;
|
|
58
|
+
},
|
|
55
59
|
hasBeenRevalidated: async (tags, lastModified) => {
|
|
56
60
|
if (globalThis.openNextConfig.dangerous?.disableTagCache) {
|
|
57
61
|
return false;
|
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
import fs from "node:fs";
|
|
2
|
-
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
import { getMonorepoRelativePath } from "../../utils/normalize-path";
|
|
4
|
+
const tagFile = path.join(getMonorepoRelativePath(), "dynamodb-provider/dynamodb-cache.json");
|
|
3
5
|
const tagContent = fs.readFileSync(tagFile, "utf-8");
|
|
4
6
|
let tags = JSON.parse(tagContent);
|
|
5
7
|
const tagCache = {
|
|
@@ -26,7 +28,7 @@ const tagCache = {
|
|
|
26
28
|
tags = unchangedTags.concat(newTags.map((tag) => ({
|
|
27
29
|
tag: { S: tag.tag },
|
|
28
30
|
path: { S: tag.path },
|
|
29
|
-
revalidatedAt: { N: String(tag.revalidatedAt) },
|
|
31
|
+
revalidatedAt: { N: String(tag.revalidatedAt ?? 1) },
|
|
30
32
|
})));
|
|
31
33
|
},
|
|
32
34
|
};
|
|
@@ -1,9 +1,11 @@
|
|
|
1
|
+
import path from "node:path";
|
|
1
2
|
import express from "express";
|
|
3
|
+
import { getMonorepoRelativePath } from "../../utils/normalize-path";
|
|
2
4
|
const wrapper = async (handler, converter) => {
|
|
3
5
|
const app = express();
|
|
4
6
|
// To serve static assets
|
|
5
|
-
app.use(express.static("
|
|
6
|
-
const imageHandlerPath = "
|
|
7
|
+
app.use(express.static(path.join(getMonorepoRelativePath(), "assets")));
|
|
8
|
+
const imageHandlerPath = path.join(getMonorepoRelativePath(), "image-optimization-function/index.mjs");
|
|
7
9
|
const imageHandler = await import(imageHandlerPath).then((m) => m.handler);
|
|
8
10
|
app.all("/_next/image", async (req, res) => {
|
|
9
11
|
const internalEvent = await converter.convertFrom(req);
|
package/dist/plugins/edge.js
CHANGED
|
@@ -3,6 +3,7 @@ import path from "node:path";
|
|
|
3
3
|
import chalk from "chalk";
|
|
4
4
|
import { loadAppPathRoutesManifest, loadAppPathsManifest, loadAppPathsManifestKeys, loadBuildId, loadConfig, loadConfigHeaders, loadFunctionsConfigManifest, loadHtmlPages, loadMiddlewareManifest, loadPrerenderManifest, loadRoutesManifest, } from "../adapters/config/util.js";
|
|
5
5
|
import logger from "../logger.js";
|
|
6
|
+
import { normalizePath } from "../utils/normalize-path.js";
|
|
6
7
|
import { getCrossPlatformPathRegex } from "../utils/regex.js";
|
|
7
8
|
/**
|
|
8
9
|
* @param opts.nextDir - The path to the .next directory
|
|
@@ -11,7 +12,7 @@ import { getCrossPlatformPathRegex } from "../utils/regex.js";
|
|
|
11
12
|
* @returns
|
|
12
13
|
*/
|
|
13
14
|
export function openNextEdgePlugins({ nextDir, middlewareInfo, isInCloudflare, }) {
|
|
14
|
-
const entryFiles = middlewareInfo?.files.map((file) => path.join(nextDir, file)) ?? [];
|
|
15
|
+
const entryFiles = middlewareInfo?.files.map((file) => normalizePath(path.join(nextDir, file))) ?? [];
|
|
15
16
|
const routes = middlewareInfo
|
|
16
17
|
? [
|
|
17
18
|
{
|
package/dist/types/cache.d.ts
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import type { ReadableStream } from "node:stream/web";
|
|
1
2
|
interface CachedFetchValue {
|
|
2
3
|
kind: "FETCH";
|
|
3
4
|
data: {
|
|
@@ -61,7 +62,7 @@ export interface CacheHandlerValue {
|
|
|
61
62
|
cacheState?: string;
|
|
62
63
|
value: IncrementalCacheValue | null;
|
|
63
64
|
}
|
|
64
|
-
export type Extension = "cache" | "fetch";
|
|
65
|
+
export type Extension = "cache" | "fetch" | "composable";
|
|
65
66
|
type MetaHeaders = {
|
|
66
67
|
"x-next-cache-tags"?: string;
|
|
67
68
|
[k: string]: string | string[] | undefined;
|
|
@@ -82,11 +83,54 @@ export type TagCacheMetaFile = {
|
|
|
82
83
|
N: string;
|
|
83
84
|
};
|
|
84
85
|
};
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
86
|
+
interface SetIncrementalFetchCacheContext {
|
|
87
|
+
fetchCache: true;
|
|
88
|
+
fetchUrl?: string;
|
|
89
|
+
fetchIdx?: number;
|
|
90
|
+
tags?: string[];
|
|
91
|
+
}
|
|
92
|
+
interface SetIncrementalResponseCacheContext {
|
|
93
|
+
fetchCache?: false;
|
|
94
|
+
cacheControl?: {
|
|
95
|
+
revalidate: number | false;
|
|
96
|
+
expire?: number;
|
|
97
|
+
};
|
|
98
|
+
/**
|
|
99
|
+
* True if the route is enabled for PPR.
|
|
100
|
+
*/
|
|
101
|
+
isRoutePPREnabled?: boolean;
|
|
102
|
+
/**
|
|
103
|
+
* True if this is a fallback request.
|
|
104
|
+
*/
|
|
105
|
+
isFallback?: boolean;
|
|
106
|
+
}
|
|
107
|
+
interface SetIncrementalCacheContext {
|
|
108
|
+
revalidate?: number | false;
|
|
109
|
+
isRoutePPREnabled?: boolean;
|
|
110
|
+
isFallback?: boolean;
|
|
111
|
+
}
|
|
112
|
+
type OldSetIncrementalCacheContext = number | false | undefined;
|
|
113
|
+
export type IncrementalCacheContext = OldSetIncrementalCacheContext | SetIncrementalCacheContext | SetIncrementalFetchCacheContext | SetIncrementalResponseCacheContext;
|
|
114
|
+
export interface ComposableCacheEntry {
|
|
115
|
+
value: ReadableStream<Uint8Array>;
|
|
116
|
+
tags: string[];
|
|
117
|
+
stale: number;
|
|
118
|
+
timestamp: number;
|
|
119
|
+
expire: number;
|
|
120
|
+
revalidate: number;
|
|
121
|
+
}
|
|
122
|
+
export type StoredComposableCacheEntry = Omit<ComposableCacheEntry, "value"> & {
|
|
123
|
+
value: string;
|
|
91
124
|
};
|
|
125
|
+
export interface ComposableCacheHandler {
|
|
126
|
+
get(cacheKey: string): Promise<ComposableCacheEntry | undefined>;
|
|
127
|
+
set(cacheKey: string, pendingEntry: Promise<ComposableCacheEntry>): Promise<void>;
|
|
128
|
+
refreshTags(): Promise<void>;
|
|
129
|
+
getExpiration(...tags: string[]): Promise<number>;
|
|
130
|
+
expireTags(...tags: string[]): Promise<void>;
|
|
131
|
+
/**
|
|
132
|
+
* This function is only there for older versions and do nothing
|
|
133
|
+
*/
|
|
134
|
+
receiveExpiredTags(...tags: string[]): Promise<void>;
|
|
135
|
+
}
|
|
92
136
|
export {};
|
package/dist/types/global.d.ts
CHANGED
|
@@ -185,5 +185,10 @@ declare global {
|
|
|
185
185
|
* Only available in main functions.
|
|
186
186
|
*/
|
|
187
187
|
var __next_route_preloader: (stage: "waitUntil" | "start" | "warmerEvent" | "onDemand") => Promise<void>;
|
|
188
|
+
/**
|
|
189
|
+
* This is the relative package path of the monorepo. It will be an empty string "" in normal repos.
|
|
190
|
+
* ex. `packages/web`
|
|
191
|
+
*/
|
|
192
|
+
var monorepoPackagePath: string;
|
|
188
193
|
}
|
|
189
194
|
export {};
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import type { Readable } from "node:stream";
|
|
2
|
-
import type { Meta } from "./cache";
|
|
2
|
+
import type { Extension, Meta, StoredComposableCacheEntry } from "./cache";
|
|
3
3
|
import type { BaseEventOrResult, BaseOverride, InternalEvent, InternalResult, Origin, ResolvedRoute, StreamCreator, WaitUntil } from "./open-next";
|
|
4
4
|
export interface QueueMessage {
|
|
5
5
|
MessageDeduplicationId: string;
|
|
@@ -51,12 +51,16 @@ export type WithLastModified<T> = {
|
|
|
51
51
|
lastModified?: number;
|
|
52
52
|
value?: T;
|
|
53
53
|
};
|
|
54
|
-
export type
|
|
54
|
+
export type CacheEntryType = Extension;
|
|
55
|
+
export type CacheValue<CacheType extends CacheEntryType> = (CacheType extends "fetch" ? CachedFetchValue : CacheType extends "cache" ? CachedFile : StoredComposableCacheEntry) & {
|
|
56
|
+
/**
|
|
57
|
+
* This is available for page cache entry, but only at runtime.
|
|
58
|
+
*/
|
|
55
59
|
revalidate?: number | false;
|
|
56
60
|
};
|
|
57
61
|
export type IncrementalCache = {
|
|
58
|
-
get<
|
|
59
|
-
set<
|
|
62
|
+
get<CacheType extends CacheEntryType = "cache">(key: string, cacheType?: CacheType): Promise<WithLastModified<CacheValue<CacheType>> | null>;
|
|
63
|
+
set<CacheType extends CacheEntryType = "cache">(key: string, value: CacheValue<CacheType>, isFetch?: CacheType): Promise<void>;
|
|
60
64
|
delete(key: string): Promise<void>;
|
|
61
65
|
name: string;
|
|
62
66
|
};
|
|
@@ -86,6 +90,7 @@ Cons :
|
|
|
86
90
|
*/
|
|
87
91
|
export type NextModeTagCache = BaseTagCache & {
|
|
88
92
|
mode: "nextMode";
|
|
93
|
+
getLastRevalidated(tags: string[]): Promise<number>;
|
|
89
94
|
hasBeenRevalidated(tags: string[], lastModified?: number): Promise<boolean>;
|
|
90
95
|
writeTags(tags: string[]): Promise<void>;
|
|
91
96
|
getPathsByTags?: (tags: string[]) => Promise<string[]>;
|
package/dist/utils/cache.d.ts
CHANGED
|
@@ -1,3 +1,3 @@
|
|
|
1
1
|
import type { CacheValue, WithLastModified } from "../types/overrides";
|
|
2
2
|
export declare function hasBeenRevalidated(key: string, tags: string[], cacheEntry: WithLastModified<CacheValue<any>>): Promise<boolean>;
|
|
3
|
-
export declare function getTagsFromValue(value?: CacheValue<
|
|
3
|
+
export declare function getTagsFromValue(value?: CacheValue<"cache">): string[];
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import path from "node:path";
|
|
2
|
+
export function normalizePath(path) {
|
|
3
|
+
return path.replace(/\\/g, "/");
|
|
4
|
+
}
|
|
5
|
+
export function getMonorepoRelativePath(relativePath = "../..") {
|
|
6
|
+
return path.join(globalThis.monorepoPackagePath
|
|
7
|
+
.split("/")
|
|
8
|
+
.filter(Boolean)
|
|
9
|
+
.map(() => "..")
|
|
10
|
+
.join("/"), relativePath);
|
|
11
|
+
}
|