Skip to content

Commit 2d82fad

Browse files
authored
Sync with aws for 3.6 and composable cache (#613)
* bump aws and fix for composable cache * bug fix * changeset and lint fix * lint fix * review fix * lint fix * bump aws to 3.6.0 * lint fix
1 parent f129602 commit 2d82fad

20 files changed

+414
-209
lines changed

.changeset/grumpy-dingos-pretend.md

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
---
2+
"@opennextjs/cloudflare": minor
3+
---
4+
5+
Bump aws to 3.6.0
6+
7+
Introduce support for the composable cache
8+
9+
BREAKING CHANGE: The interface for the Incremental cache has changed. The new interface use a Cache type instead of a boolean to distinguish between the different types of caches. It also includes a new Cache type for the composable cache. The new interface is as follows:
10+
11+
```ts
12+
export type CacheEntryType = "cache" | "fetch" | "composable";
13+
14+
export type IncrementalCache = {
15+
get<CacheType extends CacheEntryType = "cache">(
16+
key: string,
17+
cacheType?: CacheType
18+
): Promise<WithLastModified<CacheValue<CacheType>> | null>;
19+
set<CacheType extends CacheEntryType = "cache">(
20+
key: string,
21+
value: CacheValue<CacheType>,
22+
isFetch?: CacheType
23+
): Promise<void>;
24+
delete(key: string): Promise<void>;
25+
name: string;
26+
};
27+
```
28+
29+
NextModeTagCache also get a new function `getLastRevalidated` used for the composable cache:
30+
31+
```ts
32+
getLastRevalidated(tags: string[]): Promise<number>;
33+
```

packages/cloudflare/package.json

Lines changed: 1 addition & 1 deletion
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": "3.6.0",
5757
"enquirer": "^2.4.1",
5858
"glob": "catalog:",
5959
"ts-tqdm": "^0.8.6"

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 MAX(revalidatedAt) AS time FROM revalidations WHERE tag IN (${tags.map(() => "?").join(", ")})`,
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]?.time 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: 18 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,10 @@
11
import { error } from "@opennextjs/aws/adapters/logger.js";
2-
import type { CacheValue, IncrementalCache, WithLastModified } from "@opennextjs/aws/types/overrides.js";
2+
import type {
3+
CacheEntryType,
4+
CacheValue,
5+
IncrementalCache,
6+
WithLastModified,
7+
} from "@opennextjs/aws/types/overrides.js";
38
import { IgnorableError } from "@opennextjs/aws/utils/error.js";
49

510
import { getCloudflareContext } from "../../cloudflare-context.js";
@@ -24,20 +29,17 @@ export const PREFIX_ENV_NAME = "NEXT_INC_CACHE_KV_PREFIX";
2429
class KVIncrementalCache implements IncrementalCache {
2530
readonly name = NAME;
2631

27-
async get<IsFetch extends boolean = false>(
32+
async get<CacheType extends CacheEntryType = "cache">(
2833
key: string,
29-
isFetch?: IsFetch
30-
): Promise<WithLastModified<CacheValue<IsFetch>> | null> {
34+
cacheType?: CacheType
35+
): Promise<WithLastModified<CacheValue<CacheType>> | null> {
3136
const kv = getCloudflareContext().env[BINDING_NAME];
3237
if (!kv) throw new IgnorableError("No KV Namespace");
3338

3439
debugCache(`Get ${key}`);
3540

3641
try {
37-
const entry = await kv.get<IncrementalCacheEntry<IsFetch> | CacheValue<IsFetch>>(
38-
this.getKVKey(key, isFetch),
39-
"json"
40-
);
42+
const entry = await kv.get<IncrementalCacheEntry<CacheType>>(this.getKVKey(key, cacheType), "json");
4143

4244
if (!entry) return null;
4345

@@ -56,10 +58,10 @@ class KVIncrementalCache implements IncrementalCache {
5658
}
5759
}
5860

59-
async set<IsFetch extends boolean = false>(
61+
async set<CacheType extends CacheEntryType = "cache">(
6062
key: string,
61-
value: CacheValue<IsFetch>,
62-
isFetch?: IsFetch
63+
value: CacheValue<CacheType>,
64+
cacheType?: CacheType
6365
): Promise<void> {
6466
const kv = getCloudflareContext().env[BINDING_NAME];
6567
if (!kv) throw new IgnorableError("No KV Namespace");
@@ -68,7 +70,7 @@ class KVIncrementalCache implements IncrementalCache {
6870

6971
try {
7072
await kv.put(
71-
this.getKVKey(key, isFetch),
73+
this.getKVKey(key, cacheType),
7274
JSON.stringify({
7375
value,
7476
// Note: `Date.now()` returns the time of the last IO rather than the actual time.
@@ -90,17 +92,18 @@ class KVIncrementalCache implements IncrementalCache {
9092
debugCache(`Delete ${key}`);
9193

9294
try {
93-
await kv.delete(this.getKVKey(key, /* isFetch= */ false));
95+
// Only cache that gets deleted is the ISR/SSG cache.
96+
await kv.delete(this.getKVKey(key, "cache"));
9497
} catch (e) {
9598
error("Failed to delete from cache", e);
9699
}
97100
}
98101

99-
protected getKVKey(key: string, isFetch?: boolean): string {
102+
protected getKVKey(key: string, cacheType?: CacheEntryType): string {
100103
return computeCacheKey(key, {
101104
prefix: getCloudflareContext().env[PREFIX_ENV_NAME],
102105
buildId: process.env.NEXT_BUILD_ID,
103-
isFetch,
106+
cacheType,
104107
});
105108
}
106109
}

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

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

510
import { getCloudflareContext } from "../../cloudflare-context.js";
@@ -21,17 +26,17 @@ export const PREFIX_ENV_NAME = "NEXT_INC_CACHE_R2_PREFIX";
2126
class R2IncrementalCache implements IncrementalCache {
2227
readonly name = NAME;
2328

24-
async get<IsFetch extends boolean = false>(
29+
async get<CacheType extends CacheEntryType = "cache">(
2530
key: string,
26-
isFetch?: IsFetch
27-
): Promise<WithLastModified<CacheValue<IsFetch>> | null> {
31+
cacheType?: CacheType
32+
): Promise<WithLastModified<CacheValue<CacheType>> | null> {
2833
const r2 = getCloudflareContext().env[BINDING_NAME];
2934
if (!r2) throw new IgnorableError("No R2 bucket");
3035

3136
debugCache(`Get ${key}`);
3237

3338
try {
34-
const r2Object = await r2.get(this.getR2Key(key, isFetch));
39+
const r2Object = await r2.get(this.getR2Key(key, cacheType));
3540
if (!r2Object) return null;
3641

3742
return {
@@ -44,18 +49,18 @@ class R2IncrementalCache implements IncrementalCache {
4449
}
4550
}
4651

47-
async set<IsFetch extends boolean = false>(
52+
async set<CacheType extends CacheEntryType = "cache">(
4853
key: string,
49-
value: CacheValue<IsFetch>,
50-
isFetch?: IsFetch
54+
value: CacheValue<CacheType>,
55+
cacheType?: CacheType
5156
): Promise<void> {
5257
const r2 = getCloudflareContext().env[BINDING_NAME];
5358
if (!r2) throw new IgnorableError("No R2 bucket");
5459

5560
debugCache(`Set ${key}`);
5661

5762
try {
58-
await r2.put(this.getR2Key(key, isFetch), JSON.stringify(value));
63+
await r2.put(this.getR2Key(key, cacheType), JSON.stringify(value));
5964
} catch (e) {
6065
error("Failed to set to cache", e);
6166
}
@@ -74,11 +79,11 @@ class R2IncrementalCache implements IncrementalCache {
7479
}
7580
}
7681

77-
protected getR2Key(key: string, isFetch?: boolean): string {
82+
protected getR2Key(key: string, cacheType?: CacheEntryType): string {
7883
return computeCacheKey(key, {
7984
prefix: getCloudflareContext().env[PREFIX_ENV_NAME],
8085
buildId: process.env.NEXT_BUILD_ID,
81-
isFetch,
86+
cacheType,
8287
});
8388
}
8489
}

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)