@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
@@ -0,0 +1,110 @@
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 { existsSync, readFileSync } from "node:fs";
11
+ import path from "node:path";
12
+ import { getPackagePath } from "@opennextjs/aws/build/helper.js";
13
+ import { patchCode } from "@opennextjs/aws/build/patch/astCodePatcher.js";
14
+ import { getCrossPlatformPathRegex } from "@opennextjs/aws/utils/regex.js";
15
+ import { normalizePath } from "../../utils/index.js";
16
+ export function patchNextServer(updater, buildOpts) {
17
+ return updater.updateContent("next-server", [
18
+ {
19
+ field: {
20
+ filter: getCrossPlatformPathRegex(String.raw `/next/dist/server/next-server\.js$`, {
21
+ escape: false,
22
+ }),
23
+ contentFilter: /getBuildId\(/,
24
+ callback: async ({ contents }) => {
25
+ const { outputDir } = buildOpts;
26
+ contents = patchCode(contents, buildIdRule);
27
+ const manifestPath = path.join(outputDir, "server-functions/default", getPackagePath(buildOpts), ".next/server/middleware-manifest.json");
28
+ const manifest = existsSync(manifestPath)
29
+ ? JSON.parse(await readFileSync(manifestPath, "utf-8"))
30
+ : {};
31
+ contents = patchCode(contents, createMiddlewareManifestRule(manifest));
32
+ const outputPath = path.join(outputDir, "server-functions/default");
33
+ const cacheHandler = path.join(outputPath, getPackagePath(buildOpts), "cache.cjs");
34
+ contents = patchCode(contents, createCacheHandlerRule(cacheHandler));
35
+ const composableCacheHandler = path.join(outputPath, getPackagePath(buildOpts), "composable-cache.cjs");
36
+ contents = patchCode(contents, createComposableCacheHandlersRule(composableCacheHandler));
37
+ return contents;
38
+ },
39
+ },
40
+ },
41
+ ]);
42
+ }
43
+ export const buildIdRule = `
44
+ rule:
45
+ pattern:
46
+ selector: method_definition
47
+ context: "class { getBuildId($$$PARAMS) { $$$_ } }"
48
+ fix: |-
49
+ getBuildId($$$PARAMS) {
50
+ return process.env.NEXT_BUILD_ID;
51
+ }
52
+ `;
53
+ export function createMiddlewareManifestRule(manifest) {
54
+ return `
55
+ rule:
56
+ pattern:
57
+ selector: method_definition
58
+ context: "class { getMiddlewareManifest($$$PARAMS) { $$$_ } }"
59
+ fix: |-
60
+ getMiddlewareManifest($$$PARAMS) {
61
+ return ${JSON.stringify(manifest)};
62
+ }
63
+ `;
64
+ }
65
+ /**
66
+ * The cache handler used by Next.js is normally defined in the config file as a path. At runtime,
67
+ * Next.js would then do a dynamic require on a transformed version of the path to retrieve the
68
+ * cache handler and create a new instance of it.
69
+ *
70
+ * This is problematic in workerd due to the dynamic import of the file that is not known from
71
+ * build-time. Therefore, we have to manually override the default way that the cache handler is
72
+ * instantiated with a dynamic require that uses a string literal for the path.
73
+ */
74
+ export function createCacheHandlerRule(handlerPath) {
75
+ return `
76
+ rule:
77
+ pattern: "const { cacheHandler } = this.nextConfig;"
78
+ inside:
79
+ kind: method_definition
80
+ has:
81
+ field: name
82
+ regex: ^getIncrementalCache$
83
+ stopBy: end
84
+
85
+ fix: |-
86
+ const cacheHandler = null;
87
+ CacheHandler = require('${normalizePath(handlerPath)}').default;
88
+ `;
89
+ }
90
+ export function createComposableCacheHandlersRule(handlerPath) {
91
+ return `
92
+ rule:
93
+ pattern: "const { cacheHandlers } = this.nextConfig.experimental;"
94
+ inside:
95
+ kind: method_definition
96
+ has:
97
+ field: name
98
+ regex: ^loadCustomCacheHandlers$
99
+ stopBy: end
100
+
101
+ fix: |-
102
+ const cacheHandlers = null;
103
+ const handlersSymbol = Symbol.for('@next/cache-handlers');
104
+ const handlersMapSymbol = Symbol.for('@next/cache-handlers-map');
105
+ const handlersSetSymbol = Symbol.for('@next/cache-handlers-set');
106
+ globalThis[handlersMapSymbol] = new Map();
107
+ globalThis[handlersMapSymbol].set("default", require('${normalizePath(handlerPath)}').default);
108
+ globalThis[handlersSetSymbol] = new Set(globalThis[handlersMapSymbol].values());
109
+ `;
110
+ }
@@ -0,0 +1,429 @@
1
+ import { patchCode } from "@opennextjs/aws/build/patch/astCodePatcher.js";
2
+ import { describe, expect, test } from "vitest";
3
+ import { buildIdRule, createCacheHandlerRule, createComposableCacheHandlersRule, createMiddlewareManifestRule, } from "./next-server.js";
4
+ describe("Next Server", () => {
5
+ const nextServerCode = `
6
+ class NextNodeServer extends _baseserver.default {
7
+ constructor(options){
8
+ // Initialize super class
9
+ super(options);
10
+ this.handleNextImageRequest = async (req, res, parsedUrl) => { /* ... */ };
11
+ }
12
+ async handleUpgrade() {
13
+ // The web server does not support web sockets, it's only used for HMR in
14
+ // development.
15
+ }
16
+ loadEnvConfig({ dev, forceReload, silent }) {
17
+ (0, _env.loadEnvConfig)(this.dir, dev, silent ? {
18
+ info: ()=>{},
19
+ error: ()=>{}
20
+ } : _log, forceReload);
21
+ }
22
+ async hasPage(pathname) {
23
+ var _this_nextConfig_i18n;
24
+ return !!(0, _require.getMaybePagePath)(pathname, this.distDir, (_this_nextConfig_i18n = this.nextConfig.i18n) == null ? void 0 : _this_nextConfig_i18n.locales, this.enabledDirectories.app);
25
+ }
26
+ getBuildId() {
27
+ const buildIdFile = (0, _path.join)(this.distDir, _constants.BUILD_ID_FILE);
28
+ try {
29
+ return _fs.default.readFileSync(buildIdFile, "utf8").trim();
30
+ } catch (err) {
31
+ if (err.code === "ENOENT") {
32
+ throw new Error(\`Could not find a production build in the '\${this.distDir}' directory. Try building your app with 'next build' before starting the production server. https://nextjs.org/docs/messages/production-start-no-build-id\`);
33
+ }
34
+ throw err;
35
+ }
36
+ }
37
+ getMiddlewareManifest() {
38
+ if (this.minimalMode) return null;
39
+ const manifest = require(this.middlewareManifestPath);
40
+ return manifest;
41
+ }
42
+ async loadCustomCacheHandlers() {
43
+ const { cacheHandlers } = this.nextConfig.experimental;
44
+ if (!cacheHandlers) return;
45
+ // If we've already initialized the cache handlers interface, don't do it
46
+ // again.
47
+ if (!(0, _handlers.initializeCacheHandlers)()) return;
48
+ for (const [kind, handler] of Object.entries(cacheHandlers)){
49
+ if (!handler) continue;
50
+ (0, _handlers.setCacheHandler)(kind, (0, _interopdefault.interopDefault)(await dynamicImportEsmDefault((0, _formatdynamicimportpath.formatDynamicImportPath)(this.distDir, handler))));
51
+ }
52
+ }
53
+ async getIncrementalCache({ requestHeaders, requestProtocol }) {
54
+ const dev = !!this.renderOpts.dev;
55
+ let CacheHandler;
56
+ const { cacheHandler } = this.nextConfig;
57
+ if (cacheHandler) {
58
+ CacheHandler = (0, _interopdefault.interopDefault)(await dynamicImportEsmDefault((0, _formatdynamicimportpath.formatDynamicImportPath)(this.distDir, cacheHandler)));
59
+ }
60
+ await this.loadCustomCacheHandlers();
61
+ // incremental-cache is request specific
62
+ // although can have shared caches in module scope
63
+ // per-cache handler
64
+ return new _incrementalcache.IncrementalCache({
65
+ fs: this.getCacheFilesystem(),
66
+ dev,
67
+ requestHeaders,
68
+ requestProtocol,
69
+ allowedRevalidateHeaderKeys: this.nextConfig.experimental.allowedRevalidateHeaderKeys,
70
+ minimalMode: this.minimalMode,
71
+ serverDistDir: this.serverDistDir,
72
+ fetchCacheKeyPrefix: this.nextConfig.experimental.fetchCacheKeyPrefix,
73
+ maxMemoryCacheSize: this.nextConfig.cacheMaxMemorySize,
74
+ flushToDisk: !this.minimalMode && this.nextConfig.experimental.isrFlushToDisk,
75
+ getPrerenderManifest: ()=>this.getPrerenderManifest(),
76
+ CurCacheHandler: CacheHandler
77
+ });
78
+ }
79
+ getEnabledDirectories(dev) {
80
+ const dir = dev ? this.dir : this.serverDistDir;
81
+ return {
82
+ app: (0, _findpagesdir.findDir)(dir, "app") ? true : false,
83
+ pages: (0, _findpagesdir.findDir)(dir, "pages") ? true : false
84
+ };
85
+ }
86
+ // ...
87
+ }`;
88
+ test("build ID", () => {
89
+ expect(patchCode(nextServerCode, buildIdRule)).toMatchInlineSnapshot(`
90
+ "class NextNodeServer extends _baseserver.default {
91
+ constructor(options){
92
+ // Initialize super class
93
+ super(options);
94
+ this.handleNextImageRequest = async (req, res, parsedUrl) => { /* ... */ };
95
+ }
96
+ async handleUpgrade() {
97
+ // The web server does not support web sockets, it's only used for HMR in
98
+ // development.
99
+ }
100
+ loadEnvConfig({ dev, forceReload, silent }) {
101
+ (0, _env.loadEnvConfig)(this.dir, dev, silent ? {
102
+ info: ()=>{},
103
+ error: ()=>{}
104
+ } : _log, forceReload);
105
+ }
106
+ async hasPage(pathname) {
107
+ var _this_nextConfig_i18n;
108
+ return !!(0, _require.getMaybePagePath)(pathname, this.distDir, (_this_nextConfig_i18n = this.nextConfig.i18n) == null ? void 0 : _this_nextConfig_i18n.locales, this.enabledDirectories.app);
109
+ }
110
+ getBuildId() {
111
+ return process.env.NEXT_BUILD_ID;
112
+ }
113
+ getMiddlewareManifest() {
114
+ if (this.minimalMode) return null;
115
+ const manifest = require(this.middlewareManifestPath);
116
+ return manifest;
117
+ }
118
+ async loadCustomCacheHandlers() {
119
+ const { cacheHandlers } = this.nextConfig.experimental;
120
+ if (!cacheHandlers) return;
121
+ // If we've already initialized the cache handlers interface, don't do it
122
+ // again.
123
+ if (!(0, _handlers.initializeCacheHandlers)()) return;
124
+ for (const [kind, handler] of Object.entries(cacheHandlers)){
125
+ if (!handler) continue;
126
+ (0, _handlers.setCacheHandler)(kind, (0, _interopdefault.interopDefault)(await dynamicImportEsmDefault((0, _formatdynamicimportpath.formatDynamicImportPath)(this.distDir, handler))));
127
+ }
128
+ }
129
+ async getIncrementalCache({ requestHeaders, requestProtocol }) {
130
+ const dev = !!this.renderOpts.dev;
131
+ let CacheHandler;
132
+ const { cacheHandler } = this.nextConfig;
133
+ if (cacheHandler) {
134
+ CacheHandler = (0, _interopdefault.interopDefault)(await dynamicImportEsmDefault((0, _formatdynamicimportpath.formatDynamicImportPath)(this.distDir, cacheHandler)));
135
+ }
136
+ await this.loadCustomCacheHandlers();
137
+ // incremental-cache is request specific
138
+ // although can have shared caches in module scope
139
+ // per-cache handler
140
+ return new _incrementalcache.IncrementalCache({
141
+ fs: this.getCacheFilesystem(),
142
+ dev,
143
+ requestHeaders,
144
+ requestProtocol,
145
+ allowedRevalidateHeaderKeys: this.nextConfig.experimental.allowedRevalidateHeaderKeys,
146
+ minimalMode: this.minimalMode,
147
+ serverDistDir: this.serverDistDir,
148
+ fetchCacheKeyPrefix: this.nextConfig.experimental.fetchCacheKeyPrefix,
149
+ maxMemoryCacheSize: this.nextConfig.cacheMaxMemorySize,
150
+ flushToDisk: !this.minimalMode && this.nextConfig.experimental.isrFlushToDisk,
151
+ getPrerenderManifest: ()=>this.getPrerenderManifest(),
152
+ CurCacheHandler: CacheHandler
153
+ });
154
+ }
155
+ getEnabledDirectories(dev) {
156
+ const dir = dev ? this.dir : this.serverDistDir;
157
+ return {
158
+ app: (0, _findpagesdir.findDir)(dir, "app") ? true : false,
159
+ pages: (0, _findpagesdir.findDir)(dir, "pages") ? true : false
160
+ };
161
+ }
162
+ // ...
163
+ }"
164
+ `);
165
+ });
166
+ test("middleware manifest", () => {
167
+ expect(patchCode(nextServerCode, createMiddlewareManifestRule("manifest"))).toMatchInlineSnapshot(`
168
+ "class NextNodeServer extends _baseserver.default {
169
+ constructor(options){
170
+ // Initialize super class
171
+ super(options);
172
+ this.handleNextImageRequest = async (req, res, parsedUrl) => { /* ... */ };
173
+ }
174
+ async handleUpgrade() {
175
+ // The web server does not support web sockets, it's only used for HMR in
176
+ // development.
177
+ }
178
+ loadEnvConfig({ dev, forceReload, silent }) {
179
+ (0, _env.loadEnvConfig)(this.dir, dev, silent ? {
180
+ info: ()=>{},
181
+ error: ()=>{}
182
+ } : _log, forceReload);
183
+ }
184
+ async hasPage(pathname) {
185
+ var _this_nextConfig_i18n;
186
+ return !!(0, _require.getMaybePagePath)(pathname, this.distDir, (_this_nextConfig_i18n = this.nextConfig.i18n) == null ? void 0 : _this_nextConfig_i18n.locales, this.enabledDirectories.app);
187
+ }
188
+ getBuildId() {
189
+ const buildIdFile = (0, _path.join)(this.distDir, _constants.BUILD_ID_FILE);
190
+ try {
191
+ return _fs.default.readFileSync(buildIdFile, "utf8").trim();
192
+ } catch (err) {
193
+ if (err.code === "ENOENT") {
194
+ throw new Error(\`Could not find a production build in the '\${this.distDir}' directory. Try building your app with 'next build' before starting the production server. https://nextjs.org/docs/messages/production-start-no-build-id\`);
195
+ }
196
+ throw err;
197
+ }
198
+ }
199
+ getMiddlewareManifest() {
200
+ return "manifest";
201
+ }
202
+ async loadCustomCacheHandlers() {
203
+ const { cacheHandlers } = this.nextConfig.experimental;
204
+ if (!cacheHandlers) return;
205
+ // If we've already initialized the cache handlers interface, don't do it
206
+ // again.
207
+ if (!(0, _handlers.initializeCacheHandlers)()) return;
208
+ for (const [kind, handler] of Object.entries(cacheHandlers)){
209
+ if (!handler) continue;
210
+ (0, _handlers.setCacheHandler)(kind, (0, _interopdefault.interopDefault)(await dynamicImportEsmDefault((0, _formatdynamicimportpath.formatDynamicImportPath)(this.distDir, handler))));
211
+ }
212
+ }
213
+ async getIncrementalCache({ requestHeaders, requestProtocol }) {
214
+ const dev = !!this.renderOpts.dev;
215
+ let CacheHandler;
216
+ const { cacheHandler } = this.nextConfig;
217
+ if (cacheHandler) {
218
+ CacheHandler = (0, _interopdefault.interopDefault)(await dynamicImportEsmDefault((0, _formatdynamicimportpath.formatDynamicImportPath)(this.distDir, cacheHandler)));
219
+ }
220
+ await this.loadCustomCacheHandlers();
221
+ // incremental-cache is request specific
222
+ // although can have shared caches in module scope
223
+ // per-cache handler
224
+ return new _incrementalcache.IncrementalCache({
225
+ fs: this.getCacheFilesystem(),
226
+ dev,
227
+ requestHeaders,
228
+ requestProtocol,
229
+ allowedRevalidateHeaderKeys: this.nextConfig.experimental.allowedRevalidateHeaderKeys,
230
+ minimalMode: this.minimalMode,
231
+ serverDistDir: this.serverDistDir,
232
+ fetchCacheKeyPrefix: this.nextConfig.experimental.fetchCacheKeyPrefix,
233
+ maxMemoryCacheSize: this.nextConfig.cacheMaxMemorySize,
234
+ flushToDisk: !this.minimalMode && this.nextConfig.experimental.isrFlushToDisk,
235
+ getPrerenderManifest: ()=>this.getPrerenderManifest(),
236
+ CurCacheHandler: CacheHandler
237
+ });
238
+ }
239
+ getEnabledDirectories(dev) {
240
+ const dir = dev ? this.dir : this.serverDistDir;
241
+ return {
242
+ app: (0, _findpagesdir.findDir)(dir, "app") ? true : false,
243
+ pages: (0, _findpagesdir.findDir)(dir, "pages") ? true : false
244
+ };
245
+ }
246
+ // ...
247
+ }"
248
+ `);
249
+ });
250
+ test("cache handler", () => {
251
+ expect(patchCode(nextServerCode, createCacheHandlerRule("manifest"))).toMatchInlineSnapshot(`
252
+ "class NextNodeServer extends _baseserver.default {
253
+ constructor(options){
254
+ // Initialize super class
255
+ super(options);
256
+ this.handleNextImageRequest = async (req, res, parsedUrl) => { /* ... */ };
257
+ }
258
+ async handleUpgrade() {
259
+ // The web server does not support web sockets, it's only used for HMR in
260
+ // development.
261
+ }
262
+ loadEnvConfig({ dev, forceReload, silent }) {
263
+ (0, _env.loadEnvConfig)(this.dir, dev, silent ? {
264
+ info: ()=>{},
265
+ error: ()=>{}
266
+ } : _log, forceReload);
267
+ }
268
+ async hasPage(pathname) {
269
+ var _this_nextConfig_i18n;
270
+ return !!(0, _require.getMaybePagePath)(pathname, this.distDir, (_this_nextConfig_i18n = this.nextConfig.i18n) == null ? void 0 : _this_nextConfig_i18n.locales, this.enabledDirectories.app);
271
+ }
272
+ getBuildId() {
273
+ const buildIdFile = (0, _path.join)(this.distDir, _constants.BUILD_ID_FILE);
274
+ try {
275
+ return _fs.default.readFileSync(buildIdFile, "utf8").trim();
276
+ } catch (err) {
277
+ if (err.code === "ENOENT") {
278
+ throw new Error(\`Could not find a production build in the '\${this.distDir}' directory. Try building your app with 'next build' before starting the production server. https://nextjs.org/docs/messages/production-start-no-build-id\`);
279
+ }
280
+ throw err;
281
+ }
282
+ }
283
+ getMiddlewareManifest() {
284
+ if (this.minimalMode) return null;
285
+ const manifest = require(this.middlewareManifestPath);
286
+ return manifest;
287
+ }
288
+ async loadCustomCacheHandlers() {
289
+ const { cacheHandlers } = this.nextConfig.experimental;
290
+ if (!cacheHandlers) return;
291
+ // If we've already initialized the cache handlers interface, don't do it
292
+ // again.
293
+ if (!(0, _handlers.initializeCacheHandlers)()) return;
294
+ for (const [kind, handler] of Object.entries(cacheHandlers)){
295
+ if (!handler) continue;
296
+ (0, _handlers.setCacheHandler)(kind, (0, _interopdefault.interopDefault)(await dynamicImportEsmDefault((0, _formatdynamicimportpath.formatDynamicImportPath)(this.distDir, handler))));
297
+ }
298
+ }
299
+ async getIncrementalCache({ requestHeaders, requestProtocol }) {
300
+ const dev = !!this.renderOpts.dev;
301
+ let CacheHandler;
302
+ const cacheHandler = null;
303
+ CacheHandler = require('manifest').default;
304
+ if (cacheHandler) {
305
+ CacheHandler = (0, _interopdefault.interopDefault)(await dynamicImportEsmDefault((0, _formatdynamicimportpath.formatDynamicImportPath)(this.distDir, cacheHandler)));
306
+ }
307
+ await this.loadCustomCacheHandlers();
308
+ // incremental-cache is request specific
309
+ // although can have shared caches in module scope
310
+ // per-cache handler
311
+ return new _incrementalcache.IncrementalCache({
312
+ fs: this.getCacheFilesystem(),
313
+ dev,
314
+ requestHeaders,
315
+ requestProtocol,
316
+ allowedRevalidateHeaderKeys: this.nextConfig.experimental.allowedRevalidateHeaderKeys,
317
+ minimalMode: this.minimalMode,
318
+ serverDistDir: this.serverDistDir,
319
+ fetchCacheKeyPrefix: this.nextConfig.experimental.fetchCacheKeyPrefix,
320
+ maxMemoryCacheSize: this.nextConfig.cacheMaxMemorySize,
321
+ flushToDisk: !this.minimalMode && this.nextConfig.experimental.isrFlushToDisk,
322
+ getPrerenderManifest: ()=>this.getPrerenderManifest(),
323
+ CurCacheHandler: CacheHandler
324
+ });
325
+ }
326
+ getEnabledDirectories(dev) {
327
+ const dir = dev ? this.dir : this.serverDistDir;
328
+ return {
329
+ app: (0, _findpagesdir.findDir)(dir, "app") ? true : false,
330
+ pages: (0, _findpagesdir.findDir)(dir, "pages") ? true : false
331
+ };
332
+ }
333
+ // ...
334
+ }"
335
+ `);
336
+ });
337
+ test("composable cache handler", () => {
338
+ expect(patchCode(nextServerCode, createComposableCacheHandlersRule("manifest"))).toMatchInlineSnapshot(`
339
+ "class NextNodeServer extends _baseserver.default {
340
+ constructor(options){
341
+ // Initialize super class
342
+ super(options);
343
+ this.handleNextImageRequest = async (req, res, parsedUrl) => { /* ... */ };
344
+ }
345
+ async handleUpgrade() {
346
+ // The web server does not support web sockets, it's only used for HMR in
347
+ // development.
348
+ }
349
+ loadEnvConfig({ dev, forceReload, silent }) {
350
+ (0, _env.loadEnvConfig)(this.dir, dev, silent ? {
351
+ info: ()=>{},
352
+ error: ()=>{}
353
+ } : _log, forceReload);
354
+ }
355
+ async hasPage(pathname) {
356
+ var _this_nextConfig_i18n;
357
+ return !!(0, _require.getMaybePagePath)(pathname, this.distDir, (_this_nextConfig_i18n = this.nextConfig.i18n) == null ? void 0 : _this_nextConfig_i18n.locales, this.enabledDirectories.app);
358
+ }
359
+ getBuildId() {
360
+ const buildIdFile = (0, _path.join)(this.distDir, _constants.BUILD_ID_FILE);
361
+ try {
362
+ return _fs.default.readFileSync(buildIdFile, "utf8").trim();
363
+ } catch (err) {
364
+ if (err.code === "ENOENT") {
365
+ throw new Error(\`Could not find a production build in the '\${this.distDir}' directory. Try building your app with 'next build' before starting the production server. https://nextjs.org/docs/messages/production-start-no-build-id\`);
366
+ }
367
+ throw err;
368
+ }
369
+ }
370
+ getMiddlewareManifest() {
371
+ if (this.minimalMode) return null;
372
+ const manifest = require(this.middlewareManifestPath);
373
+ return manifest;
374
+ }
375
+ async loadCustomCacheHandlers() {
376
+ const cacheHandlers = null;
377
+ const handlersSymbol = Symbol.for('@next/cache-handlers');
378
+ const handlersMapSymbol = Symbol.for('@next/cache-handlers-map');
379
+ const handlersSetSymbol = Symbol.for('@next/cache-handlers-set');
380
+ globalThis[handlersMapSymbol] = new Map();
381
+ globalThis[handlersMapSymbol].set("default", require('manifest').default);
382
+ globalThis[handlersSetSymbol] = new Set(globalThis[handlersMapSymbol].values());
383
+ if (!cacheHandlers) return;
384
+ // If we've already initialized the cache handlers interface, don't do it
385
+ // again.
386
+ if (!(0, _handlers.initializeCacheHandlers)()) return;
387
+ for (const [kind, handler] of Object.entries(cacheHandlers)){
388
+ if (!handler) continue;
389
+ (0, _handlers.setCacheHandler)(kind, (0, _interopdefault.interopDefault)(await dynamicImportEsmDefault((0, _formatdynamicimportpath.formatDynamicImportPath)(this.distDir, handler))));
390
+ }
391
+ }
392
+ async getIncrementalCache({ requestHeaders, requestProtocol }) {
393
+ const dev = !!this.renderOpts.dev;
394
+ let CacheHandler;
395
+ const { cacheHandler } = this.nextConfig;
396
+ if (cacheHandler) {
397
+ CacheHandler = (0, _interopdefault.interopDefault)(await dynamicImportEsmDefault((0, _formatdynamicimportpath.formatDynamicImportPath)(this.distDir, cacheHandler)));
398
+ }
399
+ await this.loadCustomCacheHandlers();
400
+ // incremental-cache is request specific
401
+ // although can have shared caches in module scope
402
+ // per-cache handler
403
+ return new _incrementalcache.IncrementalCache({
404
+ fs: this.getCacheFilesystem(),
405
+ dev,
406
+ requestHeaders,
407
+ requestProtocol,
408
+ allowedRevalidateHeaderKeys: this.nextConfig.experimental.allowedRevalidateHeaderKeys,
409
+ minimalMode: this.minimalMode,
410
+ serverDistDir: this.serverDistDir,
411
+ fetchCacheKeyPrefix: this.nextConfig.experimental.fetchCacheKeyPrefix,
412
+ maxMemoryCacheSize: this.nextConfig.cacheMaxMemorySize,
413
+ flushToDisk: !this.minimalMode && this.nextConfig.experimental.isrFlushToDisk,
414
+ getPrerenderManifest: ()=>this.getPrerenderManifest(),
415
+ CurCacheHandler: CacheHandler
416
+ });
417
+ }
418
+ getEnabledDirectories(dev) {
419
+ const dir = dev ? this.dir : this.serverDistDir;
420
+ return {
421
+ app: (0, _findpagesdir.findDir)(dir, "app") ? true : false,
422
+ pages: (0, _findpagesdir.findDir)(dir, "pages") ? true : false
423
+ };
424
+ }
425
+ // ...
426
+ }"
427
+ `);
428
+ });
429
+ });
@@ -0,0 +1,8 @@
1
+ /**
2
+ * Removed unused `require.resolve` calls in Open Next.
3
+ */
4
+ import { type BuildOptions } from "@opennextjs/aws/build/helper.js";
5
+ import type { ContentUpdater, Plugin } from "@opennextjs/aws/plugins/content-updater.js";
6
+ export declare function patchResolveCache(updater: ContentUpdater, buildOpts: BuildOptions): Plugin;
7
+ export declare const cacheHandlerRule = "\nrule:\n pattern: var cacheHandlerPath = __require.resolve(\"./cache.cjs\");\nfix: |-\n var cacheHandlerPath = \"\";\n";
8
+ export declare const compositeCacheHandlerRule = "\nrule:\n pattern: var composableCacheHandlerPath = __require.resolve(\"./composable-cache.cjs\");\nfix: |-\n var composableCacheHandlerPath = \"\";\n";
@@ -0,0 +1,39 @@
1
+ /**
2
+ * Removed unused `require.resolve` calls in Open Next.
3
+ */
4
+ import path from "node:path";
5
+ import { getPackagePath } from "@opennextjs/aws/build/helper.js";
6
+ import { patchCode } from "@opennextjs/aws/build/patch/astCodePatcher.js";
7
+ import { getCrossPlatformPathRegex } from "@opennextjs/aws/utils/regex.js";
8
+ export function patchResolveCache(updater, buildOpts) {
9
+ const { outputDir } = buildOpts;
10
+ const packagePath = getPackagePath(buildOpts);
11
+ const outputPath = path.join(outputDir, "server-functions/default");
12
+ const indexPath = path.relative(buildOpts.appBuildOutputPath, path.join(outputPath, packagePath, `index.mjs`));
13
+ console.error({ index: indexPath });
14
+ return updater.updateContent("patch-resolve-cache", [
15
+ {
16
+ field: {
17
+ filter: getCrossPlatformPathRegex(indexPath),
18
+ contentFilter: /cacheHandlerPath/,
19
+ callback: async ({ contents }) => {
20
+ contents = patchCode(contents, cacheHandlerRule);
21
+ contents = patchCode(contents, compositeCacheHandlerRule);
22
+ return contents;
23
+ },
24
+ },
25
+ },
26
+ ]);
27
+ }
28
+ export const cacheHandlerRule = `
29
+ rule:
30
+ pattern: var cacheHandlerPath = __require.resolve("./cache.cjs");
31
+ fix: |-
32
+ var cacheHandlerPath = "";
33
+ `;
34
+ export const compositeCacheHandlerRule = `
35
+ rule:
36
+ pattern: var composableCacheHandlerPath = __require.resolve("./composable-cache.cjs");
37
+ fix: |-
38
+ var composableCacheHandlerPath = "";
39
+ `;
@@ -108,5 +108,10 @@ function populateProcessEnv(url, env) {
108
108
  port: url.port,
109
109
  },
110
110
  });
111
+ /* We need to set this environment variable to make redirects work properly in preview mode.
112
+ * Next sets this in standalone mode during `startServer`. Without this the protocol would always be `https` here:
113
+ * https://github.com/vercel/next.js/blob/6b1e48080e896e0d44a05fe009cb79d2d3f91774/packages/next/src/server/app-render/action-handler.ts#L307-L316
114
+ */
115
+ process.env.__NEXT_PRIVATE_ORIGIN = url.origin;
111
116
  }
112
117
  /* eslint-enable no-var */
@@ -0,0 +1,2 @@
1
+ declare const _default: {};
2
+ export default _default;
@@ -0,0 +1,2 @@
1
+ throw "OpenNext shim";
2
+ export default {};
@@ -1,5 +1,6 @@
1
1
  export { DOQueueHandler } from "./.build/durable-objects/queue.js";
2
2
  export { DOShardedTagCache } from "./.build/durable-objects/sharded-tag-cache.js";
3
+ export { BucketCachePurge } from "./.build/durable-objects/bucket-cache-purge.js";
3
4
  declare const _default: {
4
5
  fetch(request: Request<unknown, IncomingRequestCfProperties<unknown>>, env: CloudflareEnv, ctx: ExecutionContext): Promise<any>;
5
6
  };
@@ -6,6 +6,8 @@ import { handler as middlewareHandler } from "./middleware/handler.mjs";
6
6
  export { DOQueueHandler } from "./.build/durable-objects/queue.js";
7
7
  //@ts-expect-error: Will be resolved by wrangler build
8
8
  export { DOShardedTagCache } from "./.build/durable-objects/sharded-tag-cache.js";
9
+ //@ts-expect-error: Will be resolved by wrangler build
10
+ export { BucketCachePurge } from "./.build/durable-objects/bucket-cache-purge.js";
9
11
  export default {
10
12
  async fetch(request, env, ctx) {
11
13
  return runWithCloudflareRequestContext(request, env, ctx, async () => {
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.4",
4
+ "version": "1.2.0",
5
5
  "type": "module",
6
6
  "bin": {
7
7
  "opennextjs-cloudflare": "dist/cli/index.js"
@@ -43,7 +43,7 @@
43
43
  "homepage": "https://github.com/opennextjs/opennextjs-cloudflare",
44
44
  "dependencies": {
45
45
  "@dotenvx/dotenvx": "1.31.0",
46
- "@opennextjs/aws": "^3.6.2",
46
+ "@opennextjs/aws": "3.6.5",
47
47
  "enquirer": "^2.4.1",
48
48
  "glob": "^11.0.0",
49
49
  "ts-tqdm": "^0.8.6"
@@ -68,7 +68,7 @@
68
68
  "vitest": "^2.1.1"
69
69
  },
70
70
  "peerDependencies": {
71
- "wrangler": "^4.14.0"
71
+ "wrangler": "^4.19.1"
72
72
  },
73
73
  "scripts": {
74
74
  "clean": "rimraf dist",
@@ -1,14 +0,0 @@
1
- import { type BuildOptions } from "@opennextjs/aws/build/helper.js";
2
- /**
3
- * Sets up the OpenNext cache handler in a Next.js build.
4
- *
5
- * The cache handler used by Next.js is normally defined in the config file as a path. At runtime,
6
- * Next.js would then do a dynamic require on a transformed version of the path to retrieve the
7
- * cache handler and create a new instance of it.
8
- *
9
- * This is problematic in workerd due to the dynamic import of the file that is not known from
10
- * build-time. Therefore, we have to manually override the default way that the cache handler is
11
- * instantiated with a dynamic require that uses a string literal for the path.
12
- */
13
- export declare function patchCache(code: string, buildOpts: BuildOptions): Promise<string>;
14
- export declare function patchComposableCache(code: string, buildOpts: BuildOptions): Promise<string>;