Skip to content

Commit 3354564

Browse files
committed
precompile the durable object
1 parent 5eebceb commit 3354564

File tree

7 files changed

+63
-35
lines changed

7 files changed

+63
-35
lines changed

packages/cloudflare/src/api/durable-objects/queue.spec.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,7 @@ const createDurableObjectQueue = ({
3030
getAlarm: vi.fn(),
3131
sql: {
3232
exec: vi.fn().mockImplementation(() => ({
33-
one: vi.fn()
33+
one: vi.fn(),
3434
})),
3535
},
3636
},
@@ -67,6 +67,7 @@ const createMessage = (dedupId: string, lastModified = Date.now()) => ({
6767
describe("DurableObjectQueue", () => {
6868
describe("successful revalidation", () => {
6969
it("should process a single revalidation", async () => {
70+
process.env.__NEXT_PREVIEW_MODE_ID = "test";
7071
const queue = createDurableObjectQueue({ fetchDuration: 10 });
7172
const firstRequest = await queue.revalidate(createMessage("id"));
7273
expect(firstRequest).toBeUndefined();

packages/cloudflare/src/api/durable-objects/queue.ts

Lines changed: 16 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -11,12 +11,8 @@ import { DurableObject } from "cloudflare:workers";
1111
const MAX_REVALIDATION_BY_DURABLE_OBJECT = 5;
1212
const DEFAULT_REVALIDATION_TIMEOUT_MS = 10_000;
1313

14-
interface ExtendedQueueMessage extends QueueMessage {
15-
previewModeId: string;
16-
}
17-
1814
interface FailedState {
19-
msg: ExtendedQueueMessage;
15+
msg: QueueMessage;
2016
retryCount: number;
2117
nextAlarm: number;
2218
}
@@ -48,7 +44,7 @@ export class DurableObjectQueueHandler extends DurableObject<CloudflareEnv> {
4844
ctx.blockConcurrencyWhile(() => this.initState());
4945
}
5046

51-
async revalidate(msg: ExtendedQueueMessage) {
47+
async revalidate(msg: QueueMessage) {
5248
// If there is already an ongoing revalidation, we don't need to revalidate again
5349
if (this.ongoingRevalidations.has(msg.MessageDeduplicationId)) return;
5450

@@ -72,25 +68,24 @@ export class DurableObjectQueueHandler extends DurableObject<CloudflareEnv> {
7268
this.ctx.waitUntil(revalidationPromise);
7369
}
7470

75-
private async executeRevalidation(msg: ExtendedQueueMessage) {
71+
private async executeRevalidation(msg: QueueMessage) {
7672
try {
7773
const {
7874
MessageBody: { host, url },
79-
previewModeId,
8075
} = msg;
8176
const protocol = host.includes("localhost") ? "http" : "https";
8277

8378
const response = await this.service.fetch(`${protocol}://${host}${url}`, {
8479
method: "HEAD",
8580
headers: {
86-
"x-prerender-revalidate": previewModeId,
81+
// This is defined during build
82+
"x-prerender-revalidate": process.env.__NEXT_PREVIEW_MODE_ID!,
8783
"x-isr": "1",
8884
},
8985
signal: AbortSignal.timeout(DEFAULT_REVALIDATION_TIMEOUT_MS),
9086
});
9187
// Now we need to handle errors from the fetch
9288
if (response.status === 200 && response.headers.get("x-nextjs-cache") !== "REVALIDATED") {
93-
// TODO: when restoring from the failed state during a new deployment, previewModeId will be different and we'll be in this case. Figure out how to handle this.
9489
this.routeInFailedState.delete(msg.MessageDeduplicationId);
9590
throw new FatalError(
9691
`The revalidation for ${host}${url} cannot be done. This error should never happen.`
@@ -149,7 +144,7 @@ export class DurableObjectQueueHandler extends DurableObject<CloudflareEnv> {
149144
}
150145
}
151146

152-
async addToFailedState(msg: ExtendedQueueMessage) {
147+
async addToFailedState(msg: QueueMessage) {
153148
const existingFailedState = this.routeInFailedState.get(msg.MessageDeduplicationId);
154149
let nextAlarm = Date.now() + 2_000;
155150

@@ -223,21 +218,21 @@ export class DurableObjectQueueHandler extends DurableObject<CloudflareEnv> {
223218
}
224219

225220
/**
226-
*
227-
* @param msg
221+
*
222+
* @param msg
228223
* @returns `true` if the route has been revalidated since the lastModified from the message, `false` otherwise
229224
*/
230-
checkSyncTable(msg: ExtendedQueueMessage) {
225+
checkSyncTable(msg: QueueMessage) {
231226
try {
232-
const isNewer = this.sql.exec<{ isNewer: number }>(
233-
"SELECT COUNT(*) as isNewer FROM sync WHERE id = ? AND lastSuccess > ?",
234-
`${msg.MessageBody.host}${msg.MessageBody.url}`,
235-
Math.round(msg.MessageBody.lastModified/1000)
236-
).one().isNewer;
227+
const isNewer = this.sql
228+
.exec<{
229+
isNewer: number;
230+
}>("SELECT COUNT(*) as isNewer FROM sync WHERE id = ? AND lastSuccess > ?", `${msg.MessageBody.host}${msg.MessageBody.url}`, Math.round(msg.MessageBody.lastModified / 1000))
231+
.one().isNewer;
237232

238233
return isNewer > 0;
239-
// eslint-disable-next-line @typescript-eslint/no-unused-vars
240-
}catch(e: unknown){
234+
// eslint-disable-next-line @typescript-eslint/no-unused-vars
235+
} catch (e: unknown) {
241236
return false;
242237
}
243238
}

packages/cloudflare/src/api/durable-queue.ts

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,10 +11,8 @@ export default {
1111

1212
const id = durableObject.idFromName(msg.MessageGroupId);
1313
const stub = durableObject.get(id);
14-
const previewModeId = process.env.__NEXT_PREVIEW_MODE_ID!;
1514
await stub.revalidate({
1615
...msg,
17-
previewModeId,
1816
});
1917
},
2018
} satisfies Queue;

packages/cloudflare/src/cli/build/build.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ import type { ProjectOptions } from "../project-options.js";
1414
import { bundleServer } from "./bundle-server.js";
1515
import { compileCacheAssetsManifestSqlFile } from "./open-next/compile-cache-assets-manifest.js";
1616
import { compileEnvFiles } from "./open-next/compile-env-files.js";
17+
import { compileDurableObjects } from "./open-next/compileDurableObjects.js";
1718
import { copyCacheAssets } from "./open-next/copyCacheAssets.js";
1819
import { createServerBundle } from "./open-next/createServerBundle.js";
1920
import {
@@ -97,6 +98,8 @@ export async function build(projectOpts: ProjectOptions): Promise<void> {
9798

9899
await createServerBundle(options);
99100

101+
await compileDurableObjects(options);
102+
100103
await bundleServer(options);
101104

102105
if (!projectOpts.skipWranglerConfigCheck) {

packages/cloudflare/src/cli/build/bundle-server.ts

Lines changed: 0 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -58,14 +58,6 @@ export async function bundleServer(buildOpts: BuildOptions): Promise<void> {
5858
const serverFiles = path.join(baseManifestPath, "required-server-files.json");
5959
const nextConfig = JSON.parse(fs.readFileSync(serverFiles, "utf-8")).config;
6060

61-
// TODO: This is a temporary solution to get the previewModeId from the prerender-manifest.json
62-
// We should find a better way to get this value, probably directly provided from aws
63-
// probably in an env variable exactly as for BUILD_ID
64-
const prerenderManifest = path.join(baseManifestPath, "prerender-manifest.json");
65-
const prerenderManifestContent = fs.readFileSync(prerenderManifest, "utf-8");
66-
const prerenderManifestJson = JSON.parse(prerenderManifestContent);
67-
const previewModeId = prerenderManifestJson.preview.previewModeId;
68-
6961
console.log(`\x1b[35m⚙️ Bundling the OpenNext server...\n\x1b[0m`);
7062

7163
await patchWebpackRuntime(buildOpts);
@@ -153,8 +145,6 @@ export async function bundleServer(buildOpts: BuildOptions): Promise<void> {
153145
"process.env.TURBOPACK": "false",
154146
// This define should be safe to use for Next 14.2+, earlier versions (13.5 and less) will cause trouble
155147
"process.env.__NEXT_EXPERIMENTAL_REACT": `${needsExperimentalReact(nextConfig)}`,
156-
// Used for the durable object queue handler
157-
"process.env.__NEXT_PREVIEW_MODE_ID": `"${previewModeId}"`,
158148
},
159149
platform: "node",
160150
banner: {
Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
import fs from "node:fs";
2+
import { createRequire } from "node:module";
3+
import path from "node:path";
4+
5+
import { type BuildOptions, getPackagePath } from "@opennextjs/aws/build/helper.js";
6+
import { build } from "esbuild";
7+
8+
export function compileDurableObjects(buildOpts: BuildOptions) {
9+
const _require = createRequire(import.meta.url);
10+
const entryPoints = [_require.resolve("@opennextjs/cloudflare/durable-objects/queue")];
11+
12+
const { outputDir } = buildOpts;
13+
14+
const baseManifestPath = path.join(
15+
outputDir,
16+
"server-functions/default",
17+
getPackagePath(buildOpts),
18+
".next"
19+
);
20+
21+
// TODO: Reuse the manifest
22+
const prerenderManifest = path.join(baseManifestPath, "prerender-manifest.json");
23+
const prerenderManifestContent = fs.readFileSync(prerenderManifest, "utf-8");
24+
const prerenderManifestJson = JSON.parse(prerenderManifestContent);
25+
const previewModeId = prerenderManifestJson.preview.previewModeId;
26+
27+
const BUILD_ID = fs.readFileSync(path.join(baseManifestPath, "BUILD_ID"), "utf-8");
28+
29+
return build({
30+
entryPoints,
31+
bundle: true,
32+
platform: "node",
33+
format: "esm",
34+
outdir: path.join(buildOpts.buildDir, "durable-objects"),
35+
external: ["cloudflare:workers"],
36+
define: {
37+
"process.env.__NEXT_PREVIEW_MODE_ID": `"${previewModeId}"`,
38+
"process.env.__NEXT_BUILD_ID": `"${BUILD_ID}"`,
39+
},
40+
});
41+
}

packages/cloudflare/src/cli/templates/worker.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ Object.defineProperty(globalThis, Symbol.for("__cloudflare-context__"), {
1818
});
1919

2020
//@ts-expect-error: Will be resolved by wrangler build
21-
export { DurableObjectQueueHandler } from "@opennextjs/cloudflare/durable-objects/queue";
21+
export { DurableObjectQueueHandler } from "./.build/durable-objects/queue.js";
2222

2323
// Populate process.env on the first request
2424
let processEnvPopulated = false;

0 commit comments

Comments
 (0)