@opennextjs/cloudflare 1.0.0-beta.0 → 1.0.0-beta.2

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,9 +1,8 @@
1
- import type { CacheValue, IncrementalCache, WithLastModified } from "@opennextjs/aws/types/overrides";
2
- export declare const CACHE_ASSET_DIR = "cdn-cgi/_next_cache";
3
- export declare const STATUS_DELETED = 1;
1
+ import type { CacheValue, IncrementalCache, WithLastModified } from "@opennextjs/aws/types/overrides.js";
4
2
  export declare const NAME = "cf-kv-incremental-cache";
3
+ export declare const BINDING_NAME = "NEXT_INC_CACHE_KV";
5
4
  /**
6
- * Open Next cache based on cloudflare KV and Assets.
5
+ * Open Next cache based on Cloudflare KV.
7
6
  *
8
7
  * Note: The class is instantiated outside of the request context.
9
8
  * The cloudflare context and process.env are not initialized yet
@@ -15,9 +14,6 @@ declare class KVIncrementalCache implements IncrementalCache {
15
14
  set<IsFetch extends boolean = false>(key: string, value: CacheValue<IsFetch>, isFetch?: IsFetch): Promise<void>;
16
15
  delete(key: string): Promise<void>;
17
16
  protected getKVKey(key: string, isFetch?: boolean): string;
18
- protected getAssetUrl(key: string, isFetch?: boolean): string;
19
- protected debug(...args: unknown[]): void;
20
- protected getBuildId(): string;
21
17
  }
22
18
  declare const _default: KVIncrementalCache;
23
19
  export default _default;
@@ -1,10 +1,11 @@
1
- import { IgnorableError, RecoverableError } from "@opennextjs/aws/utils/error.js";
1
+ import { error } from "@opennextjs/aws/adapters/logger.js";
2
+ import { IgnorableError } from "@opennextjs/aws/utils/error.js";
2
3
  import { getCloudflareContext } from "../../cloudflare-context.js";
3
- export const CACHE_ASSET_DIR = "cdn-cgi/_next_cache";
4
- export const STATUS_DELETED = 1;
4
+ import { debugCache, FALLBACK_BUILD_ID } from "../internal.js";
5
5
  export const NAME = "cf-kv-incremental-cache";
6
+ export const BINDING_NAME = "NEXT_INC_CACHE_KV";
6
7
  /**
7
- * Open Next cache based on cloudflare KV and Assets.
8
+ * Open Next cache based on Cloudflare KV.
8
9
  *
9
10
  * Note: The class is instantiated outside of the request context.
10
11
  * The cloudflare context and process.env are not initialized yet
@@ -13,112 +14,64 @@ export const NAME = "cf-kv-incremental-cache";
13
14
  class KVIncrementalCache {
14
15
  name = NAME;
15
16
  async get(key, isFetch) {
16
- const cfEnv = getCloudflareContext().env;
17
- const kv = cfEnv.NEXT_INC_CACHE_KV;
18
- const assets = cfEnv.ASSETS;
19
- if (!(kv || assets)) {
20
- throw new IgnorableError(`No KVNamespace nor Fetcher`);
21
- }
22
- this.debug(`Get ${key}`);
17
+ const kv = getCloudflareContext().env[BINDING_NAME];
18
+ if (!kv)
19
+ throw new IgnorableError("No KV Namespace");
20
+ debugCache(`Get ${key}`);
23
21
  try {
24
- let entry = null;
25
- if (kv) {
26
- this.debug(`- From KV`);
27
- const kvKey = this.getKVKey(key, isFetch);
28
- entry = await kv.get(kvKey, "json");
29
- if (entry?.status === STATUS_DELETED) {
30
- return null;
31
- }
32
- }
33
- if (!entry && assets) {
34
- this.debug(`- From Assets`);
35
- const url = this.getAssetUrl(key, isFetch);
36
- const response = await assets.fetch(url);
37
- if (response.ok) {
38
- // TODO: consider populating KV with the asset value if faster.
39
- // This could be optional as KV writes are $$.
40
- // See https://github.com/opennextjs/opennextjs-cloudflare/pull/194#discussion_r1893166026
41
- entry = {
42
- value: await response.json(),
43
- // __BUILD_TIMESTAMP_MS__ is injected by ESBuild.
44
- lastModified: globalThis.__BUILD_TIMESTAMP_MS__,
45
- };
46
- }
47
- if (!kv) {
48
- // The cache can not be updated when there is no KV
49
- // As we don't want to keep serving stale data for ever,
50
- // we pretend the entry is not in cache
51
- if (entry?.value &&
52
- "kind" in entry.value &&
53
- entry.value.kind === "FETCH" &&
54
- entry.value.data?.headers?.expires) {
55
- const expiresTime = new Date(entry.value.data.headers.expires).getTime();
56
- if (!isNaN(expiresTime) && expiresTime <= Date.now()) {
57
- this.debug(`found expired entry (expire time: ${entry.value.data.headers.expires})`);
58
- return null;
59
- }
60
- }
61
- }
22
+ const entry = await kv.get(this.getKVKey(key, isFetch), "json");
23
+ if (!entry)
24
+ return null;
25
+ if ("lastModified" in entry) {
26
+ return entry;
62
27
  }
63
- this.debug(entry ? `-> hit` : `-> miss`);
64
- return { value: entry?.value, lastModified: entry?.lastModified };
28
+ // if there is no lastModified property, the file was stored during build-time cache population.
29
+ return {
30
+ value: entry,
31
+ // __BUILD_TIMESTAMP_MS__ is injected by ESBuild.
32
+ lastModified: globalThis.__BUILD_TIMESTAMP_MS__,
33
+ };
65
34
  }
66
- catch {
67
- throw new RecoverableError(`Failed to get cache [${key}]`);
35
+ catch (e) {
36
+ error("Failed to get from cache", e);
37
+ return null;
68
38
  }
69
39
  }
70
40
  async set(key, value, isFetch) {
71
- const kv = getCloudflareContext().env.NEXT_INC_CACHE_KV;
72
- if (!kv) {
73
- throw new IgnorableError(`No KVNamespace`);
74
- }
75
- this.debug(`Set ${key}`);
41
+ const kv = getCloudflareContext().env[BINDING_NAME];
42
+ if (!kv)
43
+ throw new IgnorableError("No KV Namespace");
44
+ debugCache(`Set ${key}`);
76
45
  try {
77
- const kvKey = this.getKVKey(key, isFetch);
78
- // Note: We can not set a TTL as we might fallback to assets,
79
- // still removing old data (old BUILD_ID) could help avoiding
80
- // the cache growing too big.
81
- await kv.put(kvKey, JSON.stringify({
46
+ await kv.put(this.getKVKey(key, isFetch), JSON.stringify({
82
47
  value,
83
48
  // Note: `Date.now()` returns the time of the last IO rather than the actual time.
84
49
  // See https://developers.cloudflare.com/workers/reference/security-model/
85
50
  lastModified: Date.now(),
86
- }));
51
+ })
52
+ // TODO: Figure out how to best leverage KV's TTL.
53
+ // NOTE: Ideally, the cache should operate in an SWR-like manner.
54
+ );
87
55
  }
88
- catch {
89
- throw new RecoverableError(`Failed to set cache [${key}]`);
56
+ catch (e) {
57
+ error("Failed to set to cache", e);
90
58
  }
91
59
  }
92
60
  async delete(key) {
93
- const kv = getCloudflareContext().env.NEXT_INC_CACHE_KV;
94
- if (!kv) {
95
- throw new IgnorableError(`No KVNamespace`);
96
- }
97
- this.debug(`Delete ${key}`);
61
+ const kv = getCloudflareContext().env[BINDING_NAME];
62
+ if (!kv)
63
+ throw new IgnorableError("No KV Namespace");
64
+ debugCache(`Delete ${key}`);
98
65
  try {
99
- const kvKey = this.getKVKey(key, /* isFetch= */ false);
100
- // Do not delete the key as we would then fallback to the assets.
101
- await kv.put(kvKey, JSON.stringify({ status: STATUS_DELETED }));
66
+ await kv.delete(this.getKVKey(key, /* isFetch= */ false));
102
67
  }
103
- catch {
104
- throw new RecoverableError(`Failed to delete cache [${key}]`);
68
+ catch (e) {
69
+ error("Failed to delete from cache", e);
105
70
  }
106
71
  }
107
72
  getKVKey(key, isFetch) {
108
- return `${this.getBuildId()}/${key}.${isFetch ? "fetch" : "cache"}`;
109
- }
110
- getAssetUrl(key, isFetch) {
111
- return isFetch
112
- ? `http://assets.local/${CACHE_ASSET_DIR}/__fetch/${this.getBuildId()}/${key}`
113
- : `http://assets.local/${CACHE_ASSET_DIR}/${this.getBuildId()}/${key}.cache`;
114
- }
115
- debug(...args) {
116
- if (process.env.NEXT_PRIVATE_DEBUG_CACHE) {
117
- console.log(`[Cache ${this.name}] `, ...args);
118
- }
119
- }
120
- getBuildId() {
121
- return process.env.NEXT_BUILD_ID ?? "no-build-id";
73
+ const buildId = process.env.NEXT_BUILD_ID ?? FALLBACK_BUILD_ID;
74
+ return `${buildId}/${key}.${isFetch ? "fetch" : "cache"}`.replace(/\/+/g, "/");
122
75
  }
123
76
  }
124
77
  export default new KVIncrementalCache();
@@ -1,5 +1,8 @@
1
1
  import type { CacheValue, IncrementalCache, WithLastModified } from "@opennextjs/aws/types/overrides.js";
2
2
  export declare const NAME = "cf-r2-incremental-cache";
3
+ export declare const BINDING_NAME = "NEXT_INC_CACHE_R2_BUCKET";
4
+ export declare const PREFIX_ENV_NAME = "NEXT_INC_CACHE_R2_PREFIX";
5
+ export declare const DEFAULT_PREFIX = "incremental-cache";
3
6
  /**
4
7
  * An instance of the Incremental Cache that uses an R2 bucket (`NEXT_INC_CACHE_R2_BUCKET`) as it's
5
8
  * underlying data store.
@@ -1,7 +1,11 @@
1
- import { debug, error } from "@opennextjs/aws/adapters/logger.js";
1
+ import { error } from "@opennextjs/aws/adapters/logger.js";
2
2
  import { IgnorableError } from "@opennextjs/aws/utils/error.js";
3
3
  import { getCloudflareContext } from "../../cloudflare-context.js";
4
+ import { debugCache, FALLBACK_BUILD_ID } from "../internal.js";
4
5
  export const NAME = "cf-r2-incremental-cache";
6
+ export const BINDING_NAME = "NEXT_INC_CACHE_R2_BUCKET";
7
+ export const PREFIX_ENV_NAME = "NEXT_INC_CACHE_R2_PREFIX";
8
+ export const DEFAULT_PREFIX = "incremental-cache";
5
9
  /**
6
10
  * An instance of the Incremental Cache that uses an R2 bucket (`NEXT_INC_CACHE_R2_BUCKET`) as it's
7
11
  * underlying data store.
@@ -12,10 +16,10 @@ export const NAME = "cf-r2-incremental-cache";
12
16
  class R2IncrementalCache {
13
17
  name = NAME;
14
18
  async get(key, isFetch) {
15
- const r2 = getCloudflareContext().env.NEXT_INC_CACHE_R2_BUCKET;
19
+ const r2 = getCloudflareContext().env[BINDING_NAME];
16
20
  if (!r2)
17
21
  throw new IgnorableError("No R2 bucket");
18
- debug(`Get ${key}`);
22
+ debugCache(`Get ${key}`);
19
23
  try {
20
24
  const r2Object = await r2.get(this.getR2Key(key, isFetch));
21
25
  if (!r2Object)
@@ -31,10 +35,10 @@ class R2IncrementalCache {
31
35
  }
32
36
  }
33
37
  async set(key, value, isFetch) {
34
- const r2 = getCloudflareContext().env.NEXT_INC_CACHE_R2_BUCKET;
38
+ const r2 = getCloudflareContext().env[BINDING_NAME];
35
39
  if (!r2)
36
40
  throw new IgnorableError("No R2 bucket");
37
- debug(`Set ${key}`);
41
+ debugCache(`Set ${key}`);
38
42
  try {
39
43
  await r2.put(this.getR2Key(key, isFetch), JSON.stringify(value));
40
44
  }
@@ -43,10 +47,10 @@ class R2IncrementalCache {
43
47
  }
44
48
  }
45
49
  async delete(key) {
46
- const r2 = getCloudflareContext().env.NEXT_INC_CACHE_R2_BUCKET;
50
+ const r2 = getCloudflareContext().env[BINDING_NAME];
47
51
  if (!r2)
48
52
  throw new IgnorableError("No R2 bucket");
49
- debug(`Delete ${key}`);
53
+ debugCache(`Delete ${key}`);
50
54
  try {
51
55
  await r2.delete(this.getR2Key(key));
52
56
  }
@@ -55,8 +59,8 @@ class R2IncrementalCache {
55
59
  }
56
60
  }
57
61
  getR2Key(key, isFetch) {
58
- const directory = getCloudflareContext().env.NEXT_INC_CACHE_R2_PREFIX ?? "incremental-cache";
59
- return `${directory}/${process.env.NEXT_BUILD_ID ?? "no-build-id"}/${key}.${isFetch ? "fetch" : "cache"}`;
62
+ const directory = getCloudflareContext().env[PREFIX_ENV_NAME] ?? DEFAULT_PREFIX;
63
+ return `${directory}/${process.env.NEXT_BUILD_ID ?? FALLBACK_BUILD_ID}/${key}.${isFetch ? "fetch" : "cache"}`.replace(/\/+/g, "/");
60
64
  }
61
65
  }
62
66
  export default new R2IncrementalCache();
@@ -1,5 +1,5 @@
1
1
  import { CacheValue, IncrementalCache, WithLastModified } from "@opennextjs/aws/types/overrides.js";
2
- import { IncrementalCacheEntry } from "./internal.js";
2
+ import { IncrementalCacheEntry } from "../internal.js";
3
3
  type Options = {
4
4
  /**
5
5
  * The mode to use for the regional cache.
@@ -1,5 +1,6 @@
1
- import { debug, error } from "@opennextjs/aws/adapters/logger.js";
1
+ import { error } from "@opennextjs/aws/adapters/logger.js";
2
2
  import { getCloudflareContext } from "../../cloudflare-context.js";
3
+ import { debugCache, FALLBACK_BUILD_ID } from "../internal.js";
3
4
  import { NAME as KV_CACHE_NAME } from "./kv-incremental-cache.js";
4
5
  const ONE_MINUTE_IN_SECONDS = 60;
5
6
  const THIRTY_MINUTES_IN_SECONDS = ONE_MINUTE_IN_SECONDS * 30;
@@ -27,7 +28,7 @@ class RegionalCache {
27
28
  // Check for a cached entry as this will be faster than the store response.
28
29
  const cachedResponse = await cache.match(localCacheKey);
29
30
  if (cachedResponse) {
30
- debug("Get - cached response");
31
+ debugCache("Get - cached response");
31
32
  // Re-fetch from the store and update the regional cache in the background
32
33
  if (this.opts.shouldLazilyUpdateOnCacheHit) {
33
34
  getCloudflareContext().ctx.waitUntil(this.store.get(key, isFetch).then(async (rawEntry) => {
@@ -83,7 +84,7 @@ class RegionalCache {
83
84
  return this.localCache;
84
85
  }
85
86
  getCacheKey(key, isFetch) {
86
- return new Request(new URL(`${process.env.NEXT_BUILD_ID ?? "no-build-id"}/${key}.${isFetch ? "fetch" : "cache"}`, "http://cache.local"));
87
+ return new Request(new URL(`${process.env.NEXT_BUILD_ID ?? FALLBACK_BUILD_ID}/${key}.${isFetch ? "fetch" : "cache"}`, "http://cache.local"));
87
88
  }
88
89
  async putToCache(key, entry) {
89
90
  const cache = await this.getCacheInstance();
@@ -0,0 +1,17 @@
1
+ import type { CacheValue, IncrementalCache, WithLastModified } from "@opennextjs/aws/types/overrides.js";
2
+ export declare const CACHE_DIR = "cdn-cgi/_next_cache";
3
+ export declare const NAME = "cf-static-assets-incremental-cache";
4
+ /**
5
+ * This cache uses Workers static assets.
6
+ *
7
+ * It should only be used for applications that do NOT want revalidation and ONLY want to serve prerendered data.
8
+ */
9
+ declare class StaticAssetsIncrementalCache implements IncrementalCache {
10
+ readonly name = "cf-static-assets-incremental-cache";
11
+ get<IsFetch extends boolean = false>(key: string, isFetch?: IsFetch): Promise<WithLastModified<CacheValue<IsFetch>> | null>;
12
+ set(): Promise<void>;
13
+ delete(): Promise<void>;
14
+ protected getAssetUrl(key: string, isFetch?: boolean): string;
15
+ }
16
+ declare const _default: StaticAssetsIncrementalCache;
17
+ export default _default;
@@ -0,0 +1,47 @@
1
+ import { error } from "@opennextjs/aws/adapters/logger.js";
2
+ import { IgnorableError } from "@opennextjs/aws/utils/error.js";
3
+ import { getCloudflareContext } from "../../cloudflare-context.js";
4
+ import { debugCache, FALLBACK_BUILD_ID } from "../internal.js";
5
+ // Assets inside `cdn-cgi/...` are only accessible by the worker.
6
+ export const CACHE_DIR = "cdn-cgi/_next_cache";
7
+ export const NAME = "cf-static-assets-incremental-cache";
8
+ /**
9
+ * This cache uses Workers static assets.
10
+ *
11
+ * It should only be used for applications that do NOT want revalidation and ONLY want to serve prerendered data.
12
+ */
13
+ class StaticAssetsIncrementalCache {
14
+ name = NAME;
15
+ async get(key, isFetch) {
16
+ const assets = getCloudflareContext().env.ASSETS;
17
+ if (!assets)
18
+ throw new IgnorableError("No Static Assets");
19
+ debugCache(`Get ${key}`);
20
+ try {
21
+ const response = await assets.fetch(this.getAssetUrl(key, isFetch));
22
+ if (!response.ok)
23
+ return null;
24
+ return {
25
+ value: await response.json(),
26
+ // __BUILD_TIMESTAMP_MS__ is injected by ESBuild.
27
+ lastModified: globalThis.__BUILD_TIMESTAMP_MS__,
28
+ };
29
+ }
30
+ catch (e) {
31
+ error("Failed to get from cache", e);
32
+ return null;
33
+ }
34
+ }
35
+ async set() {
36
+ error("Failed to set to read-only cache");
37
+ }
38
+ async delete() {
39
+ error("Failed to delete from read-only cache");
40
+ }
41
+ getAssetUrl(key, isFetch) {
42
+ const buildId = process.env.NEXT_BUILD_ID ?? FALLBACK_BUILD_ID;
43
+ const name = (isFetch ? `${CACHE_DIR}/__fetch/${buildId}/${key}` : `${CACHE_DIR}/${buildId}/${key}.cache`).replace(/\/+/g, "/");
44
+ return `http://assets.local/${name}`;
45
+ }
46
+ }
47
+ export default new StaticAssetsIncrementalCache();
@@ -3,3 +3,5 @@ export type IncrementalCacheEntry<IsFetch extends boolean> = {
3
3
  value: CacheValue<IsFetch>;
4
4
  lastModified: number;
5
5
  };
6
+ export declare const debugCache: (name: string, ...args: unknown[]) => void;
7
+ export declare const FALLBACK_BUILD_ID = "no-build-id";
@@ -0,0 +1,6 @@
1
+ export const debugCache = (name, ...args) => {
2
+ if (process.env.NEXT_PRIVATE_DEBUG_CACHE) {
3
+ console.log(`[${name}] `, ...args);
4
+ }
5
+ };
6
+ export const FALLBACK_BUILD_ID = "no-build-id";
@@ -1,6 +1,7 @@
1
- import { debug, error } from "@opennextjs/aws/adapters/logger.js";
1
+ import { error } from "@opennextjs/aws/adapters/logger.js";
2
2
  import { IgnorableError } from "@opennextjs/aws/utils/error.js";
3
3
  import { getCloudflareContext } from "../../cloudflare-context";
4
+ import { debugCache } from "../internal";
4
5
  export const DEFAULT_REVALIDATION_TIMEOUT_MS = 10_000;
5
6
  /**
6
7
  * The Memory Queue offers basic ISR revalidation by directly requesting a revalidation of a route.
@@ -41,7 +42,7 @@ export class MemoryQueue {
41
42
  if (response.status !== 200 || response.headers.get("x-nextjs-cache") !== "REVALIDATED") {
42
43
  error(`Revalidation failed for ${url} with status ${response.status}`);
43
44
  }
44
- debug(`Revalidation successful for ${url}`);
45
+ debugCache(`Revalidation successful for ${url}`);
45
46
  }
46
47
  catch (e) {
47
48
  error(e);
@@ -1,5 +1,6 @@
1
1
  import type { NextModeTagCache } from "@opennextjs/aws/types/overrides.js";
2
2
  export declare const NAME = "d1-next-mode-tag-cache";
3
+ export declare const BINDING_NAME = "NEXT_TAG_CACHE_D1";
3
4
  export declare class D1NextModeTagCache implements NextModeTagCache {
4
5
  readonly mode: "nextMode";
5
6
  readonly name = "d1-next-mode-tag-cache";
@@ -1,7 +1,9 @@
1
- import { debug, error } from "@opennextjs/aws/adapters/logger.js";
1
+ import { error } from "@opennextjs/aws/adapters/logger.js";
2
2
  import { RecoverableError } from "@opennextjs/aws/utils/error.js";
3
3
  import { getCloudflareContext } from "../../cloudflare-context.js";
4
+ import { debugCache, FALLBACK_BUILD_ID } from "../internal.js";
4
5
  export const NAME = "d1-next-mode-tag-cache";
6
+ export const BINDING_NAME = "NEXT_TAG_CACHE_D1";
5
7
  export class D1NextModeTagCache {
6
8
  mode = "nextMode";
7
9
  name = NAME;
@@ -34,10 +36,9 @@ export class D1NextModeTagCache {
34
36
  throw new RecoverableError(`D1 insert failed for ${tags}`);
35
37
  }
36
38
  getConfig() {
37
- const cfEnv = getCloudflareContext().env;
38
- const db = cfEnv.NEXT_TAG_CACHE_D1;
39
+ const db = getCloudflareContext().env[BINDING_NAME];
39
40
  if (!db)
40
- debug("No D1 database found");
41
+ debugCache("No D1 database found");
41
42
  const isDisabled = !!globalThis.openNextConfig
42
43
  .dangerous?.disableTagCache;
43
44
  return !db || isDisabled
@@ -54,7 +55,7 @@ export class D1NextModeTagCache {
54
55
  return `${this.getBuildId()}/${key}`.replaceAll("//", "/");
55
56
  }
56
57
  getBuildId() {
57
- return process.env.NEXT_BUILD_ID ?? "no-build-id";
58
+ return process.env.NEXT_BUILD_ID ?? FALLBACK_BUILD_ID;
58
59
  }
59
60
  }
60
61
  export default new D1NextModeTagCache();
@@ -1,7 +1,8 @@
1
- import { debug, error } from "@opennextjs/aws/adapters/logger.js";
1
+ import { error } from "@opennextjs/aws/adapters/logger.js";
2
2
  import { generateShardId } from "@opennextjs/aws/core/routing/queue.js";
3
3
  import { IgnorableError } from "@opennextjs/aws/utils/error.js";
4
4
  import { getCloudflareContext } from "../../cloudflare-context";
5
+ import { debugCache } from "../internal";
5
6
  export const DEFAULT_WRITE_RETRIES = 3;
6
7
  export const DEFAULT_NUM_SHARDS = 4;
7
8
  export const NAME = "do-sharded-tag-cache";
@@ -102,7 +103,7 @@ class ShardedDOTagCache {
102
103
  const cfEnv = getCloudflareContext().env;
103
104
  const db = cfEnv.NEXT_TAG_CACHE_DO_SHARDED;
104
105
  if (!db)
105
- debug("No Durable object found");
106
+ debugCache("No Durable object found");
106
107
  const isDisabled = !!globalThis.openNextConfig
107
108
  .dangerous?.disableTagCache;
108
109
  return !db || isDisabled
@@ -234,7 +235,7 @@ class ShardedDOTagCache {
234
235
  await cache.delete(key);
235
236
  }
236
237
  catch (e) {
237
- debug("Error while deleting from regional cache", e);
238
+ debugCache("Error while deleting from regional cache", e);
238
239
  }
239
240
  }
240
241
  }
@@ -9,7 +9,6 @@ import { bundleServer } from "./bundle-server.js";
9
9
  import { compileCacheAssetsManifestSqlFile } from "./open-next/compile-cache-assets-manifest.js";
10
10
  import { compileEnvFiles } from "./open-next/compile-env-files.js";
11
11
  import { compileDurableObjects } from "./open-next/compileDurableObjects.js";
12
- import { copyCacheAssets } from "./open-next/copyCacheAssets.js";
13
12
  import { createServerBundle } from "./open-next/createServerBundle.js";
14
13
  import { createWranglerConfigIfNotExistent } from "./utils/index.js";
15
14
  import { getVersion } from "./utils/version.js";
@@ -55,7 +54,6 @@ export async function build(options, config, projectOpts) {
55
54
  createStaticAssets(options);
56
55
  if (config.dangerous?.disableIncrementalCache !== true) {
57
56
  const { useTagCache, metaFiles } = createCacheAssets(options);
58
- copyCacheAssets(options);
59
57
  if (useTagCache) {
60
58
  compileCacheAssetsManifestSqlFile(options, metaFiles);
61
59
  }
@@ -1,9 +1,13 @@
1
- import { existsSync } from "node:fs";
1
+ import { cpSync, existsSync } from "node:fs";
2
2
  import path from "node:path";
3
3
  import logger from "@opennextjs/aws/logger.js";
4
4
  import { globSync } from "glob";
5
- import { NAME as R2_CACHE_NAME } from "../../api/overrides/incremental-cache/r2-incremental-cache.js";
6
- import { NAME as D1_TAG_NAME } from "../../api/overrides/tag-cache/d1-next-tag-cache.js";
5
+ import { tqdm } from "ts-tqdm";
6
+ import { unstable_readConfig } from "wrangler";
7
+ import { BINDING_NAME as KV_CACHE_BINDING_NAME, NAME as KV_CACHE_NAME, } from "../../api/overrides/incremental-cache/kv-incremental-cache.js";
8
+ import { BINDING_NAME as R2_CACHE_BINDING_NAME, DEFAULT_PREFIX as R2_CACHE_DEFAULT_PREFIX, NAME as R2_CACHE_NAME, PREFIX_ENV_NAME as R2_CACHE_PREFIX_ENV_NAME, } from "../../api/overrides/incremental-cache/r2-incremental-cache.js";
9
+ import { CACHE_DIR as STATIC_ASSETS_CACHE_DIR, NAME as STATIC_ASSETS_CACHE_NAME, } from "../../api/overrides/incremental-cache/static-assets-incremental-cache.js";
10
+ import { BINDING_NAME as D1_TAG_BINDING_NAME, NAME as D1_TAG_NAME, } from "../../api/overrides/tag-cache/d1-next-tag-cache.js";
7
11
  import { runWrangler } from "../utils/run-wrangler.js";
8
12
  async function resolveCacheName(value) {
9
13
  return typeof value === "function" ? (await value()).name : value;
@@ -24,6 +28,64 @@ function getCacheAssetPaths(opts) {
24
28
  };
25
29
  });
26
30
  }
31
+ function populateR2IncrementalCache(options, populateCacheOptions) {
32
+ logger.info("\nPopulating R2 incremental cache...");
33
+ const config = unstable_readConfig({ env: populateCacheOptions.environment });
34
+ const binding = config.r2_buckets.find(({ binding }) => binding === R2_CACHE_BINDING_NAME);
35
+ if (!binding) {
36
+ throw new Error(`No R2 binding ${JSON.stringify(R2_CACHE_BINDING_NAME)} found!`);
37
+ }
38
+ const bucket = binding.bucket_name;
39
+ if (!bucket) {
40
+ throw new Error(`R2 binding ${JSON.stringify(R2_CACHE_BINDING_NAME)} should have a 'bucket_name'`);
41
+ }
42
+ const assets = getCacheAssetPaths(options);
43
+ for (const { fsPath, destPath } of tqdm(assets)) {
44
+ const fullDestPath = path.join(bucket, process.env[R2_CACHE_PREFIX_ENV_NAME] ?? R2_CACHE_DEFAULT_PREFIX, destPath);
45
+ runWrangler(options, ["r2 object put", JSON.stringify(fullDestPath), `--file ${JSON.stringify(fsPath)}`],
46
+ // NOTE: R2 does not support the environment flag and results in the following error:
47
+ // Incorrect type for the 'cacheExpiry' field on 'HttpMetadata': the provided value is not of type 'date'.
48
+ { target: populateCacheOptions.target, excludeRemoteFlag: true, logging: "error" });
49
+ }
50
+ logger.info(`Successfully populated cache with ${assets.length} assets`);
51
+ }
52
+ function populateKVIncrementalCache(options, populateCacheOptions) {
53
+ logger.info("\nPopulating KV incremental cache...");
54
+ const config = unstable_readConfig({ env: populateCacheOptions.environment });
55
+ const binding = config.kv_namespaces.find(({ binding }) => binding === KV_CACHE_BINDING_NAME);
56
+ if (!binding) {
57
+ throw new Error(`No KV binding ${JSON.stringify(KV_CACHE_BINDING_NAME)} found!`);
58
+ }
59
+ const assets = getCacheAssetPaths(options);
60
+ for (const { fsPath, destPath } of tqdm(assets)) {
61
+ runWrangler(options, [
62
+ "kv key put",
63
+ JSON.stringify(destPath),
64
+ `--binding ${JSON.stringify(KV_CACHE_BINDING_NAME)}`,
65
+ `--path ${JSON.stringify(fsPath)}`,
66
+ ], { ...populateCacheOptions, logging: "error" });
67
+ }
68
+ logger.info(`Successfully populated cache with ${assets.length} assets`);
69
+ }
70
+ function populateD1TagCache(options, populateCacheOptions) {
71
+ logger.info("\nCreating D1 table if necessary...");
72
+ const config = unstable_readConfig({ env: populateCacheOptions.environment });
73
+ const binding = config.d1_databases.find(({ binding }) => binding === D1_TAG_BINDING_NAME);
74
+ if (!binding) {
75
+ throw new Error(`No D1 binding ${JSON.stringify(D1_TAG_BINDING_NAME)} found!`);
76
+ }
77
+ runWrangler(options, [
78
+ "d1 execute",
79
+ JSON.stringify(D1_TAG_BINDING_NAME),
80
+ `--command "CREATE TABLE IF NOT EXISTS revalidations (tag TEXT NOT NULL, revalidatedAt INTEGER NOT NULL, UNIQUE(tag) ON CONFLICT REPLACE);"`,
81
+ ], { ...populateCacheOptions, logging: "error" });
82
+ logger.info("\nSuccessfully created D1 table");
83
+ }
84
+ function populateStaticAssetsIncrementalCache(options) {
85
+ logger.info("\nPopulating Workers static assets...");
86
+ cpSync(path.join(options.outputDir, "cache"), path.join(options.outputDir, "assets", STATIC_ASSETS_CACHE_DIR), { recursive: true });
87
+ logger.info(`Successfully populated static assets cache`);
88
+ }
27
89
  export async function populateCache(options, config, populateCacheOptions) {
28
90
  const { incrementalCache, tagCache } = config.default.override ?? {};
29
91
  if (!existsSync(options.outputDir)) {
@@ -33,19 +95,15 @@ export async function populateCache(options, config, populateCacheOptions) {
33
95
  if (!config.dangerous?.disableIncrementalCache && incrementalCache) {
34
96
  const name = await resolveCacheName(incrementalCache);
35
97
  switch (name) {
36
- case R2_CACHE_NAME: {
37
- logger.info("\nPopulating R2 incremental cache...");
38
- const assets = getCacheAssetPaths(options);
39
- assets.forEach(({ fsPath, destPath }) => {
40
- const fullDestPath = path.join("NEXT_INC_CACHE_R2_BUCKET", process.env.NEXT_INC_CACHE_R2_PREFIX ?? "incremental-cache", destPath);
41
- runWrangler(options, ["r2 object put", JSON.stringify(fullDestPath), `--file ${JSON.stringify(fsPath)}`],
42
- // NOTE: R2 does not support the environment flag and results in the following error:
43
- // Incorrect type for the 'cacheExpiry' field on 'HttpMetadata': the provided value is not of type 'date'.
44
- { target: populateCacheOptions.target, excludeRemoteFlag: true, logging: "error" });
45
- });
46
- logger.info(`Successfully populated cache with ${assets.length} assets`);
98
+ case R2_CACHE_NAME:
99
+ populateR2IncrementalCache(options, populateCacheOptions);
100
+ break;
101
+ case KV_CACHE_NAME:
102
+ populateKVIncrementalCache(options, populateCacheOptions);
103
+ break;
104
+ case STATIC_ASSETS_CACHE_NAME:
105
+ populateStaticAssetsIncrementalCache(options);
47
106
  break;
48
- }
49
107
  default:
50
108
  logger.info("Incremental cache does not need populating");
51
109
  }
@@ -53,16 +111,9 @@ export async function populateCache(options, config, populateCacheOptions) {
53
111
  if (!config.dangerous?.disableTagCache && !config.dangerous?.disableIncrementalCache && tagCache) {
54
112
  const name = await resolveCacheName(tagCache);
55
113
  switch (name) {
56
- case D1_TAG_NAME: {
57
- logger.info("\nCreating D1 table if necessary...");
58
- runWrangler(options, [
59
- "d1 execute",
60
- "NEXT_TAG_CACHE_D1",
61
- `--command "CREATE TABLE IF NOT EXISTS revalidations (tag TEXT NOT NULL, revalidatedAt INTEGER NOT NULL, UNIQUE(tag) ON CONFLICT REPLACE);"`,
62
- ], { ...populateCacheOptions, logging: "error" });
63
- logger.info("\nSuccessfully created D1 table");
114
+ case D1_TAG_NAME:
115
+ populateD1TagCache(options, populateCacheOptions);
64
116
  break;
65
- }
66
117
  default:
67
118
  logger.info("Tag cache does not need populating");
68
119
  }
@@ -49,6 +49,10 @@ export function runWrangler(options, args, wranglerOpts = {}) {
49
49
  ], {
50
50
  shell: true,
51
51
  stdio: wranglerOpts.logging === "error" ? ["ignore", "ignore", "inherit"] : "inherit",
52
+ env: {
53
+ ...process.env,
54
+ ...(wranglerOpts.logging === "error" ? { WRANGLER_LOG: "error" } : undefined),
55
+ },
52
56
  });
53
57
  if (result.status !== 0) {
54
58
  logger.error("Wrangler command failed");
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.0.0-beta.0",
4
+ "version": "1.0.0-beta.2",
5
5
  "type": "module",
6
6
  "bin": {
7
7
  "opennextjs-cloudflare": "dist/cli/index.js"
@@ -43,9 +43,10 @@
43
43
  "homepage": "https://github.com/opennextjs/opennextjs-cloudflare",
44
44
  "dependencies": {
45
45
  "@dotenvx/dotenvx": "1.31.0",
46
- "@opennextjs/aws": "3.5.4",
46
+ "@opennextjs/aws": "3.5.5",
47
47
  "enquirer": "^2.4.1",
48
- "glob": "^11.0.0"
48
+ "glob": "^11.0.0",
49
+ "ts-tqdm": "^0.8.6"
49
50
  },
50
51
  "devDependencies": {
51
52
  "@cloudflare/workers-types": "^4.20250224.0",
@@ -1 +0,0 @@
1
- export {};
@@ -1,2 +0,0 @@
1
- import * as buildHelper from "@opennextjs/aws/build/helper.js";
2
- export declare function copyCacheAssets(options: buildHelper.BuildOptions): void;
@@ -1,10 +0,0 @@
1
- import { cpSync, mkdirSync } from "node:fs";
2
- import { join } from "node:path";
3
- import { CACHE_ASSET_DIR } from "../../../api/overrides/incremental-cache/kv-incremental-cache.js";
4
- export function copyCacheAssets(options) {
5
- const { outputDir } = options;
6
- const srcPath = join(outputDir, "cache");
7
- const dstPath = join(outputDir, "assets", CACHE_ASSET_DIR);
8
- mkdirSync(dstPath, { recursive: true });
9
- cpSync(srcPath, dstPath, { recursive: true });
10
- }