@opennextjs/cloudflare 1.0.0-beta.4 → 1.0.0

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.
Files changed (51) hide show
  1. package/dist/api/cloudflare-context.d.ts +6 -3
  2. package/dist/api/config.d.ts +28 -1
  3. package/dist/api/config.js +15 -1
  4. package/dist/api/durable-objects/sharded-tag-cache.d.ts +1 -0
  5. package/dist/api/durable-objects/sharded-tag-cache.js +16 -0
  6. package/dist/api/index.d.ts +1 -1
  7. package/dist/api/overrides/incremental-cache/kv-incremental-cache.d.ts +8 -9
  8. package/dist/api/overrides/incremental-cache/kv-incremental-cache.js +14 -14
  9. package/dist/api/overrides/incremental-cache/r2-incremental-cache.d.ts +4 -11
  10. package/dist/api/overrides/incremental-cache/r2-incremental-cache.js +8 -15
  11. package/dist/api/overrides/incremental-cache/regional-cache.d.ts +7 -7
  12. package/dist/api/overrides/incremental-cache/regional-cache.js +16 -13
  13. package/dist/api/overrides/incremental-cache/static-assets-incremental-cache.d.ts +3 -3
  14. package/dist/api/overrides/incremental-cache/static-assets-incremental-cache.js +9 -4
  15. package/dist/api/overrides/internal.d.ts +10 -3
  16. package/dist/api/overrides/internal.js +7 -0
  17. package/dist/api/overrides/tag-cache/d1-next-tag-cache.d.ts +1 -0
  18. package/dist/api/overrides/tag-cache/d1-next-tag-cache.js +20 -0
  19. package/dist/api/overrides/tag-cache/do-sharded-tag-cache.d.ts +10 -4
  20. package/dist/api/overrides/tag-cache/do-sharded-tag-cache.js +50 -12
  21. package/dist/api/overrides/tag-cache/do-sharded-tag-cache.spec.js +9 -5
  22. package/dist/api/overrides/tag-cache/tag-cache-filter.js +7 -0
  23. package/dist/api/overrides/tag-cache/tag-cache-filter.spec.js +1 -0
  24. package/dist/cli/args.d.ts +2 -0
  25. package/dist/cli/args.js +3 -0
  26. package/dist/cli/build/build.d.ts +1 -1
  27. package/dist/cli/build/bundle-server.js +16 -4
  28. package/dist/cli/build/open-next/createServerBundle.js +15 -2
  29. package/dist/cli/build/patches/investigated/patch-cache.d.ts +1 -0
  30. package/dist/cli/build/patches/investigated/patch-cache.js +16 -0
  31. package/dist/cli/build/patches/plugins/eval-manifest.js +1 -1
  32. package/dist/cli/build/patches/plugins/load-manifest.js +1 -1
  33. package/dist/cli/build/patches/plugins/wrangler-external.js +2 -1
  34. package/dist/cli/build/utils/ensure-cf-config.d.ts +1 -1
  35. package/dist/cli/build/utils/workerd.d.ts +38 -0
  36. package/dist/cli/build/utils/workerd.js +80 -0
  37. package/dist/cli/build/utils/workerd.spec.d.ts +1 -0
  38. package/dist/cli/build/utils/workerd.spec.js +188 -0
  39. package/dist/cli/commands/deploy.d.ts +2 -1
  40. package/dist/cli/commands/deploy.js +1 -0
  41. package/dist/cli/commands/populate-cache.d.ts +1 -0
  42. package/dist/cli/commands/populate-cache.js +56 -24
  43. package/dist/cli/commands/preview.d.ts +2 -1
  44. package/dist/cli/commands/preview.js +1 -0
  45. package/dist/cli/commands/upload.d.ts +2 -1
  46. package/dist/cli/commands/upload.js +1 -0
  47. package/dist/cli/templates/init.js +3 -0
  48. package/dist/cli/utils/run-wrangler.d.ts +0 -1
  49. package/dist/cli/utils/run-wrangler.js +1 -1
  50. package/package.json +3 -3
  51. package/templates/wrangler.jsonc +1 -1
@@ -1,14 +1,17 @@
1
1
  import type { GetPlatformProxyOptions } from "wrangler";
2
- import type { DOQueueHandler } from "./durable-objects/queue";
3
- import { DOShardedTagCache } from "./durable-objects/sharded-tag-cache";
2
+ import type { DOQueueHandler } from "./durable-objects/queue.js";
3
+ import type { DOShardedTagCache } from "./durable-objects/sharded-tag-cache.js";
4
+ import type { PREFIX_ENV_NAME as KV_CACHE_PREFIX_ENV_NAME } from "./overrides/incremental-cache/kv-incremental-cache.js";
5
+ import type { PREFIX_ENV_NAME as R2_CACHE_PREFIX_ENV_NAME } from "./overrides/incremental-cache/r2-incremental-cache.js";
4
6
  declare global {
5
7
  interface CloudflareEnv {
6
8
  ASSETS?: Fetcher;
7
9
  NEXTJS_ENV?: string;
8
10
  WORKER_SELF_REFERENCE?: Service;
9
11
  NEXT_INC_CACHE_KV?: KVNamespace;
12
+ [KV_CACHE_PREFIX_ENV_NAME]?: string;
10
13
  NEXT_INC_CACHE_R2_BUCKET?: R2Bucket;
11
- NEXT_INC_CACHE_R2_PREFIX?: string;
14
+ [R2_CACHE_PREFIX_ENV_NAME]?: string;
12
15
  NEXT_TAG_CACHE_D1?: D1Database;
13
16
  NEXT_TAG_CACHE_DO_SHARDED?: DurableObjectNamespace<DOShardedTagCache>;
14
17
  NEXT_TAG_CACHE_DO_SHARDED_DLQ?: Queue;
@@ -1,4 +1,5 @@
1
- import { BaseOverride, LazyLoadedOverride, OpenNextConfig } from "@opennextjs/aws/types/open-next";
1
+ import type { BuildOptions } from "@opennextjs/aws/build/helper";
2
+ import { BaseOverride, LazyLoadedOverride, OpenNextConfig as AwsOpenNextConfig } from "@opennextjs/aws/types/open-next";
2
3
  import type { IncrementalCache, Queue, TagCache } from "@opennextjs/aws/types/overrides";
3
4
  export type Override<T extends BaseOverride> = "dummy" | T | LazyLoadedOverride<T>;
4
5
  /**
@@ -19,6 +20,12 @@ export type CloudflareOverrides = {
19
20
  * Sets the revalidation queue implementation
20
21
  */
21
22
  queue?: "direct" | Override<Queue>;
23
+ /**
24
+ * Enable cache interception
25
+ * Should be `false` when PPR is used
26
+ * @default false
27
+ */
28
+ enableCacheInterception?: boolean;
22
29
  };
23
30
  /**
24
31
  * Defines the OpenNext configuration that targets the Cloudflare adapter
@@ -27,3 +34,23 @@ export type CloudflareOverrides = {
27
34
  * @returns the OpenNext configuration object
28
35
  */
29
36
  export declare function defineCloudflareConfig(config?: CloudflareOverrides): OpenNextConfig;
37
+ interface OpenNextConfig extends AwsOpenNextConfig {
38
+ cloudflare?: {
39
+ /**
40
+ * Whether to use the "workerd" build conditions when bundling the server.
41
+ * It is recommended to set it to `true` so that code specifically targeted to the
42
+ * workerd runtime is bundled.
43
+ *
44
+ * See https://esbuild.github.io/api/#conditions
45
+ *
46
+ * @default true
47
+ */
48
+ useWorkerdCondition?: boolean;
49
+ };
50
+ }
51
+ /**
52
+ * @param buildOpts build options from AWS
53
+ * @returns The OpenConfig specific to cloudflare
54
+ */
55
+ export declare function getOpenNextConfig(buildOpts: BuildOptions): OpenNextConfig;
56
+ export type { OpenNextConfig };
@@ -5,7 +5,7 @@
5
5
  * @returns the OpenNext configuration object
6
6
  */
7
7
  export function defineCloudflareConfig(config = {}) {
8
- const { incrementalCache, tagCache, queue } = config;
8
+ const { incrementalCache, tagCache, queue, enableCacheInterception = false } = config;
9
9
  return {
10
10
  default: {
11
11
  override: {
@@ -16,9 +16,16 @@ export function defineCloudflareConfig(config = {}) {
16
16
  tagCache: resolveTagCache(tagCache),
17
17
  queue: resolveQueue(queue),
18
18
  },
19
+ routePreloadingBehavior: "withWaitUntil",
19
20
  },
20
21
  // node:crypto is used to compute cache keys
21
22
  edgeExternals: ["node:crypto"],
23
+ cloudflare: {
24
+ useWorkerdCondition: true,
25
+ },
26
+ dangerous: {
27
+ enableCacheInterception,
28
+ },
22
29
  };
23
30
  }
24
31
  function resolveIncrementalCache(value = "dummy") {
@@ -39,3 +46,10 @@ function resolveQueue(value = "dummy") {
39
46
  }
40
47
  return typeof value === "function" ? value : () => value;
41
48
  }
49
+ /**
50
+ * @param buildOpts build options from AWS
51
+ * @returns The OpenConfig specific to cloudflare
52
+ */
53
+ export function getOpenNextConfig(buildOpts) {
54
+ return buildOpts.config;
55
+ }
@@ -2,6 +2,7 @@ import { DurableObject } from "cloudflare:workers";
2
2
  export declare class DOShardedTagCache extends DurableObject<CloudflareEnv> {
3
3
  sql: SqlStorage;
4
4
  constructor(state: DurableObjectState, env: CloudflareEnv);
5
+ getLastRevalidated(tags: string[]): Promise<number>;
5
6
  hasBeenRevalidated(tags: string[], lastModified?: number): Promise<boolean>;
6
7
  writeTags(tags: string[], lastModified: number): Promise<void>;
7
8
  }
@@ -8,6 +8,22 @@ export class DOShardedTagCache extends DurableObject {
8
8
  this.sql.exec(`CREATE TABLE IF NOT EXISTS revalidations (tag TEXT PRIMARY KEY, revalidatedAt INTEGER)`);
9
9
  });
10
10
  }
11
+ async getLastRevalidated(tags) {
12
+ try {
13
+ const result = this.sql
14
+ .exec(`SELECT MAX(revalidatedAt) AS time FROM revalidations WHERE tag IN (${tags.map(() => "?").join(", ")})`, ...tags)
15
+ .toArray();
16
+ if (result.length === 0)
17
+ return 0;
18
+ // We only care about the most recent revalidation
19
+ return result[0]?.time;
20
+ }
21
+ catch (e) {
22
+ console.error(e);
23
+ // By default we don't want to crash here, so we return 0
24
+ return 0;
25
+ }
26
+ }
11
27
  async hasBeenRevalidated(tags, lastModified) {
12
28
  return (this.sql
13
29
  .exec(`SELECT 1 FROM revalidations WHERE tag IN (${tags.map(() => "?").join(", ")}) AND revalidatedAt > ? LIMIT 1`, ...tags, lastModified ?? Date.now())
@@ -1,2 +1,2 @@
1
1
  export * from "./cloudflare-context.js";
2
- export { defineCloudflareConfig } from "./config.js";
2
+ export { defineCloudflareConfig, type OpenNextConfig } from "./config.js";
@@ -1,24 +1,23 @@
1
- import type { CacheValue, IncrementalCache, WithLastModified } from "@opennextjs/aws/types/overrides.js";
1
+ import type { CacheEntryType, CacheValue, IncrementalCache, WithLastModified } from "@opennextjs/aws/types/overrides.js";
2
2
  export declare const NAME = "cf-kv-incremental-cache";
3
3
  export declare const BINDING_NAME = "NEXT_INC_CACHE_KV";
4
- export type KeyOptions = {
5
- isFetch?: boolean;
6
- buildId?: string;
7
- };
8
- export declare function computeCacheKey(key: string, options: KeyOptions): string;
4
+ export declare const PREFIX_ENV_NAME = "NEXT_INC_CACHE_KV_PREFIX";
9
5
  /**
10
6
  * Open Next cache based on Cloudflare KV.
11
7
  *
8
+ * The prefix that the cache entries are stored under can be configured with the `NEXT_INC_CACHE_KV_PREFIX`
9
+ * environment variable, and defaults to `incremental-cache`.
10
+ *
12
11
  * Note: The class is instantiated outside of the request context.
13
12
  * The cloudflare context and process.env are not initialized yet
14
13
  * when the constructor is called.
15
14
  */
16
15
  declare class KVIncrementalCache implements IncrementalCache {
17
16
  readonly name = "cf-kv-incremental-cache";
18
- get<IsFetch extends boolean = false>(key: string, isFetch?: IsFetch): Promise<WithLastModified<CacheValue<IsFetch>> | null>;
19
- set<IsFetch extends boolean = false>(key: string, value: CacheValue<IsFetch>, isFetch?: IsFetch): Promise<void>;
17
+ get<CacheType extends CacheEntryType = "cache">(key: string, cacheType?: CacheType): Promise<WithLastModified<CacheValue<CacheType>> | null>;
18
+ set<CacheType extends CacheEntryType = "cache">(key: string, value: CacheValue<CacheType>, cacheType?: CacheType): Promise<void>;
20
19
  delete(key: string): Promise<void>;
21
- protected getKVKey(key: string, isFetch?: boolean): string;
20
+ protected getKVKey(key: string, cacheType?: CacheEntryType): string;
22
21
  }
23
22
  declare const _default: KVIncrementalCache;
24
23
  export default _default;
@@ -1,31 +1,29 @@
1
- import { createHash } from "node:crypto";
2
1
  import { error } from "@opennextjs/aws/adapters/logger.js";
3
2
  import { IgnorableError } from "@opennextjs/aws/utils/error.js";
4
3
  import { getCloudflareContext } from "../../cloudflare-context.js";
5
- import { debugCache, FALLBACK_BUILD_ID } from "../internal.js";
4
+ import { computeCacheKey, debugCache } from "../internal.js";
6
5
  export const NAME = "cf-kv-incremental-cache";
7
6
  export const BINDING_NAME = "NEXT_INC_CACHE_KV";
8
- export function computeCacheKey(key, options) {
9
- const { isFetch = false, buildId = FALLBACK_BUILD_ID } = options;
10
- const hash = createHash("sha256").update(key).digest("hex");
11
- return `${buildId}/${hash}.${isFetch ? "fetch" : "cache"}`.replace(/\/+/g, "/");
12
- }
7
+ export const PREFIX_ENV_NAME = "NEXT_INC_CACHE_KV_PREFIX";
13
8
  /**
14
9
  * Open Next cache based on Cloudflare KV.
15
10
  *
11
+ * The prefix that the cache entries are stored under can be configured with the `NEXT_INC_CACHE_KV_PREFIX`
12
+ * environment variable, and defaults to `incremental-cache`.
13
+ *
16
14
  * Note: The class is instantiated outside of the request context.
17
15
  * The cloudflare context and process.env are not initialized yet
18
16
  * when the constructor is called.
19
17
  */
20
18
  class KVIncrementalCache {
21
19
  name = NAME;
22
- async get(key, isFetch) {
20
+ async get(key, cacheType) {
23
21
  const kv = getCloudflareContext().env[BINDING_NAME];
24
22
  if (!kv)
25
23
  throw new IgnorableError("No KV Namespace");
26
24
  debugCache(`Get ${key}`);
27
25
  try {
28
- const entry = await kv.get(this.getKVKey(key, isFetch), "json");
26
+ const entry = await kv.get(this.getKVKey(key, cacheType), "json");
29
27
  if (!entry)
30
28
  return null;
31
29
  if ("lastModified" in entry) {
@@ -42,13 +40,13 @@ class KVIncrementalCache {
42
40
  return null;
43
41
  }
44
42
  }
45
- async set(key, value, isFetch) {
43
+ async set(key, value, cacheType) {
46
44
  const kv = getCloudflareContext().env[BINDING_NAME];
47
45
  if (!kv)
48
46
  throw new IgnorableError("No KV Namespace");
49
47
  debugCache(`Set ${key}`);
50
48
  try {
51
- await kv.put(this.getKVKey(key, isFetch), JSON.stringify({
49
+ await kv.put(this.getKVKey(key, cacheType), JSON.stringify({
52
50
  value,
53
51
  // Note: `Date.now()` returns the time of the last IO rather than the actual time.
54
52
  // See https://developers.cloudflare.com/workers/reference/security-model/
@@ -68,16 +66,18 @@ class KVIncrementalCache {
68
66
  throw new IgnorableError("No KV Namespace");
69
67
  debugCache(`Delete ${key}`);
70
68
  try {
71
- await kv.delete(this.getKVKey(key, /* isFetch= */ false));
69
+ // Only cache that gets deleted is the ISR/SSG cache.
70
+ await kv.delete(this.getKVKey(key, "cache"));
72
71
  }
73
72
  catch (e) {
74
73
  error("Failed to delete from cache", e);
75
74
  }
76
75
  }
77
- getKVKey(key, isFetch) {
76
+ getKVKey(key, cacheType) {
78
77
  return computeCacheKey(key, {
78
+ prefix: getCloudflareContext().env[PREFIX_ENV_NAME],
79
79
  buildId: process.env.NEXT_BUILD_ID,
80
- isFetch,
80
+ cacheType,
81
81
  });
82
82
  }
83
83
  }
@@ -1,14 +1,7 @@
1
- import type { CacheValue, IncrementalCache, WithLastModified } from "@opennextjs/aws/types/overrides.js";
1
+ import type { CacheEntryType, CacheValue, IncrementalCache, WithLastModified } from "@opennextjs/aws/types/overrides.js";
2
2
  export declare const NAME = "cf-r2-incremental-cache";
3
3
  export declare const BINDING_NAME = "NEXT_INC_CACHE_R2_BUCKET";
4
4
  export declare const PREFIX_ENV_NAME = "NEXT_INC_CACHE_R2_PREFIX";
5
- export declare const DEFAULT_PREFIX = "incremental-cache";
6
- export type KeyOptions = {
7
- isFetch?: boolean;
8
- directory?: string;
9
- buildId?: string;
10
- };
11
- export declare function computeCacheKey(key: string, options: KeyOptions): string;
12
5
  /**
13
6
  * An instance of the Incremental Cache that uses an R2 bucket (`NEXT_INC_CACHE_R2_BUCKET`) as it's
14
7
  * underlying data store.
@@ -18,10 +11,10 @@ export declare function computeCacheKey(key: string, options: KeyOptions): strin
18
11
  */
19
12
  declare class R2IncrementalCache implements IncrementalCache {
20
13
  readonly name = "cf-r2-incremental-cache";
21
- get<IsFetch extends boolean = false>(key: string, isFetch?: IsFetch): Promise<WithLastModified<CacheValue<IsFetch>> | null>;
22
- set<IsFetch extends boolean = false>(key: string, value: CacheValue<IsFetch>, isFetch?: IsFetch): Promise<void>;
14
+ get<CacheType extends CacheEntryType = "cache">(key: string, cacheType?: CacheType): Promise<WithLastModified<CacheValue<CacheType>> | null>;
15
+ set<CacheType extends CacheEntryType = "cache">(key: string, value: CacheValue<CacheType>, cacheType?: CacheType): Promise<void>;
23
16
  delete(key: string): Promise<void>;
24
- protected getR2Key(key: string, isFetch?: boolean): string;
17
+ protected getR2Key(key: string, cacheType?: CacheEntryType): string;
25
18
  }
26
19
  declare const _default: R2IncrementalCache;
27
20
  export default _default;
@@ -1,17 +1,10 @@
1
- import { createHash } from "node:crypto";
2
1
  import { error } from "@opennextjs/aws/adapters/logger.js";
3
2
  import { IgnorableError } from "@opennextjs/aws/utils/error.js";
4
3
  import { getCloudflareContext } from "../../cloudflare-context.js";
5
- import { debugCache, FALLBACK_BUILD_ID } from "../internal.js";
4
+ import { computeCacheKey, debugCache } from "../internal.js";
6
5
  export const NAME = "cf-r2-incremental-cache";
7
6
  export const BINDING_NAME = "NEXT_INC_CACHE_R2_BUCKET";
8
7
  export const PREFIX_ENV_NAME = "NEXT_INC_CACHE_R2_PREFIX";
9
- export const DEFAULT_PREFIX = "incremental-cache";
10
- export function computeCacheKey(key, options) {
11
- const { isFetch = false, directory = DEFAULT_PREFIX, buildId = FALLBACK_BUILD_ID } = options;
12
- const hash = createHash("sha256").update(key).digest("hex");
13
- return `${directory}/${buildId}/${hash}.${isFetch ? "fetch" : "cache"}`.replace(/\/+/g, "/");
14
- }
15
8
  /**
16
9
  * An instance of the Incremental Cache that uses an R2 bucket (`NEXT_INC_CACHE_R2_BUCKET`) as it's
17
10
  * underlying data store.
@@ -21,13 +14,13 @@ export function computeCacheKey(key, options) {
21
14
  */
22
15
  class R2IncrementalCache {
23
16
  name = NAME;
24
- async get(key, isFetch) {
17
+ async get(key, cacheType) {
25
18
  const r2 = getCloudflareContext().env[BINDING_NAME];
26
19
  if (!r2)
27
20
  throw new IgnorableError("No R2 bucket");
28
21
  debugCache(`Get ${key}`);
29
22
  try {
30
- const r2Object = await r2.get(this.getR2Key(key, isFetch));
23
+ const r2Object = await r2.get(this.getR2Key(key, cacheType));
31
24
  if (!r2Object)
32
25
  return null;
33
26
  return {
@@ -40,13 +33,13 @@ class R2IncrementalCache {
40
33
  return null;
41
34
  }
42
35
  }
43
- async set(key, value, isFetch) {
36
+ async set(key, value, cacheType) {
44
37
  const r2 = getCloudflareContext().env[BINDING_NAME];
45
38
  if (!r2)
46
39
  throw new IgnorableError("No R2 bucket");
47
40
  debugCache(`Set ${key}`);
48
41
  try {
49
- await r2.put(this.getR2Key(key, isFetch), JSON.stringify(value));
42
+ await r2.put(this.getR2Key(key, cacheType), JSON.stringify(value));
50
43
  }
51
44
  catch (e) {
52
45
  error("Failed to set to cache", e);
@@ -64,11 +57,11 @@ class R2IncrementalCache {
64
57
  error("Failed to delete from cache", e);
65
58
  }
66
59
  }
67
- getR2Key(key, isFetch) {
60
+ getR2Key(key, cacheType) {
68
61
  return computeCacheKey(key, {
69
- directory: getCloudflareContext().env[PREFIX_ENV_NAME],
62
+ prefix: getCloudflareContext().env[PREFIX_ENV_NAME],
70
63
  buildId: process.env.NEXT_BUILD_ID,
71
- isFetch,
64
+ cacheType,
72
65
  });
73
66
  }
74
67
  }
@@ -1,4 +1,4 @@
1
- import { CacheValue, IncrementalCache, WithLastModified } from "@opennextjs/aws/types/overrides.js";
1
+ import { CacheEntryType, CacheValue, IncrementalCache, WithLastModified } from "@opennextjs/aws/types/overrides.js";
2
2
  import { IncrementalCacheEntry } from "../internal.js";
3
3
  type Options = {
4
4
  /**
@@ -26,8 +26,8 @@ type Options = {
26
26
  };
27
27
  interface PutToCacheInput {
28
28
  key: string;
29
- isFetch: boolean | undefined;
30
- entry: IncrementalCacheEntry<boolean>;
29
+ cacheType?: CacheEntryType;
30
+ entry: IncrementalCacheEntry<CacheEntryType>;
31
31
  }
32
32
  /**
33
33
  * Wrapper adding a regional cache on an `IncrementalCache` implementation
@@ -38,12 +38,12 @@ declare class RegionalCache implements IncrementalCache {
38
38
  name: string;
39
39
  protected localCache: Cache | undefined;
40
40
  constructor(store: IncrementalCache, opts: Options);
41
- get<IsFetch extends boolean = false>(key: string, isFetch?: IsFetch): Promise<WithLastModified<CacheValue<IsFetch>> | null>;
42
- set<IsFetch extends boolean = false>(key: string, value: CacheValue<IsFetch>, isFetch?: IsFetch): Promise<void>;
41
+ get<CacheType extends CacheEntryType = "cache">(key: string, cacheType?: CacheType): Promise<WithLastModified<CacheValue<CacheType>> | null>;
42
+ set<CacheType extends CacheEntryType = "cache">(key: string, value: CacheValue<CacheType>, cacheType?: CacheType): Promise<void>;
43
43
  delete(key: string): Promise<void>;
44
44
  protected getCacheInstance(): Promise<Cache>;
45
- protected getCacheUrlKey(key: string, isFetch?: boolean): string;
46
- protected putToCache({ key, isFetch, entry }: PutToCacheInput): Promise<void>;
45
+ protected getCacheUrlKey(key: string, cacheType?: CacheEntryType): string;
46
+ protected putToCache({ key, cacheType, entry }: PutToCacheInput): Promise<void>;
47
47
  }
48
48
  /**
49
49
  * A regional cache will wrap an incremental cache and provide faster cache lookups for an entry
@@ -21,31 +21,31 @@ class RegionalCache {
21
21
  this.name = this.store.name;
22
22
  this.opts.shouldLazilyUpdateOnCacheHit ??= this.opts.mode === "long-lived";
23
23
  }
24
- async get(key, isFetch) {
24
+ async get(key, cacheType) {
25
25
  try {
26
26
  const cache = await this.getCacheInstance();
27
- const urlKey = this.getCacheUrlKey(key, isFetch);
27
+ const urlKey = this.getCacheUrlKey(key, cacheType);
28
28
  // Check for a cached entry as this will be faster than the store response.
29
29
  const cachedResponse = await cache.match(urlKey);
30
30
  if (cachedResponse) {
31
31
  debugCache("Get - cached response");
32
32
  // Re-fetch from the store and update the regional cache in the background
33
33
  if (this.opts.shouldLazilyUpdateOnCacheHit) {
34
- getCloudflareContext().ctx.waitUntil(this.store.get(key, isFetch).then(async (rawEntry) => {
34
+ getCloudflareContext().ctx.waitUntil(this.store.get(key, cacheType).then(async (rawEntry) => {
35
35
  const { value, lastModified } = rawEntry ?? {};
36
36
  if (value && typeof lastModified === "number") {
37
- await this.putToCache({ key, isFetch, entry: { value, lastModified } });
37
+ await this.putToCache({ key, cacheType, entry: { value, lastModified } });
38
38
  }
39
39
  }));
40
40
  }
41
41
  return cachedResponse.json();
42
42
  }
43
- const rawEntry = await this.store.get(key, isFetch);
43
+ const rawEntry = await this.store.get(key, cacheType);
44
44
  const { value, lastModified } = rawEntry ?? {};
45
45
  if (!value || typeof lastModified !== "number")
46
46
  return null;
47
47
  // Update the locale cache after retrieving from the store.
48
- getCloudflareContext().ctx.waitUntil(this.putToCache({ key, isFetch, entry: { value, lastModified } }));
48
+ getCloudflareContext().ctx.waitUntil(this.putToCache({ key, cacheType, entry: { value, lastModified } }));
49
49
  return { value, lastModified };
50
50
  }
51
51
  catch (e) {
@@ -53,12 +53,12 @@ class RegionalCache {
53
53
  return null;
54
54
  }
55
55
  }
56
- async set(key, value, isFetch) {
56
+ async set(key, value, cacheType) {
57
57
  try {
58
- await this.store.set(key, value, isFetch);
58
+ await this.store.set(key, value, cacheType);
59
59
  await this.putToCache({
60
60
  key,
61
- isFetch,
61
+ cacheType,
62
62
  entry: {
63
63
  value,
64
64
  // Note: `Date.now()` returns the time of the last IO rather than the actual time.
@@ -87,12 +87,12 @@ class RegionalCache {
87
87
  this.localCache = await caches.open("incremental-cache");
88
88
  return this.localCache;
89
89
  }
90
- getCacheUrlKey(key, isFetch) {
90
+ getCacheUrlKey(key, cacheType) {
91
91
  const buildId = process.env.NEXT_BUILD_ID ?? FALLBACK_BUILD_ID;
92
- return ("http://cache.local" + `/${buildId}/${key}`.replace(/\/+/g, "/") + `.${isFetch ? "fetch" : "cache"}`);
92
+ return "http://cache.local" + `/${buildId}/${key}`.replace(/\/+/g, "/") + `.${cacheType ?? "cache"}`;
93
93
  }
94
- async putToCache({ key, isFetch, entry }) {
95
- const urlKey = this.getCacheUrlKey(key, isFetch);
94
+ async putToCache({ key, cacheType, entry }) {
95
+ const urlKey = this.getCacheUrlKey(key, cacheType);
96
96
  const cache = await this.getCacheInstance();
97
97
  const age = this.opts.mode === "short-lived"
98
98
  ? ONE_MINUTE_IN_SECONDS
@@ -154,4 +154,7 @@ function getTagsFromCacheEntry(entry) {
154
154
  return rawTags.split(",");
155
155
  }
156
156
  }
157
+ if ("value" in entry.value) {
158
+ return entry.value.tags;
159
+ }
157
160
  }
@@ -1,4 +1,4 @@
1
- import type { CacheValue, IncrementalCache, WithLastModified } from "@opennextjs/aws/types/overrides.js";
1
+ import type { CacheEntryType, CacheValue, IncrementalCache, WithLastModified } from "@opennextjs/aws/types/overrides.js";
2
2
  export declare const CACHE_DIR = "cdn-cgi/_next_cache";
3
3
  export declare const NAME = "cf-static-assets-incremental-cache";
4
4
  /**
@@ -8,10 +8,10 @@ export declare const NAME = "cf-static-assets-incremental-cache";
8
8
  */
9
9
  declare class StaticAssetsIncrementalCache implements IncrementalCache {
10
10
  readonly name = "cf-static-assets-incremental-cache";
11
- get<IsFetch extends boolean = false>(key: string, isFetch?: IsFetch): Promise<WithLastModified<CacheValue<IsFetch>> | null>;
11
+ get<CacheType extends CacheEntryType = "cache">(key: string, cacheType?: CacheType): Promise<WithLastModified<CacheValue<CacheType>> | null>;
12
12
  set(): Promise<void>;
13
13
  delete(): Promise<void>;
14
- protected getAssetUrl(key: string, isFetch?: boolean): string;
14
+ protected getAssetUrl(key: string, cacheType?: CacheEntryType): string;
15
15
  }
16
16
  declare const _default: StaticAssetsIncrementalCache;
17
17
  export default _default;
@@ -12,13 +12,13 @@ export const NAME = "cf-static-assets-incremental-cache";
12
12
  */
13
13
  class StaticAssetsIncrementalCache {
14
14
  name = NAME;
15
- async get(key, isFetch) {
15
+ async get(key, cacheType) {
16
16
  const assets = getCloudflareContext().env.ASSETS;
17
17
  if (!assets)
18
18
  throw new IgnorableError("No Static Assets");
19
19
  debugCache(`Get ${key}`);
20
20
  try {
21
- const response = await assets.fetch(this.getAssetUrl(key, isFetch));
21
+ const response = await assets.fetch(this.getAssetUrl(key, cacheType));
22
22
  if (!response.ok)
23
23
  return null;
24
24
  return {
@@ -37,9 +37,14 @@ class StaticAssetsIncrementalCache {
37
37
  async delete() {
38
38
  error("Failed to delete from read-only cache");
39
39
  }
40
- getAssetUrl(key, isFetch) {
40
+ getAssetUrl(key, cacheType) {
41
+ if (cacheType === "composable") {
42
+ throw new Error("Composable cache is not supported in static assets incremental cache");
43
+ }
41
44
  const buildId = process.env.NEXT_BUILD_ID ?? FALLBACK_BUILD_ID;
42
- const name = (isFetch ? `${CACHE_DIR}/__fetch/${buildId}/${key}` : `${CACHE_DIR}/${buildId}/${key}.cache`).replace(/\/+/g, "/");
45
+ const name = (cacheType === "fetch"
46
+ ? `${CACHE_DIR}/__fetch/${buildId}/${key}`
47
+ : `${CACHE_DIR}/${buildId}/${key}.cache`).replace(/\/+/g, "/");
43
48
  return `http://assets.local/${name}`;
44
49
  }
45
50
  }
@@ -1,7 +1,14 @@
1
- import { CacheValue } from "@opennextjs/aws/types/overrides.js";
2
- export type IncrementalCacheEntry<IsFetch extends boolean> = {
3
- value: CacheValue<IsFetch>;
1
+ import type { CacheEntryType, CacheValue } from "@opennextjs/aws/types/overrides.js";
2
+ export type IncrementalCacheEntry<CacheType extends CacheEntryType> = {
3
+ value: CacheValue<CacheType>;
4
4
  lastModified: number;
5
5
  };
6
6
  export declare const debugCache: (name: string, ...args: unknown[]) => void;
7
7
  export declare const FALLBACK_BUILD_ID = "no-build-id";
8
+ export declare const DEFAULT_PREFIX = "incremental-cache";
9
+ export type KeyOptions = {
10
+ cacheType?: CacheEntryType;
11
+ prefix: string | undefined;
12
+ buildId: string | undefined;
13
+ };
14
+ export declare function computeCacheKey(key: string, options: KeyOptions): string;
@@ -1,6 +1,13 @@
1
+ import { createHash } from "node:crypto";
1
2
  export const debugCache = (name, ...args) => {
2
3
  if (process.env.NEXT_PRIVATE_DEBUG_CACHE) {
3
4
  console.log(`[${name}] `, ...args);
4
5
  }
5
6
  };
6
7
  export const FALLBACK_BUILD_ID = "no-build-id";
8
+ export const DEFAULT_PREFIX = "incremental-cache";
9
+ export function computeCacheKey(key, options) {
10
+ const { cacheType = "cache", prefix = DEFAULT_PREFIX, buildId = FALLBACK_BUILD_ID } = options;
11
+ const hash = createHash("sha256").update(key).digest("hex");
12
+ return `${prefix}/${buildId}/${hash}.${cacheType}`.replace(/\/+/g, "/");
13
+ }
@@ -4,6 +4,7 @@ export declare const BINDING_NAME = "NEXT_TAG_CACHE_D1";
4
4
  export declare class D1NextModeTagCache implements NextModeTagCache {
5
5
  readonly mode: "nextMode";
6
6
  readonly name = "d1-next-mode-tag-cache";
7
+ getLastRevalidated(tags: string[]): Promise<number>;
7
8
  hasBeenRevalidated(tags: string[], lastModified?: number): Promise<boolean>;
8
9
  writeTags(tags: string[]): Promise<void>;
9
10
  private getConfig;
@@ -6,6 +6,26 @@ export const BINDING_NAME = "NEXT_TAG_CACHE_D1";
6
6
  export class D1NextModeTagCache {
7
7
  mode = "nextMode";
8
8
  name = NAME;
9
+ async getLastRevalidated(tags) {
10
+ const { isDisabled, db } = this.getConfig();
11
+ if (isDisabled)
12
+ return 0;
13
+ try {
14
+ const result = await db
15
+ .prepare(`SELECT MAX(revalidatedAt) AS time FROM revalidations WHERE tag IN (${tags.map(() => "?").join(", ")})`)
16
+ .run();
17
+ if (result.results.length === 0)
18
+ return 0;
19
+ // We only care about the most recent revalidation
20
+ return (result.results[0]?.time ?? 0);
21
+ }
22
+ catch (e) {
23
+ error(e);
24
+ // By default we don't want to crash here, so we return false
25
+ // We still log the error though so we can debug it
26
+ return 0;
27
+ }
28
+ }
9
29
  async hasBeenRevalidated(tags, lastModified) {
10
30
  const { isDisabled, db } = this.getConfig();
11
31
  if (isDisabled)
@@ -71,6 +71,11 @@ export declare class DOId {
71
71
  private generateRandomNumberBetween;
72
72
  get key(): string;
73
73
  }
74
+ interface CacheTagKeyOptions {
75
+ doId: DOId;
76
+ tags: string[];
77
+ type: "boolean" | "number";
78
+ }
74
79
  declare class ShardedDOTagCache implements NextModeTagCache {
75
80
  private opts;
76
81
  readonly mode: "nextMode";
@@ -102,6 +107,7 @@ declare class ShardedDOTagCache implements NextModeTagCache {
102
107
  tags: string[];
103
108
  }[];
104
109
  private getConfig;
110
+ getLastRevalidated(tags: string[]): Promise<number>;
105
111
  /**
106
112
  * This function checks if the tags have been revalidated
107
113
  * It is never supposed to throw and in case of error, it will return false
@@ -119,10 +125,10 @@ declare class ShardedDOTagCache implements NextModeTagCache {
119
125
  writeTags(tags: string[]): Promise<void>;
120
126
  performWriteTagsWithRetry(doId: DOId, tags: string[], lastModified: number, retryNumber?: number): Promise<void>;
121
127
  getCacheInstance(): Promise<Cache | undefined>;
122
- getCacheUrlKey(doId: DOId, tags: string[]): string;
123
- getFromRegionalCache(doId: DOId, tags: string[]): Promise<Response | undefined>;
124
- putToRegionalCache(doId: DOId, tags: string[], hasBeenRevalidated: boolean): Promise<void>;
125
- deleteRegionalCache(doId: DOId, tags: string[]): Promise<void>;
128
+ getCacheUrlKey(opts: CacheTagKeyOptions): string;
129
+ getFromRegionalCache(opts: CacheTagKeyOptions): Promise<Response | undefined>;
130
+ putToRegionalCache(optsKey: CacheTagKeyOptions, value: number | boolean): Promise<void>;
131
+ deleteRegionalCache(optsKey: CacheTagKeyOptions): Promise<void>;
126
132
  }
127
133
  declare const _default: (opts?: ShardedDOTagCacheOptions) => ShardedDOTagCache;
128
134
  export default _default;