-
Notifications
You must be signed in to change notification settings - Fork 118
Expand file tree
/
Copy pathbucket-cache-purge.ts
More file actions
95 lines (87 loc) · 2.75 KB
/
bucket-cache-purge.ts
File metadata and controls
95 lines (87 loc) · 2.75 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
import { DurableObject } from "cloudflare:workers";
import { internalPurgeCacheByTags } from "../overrides/internal.js";
const DEFAULT_BUFFER_TIME_IN_SECONDS = 5;
// https://developers.cloudflare.com/cache/how-to/purge-cache/#hostname-tag-prefix-url-and-purge-everything-limits
const MAX_NUMBER_OF_TAGS_PER_PURGE = 100;
export class BucketCachePurge extends DurableObject<CloudflareEnv> {
bufferTimeInSeconds: number;
constructor(state: DurableObjectState, env: CloudflareEnv) {
super(state, env);
this.bufferTimeInSeconds = env.NEXT_CACHE_DO_PURGE_BUFFER_TIME_IN_SECONDS
? parseInt(env.NEXT_CACHE_DO_PURGE_BUFFER_TIME_IN_SECONDS)
: DEFAULT_BUFFER_TIME_IN_SECONDS; // Default buffer time
// Initialize the sql table if it doesn't exist
state.blockConcurrencyWhile(async () => {
state.storage.sql.exec(`
CREATE TABLE IF NOT EXISTS cache_purge (
tag TEXT NOT NULL
);
CREATE UNIQUE INDEX IF NOT EXISTS tag_index ON cache_purge (tag);
`);
});
}
async purgeCacheByTags(tags: string[]): Promise<void> {
for (const tag of tags) {
// Insert the tag into the sql table
this.ctx.storage.sql.exec(
`
INSERT OR REPLACE INTO cache_purge (tag)
VALUES (?)`,
[tag]
);
}
const nextAlarm = await this.ctx.storage.getAlarm();
if (!nextAlarm) {
// Set an alarm to trigger the cache purge
this.ctx.storage.setAlarm(Date.now() + this.bufferTimeInSeconds * 1000);
}
}
override async alarm(): Promise<void> {
let tags = this.ctx.storage.sql
.exec<{ tag: string }>(
`
SELECT * FROM cache_purge LIMIT ${MAX_NUMBER_OF_TAGS_PER_PURGE}
`
)
.toArray();
do {
if (tags.length === 0) {
// No tags to purge, we can stop
return;
}
const result = await internalPurgeCacheByTags(
this.env,
tags.map((row) => row.tag)
);
// For every other error, we just remove the tags from the sql table
// and continue
if (result === "rate-limit-exceeded") {
// Rate limit exceeded, we need to wait for the next alarm
// and try again
// We throw here to take advantage of the built-in retry
throw new Error("Rate limit exceeded");
}
// Delete the tags from the sql table
this.ctx.storage.sql.exec(
`
DELETE FROM cache_purge
WHERE tag IN (${tags.map(() => "?").join(",")})
`,
tags.map((row) => row.tag)
);
if (tags.length < MAX_NUMBER_OF_TAGS_PER_PURGE) {
// If we have less than MAX_NUMBER_OF_TAGS_PER_PURGE tags, we can stop
tags = [];
} else {
// Otherwise, we need to get the next 100 tags
tags = this.ctx.storage.sql
.exec<{ tag: string }>(
`
SELECT * FROM cache_purge LIMIT ${MAX_NUMBER_OF_TAGS_PER_PURGE}
`
)
.toArray();
}
} while (tags.length >= 0);
}
}