Skip to content

Commit 00fade9

Browse files
committed
feat: basic in-memory de-duping revalidation queue
1 parent 6a3e35e commit 00fade9

File tree

8 files changed

+164
-137
lines changed

8 files changed

+164
-137
lines changed

.changeset/witty-baboons-smile.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
"@opennextjs/cloudflare": minor
3+
---
4+
5+
feat: basic in-memory de-duping revalidation queue

examples/e2e/app-router/open-next.config.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,14 @@
11
import type { OpenNextConfig } from "@opennextjs/aws/types/open-next.js";
22
import cache from "@opennextjs/cloudflare/kv-cache";
3+
import memoryQueue from "@opennextjs/cloudflare/memory-queue";
34

45
const config: OpenNextConfig = {
56
default: {
67
override: {
78
wrapper: "cloudflare-node",
89
converter: "edge",
910
incrementalCache: async () => cache,
10-
queue: "direct",
11+
queue: () => memoryQueue,
1112
// Unused implementation
1213
tagCache: "dummy",
1314
},

packages/cloudflare/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -73,7 +73,7 @@
7373
"dependencies": {
7474
"@ast-grep/napi": "^0.34.1",
7575
"@dotenvx/dotenvx": "catalog:",
76-
"@opennextjs/aws": "https://pkg.pr.new/@opennextjs/aws@727",
76+
"@opennextjs/aws": "https://pkg.pr.new/@opennextjs/aws@0b8705a",
7777
"enquirer": "^2.4.1",
7878
"glob": "catalog:",
7979
"yaml": "^2.7.0"

packages/cloudflare/src/api/kv-cache.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ import type { CacheValue, IncrementalCache, WithLastModified } from "@opennextjs
22
import { IgnorableError, RecoverableError } from "@opennextjs/aws/utils/error.js";
33

44
import { getCloudflareContext } from "./cloudflare-context.js";
5+
import memoryQueue from "./memory-queue.js";
56

67
export const CACHE_ASSET_DIR = "cdn-cgi/_next_cache";
78

@@ -116,6 +117,8 @@ class Cache implements IncrementalCache {
116117
);
117118
} catch {
118119
throw new RecoverableError(`Failed to set cache [${key}]`);
120+
} finally {
121+
memoryQueue.remove(key);
119122
}
120123
}
121124

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
import { generateMessageGroupId } from "@opennextjs/aws/core/routing/queue.js";
2+
import logger from "@opennextjs/aws/logger.js";
3+
import type { Queue, QueueMessage } from "@opennextjs/aws/types/overrides.js";
4+
5+
/**
6+
* The Memory Queue offers basic ISR revalidation by directly requesting a revalidation of a route.
7+
*
8+
* It offers basic support for in-memory de-duping per isolate.
9+
*/
10+
class MemoryQueue implements Queue {
11+
readonly name = "memory-queue";
12+
13+
public revalidatedPaths = new Map<string, ReturnType<typeof setTimeout>>();
14+
15+
public async send({ MessageBody: { host, url }, MessageGroupId }: QueueMessage): Promise<void> {
16+
this.revalidatedPaths.set(
17+
MessageGroupId,
18+
// force remove to allow new revalidations incase something went wrong
19+
setTimeout(() => this.removeId(MessageGroupId), 10_000)
20+
);
21+
22+
try {
23+
// @ts-expect-error
24+
const manifest = await import("./.next/prerender-manifest.json");
25+
const protocol = host.includes("localhost") ? "http" : "https";
26+
27+
await globalThis.internalFetch(`${protocol}://${host}${url}`, {
28+
method: "HEAD",
29+
headers: {
30+
"x-prerender-revalidate": manifest.preview.previewModeId,
31+
"x-isr": "1",
32+
},
33+
});
34+
} catch (e) {
35+
logger.error(e);
36+
this.revalidatedPaths.delete(MessageGroupId);
37+
}
38+
}
39+
40+
private removeId(id: string) {
41+
clearTimeout(this.revalidatedPaths.get(id));
42+
this.revalidatedPaths.delete(id);
43+
}
44+
45+
public remove(path: string) {
46+
if (this.revalidatedPaths.size > 0) {
47+
this.removeId(generateMessageGroupId(path));
48+
}
49+
}
50+
}
51+
52+
export default new MemoryQueue();

packages/cloudflare/src/cli/build/utils/ensure-cf-config.ts

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,9 @@ export function ensureCloudflareConfig(config: OpenNextConfig) {
1515
typeof config.default?.override?.incrementalCache === "function",
1616
dftUseDummyTagCache: config.default?.override?.tagCache === "dummy",
1717
dftMaybeUseQueue:
18-
config.default?.override?.queue === "dummy" || config.default?.override?.queue === "direct",
18+
config.default?.override?.queue === "dummy" ||
19+
config.default?.override?.queue === "direct" ||
20+
typeof config.default?.override?.incrementalCache === "function",
1921
disableCacheInterception: config.dangerous?.enableCacheInterception !== true,
2022
mwIsMiddlewareExternal: config.middleware?.external == true,
2123
mwUseCloudflareWrapper: config.middleware?.override?.wrapper === "cloudflare-edge",
@@ -37,7 +39,7 @@ export function ensureCloudflareConfig(config: OpenNextConfig) {
3739
converter: "edge",
3840
incrementalCache: "dummy" | function,
3941
tagCache: "dummy",
40-
queue: "dummy" | "direct",
42+
queue: "dummy" | "direct" | function,
4143
},
4244
},
4345

packages/cloudflare/tsconfig.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@
1313
"noPropertyAccessFromIndexSignature": false,
1414
"outDir": "./dist",
1515
"target": "ES2022",
16-
"types": ["@cloudflare/workers-types"]
16+
"types": ["@cloudflare/workers-types", "@opennextjs/aws/types/global.d.ts"]
1717
},
1818
"include": ["src/**/*.ts"]
1919
}

0 commit comments

Comments
 (0)