Skip to content

Commit 66d91a6

Browse files
committed
bump aws and fix for composable cache
1 parent faca3e1 commit 66d91a6

File tree

17 files changed

+366
-194
lines changed

17 files changed

+366
-194
lines changed

packages/cloudflare/package.json

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -53,7 +53,7 @@
5353
"homepage": "https://github.com/opennextjs/opennextjs-cloudflare",
5454
"dependencies": {
5555
"@dotenvx/dotenvx": "catalog:",
56-
"@opennextjs/aws": "3.5.8",
56+
"@opennextjs/aws": "https://pkg.pr.new/@opennextjs/aws@843",
5757
"enquirer": "^2.4.1",
5858
"glob": "catalog:",
5959
"ts-tqdm": "^0.8.6"
@@ -80,4 +80,4 @@
8080
"peerDependencies": {
8181
"wrangler": "catalog:"
8282
}
83-
}
83+
}

packages/cloudflare/src/api/durable-objects/sharded-tag-cache.ts

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,24 @@ export class DOShardedTagCache extends DurableObject<CloudflareEnv> {
1111
});
1212
}
1313

14+
async getLastRevalidated(tags: string[]): Promise<number> {
15+
try {
16+
const result = this.sql
17+
.exec(
18+
`SELECT revalidatedAt FROM revalidations WHERE tag IN (${tags.map(() => "?").join(", ")}) ORDER BY revalidatedAt DESC LIMIT 1`,
19+
...tags
20+
)
21+
.toArray();
22+
if (result.length === 0) return 0;
23+
// We only care about the most recent revalidation
24+
return result[0]?.revalidatedAt as number;
25+
} catch (e) {
26+
console.error(e);
27+
// By default we don't want to crash here, so we return 0
28+
return 0;
29+
}
30+
}
31+
1432
async hasBeenRevalidated(tags: string[], lastModified?: number): Promise<boolean> {
1533
return (
1634
this.sql

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

Lines changed: 21 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,12 @@
11
import { createHash } from "node:crypto";
22

33
import { error } from "@opennextjs/aws/adapters/logger.js";
4-
import type { CacheValue, IncrementalCache, WithLastModified } from "@opennextjs/aws/types/overrides.js";
4+
import type {
5+
CacheEntryType,
6+
CacheValue,
7+
IncrementalCache,
8+
WithLastModified,
9+
} from "@opennextjs/aws/types/overrides.js";
510
import { IgnorableError } from "@opennextjs/aws/utils/error.js";
611

712
import { getCloudflareContext } from "../../cloudflare-context.js";
@@ -12,14 +17,14 @@ export const NAME = "cf-kv-incremental-cache";
1217
export const BINDING_NAME = "NEXT_INC_CACHE_KV";
1318

1419
export type KeyOptions = {
15-
isFetch?: boolean;
20+
cacheType?: CacheEntryType;
1621
buildId?: string;
1722
};
1823

1924
export function computeCacheKey(key: string, options: KeyOptions) {
20-
const { isFetch = false, buildId = FALLBACK_BUILD_ID } = options;
25+
const { cacheType = "cache", buildId = FALLBACK_BUILD_ID } = options;
2126
const hash = createHash("sha256").update(key).digest("hex");
22-
return `${buildId}/${hash}.${isFetch ? "fetch" : "cache"}`.replace(/\/+/g, "/");
27+
return `${buildId}/${hash}.${cacheType}`.replace(/\/+/g, "/");
2328
}
2429

2530
/**
@@ -32,20 +37,17 @@ export function computeCacheKey(key: string, options: KeyOptions) {
3237
class KVIncrementalCache implements IncrementalCache {
3338
readonly name = NAME;
3439

35-
async get<IsFetch extends boolean = false>(
40+
async get<CacheType extends CacheEntryType = "cache">(
3641
key: string,
37-
isFetch?: IsFetch
38-
): Promise<WithLastModified<CacheValue<IsFetch>> | null> {
42+
cacheType?: CacheType
43+
): Promise<WithLastModified<CacheValue<CacheType>> | null> {
3944
const kv = getCloudflareContext().env[BINDING_NAME];
4045
if (!kv) throw new IgnorableError("No KV Namespace");
4146

4247
debugCache(`Get ${key}`);
4348

4449
try {
45-
const entry = await kv.get<IncrementalCacheEntry<IsFetch> | CacheValue<IsFetch>>(
46-
this.getKVKey(key, isFetch),
47-
"json"
48-
);
50+
const entry = await kv.get<IncrementalCacheEntry<CacheType>>(this.getKVKey(key, cacheType), "json");
4951

5052
if (!entry) return null;
5153

@@ -64,10 +66,10 @@ class KVIncrementalCache implements IncrementalCache {
6466
}
6567
}
6668

67-
async set<IsFetch extends boolean = false>(
69+
async set<CacheType extends CacheEntryType = "cache">(
6870
key: string,
69-
value: CacheValue<IsFetch>,
70-
isFetch?: IsFetch
71+
value: CacheValue<CacheType>,
72+
cacheType?: CacheType
7173
): Promise<void> {
7274
const kv = getCloudflareContext().env[BINDING_NAME];
7375
if (!kv) throw new IgnorableError("No KV Namespace");
@@ -76,7 +78,7 @@ class KVIncrementalCache implements IncrementalCache {
7678

7779
try {
7880
await kv.put(
79-
this.getKVKey(key, isFetch),
81+
this.getKVKey(key, cacheType),
8082
JSON.stringify({
8183
value,
8284
// Note: `Date.now()` returns the time of the last IO rather than the actual time.
@@ -98,16 +100,17 @@ class KVIncrementalCache implements IncrementalCache {
98100
debugCache(`Delete ${key}`);
99101

100102
try {
101-
await kv.delete(this.getKVKey(key, /* isFetch= */ false));
103+
// Only cache that gets deleted is the ISR/SSG cache.
104+
await kv.delete(this.getKVKey(key, "cache"));
102105
} catch (e) {
103106
error("Failed to delete from cache", e);
104107
}
105108
}
106109

107-
protected getKVKey(key: string, isFetch?: boolean): string {
110+
protected getKVKey(key: string, cacheType?: CacheEntryType): string {
108111
return computeCacheKey(key, {
109112
buildId: process.env.NEXT_BUILD_ID,
110-
isFetch,
113+
cacheType,
111114
});
112115
}
113116
}

packages/cloudflare/src/api/overrides/incremental-cache/r2-incremental-cache.ts

Lines changed: 19 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,12 @@
11
import { createHash } from "node:crypto";
22

33
import { error } from "@opennextjs/aws/adapters/logger.js";
4-
import type { CacheValue, IncrementalCache, WithLastModified } from "@opennextjs/aws/types/overrides.js";
4+
import type {
5+
CacheEntryType,
6+
CacheValue,
7+
IncrementalCache,
8+
WithLastModified,
9+
} from "@opennextjs/aws/types/overrides.js";
510
import { IgnorableError } from "@opennextjs/aws/utils/error.js";
611

712
import { getCloudflareContext } from "../../cloudflare-context.js";
@@ -15,15 +20,15 @@ export const PREFIX_ENV_NAME = "NEXT_INC_CACHE_R2_PREFIX";
1520
export const DEFAULT_PREFIX = "incremental-cache";
1621

1722
export type KeyOptions = {
18-
isFetch?: boolean;
23+
cacheType?: CacheEntryType;
1924
directory?: string;
2025
buildId?: string;
2126
};
2227

2328
export function computeCacheKey(key: string, options: KeyOptions) {
24-
const { isFetch = false, directory = DEFAULT_PREFIX, buildId = FALLBACK_BUILD_ID } = options;
29+
const { cacheType = "cache", directory = DEFAULT_PREFIX, buildId = FALLBACK_BUILD_ID } = options;
2530
const hash = createHash("sha256").update(key).digest("hex");
26-
return `${directory}/${buildId}/${hash}.${isFetch ? "fetch" : "cache"}`.replace(/\/+/g, "/");
31+
return `${directory}/${buildId}/${hash}.${cacheType}`.replace(/\/+/g, "/");
2732
}
2833

2934
/**
@@ -36,17 +41,17 @@ export function computeCacheKey(key: string, options: KeyOptions) {
3641
class R2IncrementalCache implements IncrementalCache {
3742
readonly name = NAME;
3843

39-
async get<IsFetch extends boolean = false>(
44+
async get<CacheType extends CacheEntryType = "cache">(
4045
key: string,
41-
isFetch?: IsFetch
42-
): Promise<WithLastModified<CacheValue<IsFetch>> | null> {
46+
cacheType?: CacheType
47+
): Promise<WithLastModified<CacheValue<CacheType>> | null> {
4348
const r2 = getCloudflareContext().env[BINDING_NAME];
4449
if (!r2) throw new IgnorableError("No R2 bucket");
4550

4651
debugCache(`Get ${key}`);
4752

4853
try {
49-
const r2Object = await r2.get(this.getR2Key(key, isFetch));
54+
const r2Object = await r2.get(this.getR2Key(key, cacheType));
5055
if (!r2Object) return null;
5156

5257
return {
@@ -59,18 +64,18 @@ class R2IncrementalCache implements IncrementalCache {
5964
}
6065
}
6166

62-
async set<IsFetch extends boolean = false>(
67+
async set<CacheType extends CacheEntryType = "cache">(
6368
key: string,
64-
value: CacheValue<IsFetch>,
65-
isFetch?: IsFetch
69+
value: CacheValue<CacheType>,
70+
cacheType?: CacheType
6671
): Promise<void> {
6772
const r2 = getCloudflareContext().env[BINDING_NAME];
6873
if (!r2) throw new IgnorableError("No R2 bucket");
6974

7075
debugCache(`Set ${key}`);
7176

7277
try {
73-
await r2.put(this.getR2Key(key, isFetch), JSON.stringify(value));
78+
await r2.put(this.getR2Key(key, cacheType), JSON.stringify(value));
7479
} catch (e) {
7580
error("Failed to set to cache", e);
7681
}
@@ -89,11 +94,11 @@ class R2IncrementalCache implements IncrementalCache {
8994
}
9095
}
9196

92-
protected getR2Key(key: string, isFetch?: boolean): string {
97+
protected getR2Key(key: string, cacheType?: CacheEntryType): string {
9398
return computeCacheKey(key, {
9499
directory: getCloudflareContext().env[PREFIX_ENV_NAME],
95100
buildId: process.env.NEXT_BUILD_ID,
96-
isFetch,
101+
cacheType,
97102
});
98103
}
99104
}

packages/cloudflare/src/api/overrides/incremental-cache/regional-cache.ts

Lines changed: 31 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,10 @@
11
import { error } from "@opennextjs/aws/adapters/logger.js";
2-
import { CacheValue, IncrementalCache, WithLastModified } from "@opennextjs/aws/types/overrides.js";
2+
import {
3+
CacheEntryType,
4+
CacheValue,
5+
IncrementalCache,
6+
WithLastModified,
7+
} from "@opennextjs/aws/types/overrides.js";
38

49
import { getCloudflareContext } from "../../cloudflare-context.js";
510
import { debugCache, FALLBACK_BUILD_ID, IncrementalCacheEntry } from "../internal.js";
@@ -37,8 +42,8 @@ type Options = {
3742

3843
interface PutToCacheInput {
3944
key: string;
40-
isFetch: boolean | undefined;
41-
entry: IncrementalCacheEntry<boolean>;
45+
cacheType?: CacheEntryType;
46+
entry: IncrementalCacheEntry<CacheEntryType>;
4247
}
4348

4449
/**
@@ -60,13 +65,13 @@ class RegionalCache implements IncrementalCache {
6065
this.opts.shouldLazilyUpdateOnCacheHit ??= this.opts.mode === "long-lived";
6166
}
6267

63-
async get<IsFetch extends boolean = false>(
68+
async get<CacheType extends CacheEntryType = "cache">(
6469
key: string,
65-
isFetch?: IsFetch
66-
): Promise<WithLastModified<CacheValue<IsFetch>> | null> {
70+
cacheType?: CacheType
71+
): Promise<WithLastModified<CacheValue<CacheType>> | null> {
6772
try {
6873
const cache = await this.getCacheInstance();
69-
const urlKey = this.getCacheUrlKey(key, isFetch);
74+
const urlKey = this.getCacheUrlKey(key, cacheType);
7075

7176
// Check for a cached entry as this will be faster than the store response.
7277
const cachedResponse = await cache.match(urlKey);
@@ -76,11 +81,11 @@ class RegionalCache implements IncrementalCache {
7681
// Re-fetch from the store and update the regional cache in the background
7782
if (this.opts.shouldLazilyUpdateOnCacheHit) {
7883
getCloudflareContext().ctx.waitUntil(
79-
this.store.get(key, isFetch).then(async (rawEntry) => {
84+
this.store.get(key, cacheType).then(async (rawEntry) => {
8085
const { value, lastModified } = rawEntry ?? {};
8186

8287
if (value && typeof lastModified === "number") {
83-
await this.putToCache({ key, isFetch, entry: { value, lastModified } });
88+
await this.putToCache({ key, cacheType, entry: { value, lastModified } });
8489
}
8590
})
8691
);
@@ -89,12 +94,14 @@ class RegionalCache implements IncrementalCache {
8994
return cachedResponse.json();
9095
}
9196

92-
const rawEntry = await this.store.get(key, isFetch);
97+
const rawEntry = await this.store.get(key, cacheType);
9398
const { value, lastModified } = rawEntry ?? {};
9499
if (!value || typeof lastModified !== "number") return null;
95100

96101
// Update the locale cache after retrieving from the store.
97-
getCloudflareContext().ctx.waitUntil(this.putToCache({ key, isFetch, entry: { value, lastModified } }));
102+
getCloudflareContext().ctx.waitUntil(
103+
this.putToCache({ key, cacheType, entry: { value, lastModified } })
104+
);
98105

99106
return { value, lastModified };
100107
} catch (e) {
@@ -103,17 +110,17 @@ class RegionalCache implements IncrementalCache {
103110
}
104111
}
105112

106-
async set<IsFetch extends boolean = false>(
113+
async set<CacheType extends CacheEntryType = "cache">(
107114
key: string,
108-
value: CacheValue<IsFetch>,
109-
isFetch?: IsFetch
115+
value: CacheValue<CacheType>,
116+
cacheType?: CacheType
110117
): Promise<void> {
111118
try {
112-
await this.store.set(key, value, isFetch);
119+
await this.store.set(key, value, cacheType);
113120

114121
await this.putToCache({
115122
key,
116-
isFetch,
123+
cacheType,
117124
entry: {
118125
value,
119126
// Note: `Date.now()` returns the time of the last IO rather than the actual time.
@@ -144,15 +151,13 @@ class RegionalCache implements IncrementalCache {
144151
return this.localCache;
145152
}
146153

147-
protected getCacheUrlKey(key: string, isFetch?: boolean) {
154+
protected getCacheUrlKey(key: string, cacheType?: CacheEntryType) {
148155
const buildId = process.env.NEXT_BUILD_ID ?? FALLBACK_BUILD_ID;
149-
return (
150-
"http://cache.local" + `/${buildId}/${key}`.replace(/\/+/g, "/") + `.${isFetch ? "fetch" : "cache"}`
151-
);
156+
return "http://cache.local" + `/${buildId}/${key}`.replace(/\/+/g, "/") + `.${cacheType ?? "cache"}`;
152157
}
153158

154-
protected async putToCache({ key, isFetch, entry }: PutToCacheInput): Promise<void> {
155-
const urlKey = this.getCacheUrlKey(key, isFetch);
159+
protected async putToCache({ key, cacheType, entry }: PutToCacheInput): Promise<void> {
160+
const urlKey = this.getCacheUrlKey(key, cacheType);
156161
const cache = await this.getCacheInstance();
157162

158163
const age =
@@ -209,7 +214,7 @@ export function withRegionalCache(cache: IncrementalCache, opts: Options) {
209214
/**
210215
* Extract the list of tags from a cache entry.
211216
*/
212-
function getTagsFromCacheEntry(entry: IncrementalCacheEntry<boolean>): string[] | undefined {
217+
function getTagsFromCacheEntry(entry: IncrementalCacheEntry<CacheEntryType>): string[] | undefined {
213218
if ("tags" in entry.value && entry.value.tags) {
214219
return entry.value.tags;
215220
}
@@ -225,4 +230,7 @@ function getTagsFromCacheEntry(entry: IncrementalCacheEntry<boolean>): string[]
225230
return rawTags.split(",");
226231
}
227232
}
233+
if ("value" in entry.value) {
234+
return entry.value.tags;
235+
}
228236
}

0 commit comments

Comments
 (0)