@opennextjs/cloudflare 1.0.4 → 1.2.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 +5 -0
  2. package/dist/api/config.d.ts +12 -2
  3. package/dist/api/config.js +9 -2
  4. package/dist/api/durable-objects/bucket-cache-purge.d.ts +7 -0
  5. package/dist/api/durable-objects/bucket-cache-purge.js +75 -0
  6. package/dist/api/durable-objects/bucket-cache-purge.spec.js +121 -0
  7. package/dist/api/overrides/cache-purge/index.d.ts +12 -0
  8. package/dist/api/overrides/cache-purge/index.js +26 -0
  9. package/dist/api/overrides/internal.d.ts +2 -0
  10. package/dist/api/overrides/internal.js +52 -0
  11. package/dist/api/overrides/queue/do-queue.js +1 -1
  12. package/dist/api/overrides/queue/queue-cache.d.ts +36 -0
  13. package/dist/api/overrides/queue/queue-cache.js +93 -0
  14. package/dist/api/overrides/queue/queue-cache.spec.d.ts +1 -0
  15. package/dist/api/overrides/queue/queue-cache.spec.js +92 -0
  16. package/dist/api/overrides/tag-cache/d1-next-tag-cache.js +2 -1
  17. package/dist/api/overrides/tag-cache/do-sharded-tag-cache.d.ts +20 -0
  18. package/dist/api/overrides/tag-cache/do-sharded-tag-cache.js +70 -7
  19. package/dist/api/overrides/tag-cache/do-sharded-tag-cache.spec.js +81 -1
  20. package/dist/cli/build/bundle-server.d.ts +1 -1
  21. package/dist/cli/build/bundle-server.js +16 -38
  22. package/dist/cli/build/open-next/compileDurableObjects.js +1 -0
  23. package/dist/cli/build/open-next/createServerBundle.js +9 -10
  24. package/dist/cli/build/patches/index.d.ts +0 -1
  25. package/dist/cli/build/patches/index.js +0 -1
  26. package/dist/cli/build/patches/investigated/index.d.ts +0 -1
  27. package/dist/cli/build/patches/investigated/index.js +0 -1
  28. package/dist/cli/build/patches/plugins/load-manifest.d.ts +3 -1
  29. package/dist/cli/build/patches/plugins/load-manifest.js +49 -7
  30. package/dist/cli/build/patches/plugins/next-server.d.ts +25 -0
  31. package/dist/cli/build/patches/plugins/next-server.js +110 -0
  32. package/dist/cli/build/patches/plugins/next-server.spec.d.ts +1 -0
  33. package/dist/cli/build/patches/plugins/next-server.spec.js +429 -0
  34. package/dist/cli/build/patches/plugins/open-next.d.ts +8 -0
  35. package/dist/cli/build/patches/plugins/open-next.js +39 -0
  36. package/dist/cli/templates/init.js +5 -0
  37. package/dist/cli/templates/shims/throw.d.ts +2 -0
  38. package/dist/cli/templates/shims/throw.js +2 -0
  39. package/dist/cli/templates/worker.d.ts +1 -0
  40. package/dist/cli/templates/worker.js +2 -0
  41. package/package.json +3 -3
  42. package/dist/cli/build/patches/investigated/patch-cache.d.ts +0 -14
  43. package/dist/cli/build/patches/investigated/patch-cache.js +0 -40
  44. package/dist/cli/build/patches/plugins/build-id.d.ts +0 -6
  45. package/dist/cli/build/patches/plugins/build-id.js +0 -29
  46. package/dist/cli/build/patches/plugins/build-id.spec.js +0 -82
  47. package/dist/cli/build/patches/plugins/eval-manifest.d.ts +0 -7
  48. package/dist/cli/build/patches/plugins/eval-manifest.js +0 -61
  49. package/dist/cli/build/patches/to-investigate/inline-middleware-manifest.d.ts +0 -6
  50. package/dist/cli/build/patches/to-investigate/inline-middleware-manifest.js +0 -15
  51. /package/dist/{cli/build/patches/plugins/build-id.spec.d.ts → api/durable-objects/bucket-cache-purge.spec.d.ts} +0 -0
@@ -2,6 +2,9 @@ import type { NextModeTagCache } from "@opennextjs/aws/types/overrides.js";
2
2
  export declare const DEFAULT_WRITE_RETRIES = 3;
3
3
  export declare const DEFAULT_NUM_SHARDS = 4;
4
4
  export declare const NAME = "do-sharded-tag-cache";
5
+ export declare const DEFAULT_REGION: "enam";
6
+ export declare const AVAILABLE_REGIONS: readonly ["enam", "weur", "apac", "sam", "afr", "oc"];
7
+ type AllowedDurableObjectRegion = (typeof AVAILABLE_REGIONS)[number];
5
8
  interface ShardedDOTagCacheOptions {
6
9
  /**
7
10
  * The number of shards that will be used.
@@ -49,6 +52,18 @@ interface ShardedDOTagCacheOptions {
49
52
  shardReplication?: {
50
53
  numberOfSoftReplicas: number;
51
54
  numberOfHardReplicas: number;
55
+ /**
56
+ * Enable regional replication for the shards.
57
+ *
58
+ * If not set, no regional replication will be performed and durable objects will be created without a location hint
59
+ *
60
+ * Can be used to reduce latency for users in different regions and to spread the load across multiple regions.
61
+ *
62
+ * This will increase the number of durable objects created, as each shard will be replicated in all regions.
63
+ */
64
+ regionalReplication?: {
65
+ defaultRegion: AllowedDurableObjectRegion;
66
+ };
52
67
  };
53
68
  /**
54
69
  * The number of retries to perform when writing tags
@@ -62,11 +77,13 @@ interface DOIdOptions {
62
77
  numberOfReplicas: number;
63
78
  shardType: "soft" | "hard";
64
79
  replicaId?: number;
80
+ region?: DurableObjectLocationHint;
65
81
  }
66
82
  export declare class DOId {
67
83
  options: DOIdOptions;
68
84
  shardId: string;
69
85
  replicaId: number;
86
+ region?: DurableObjectLocationHint;
70
87
  constructor(options: DOIdOptions);
71
88
  private generateRandomNumberBetween;
72
89
  get key(): string;
@@ -83,6 +100,8 @@ declare class ShardedDOTagCache implements NextModeTagCache {
83
100
  readonly numSoftReplicas: number;
84
101
  readonly numHardReplicas: number;
85
102
  readonly maxWriteRetries: number;
103
+ readonly enableRegionalReplication: boolean;
104
+ readonly defaultRegion: AllowedDurableObjectRegion;
86
105
  localCache?: Cache;
87
106
  constructor(opts?: ShardedDOTagCacheOptions);
88
107
  private getDurableObjectStub;
@@ -94,6 +113,7 @@ declare class ShardedDOTagCache implements NextModeTagCache {
94
113
  * @returns An array of TagCacheDOId and tag
95
114
  */
96
115
  private generateDOIdArray;
116
+ getClosestRegion(): "enam" | "sam" | "weur" | "apac" | "oc" | "afr";
97
117
  /**
98
118
  * Same tags are guaranteed to be in the same shard
99
119
  * @param tags
@@ -2,26 +2,30 @@ import { debug, 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
+ import { debugCache, purgeCacheByTags } from "../internal";
6
6
  export const DEFAULT_WRITE_RETRIES = 3;
7
7
  export const DEFAULT_NUM_SHARDS = 4;
8
8
  export const NAME = "do-sharded-tag-cache";
9
9
  const SOFT_TAG_PREFIX = "_N_T_/";
10
+ export const DEFAULT_REGION = "enam";
11
+ export const AVAILABLE_REGIONS = ["enam", "weur", "apac", "sam", "afr", "oc"];
10
12
  export class DOId {
11
13
  options;
12
14
  shardId;
13
15
  replicaId;
16
+ region;
14
17
  constructor(options) {
15
18
  this.options = options;
16
- const { baseShardId, shardType, numberOfReplicas, replicaId } = options;
19
+ const { baseShardId, shardType, numberOfReplicas, replicaId, region } = options;
17
20
  this.shardId = `tag-${shardType};${baseShardId}`;
18
21
  this.replicaId = replicaId ?? this.generateRandomNumberBetween(1, numberOfReplicas);
22
+ this.region = region;
19
23
  }
20
24
  generateRandomNumberBetween(min, max) {
21
25
  return Math.floor(Math.random() * (max - min + 1) + min);
22
26
  }
23
27
  get key() {
24
- return `${this.shardId};replica-${this.replicaId}`;
28
+ return `${this.shardId};replica-${this.replicaId}${this.region ? `;region-${this.region}` : ""}`;
25
29
  }
26
30
  }
27
31
  class ShardedDOTagCache {
@@ -31,19 +35,27 @@ class ShardedDOTagCache {
31
35
  numSoftReplicas;
32
36
  numHardReplicas;
33
37
  maxWriteRetries;
38
+ enableRegionalReplication;
39
+ defaultRegion;
34
40
  localCache;
35
41
  constructor(opts = { baseShardSize: DEFAULT_NUM_SHARDS }) {
36
42
  this.opts = opts;
37
43
  this.numSoftReplicas = opts.shardReplication?.numberOfSoftReplicas ?? 1;
38
44
  this.numHardReplicas = opts.shardReplication?.numberOfHardReplicas ?? 1;
39
45
  this.maxWriteRetries = opts.maxWriteRetries ?? DEFAULT_WRITE_RETRIES;
46
+ this.enableRegionalReplication = Boolean(opts.shardReplication?.regionalReplication);
47
+ this.defaultRegion = opts.shardReplication?.regionalReplication?.defaultRegion ?? DEFAULT_REGION;
40
48
  }
41
49
  getDurableObjectStub(doId) {
42
50
  const durableObject = getCloudflareContext().env.NEXT_TAG_CACHE_DO_SHARDED;
43
51
  if (!durableObject)
44
52
  throw new IgnorableError("No durable object binding for cache revalidation");
45
53
  const id = durableObject.idFromName(doId.key);
46
- return durableObject.get(id);
54
+ debug("[shardedTagCache] - Accessing Durable Object : ", {
55
+ key: doId.key,
56
+ region: doId.region,
57
+ });
58
+ return durableObject.get(id, { locationHint: doId.region });
47
59
  }
48
60
  /**
49
61
  * Generates a list of DO ids for the shards and replicas
@@ -55,9 +67,14 @@ class ShardedDOTagCache {
55
67
  generateDOIdArray({ tags, shardType, generateAllReplicas = false, }) {
56
68
  let replicaIndexes = [1];
57
69
  const isSoft = shardType === "soft";
58
- const numReplicas = isSoft ? this.numSoftReplicas : this.numHardReplicas;
59
- replicaIndexes = generateAllReplicas ? Array.from({ length: numReplicas }, (_, i) => i + 1) : [undefined];
60
- return replicaIndexes.flatMap((replicaId) => {
70
+ let numReplicas = 1;
71
+ if (this.opts.shardReplication) {
72
+ numReplicas = isSoft ? this.numSoftReplicas : this.numHardReplicas;
73
+ replicaIndexes = generateAllReplicas
74
+ ? Array.from({ length: numReplicas }, (_, i) => i + 1)
75
+ : [undefined];
76
+ }
77
+ const regionalReplicas = replicaIndexes.flatMap((replicaId) => {
61
78
  return tags
62
79
  .filter((tag) => (isSoft ? tag.startsWith(SOFT_TAG_PREFIX) : !tag.startsWith(SOFT_TAG_PREFIX)))
63
80
  .map((tag) => {
@@ -72,6 +89,51 @@ class ShardedDOTagCache {
72
89
  };
73
90
  });
74
91
  });
92
+ if (!this.enableRegionalReplication)
93
+ return regionalReplicas;
94
+ // If we have regional replication enabled, we need to further duplicate the shards in all the regions
95
+ const regionalReplicasInAllRegions = generateAllReplicas
96
+ ? regionalReplicas.flatMap(({ doId, tag }) => {
97
+ return AVAILABLE_REGIONS.map((region) => {
98
+ return {
99
+ doId: new DOId({
100
+ baseShardId: doId.options.baseShardId,
101
+ numberOfReplicas: numReplicas,
102
+ shardType,
103
+ replicaId: doId.replicaId,
104
+ region,
105
+ }),
106
+ tag,
107
+ };
108
+ });
109
+ })
110
+ : regionalReplicas.map(({ doId, tag }) => {
111
+ doId.region = this.getClosestRegion();
112
+ return { doId, tag };
113
+ });
114
+ return regionalReplicasInAllRegions;
115
+ }
116
+ getClosestRegion() {
117
+ const continent = getCloudflareContext().cf?.continent;
118
+ if (!continent)
119
+ return this.defaultRegion;
120
+ debug("[shardedTagCache] - Continent : ", continent);
121
+ switch (continent) {
122
+ case "AF":
123
+ return "afr";
124
+ case "AS":
125
+ return "apac";
126
+ case "EU":
127
+ return "weur";
128
+ case "NA":
129
+ return "enam";
130
+ case "OC":
131
+ return "oc";
132
+ case "SA":
133
+ return "sam";
134
+ default:
135
+ return this.defaultRegion;
136
+ }
75
137
  }
76
138
  /**
77
139
  * Same tags are guaranteed to be in the same shard
@@ -196,6 +258,7 @@ class ShardedDOTagCache {
196
258
  await Promise.all(shardedTagGroups.map(async ({ doId, tags }) => {
197
259
  await this.performWriteTagsWithRetry(doId, tags, currentTime);
198
260
  }));
261
+ await purgeCacheByTags(tags);
199
262
  }
200
263
  async performWriteTagsWithRetry(doId, tags, lastModified, retryNumber = 0) {
201
264
  try {
@@ -1,5 +1,5 @@
1
1
  import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
2
- import shardedDOTagCache, { DOId } from "./do-sharded-tag-cache";
2
+ import shardedDOTagCache, { AVAILABLE_REGIONS, DOId } from "./do-sharded-tag-cache";
3
3
  const hasBeenRevalidatedMock = vi.fn();
4
4
  const writeTagsMock = vi.fn();
5
5
  const idFromNameMock = vi.fn();
@@ -7,6 +7,8 @@ const getMock = vi
7
7
  .fn()
8
8
  .mockReturnValue({ hasBeenRevalidated: hasBeenRevalidatedMock, writeTags: writeTagsMock });
9
9
  const waitUntilMock = vi.fn().mockImplementation(async (fn) => fn());
10
+ // @ts-expect-error - We define it here only for the test
11
+ globalThis.continent = undefined;
10
12
  const sendDLQMock = vi.fn();
11
13
  vi.mock("../../cloudflare-context", () => ({
12
14
  getCloudflareContext: () => ({
@@ -17,6 +19,10 @@ vi.mock("../../cloudflare-context", () => ({
17
19
  },
18
20
  },
19
21
  ctx: { waitUntil: waitUntilMock },
22
+ cf: {
23
+ // @ts-expect-error - We define it here only for the test
24
+ continent: globalThis.continent,
25
+ },
20
26
  }),
21
27
  }));
22
28
  describe("DOShardedTagCache", () => {
@@ -96,6 +102,80 @@ describe("DOShardedTagCache", () => {
96
102
  expect(secondDOId?.replicaId).toBeGreaterThanOrEqual(1);
97
103
  expect(secondDOId?.replicaId).toBeLessThanOrEqual(2);
98
104
  });
105
+ it("should generate one doIds, but in the default region", () => {
106
+ const cache = shardedDOTagCache({
107
+ baseShardSize: 4,
108
+ shardReplication: {
109
+ numberOfSoftReplicas: 2,
110
+ numberOfHardReplicas: 2,
111
+ regionalReplication: {
112
+ defaultRegion: "enam",
113
+ },
114
+ },
115
+ });
116
+ const shardedTagCollection = cache.groupTagsByDO({
117
+ tags: ["tag1", "_N_T_/tag1"],
118
+ generateAllReplicas: false,
119
+ });
120
+ expect(shardedTagCollection.length).toBe(2);
121
+ const firstDOId = shardedTagCollection[0]?.doId;
122
+ const secondDOId = shardedTagCollection[1]?.doId;
123
+ expect(firstDOId?.shardId).toBe("tag-soft;shard-3");
124
+ expect(firstDOId?.region).toBe("enam");
125
+ expect(secondDOId?.shardId).toBe("tag-hard;shard-1");
126
+ expect(secondDOId?.region).toBe("enam");
127
+ // We still need to check if the last part is between the correct boundaries
128
+ expect(firstDOId?.replicaId).toBeGreaterThanOrEqual(1);
129
+ expect(firstDOId?.replicaId).toBeLessThanOrEqual(2);
130
+ expect(secondDOId?.replicaId).toBeGreaterThanOrEqual(1);
131
+ expect(secondDOId?.replicaId).toBeLessThanOrEqual(2);
132
+ });
133
+ it("should generate one doIds, but in the correct region", () => {
134
+ // @ts-expect-error - We define it here only for the test
135
+ globalThis.continent = "EU";
136
+ const cache = shardedDOTagCache({
137
+ baseShardSize: 4,
138
+ shardReplication: {
139
+ numberOfSoftReplicas: 2,
140
+ numberOfHardReplicas: 2,
141
+ regionalReplication: {
142
+ defaultRegion: "enam",
143
+ },
144
+ },
145
+ });
146
+ const shardedTagCollection = cache.groupTagsByDO({
147
+ tags: ["tag1", "_N_T_/tag1"],
148
+ generateAllReplicas: false,
149
+ });
150
+ expect(shardedTagCollection.length).toBe(2);
151
+ expect(shardedTagCollection[0]?.doId.region).toBe("weur");
152
+ expect(shardedTagCollection[1]?.doId.region).toBe("weur");
153
+ //@ts-expect-error - We need to reset the global variable
154
+ globalThis.continent = undefined;
155
+ });
156
+ it("should generate all the appropriate replicas in all the regions with enableRegionalReplication", () => {
157
+ const cache = shardedDOTagCache({
158
+ baseShardSize: 4,
159
+ shardReplication: {
160
+ numberOfSoftReplicas: 2,
161
+ numberOfHardReplicas: 2,
162
+ regionalReplication: {
163
+ defaultRegion: "enam",
164
+ },
165
+ },
166
+ });
167
+ const shardedTagCollection = cache.groupTagsByDO({
168
+ tags: ["tag1", "_N_T_/tag1"],
169
+ generateAllReplicas: true,
170
+ });
171
+ // 6 regions times 4 shards replica
172
+ expect(shardedTagCollection.length).toBe(24);
173
+ shardedTagCollection.forEach(({ doId }) => {
174
+ expect(AVAILABLE_REGIONS).toContain(doId.region);
175
+ // It should end with the region
176
+ expect(doId.key).toMatch(/tag-(soft|hard);shard-\d;replica-\d;region-(enam|weur|sam|afr|apac|oc)$/);
177
+ });
178
+ });
99
179
  });
100
180
  });
101
181
  describe("hasBeenRevalidated", () => {
@@ -6,7 +6,7 @@ export declare function bundleServer(buildOpts: BuildOptions): Promise<void>;
6
6
  /**
7
7
  * This function applies patches required for the code to run on workers.
8
8
  */
9
- export declare function updateWorkerBundledCode(workerOutputFile: string, buildOpts: BuildOptions): Promise<void>;
9
+ export declare function updateWorkerBundledCode(workerOutputFile: string): Promise<void>;
10
10
  /**
11
11
  * Gets the path of the worker.js file generated by the build process
12
12
  *
@@ -9,12 +9,12 @@ import { getOpenNextConfig } from "../../api/config.js";
9
9
  import { patchVercelOgLibrary } from "./patches/ast/patch-vercel-og-library.js";
10
10
  import { patchWebpackRuntime } from "./patches/ast/webpack-runtime.js";
11
11
  import * as patches from "./patches/index.js";
12
- import { inlineBuildId } from "./patches/plugins/build-id.js";
13
12
  import { inlineDynamicRequires } from "./patches/plugins/dynamic-requires.js";
14
- import { inlineEvalManifest } from "./patches/plugins/eval-manifest.js";
15
13
  import { inlineFindDir } from "./patches/plugins/find-dir.js";
16
14
  import { patchInstrumentation } from "./patches/plugins/instrumentation.js";
17
15
  import { inlineLoadManifest } from "./patches/plugins/load-manifest.js";
16
+ import { patchNextServer } from "./patches/plugins/next-server.js";
17
+ import { patchResolveCache } from "./patches/plugins/open-next.js";
18
18
  import { handleOptionalDependencies } from "./patches/plugins/optional-deps.js";
19
19
  import { patchPagesRouterContext } from "./patches/plugins/pages-router-context.js";
20
20
  import { patchDepdDeprecations } from "./patches/plugins/patch-depd-deprecations.js";
@@ -80,31 +80,28 @@ export async function bundleServer(buildOpts) {
80
80
  handleOptionalDependencies(optionalDependencies),
81
81
  patchInstrumentation(updater, buildOpts),
82
82
  patchPagesRouterContext(buildOpts),
83
- inlineEvalManifest(updater, buildOpts),
84
83
  inlineFindDir(updater, buildOpts),
85
84
  inlineLoadManifest(updater, buildOpts),
86
- inlineBuildId(updater),
85
+ patchNextServer(updater, buildOpts),
87
86
  patchDepdDeprecations(updater),
87
+ patchResolveCache(updater, buildOpts),
88
88
  // Apply updater updates, must be the last plugin
89
89
  updater.plugin,
90
90
  ],
91
91
  external: ["./middleware/handler.mjs"],
92
92
  alias: {
93
- // Note: it looks like node-fetch is actually not necessary for us, so we could replace it with an empty shim
94
- // but just to be safe we replace it with a module that re-exports the native fetch
95
- // we do this to both save on bundle size (there isn't really any benefit in us shipping the node-fetch code)
96
- // and also get rid of a warning in the terminal caused by the package (because it performs an === comparison with -0)
93
+ // Workers have `fetch` so the `node-fetch` polyfill is not needed
97
94
  "next/dist/compiled/node-fetch": path.join(buildOpts.outputDir, "cloudflare-templates/shims/fetch.js"),
98
- // Note: we apply an empty shim to next/dist/compiled/ws because it generates two `eval`s:
99
- // eval("require")("bufferutil");
100
- // eval("require")("utf-8-validate");
95
+ // Workers have builtin Web Sockets
101
96
  "next/dist/compiled/ws": path.join(buildOpts.outputDir, "cloudflare-templates/shims/empty.js"),
102
- // Note: we apply an empty shim to next/dist/compiled/edge-runtime since (amongst others) it generated the following `eval`:
103
- // eval(getModuleCode)(module, module.exports, throwingRequire, params.context, ...Object.values(params.scopedContext));
104
- // which comes from https://github.com/vercel/edge-runtime/blob/6e96b55f/packages/primitives/src/primitives/load.js#L57-L63
97
+ // The toolbox optimizer pulls severals MB of dependencies (`caniuse-lite`, `terser`, `acorn`, ...)
98
+ // Drop it to optimize the code size
99
+ // See https://github.com/vercel/next.js/blob/6eb235c/packages/next/src/server/optimize-amp.ts
100
+ "next/dist/compiled/@ampproject/toolbox-optimizer": path.join(buildOpts.outputDir, "cloudflare-templates/shims/throw.js"),
101
+ // The edge runtime is not supported
105
102
  "next/dist/compiled/edge-runtime": path.join(buildOpts.outputDir, "cloudflare-templates/shims/empty.js"),
106
- // `@next/env` is a library Next.js uses for loading dotenv files, for obvious reasons we need to stub it here
107
- // source: https://github.com/vercel/next.js/tree/0ac10d79720/packages/next-env
103
+ // `@next/env` is used by Next to load environment variables from files.
104
+ // OpenNext inlines the values at build time so this is not needed.
108
105
  "@next/env": path.join(buildOpts.outputDir, "cloudflare-templates/shims/env.js"),
109
106
  },
110
107
  define: {
@@ -133,7 +130,7 @@ export async function bundleServer(buildOpts) {
133
130
  platform: "node",
134
131
  });
135
132
  fs.writeFileSync(openNextServerBundle + ".meta.json", JSON.stringify(result.metafile, null, 2));
136
- await updateWorkerBundledCode(openNextServerBundle, buildOpts);
133
+ await updateWorkerBundledCode(openNextServerBundle);
137
134
  const isMonorepo = monorepoRoot !== appPath;
138
135
  if (isMonorepo) {
139
136
  fs.writeFileSync(path.join(outputPath, "handler.mjs"), `export { handler } from "./${normalizePath(packagePath)}/handler.mjs";`);
@@ -143,28 +140,9 @@ export async function bundleServer(buildOpts) {
143
140
  /**
144
141
  * This function applies patches required for the code to run on workers.
145
142
  */
146
- export async function updateWorkerBundledCode(workerOutputFile, buildOpts) {
143
+ export async function updateWorkerBundledCode(workerOutputFile) {
147
144
  const code = await readFile(workerOutputFile, "utf8");
148
- const patchedCode = await patchCodeWithValidations(code, [
149
- ["require", patches.patchRequire],
150
- ["cacheHandler", (code) => patches.patchCache(code, buildOpts)],
151
- ["composableCache", (code) => patches.patchComposableCache(code, buildOpts), { isOptional: true }],
152
- [
153
- "'require(this.middlewareManifestPath)'",
154
- (code) => patches.inlineMiddlewareManifestRequire(code, buildOpts),
155
- { isOptional: true },
156
- ],
157
- [
158
- "`require.resolve` call",
159
- // workers do not support dynamic require nor require.resolve
160
- (code) => code.replace('require.resolve("./cache.cjs")', '"unused"'),
161
- ],
162
- [
163
- "`require.resolve composable cache` call",
164
- // workers do not support dynamic require nor require.resolve
165
- (code) => code.replace('require.resolve("./composable-cache.cjs")', '"unused"'),
166
- ],
167
- ]);
145
+ const patchedCode = await patchCodeWithValidations(code, [["require", patches.patchRequire]]);
168
146
  await writeFile(workerOutputFile, patchedCode);
169
147
  }
170
148
  /**
@@ -7,6 +7,7 @@ export function compileDurableObjects(buildOpts) {
7
7
  const entryPoints = [
8
8
  _require.resolve("@opennextjs/cloudflare/durable-objects/queue"),
9
9
  _require.resolve("@opennextjs/cloudflare/durable-objects/sharded-tag-cache"),
10
+ _require.resolve("@opennextjs/cloudflare/durable-objects/bucket-cache-purge"),
10
11
  ];
11
12
  const { outputDir } = buildOpts;
12
13
  const baseManifestPath = path.join(outputDir, "server-functions/default", getPackagePath(buildOpts), ".next");
@@ -10,9 +10,7 @@ import { copyMiddlewareResources, generateEdgeBundle } from "@opennextjs/aws/bui
10
10
  import * as buildHelper from "@opennextjs/aws/build/helper.js";
11
11
  import { installDependencies } from "@opennextjs/aws/build/installDeps.js";
12
12
  import { applyCodePatches } from "@opennextjs/aws/build/patch/codePatcher.js";
13
- import { patchEnvVars, patchFetchCacheForISR, patchFetchCacheSetMissingWaitUntil, patchNextServer, patchUnstableCacheForISR, patchUseCacheForISR, } from "@opennextjs/aws/build/patch/patches/index.js";
14
- // TODO: import from patches/index.js when https://github.com/opennextjs/opennextjs-aws/pull/827 is released
15
- import { patchBackgroundRevalidation } from "@opennextjs/aws/build/patch/patches/patchBackgroundRevalidation.js";
13
+ import * as awsPatches from "@opennextjs/aws/build/patch/patches/index.js";
16
14
  import logger from "@opennextjs/aws/logger.js";
17
15
  import { minifyAll } from "@opennextjs/aws/minimize-js.js";
18
16
  import { openNextEdgePlugins } from "@opennextjs/aws/plugins/edge.js";
@@ -133,13 +131,14 @@ async function generateBundle(name, options, fnOptions, codeCustomization) {
133
131
  }
134
132
  const additionalCodePatches = codeCustomization?.additionalCodePatches ?? [];
135
133
  await applyCodePatches(options, tracedFiles, manifests, [
136
- patchFetchCacheSetMissingWaitUntil,
137
- patchFetchCacheForISR,
138
- patchUnstableCacheForISR,
139
- patchUseCacheForISR,
140
- patchNextServer,
141
- patchEnvVars,
142
- patchBackgroundRevalidation,
134
+ awsPatches.patchFetchCacheSetMissingWaitUntil,
135
+ awsPatches.patchFetchCacheForISR,
136
+ awsPatches.patchUnstableCacheForISR,
137
+ awsPatches.patchUseCacheForISR,
138
+ awsPatches.patchNextServer,
139
+ awsPatches.patchEnvVars,
140
+ awsPatches.patchBackgroundRevalidation,
141
+ awsPatches.patchDropBabel,
143
142
  // Cloudflare specific patches
144
143
  patchResRevalidate,
145
144
  patchUseCacheIO,
@@ -1,2 +1 @@
1
1
  export * from "./investigated/index.js";
2
- export * from "./to-investigate/inline-middleware-manifest.js";
@@ -1,2 +1 @@
1
1
  export * from "./investigated/index.js";
2
- export * from "./to-investigate/inline-middleware-manifest.js";
@@ -1,3 +1,2 @@
1
1
  export * from "./copy-package-cli-files.js";
2
- export * from "./patch-cache.js";
3
2
  export * from "./patch-require.js";
@@ -1,3 +1,2 @@
1
1
  export * from "./copy-package-cli-files.js";
2
- export * from "./patch-cache.js";
3
2
  export * from "./patch-require.js";
@@ -1,5 +1,7 @@
1
1
  /**
2
- * Inline `loadManifest` as it relies on `readFileSync` that is not supported by workerd.
2
+ * Inline `loadManifest` and `evalManifest` from `load-manifest.js`
3
+ *
4
+ * They rely on `readFileSync` that is not supported by workerd.
3
5
  */
4
6
  import { type BuildOptions } from "@opennextjs/aws/build/helper.js";
5
7
  import type { ContentUpdater, Plugin } from "@opennextjs/aws/plugins/content-updater.js";
@@ -1,5 +1,7 @@
1
1
  /**
2
- * Inline `loadManifest` as it relies on `readFileSync` that is not supported by workerd.
2
+ * Inline `loadManifest` and `evalManifest` from `load-manifest.js`
3
+ *
4
+ * They rely on `readFileSync` that is not supported by workerd.
3
5
  */
4
6
  import { readFile } from "node:fs/promises";
5
7
  import { join, posix, relative, sep } from "node:path";
@@ -16,21 +18,24 @@ export function inlineLoadManifest(updater, buildOpts) {
16
18
  escape: false,
17
19
  }),
18
20
  contentFilter: /function loadManifest\(/,
19
- callback: async ({ contents }) => patchCode(contents, await getRule(buildOpts)),
21
+ callback: async ({ contents }) => {
22
+ contents = await patchCode(contents, await getLoadManifestRule(buildOpts));
23
+ contents = await patchCode(contents, await getEvalManifestRule(buildOpts));
24
+ return contents;
25
+ },
20
26
  },
21
27
  },
22
28
  ]);
23
29
  }
24
- async function getRule(buildOpts) {
30
+ async function getLoadManifestRule(buildOpts) {
25
31
  const { outputDir } = buildOpts;
26
32
  const baseDir = join(outputDir, "server-functions/default", getPackagePath(buildOpts));
27
33
  const dotNextDir = join(baseDir, ".next");
28
34
  const manifests = await glob(join(dotNextDir, "**/*-manifest.json"), { windowsPathsNoEscape: true });
29
35
  const returnManifests = (await Promise.all(manifests.map(async (manifest) => `
30
- if ($PATH.endsWith("${normalizePath("/" + relative(dotNextDir, manifest))}")) {
31
- return ${await readFile(manifest, "utf-8")};
32
- }
33
- `))).join("\n");
36
+ if ($PATH.endsWith("${normalizePath("/" + relative(dotNextDir, manifest))}")) {
37
+ return ${await readFile(manifest, "utf-8")};
38
+ }`))).join("\n");
34
39
  return {
35
40
  rule: {
36
41
  pattern: `
@@ -46,3 +51,40 @@ function loadManifest($PATH, $$$ARGS) {
46
51
  }`,
47
52
  };
48
53
  }
54
+ async function getEvalManifestRule(buildOpts) {
55
+ const { outputDir } = buildOpts;
56
+ const baseDir = join(outputDir, "server-functions/default", getPackagePath(buildOpts), ".next");
57
+ const appDir = join(baseDir, "server/app");
58
+ const manifests = await glob(join(baseDir, "**/*_client-reference-manifest.js"), {
59
+ windowsPathsNoEscape: true,
60
+ });
61
+ const returnManifests = manifests
62
+ .map((manifest) => {
63
+ const endsWith = normalizePath(relative(baseDir, manifest));
64
+ const key = normalizePath("/" + relative(appDir, manifest)).replace("_client-reference-manifest.js", "");
65
+ return `
66
+ if ($PATH.endsWith("${endsWith}")) {
67
+ require(${JSON.stringify(manifest)});
68
+ return {
69
+ __RSC_MANIFEST: {
70
+ "${key}": globalThis.__RSC_MANIFEST["${key}"],
71
+ },
72
+ };
73
+ }`;
74
+ })
75
+ .join("\n");
76
+ return {
77
+ rule: {
78
+ pattern: `
79
+ function evalManifest($PATH, $$$ARGS) {
80
+ $$$_
81
+ }`,
82
+ },
83
+ fix: `
84
+ function evalManifest($PATH, $$$ARGS) {
85
+ $PATH = $PATH.replaceAll(${JSON.stringify(sep)}, ${JSON.stringify(posix.sep)});
86
+ ${returnManifests}
87
+ throw new Error(\`Unexpected evalManifest(\${$PATH}) call!\`);
88
+ }`,
89
+ };
90
+ }
@@ -0,0 +1,25 @@
1
+ /**
2
+ * Misc patches for `next-server.js`
3
+ *
4
+ * Note: we will probably need to revisit the patches when the Next adapter API lands
5
+ *
6
+ * - Inline `getBuildId` as it relies on `readFileSync` that is not supported by workerd
7
+ * - Inline the middleware manifest
8
+ * - Override the cache and composable cache handlers
9
+ */
10
+ import { type BuildOptions } from "@opennextjs/aws/build/helper.js";
11
+ import type { ContentUpdater, Plugin } from "@opennextjs/aws/plugins/content-updater.js";
12
+ export declare function patchNextServer(updater: ContentUpdater, buildOpts: BuildOptions): Plugin;
13
+ export declare const buildIdRule = "\nrule:\n pattern:\n selector: method_definition\n context: \"class { getBuildId($$$PARAMS) { $$$_ } }\"\nfix: |-\n getBuildId($$$PARAMS) {\n return process.env.NEXT_BUILD_ID;\n }\n";
14
+ export declare function createMiddlewareManifestRule(manifest: unknown): string;
15
+ /**
16
+ * The cache handler used by Next.js is normally defined in the config file as a path. At runtime,
17
+ * Next.js would then do a dynamic require on a transformed version of the path to retrieve the
18
+ * cache handler and create a new instance of it.
19
+ *
20
+ * This is problematic in workerd due to the dynamic import of the file that is not known from
21
+ * build-time. Therefore, we have to manually override the default way that the cache handler is
22
+ * instantiated with a dynamic require that uses a string literal for the path.
23
+ */
24
+ export declare function createCacheHandlerRule(handlerPath: string): string;
25
+ export declare function createComposableCacheHandlersRule(handlerPath: string): string;