Skip to content

Commit b27a0ad

Browse files
committed
precompile the durable object
1 parent 687eb51 commit b27a0ad

File tree

7 files changed

+64
-36
lines changed

7 files changed

+64
-36
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: 17 additions & 22 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

@@ -76,25 +72,24 @@ export class DurableObjectQueueHandler extends DurableObject<CloudflareEnv> {
7672
this.ctx.waitUntil(revalidationPromise);
7773
}
7874

79-
private async executeRevalidation(msg: ExtendedQueueMessage) {
75+
private async executeRevalidation(msg: QueueMessage) {
8076
try {
8177
const {
8278
MessageBody: { host, url },
83-
previewModeId,
8479
} = msg;
8580
const protocol = host.includes("localhost") ? "http" : "https";
8681

8782
const response = await this.service.fetch(`${protocol}://${host}${url}`, {
8883
method: "HEAD",
8984
headers: {
90-
"x-prerender-revalidate": previewModeId,
85+
// This is defined during build
86+
"x-prerender-revalidate": process.env.__NEXT_PREVIEW_MODE_ID!,
9187
"x-isr": "1",
9288
},
9389
signal: AbortSignal.timeout(DEFAULT_REVALIDATION_TIMEOUT_MS),
9490
});
9591
// Now we need to handle errors from the fetch
9692
if (response.status === 200 && response.headers.get("x-nextjs-cache") !== "REVALIDATED") {
97-
// 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.
9893
this.routeInFailedState.delete(msg.MessageDeduplicationId);
9994
throw new FatalError(
10095
`The revalidation for ${host}${url} cannot be done. This error should never happen.`
@@ -122,7 +117,7 @@ export class DurableObjectQueueHandler extends DurableObject<CloudflareEnv> {
122117
throw new RecoverableError(`An unknown error occurred while revalidating ${host}${url}`);
123118
}
124119
// Everything went well, we can update the sync table
125-
// We use unixepoch here because without IO the date doesn't change and it will make the e2e tests fail
120+
// We use unixepoch here,it also works with Date.now()/1000, but not with Date.now() alone- we need to investigate this
126121
this.sql.exec(
127122
"INSERT OR REPLACE INTO sync (id, lastSuccess) VALUES (?, unixepoch())",
128123
// We cannot use the deduplication id because it's not unique per route - every time a route is revalidated, the deduplication id is different.
@@ -159,7 +154,7 @@ export class DurableObjectQueueHandler extends DurableObject<CloudflareEnv> {
159154
}
160155
}
161156

162-
async addToFailedState(msg: ExtendedQueueMessage) {
157+
async addToFailedState(msg: QueueMessage) {
163158
const existingFailedState = this.routeInFailedState.get(msg.MessageDeduplicationId);
164159
let nextAlarm = Date.now() + 2_000;
165160

@@ -233,21 +228,21 @@ export class DurableObjectQueueHandler extends DurableObject<CloudflareEnv> {
233228
}
234229

235230
/**
236-
*
237-
* @param msg
231+
*
232+
* @param msg
238233
* @returns `true` if the route has been revalidated since the lastModified from the message, `false` otherwise
239234
*/
240-
checkSyncTable(msg: ExtendedQueueMessage) {
235+
checkSyncTable(msg: QueueMessage) {
241236
try {
242-
const isNewer = this.sql.exec<{ isNewer: number }>(
243-
"SELECT COUNT(*) as isNewer FROM sync WHERE id = ? AND lastSuccess > ?",
244-
`${msg.MessageBody.host}${msg.MessageBody.url}`,
245-
Math.round(msg.MessageBody.lastModified/1000)
246-
).one().isNewer;
237+
const isNewer = this.sql
238+
.exec<{
239+
isNewer: number;
240+
}>("SELECT COUNT(*) as isNewer FROM sync WHERE id = ? AND lastSuccess > ?", `${msg.MessageBody.host}${msg.MessageBody.url}`, Math.round(msg.MessageBody.lastModified / 1000))
241+
.one().isNewer;
247242

248243
return isNewer > 0;
249-
// eslint-disable-next-line @typescript-eslint/no-unused-vars
250-
}catch(e: unknown){
244+
// eslint-disable-next-line @typescript-eslint/no-unused-vars
245+
} catch (e: unknown) {
251246
return false;
252247
}
253248
}

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)