@opennextjs/cloudflare 1.9.0 → 1.9.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.
@@ -1,4 +1,5 @@
1
1
  import { DurableObject } from "cloudflare:workers";
2
+ import { debugCache } from "../overrides/internal.js";
2
3
  export class DOShardedTagCache extends DurableObject {
3
4
  sql;
4
5
  constructor(state, env) {
@@ -13,10 +14,9 @@ export class DOShardedTagCache extends DurableObject {
13
14
  const result = this.sql
14
15
  .exec(`SELECT MAX(revalidatedAt) AS time FROM revalidations WHERE tag IN (${tags.map(() => "?").join(", ")})`, ...tags)
15
16
  .toArray();
16
- if (result.length === 0)
17
- return 0;
18
- // We only care about the most recent revalidation
19
- return result[0]?.time;
17
+ const timeMs = (result[0]?.time ?? 0);
18
+ debugCache("DOShardedTagCache", `getLastRevalidated tags=${tags} -> time=${timeMs}`);
19
+ return timeMs;
20
20
  }
21
21
  catch (e) {
22
22
  console.error(e);
@@ -25,11 +25,14 @@ export class DOShardedTagCache extends DurableObject {
25
25
  }
26
26
  }
27
27
  async hasBeenRevalidated(tags, lastModified) {
28
- return (this.sql
28
+ const revalidated = this.sql
29
29
  .exec(`SELECT 1 FROM revalidations WHERE tag IN (${tags.map(() => "?").join(", ")}) AND revalidatedAt > ? LIMIT 1`, ...tags, lastModified ?? Date.now())
30
- .toArray().length > 0);
30
+ .toArray().length > 0;
31
+ debugCache("DOShardedTagCache", `hasBeenRevalidated tags=${tags} -> revalidated=${revalidated}`);
32
+ return revalidated;
31
33
  }
32
34
  async writeTags(tags, lastModified) {
35
+ debugCache("DOShardedTagCache", `writeTags tags=${tags} time=${lastModified}`);
33
36
  tags.forEach((tag) => {
34
37
  this.sql.exec(`INSERT OR REPLACE INTO revalidations (tag, revalidatedAt) VALUES (?, ?)`, tag, lastModified);
35
38
  });
@@ -21,7 +21,7 @@ class KVIncrementalCache {
21
21
  const kv = getCloudflareContext().env[BINDING_NAME];
22
22
  if (!kv)
23
23
  throw new IgnorableError("No KV Namespace");
24
- debugCache(`Get ${key}`);
24
+ debugCache("KVIncrementalCache", `get ${key}`);
25
25
  try {
26
26
  const entry = await kv.get(this.getKVKey(key, cacheType), "json");
27
27
  if (!entry)
@@ -44,7 +44,7 @@ class KVIncrementalCache {
44
44
  const kv = getCloudflareContext().env[BINDING_NAME];
45
45
  if (!kv)
46
46
  throw new IgnorableError("No KV Namespace");
47
- debugCache(`Set ${key}`);
47
+ debugCache("KVIncrementalCache", `set ${key}`);
48
48
  try {
49
49
  await kv.put(this.getKVKey(key, cacheType), JSON.stringify({
50
50
  value,
@@ -64,7 +64,7 @@ class KVIncrementalCache {
64
64
  const kv = getCloudflareContext().env[BINDING_NAME];
65
65
  if (!kv)
66
66
  throw new IgnorableError("No KV Namespace");
67
- debugCache(`Delete ${key}`);
67
+ debugCache("KVIncrementalCache", `delete ${key}`);
68
68
  try {
69
69
  // Only cache that gets deleted is the ISR/SSG cache.
70
70
  await kv.delete(this.getKVKey(key, "cache"));
@@ -18,7 +18,7 @@ class R2IncrementalCache {
18
18
  const r2 = getCloudflareContext().env[BINDING_NAME];
19
19
  if (!r2)
20
20
  throw new IgnorableError("No R2 bucket");
21
- debugCache(`Get ${key}`);
21
+ debugCache("R2IncrementalCache", `get ${key}`);
22
22
  try {
23
23
  const r2Object = await r2.get(this.getR2Key(key, cacheType));
24
24
  if (!r2Object)
@@ -37,7 +37,7 @@ class R2IncrementalCache {
37
37
  const r2 = getCloudflareContext().env[BINDING_NAME];
38
38
  if (!r2)
39
39
  throw new IgnorableError("No R2 bucket");
40
- debugCache(`Set ${key}`);
40
+ debugCache("R2IncrementalCache", `set ${key}`);
41
41
  try {
42
42
  await r2.put(this.getR2Key(key, cacheType), JSON.stringify(value));
43
43
  }
@@ -49,7 +49,7 @@ class R2IncrementalCache {
49
49
  const r2 = getCloudflareContext().env[BINDING_NAME];
50
50
  if (!r2)
51
51
  throw new IgnorableError("No R2 bucket");
52
- debugCache(`Delete ${key}`);
52
+ debugCache("R2IncrementalCache", `delete ${key}`);
53
53
  try {
54
54
  await r2.delete(this.getR2Key(key));
55
55
  }
@@ -45,7 +45,7 @@ class RegionalCache {
45
45
  // Check for a cached entry as this will be faster than the store response.
46
46
  const cachedResponse = await cache.match(urlKey);
47
47
  if (cachedResponse) {
48
- debugCache("Get - cached response");
48
+ debugCache("RegionalCache", `get ${key} -> cached response`);
49
49
  // Re-fetch from the store and update the regional cache in the background.
50
50
  // Note: this is only useful when the Cache API is not purged automatically.
51
51
  if (this.opts.shouldLazilyUpdateOnCacheHit) {
@@ -66,6 +66,7 @@ class RegionalCache {
66
66
  const { value, lastModified } = rawEntry ?? {};
67
67
  if (!value || typeof lastModified !== "number")
68
68
  return null;
69
+ debugCache("RegionalCache", `get ${key} -> put to cache`);
69
70
  // Update the locale cache after retrieving from the store.
70
71
  getCloudflareContext().ctx.waitUntil(this.putToCache({ key, cacheType, entry: { value, lastModified } }));
71
72
  return { value, lastModified };
@@ -77,6 +78,7 @@ class RegionalCache {
77
78
  }
78
79
  async set(key, value, cacheType) {
79
80
  try {
81
+ debugCache("RegionalCache", `set ${key}`);
80
82
  await this.store.set(key, value, cacheType);
81
83
  await this.putToCache({
82
84
  key,
@@ -94,6 +96,7 @@ class RegionalCache {
94
96
  }
95
97
  }
96
98
  async delete(key) {
99
+ debugCache("RegionalCache", `delete ${key}`);
97
100
  try {
98
101
  await this.store.delete(key);
99
102
  const cache = await this.getCacheInstance();
@@ -9,7 +9,7 @@ export declare const NAME = "cf-static-assets-incremental-cache";
9
9
  declare class StaticAssetsIncrementalCache implements IncrementalCache {
10
10
  readonly name = "cf-static-assets-incremental-cache";
11
11
  get<CacheType extends CacheEntryType = "cache">(key: string, cacheType?: CacheType): Promise<WithLastModified<CacheValue<CacheType>> | null>;
12
- set(): Promise<void>;
12
+ set<CacheType extends CacheEntryType = "cache">(key: string, _value: CacheValue<CacheType>, cacheType?: CacheType): Promise<void>;
13
13
  delete(): Promise<void>;
14
14
  protected getAssetUrl(key: string, cacheType?: CacheEntryType): string;
15
15
  }
@@ -16,7 +16,7 @@ class StaticAssetsIncrementalCache {
16
16
  const assets = getCloudflareContext().env.ASSETS;
17
17
  if (!assets)
18
18
  throw new IgnorableError("No Static Assets");
19
- debugCache(`Get ${key}`);
19
+ debugCache("StaticAssetsIncrementalCache", `get ${key}`);
20
20
  try {
21
21
  const response = await assets.fetch(this.getAssetUrl(key, cacheType));
22
22
  if (!response.ok) {
@@ -33,11 +33,11 @@ class StaticAssetsIncrementalCache {
33
33
  return null;
34
34
  }
35
35
  }
36
- async set() {
37
- error("Failed to set to read-only cache");
36
+ async set(key, _value, cacheType) {
37
+ error(`StaticAssetsIncrementalCache: Failed to set to read-only cache key=${key} type=${cacheType}`);
38
38
  }
39
39
  async delete() {
40
- error("Failed to delete from read-only cache");
40
+ error("StaticAssetsIncrementalCache: Failed to delete from read-only cache");
41
41
  }
42
42
  getAssetUrl(key, cacheType) {
43
43
  if (cacheType === "composable") {
@@ -8,17 +8,17 @@ export class D1NextModeTagCache {
8
8
  name = NAME;
9
9
  async getLastRevalidated(tags) {
10
10
  const { isDisabled, db } = this.getConfig();
11
- if (isDisabled)
11
+ if (isDisabled || tags.length === 0) {
12
12
  return 0;
13
+ }
13
14
  try {
14
15
  const result = await db
15
16
  .prepare(`SELECT MAX(revalidatedAt) AS time FROM revalidations WHERE tag IN (${tags.map(() => "?").join(", ")})`)
16
17
  .bind(...tags.map((tag) => this.getCacheKey(tag)))
17
18
  .run();
18
- if (result.results.length === 0)
19
- return 0;
20
- // We only care about the most recent revalidation
21
- return (result.results[0]?.time ?? 0);
19
+ const timeMs = (result.results[0]?.time ?? 0);
20
+ debugCache("D1NextModeTagCache", `getLastRevalidated tags=${tags} -> ${timeMs}`);
21
+ return timeMs;
22
22
  }
23
23
  catch (e) {
24
24
  // By default we don't want to crash here, so we return false
@@ -29,14 +29,17 @@ export class D1NextModeTagCache {
29
29
  }
30
30
  async hasBeenRevalidated(tags, lastModified) {
31
31
  const { isDisabled, db } = this.getConfig();
32
- if (isDisabled)
32
+ if (isDisabled || tags.length === 0) {
33
33
  return false;
34
+ }
34
35
  try {
35
36
  const result = await db
36
37
  .prepare(`SELECT 1 FROM revalidations WHERE tag IN (${tags.map(() => "?").join(", ")}) AND revalidatedAt > ? LIMIT 1`)
37
38
  .bind(...tags.map((tag) => this.getCacheKey(tag)), lastModified ?? Date.now())
38
39
  .raw();
39
- return result.length > 0;
40
+ const revalidated = result.length > 0;
41
+ debugCache("D1NextModeTagCache", `hasBeenRevalidated tags=${tags} at=${lastModified} -> ${revalidated}`);
42
+ return revalidated;
40
43
  }
41
44
  catch (e) {
42
45
  error(e);
@@ -49,9 +52,11 @@ export class D1NextModeTagCache {
49
52
  const { isDisabled, db } = this.getConfig();
50
53
  if (isDisabled || tags.length === 0)
51
54
  return Promise.resolve();
55
+ const nowMs = Date.now();
52
56
  await db.batch(tags.map((tag) => db
53
57
  .prepare(`INSERT INTO revalidations (tag, revalidatedAt) VALUES (?, ?)`)
54
- .bind(this.getCacheKey(tag), Date.now())));
58
+ .bind(this.getCacheKey(tag), nowMs)));
59
+ debugCache("D1NextModeTagCache", `writeTags tags=${tags} time=${nowMs}`);
55
60
  // TODO: See https://github.com/opennextjs/opennextjs-aws/issues/986
56
61
  if (isPurgeCacheEnabled()) {
57
62
  await purgeCacheByTags(tags);
@@ -32,10 +32,9 @@ class ShardedDOTagCache {
32
32
  */
33
33
  async getLastRevalidated(tags) {
34
34
  const { isDisabled } = this.getConfig();
35
- if (isDisabled)
35
+ if (isDisabled || tags.length === 0) {
36
36
  return 0;
37
- if (tags.length === 0)
38
- return 0; // No tags to check
37
+ }
39
38
  const deduplicatedTags = Array.from(new Set(tags)); // We deduplicate the tags to avoid unnecessary requests
40
39
  try {
41
40
  const shardedTagGroups = this.groupTagsByDO({ tags: deduplicatedTags });
@@ -43,16 +42,19 @@ class ShardedDOTagCache {
43
42
  const cachedValue = await this.getFromRegionalCache({ doId, tags });
44
43
  // If all the value were found in the regional cache, we can just return the max value
45
44
  if (cachedValue.length === tags.length) {
46
- return Math.max(...cachedValue.map((item) => item.time));
45
+ const timeMs = Math.max(...cachedValue.map((item) => item.time));
46
+ debugCache("ShardedDOTagCache", `getLastRevalidated tags=${tags} -> ${timeMs} (regional cache)`);
47
+ return timeMs;
47
48
  }
48
49
  // Otherwise we need to check the durable object on the ones that were not found in the cache
49
50
  const filteredTags = deduplicatedTags.filter((tag) => !cachedValue.some((item) => item.tag === tag));
50
51
  const stub = this.getDurableObjectStub(doId);
51
52
  const lastRevalidated = await stub.getLastRevalidated(filteredTags);
52
- const result = Math.max(...cachedValue.map((item) => item.time), lastRevalidated);
53
+ const timeMs = Math.max(...cachedValue.map((item) => item.time), lastRevalidated);
53
54
  // We then need to populate the regional cache with the missing tags
54
55
  getCloudflareContext().ctx.waitUntil(this.putToRegionalCache({ doId, tags }, stub));
55
- return result;
56
+ debugCache("ShardedDOTagCache", `getLastRevalidated tags=${tags} -> ${timeMs}`);
57
+ return timeMs;
56
58
  }));
57
59
  return Math.max(...shardedTagRevalidationOutcomes);
58
60
  }
@@ -70,8 +72,9 @@ class ShardedDOTagCache {
70
72
  */
71
73
  async hasBeenRevalidated(tags, lastModified) {
72
74
  const { isDisabled } = this.getConfig();
73
- if (isDisabled)
75
+ if (isDisabled || tags.length === 0) {
74
76
  return false;
77
+ }
75
78
  try {
76
79
  const shardedTagGroups = this.groupTagsByDO({ tags });
77
80
  const shardedTagRevalidationOutcomes = await Promise.all(shardedTagGroups.map(async ({ doId, tags }) => {
@@ -81,6 +84,7 @@ class ShardedDOTagCache {
81
84
  return (cachedValue.time ?? 0) > (lastModified ?? Date.now());
82
85
  });
83
86
  if (cacheHasBeenRevalidated) {
87
+ debugCache("ShardedDOTagCache", `hasBeenRevalidated tags=${tags} at=${lastModified} -> true (regional cache)`);
84
88
  return true;
85
89
  }
86
90
  const stub = this.getDurableObjectStub(doId);
@@ -90,6 +94,7 @@ class ShardedDOTagCache {
90
94
  // We need to put the missing tags in the regional cache
91
95
  getCloudflareContext().ctx.waitUntil(this.putToRegionalCache({ doId, tags: remainingTags }, stub));
92
96
  }
97
+ debugCache("ShardedDOTagCache", `hasBeenRevalidated tags=${tags} at=${lastModified} -> ${_hasBeenRevalidated}`);
93
98
  return _hasBeenRevalidated;
94
99
  }));
95
100
  return shardedTagRevalidationOutcomes.some((result) => result);
@@ -109,11 +114,12 @@ class ShardedDOTagCache {
109
114
  const { isDisabled } = this.getConfig();
110
115
  if (isDisabled)
111
116
  return;
112
- const shardedTagGroups = this.groupTagsByDO({ tags, generateAllReplicas: true });
113
117
  // We want to use the same revalidation time for all tags
114
- const currentTime = Date.now();
118
+ const nowMs = Date.now();
119
+ debugCache("ShardedDOTagCache", `writeTags tags=${tags} time=${nowMs}`);
120
+ const shardedTagGroups = this.groupTagsByDO({ tags, generateAllReplicas: true });
115
121
  await Promise.all(shardedTagGroups.map(async ({ doId, tags }) => {
116
- await this.performWriteTagsWithRetry(doId, tags, currentTime);
122
+ await this.performWriteTagsWithRetry(doId, tags, nowMs);
117
123
  }));
118
124
  // TODO: See https://github.com/opennextjs/opennextjs-aws/issues/986
119
125
  if (isPurgeCacheEnabled()) {
@@ -14,6 +14,7 @@ export declare const BINDING_NAME = "NEXT_TAG_CACHE_KV";
14
14
  * are revalidated resulting in the page being generated based on outdated data.
15
15
  */
16
16
  export declare class KVNextModeTagCache implements NextModeTagCache {
17
+ #private;
17
18
  readonly mode: "nextMode";
18
19
  readonly name = "kv-next-mode-tag-cache";
19
20
  getLastRevalidated(tags: string[]): Promise<number>;
@@ -1,6 +1,6 @@
1
1
  import { error } from "@opennextjs/aws/adapters/logger.js";
2
2
  import { getCloudflareContext } from "../../cloudflare-context.js";
3
- import { FALLBACK_BUILD_ID, isPurgeCacheEnabled, purgeCacheByTags } from "../internal.js";
3
+ import { debugCache, FALLBACK_BUILD_ID, isPurgeCacheEnabled, purgeCacheByTags } from "../internal.js";
4
4
  export const NAME = "kv-next-mode-tag-cache";
5
5
  export const BINDING_NAME = "NEXT_TAG_CACHE_KV";
6
6
  /**
@@ -19,8 +19,18 @@ export class KVNextModeTagCache {
19
19
  mode = "nextMode";
20
20
  name = NAME;
21
21
  async getLastRevalidated(tags) {
22
+ const timeMs = await this.#getLastRevalidated(tags);
23
+ debugCache("KVNextModeTagCache", `getLastRevalidated tags=${tags} -> time=${timeMs}`);
24
+ return timeMs;
25
+ }
26
+ /**
27
+ * Implementation of `getLastRevalidated`.
28
+ *
29
+ * This implementation is separated so that `hasBeenRevalidated` do not include logs from `getLastRevalidated`.
30
+ */
31
+ async #getLastRevalidated(tags) {
22
32
  const kv = this.getKv();
23
- if (!kv) {
33
+ if (!kv || tags.length === 0) {
24
34
  return 0;
25
35
  }
26
36
  try {
@@ -38,17 +48,20 @@ export class KVNextModeTagCache {
38
48
  }
39
49
  }
40
50
  async hasBeenRevalidated(tags, lastModified) {
41
- return (await this.getLastRevalidated(tags)) > (lastModified ?? Date.now());
51
+ const revalidated = (await this.#getLastRevalidated(tags)) > (lastModified ?? Date.now());
52
+ debugCache("KVNextModeTagCache", `hasBeenRevalidated tags=${tags} lastModified=${lastModified} -> ${revalidated}`);
53
+ return revalidated;
42
54
  }
43
55
  async writeTags(tags) {
44
56
  const kv = this.getKv();
45
57
  if (!kv || tags.length === 0) {
46
58
  return Promise.resolve();
47
59
  }
48
- const timeMs = String(Date.now());
60
+ const nowMs = Date.now();
49
61
  await Promise.all(tags.map(async (tag) => {
50
- await kv.put(this.getCacheKey(tag), timeMs);
62
+ await kv.put(this.getCacheKey(tag), String(nowMs));
51
63
  }));
64
+ debugCache("KVNextModeTagCache", `writeTags tags=${tags} time=${nowMs}`);
52
65
  // TODO: See https://github.com/opennextjs/opennextjs-aws/issues/986
53
66
  if (isPurgeCacheEnabled()) {
54
67
  await purgeCacheByTags(tags);
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@opennextjs/cloudflare",
3
3
  "description": "Cloudflare builder for next apps",
4
- "version": "1.9.0",
4
+ "version": "1.9.1",
5
5
  "type": "module",
6
6
  "bin": {
7
7
  "opennextjs-cloudflare": "dist/cli/index.js"
@@ -43,7 +43,7 @@
43
43
  "homepage": "https://github.com/opennextjs/opennextjs-cloudflare",
44
44
  "dependencies": {
45
45
  "@dotenvx/dotenvx": "1.31.0",
46
- "@opennextjs/aws": "3.7.7",
46
+ "@opennextjs/aws": "3.8.0",
47
47
  "cloudflare": "^4.4.1",
48
48
  "enquirer": "^2.4.1",
49
49
  "glob": "^11.0.0",