Skip to content

Commit 7f17067

Browse files
committed
precompile the durable object
1 parent 9c46ced commit 7f17067

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
@@ -28,7 +28,7 @@ const createDurableObjectQueue = ({
2828
getAlarm: vi.fn(),
2929
sql: {
3030
exec: vi.fn().mockImplementation(() => ({
31-
one: vi.fn()
31+
one: vi.fn(),
3232
})),
3333
},
3434
},
@@ -65,6 +65,7 @@ const createMessage = (dedupId: string, lastModified = Date.now()) => ({
6565
describe("DurableObjectQueue", () => {
6666
describe("successful revalidation", () => {
6767
it("should process a single revalidation", async () => {
68+
process.env.__NEXT_PREVIEW_MODE_ID = "test";
6869
const queue = createDurableObjectQueue({ fetchDuration: 10 });
6970
const firstRequest = await queue.revalidate(createMessage("id"));
7071
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
nextAlarmMs: number;
2218
}
@@ -47,7 +43,7 @@ export class DurableObjectQueueHandler extends DurableObject<CloudflareEnv> {
4743
ctx.blockConcurrencyWhile(() => this.initState());
4844
}
4945

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

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

74-
private async executeRevalidation(msg: ExtendedQueueMessage) {
70+
private async executeRevalidation(msg: QueueMessage) {
7571
try {
7672
const {
7773
MessageBody: { host, url },
78-
previewModeId,
7974
} = msg;
8075
const protocol = host.includes("localhost") ? "http" : "https";
8176

8277
const response = await this.service.fetch(`${protocol}://${host}${url}`, {
8378
method: "HEAD",
8479
headers: {
85-
"x-prerender-revalidate": previewModeId,
80+
// This is defined during build
81+
"x-prerender-revalidate": process.env.__NEXT_PREVIEW_MODE_ID!,
8682
"x-isr": "1",
8783
},
8884
signal: AbortSignal.timeout(DEFAULT_REVALIDATION_TIMEOUT_MS),
8985
});
9086
// Now we need to handle errors from the fetch
9187
if (response.status === 200 && response.headers.get("x-nextjs-cache") !== "REVALIDATED") {
92-
// 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.
9388
this.routeInFailedState.delete(msg.MessageDeduplicationId);
9489
throw new FatalError(
9590
`The revalidation for ${host}${url} cannot be done. This error should never happen.`
@@ -146,7 +141,7 @@ export class DurableObjectQueueHandler extends DurableObject<CloudflareEnv> {
146141
}
147142
}
148143

149-
async addToFailedState(msg: ExtendedQueueMessage) {
144+
async addToFailedState(msg: QueueMessage) {
150145
const existingFailedState = this.routeInFailedState.get(msg.MessageDeduplicationId);
151146

152147
let updatedFailedState: FailedState;
@@ -218,21 +213,21 @@ export class DurableObjectQueueHandler extends DurableObject<CloudflareEnv> {
218213
}
219214

220215
/**
221-
*
222-
* @param msg
216+
*
217+
* @param msg
223218
* @returns `true` if the route has been revalidated since the lastModified from the message, `false` otherwise
224219
*/
225-
checkSyncTable(msg: ExtendedQueueMessage) {
220+
checkSyncTable(msg: QueueMessage) {
226221
try {
227-
const isNewer = this.sql.exec<{ isNewer: number }>(
228-
"SELECT COUNT(*) as isNewer FROM sync WHERE id = ? AND lastSuccess > ?",
229-
`${msg.MessageBody.host}${msg.MessageBody.url}`,
230-
Math.round(msg.MessageBody.lastModified/1000)
231-
).one().isNewer;
222+
const isNewer = this.sql
223+
.exec<{
224+
isNewer: number;
225+
}>("SELECT COUNT(*) as isNewer FROM sync WHERE id = ? AND lastSuccess > ?", `${msg.MessageBody.host}${msg.MessageBody.url}`, Math.round(msg.MessageBody.lastModified / 1000))
226+
.one().isNewer;
232227

233228
return isNewer > 0;
234-
// eslint-disable-next-line @typescript-eslint/no-unused-vars
235-
}catch(e: unknown){
229+
// eslint-disable-next-line @typescript-eslint/no-unused-vars
230+
} catch (e: unknown) {
236231
return false;
237232
}
238233
}

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)