Skip to content

Commit af15fd1

Browse files
implement getCloudflareContext for production (#31)
* implement `getCloudflareContext` for production --------- Co-authored-by: Victor Berchet <[email protected]>
1 parent 9758666 commit af15fd1

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

44 files changed

+181
-59
lines changed

examples/api/app/api/hello/route.ts

Lines changed: 12 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,19 @@
11
import { headers } from "next/headers";
22

3+
import { getCloudflareContext } from "@opennextjs/cloudflare";
4+
35
export async function GET() {
4-
// Note: we use headers just so that the route is not built as a static one
56
const headersList = headers();
6-
const sayHi = !!headersList.get("should-say-hi");
7-
return new Response(sayHi ? "Hi World!" : "Hello World!");
7+
8+
const fromCloudflareContext = headersList.has("from-cloudflare-context");
9+
10+
if (!fromCloudflareContext) {
11+
return new Response("Hello World!");
12+
}
13+
14+
// Retrieve the bindings defined in wrangler.toml
15+
const { env } = await getCloudflareContext();
16+
return new Response(env.hello);
817
}
918

1019
export async function POST(request: Request) {

examples/api/e2e-tests/base.spec.ts

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,16 @@ test("the hello-world api GET route works as intended", async ({ page }) => {
1515
expect(await res.text()).toEqual("Hello World!");
1616
});
1717

18+
test("returns a hello world string from the cloudflare context env", async ({ page }) => {
19+
const res = await page.request.get("/api/hello", {
20+
headers: {
21+
"from-cloudflare-context": "true",
22+
},
23+
});
24+
expect(res.headers()["content-type"]).toContain("text/plain");
25+
expect(await res.text()).toEqual("Hello World from the cloudflare context!");
26+
});
27+
1828
test("the hello-world api POST route works as intended", async ({ page }) => {
1929
const res = await page.request.post("/api/hello", { data: "some body" });
2030
expect(res.headers()["content-type"]).toContain("text/plain");

examples/api/package.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,8 @@
1010
"build:worker": "pnpm cloudflare",
1111
"dev:worker": "wrangler dev --port 8770",
1212
"preview:worker": "pnpm build:worker && pnpm dev:worker",
13-
"e2e": "playwright test"
13+
"e2e": "playwright test",
14+
"cf-typegen": "wrangler types --env-interface CloudflareEnv"
1415
},
1516
"dependencies": {
1617
"next": "catalog:",

examples/api/tsconfig.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,6 @@
1818
}
1919
]
2020
},
21-
"include": ["next-env.d.ts", ".next/types/**/*.ts", "**/*.ts", "**/*.tsx"],
21+
"include": ["next-env.d.ts", ".next/types/**/*.ts", "**/*.ts", "**/*.tsx", "worker-configuration.d.ts"],
2222
"exclude": ["node_modules"]
2323
}
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
// Generated by Wrangler by running `wrangler types --env-interface CloudflareEnv`
2+
3+
interface CloudflareEnv {
4+
hello: "Hello World from the cloudflare context!";
5+
}

examples/api/wrangler.toml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,3 +5,6 @@ compatibility_date = "2024-09-16"
55
compatibility_flags = ["nodejs_compat_v2"]
66

77
experimental_assets = { directory = ".worker-next/assets", binding = "ASSETS" }
8+
9+
[vars]
10+
hello = 'Hello World from the cloudflare context!'

packages/cloudflare/package.json

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,15 @@
88
"test": "vitest --run",
99
"test:watch": "vitest"
1010
},
11-
"bin": "dist/index.mjs",
11+
"bin": "dist/cli/index.mjs",
12+
"main": "./dist/api/index.mjs",
13+
"types": "./dist/api/index.d.mts",
14+
"exports": {
15+
".": {
16+
"import": "./dist/api/index.mjs",
17+
"types": "./dist/api/index.d.mts"
18+
}
19+
},
1220
"files": [
1321
"README.md",
1422
"dist"
Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
import "server-only";
2+
3+
declare global {
4+
// eslint-disable-next-line @typescript-eslint/no-empty-interface
5+
interface CloudflareEnv {}
6+
}
7+
8+
export type CloudflareContext<
9+
CfProperties extends Record<string, unknown> = IncomingRequestCfProperties,
10+
Context = ExecutionContext,
11+
> = {
12+
/**
13+
* the worker's [bindings](https://developers.cloudflare.com/workers/runtime-apis/bindings/)
14+
*/
15+
env: CloudflareEnv;
16+
/**
17+
* the request's [cf properties](https://developers.cloudflare.com/workers/runtime-apis/request/#the-cf-property-requestinitcfproperties)
18+
*/
19+
cf: CfProperties;
20+
/**
21+
* the current [execution context](https://developers.cloudflare.com/workers/runtime-apis/context)
22+
*/
23+
ctx: Context;
24+
};
25+
26+
// Note: this symbol needs to be kept in sync with the one used in `src/cli/templates/worker.ts`
27+
const cloudflareContextSymbol = Symbol.for("__cloudflare-context__");
28+
29+
/**
30+
* Utility to get the current Cloudflare context
31+
*
32+
* Throws an error if the context could not be retrieved
33+
*
34+
* @returns the cloudflare context
35+
*/
36+
export async function getCloudflareContext<
37+
CfProperties extends Record<string, unknown> = IncomingRequestCfProperties,
38+
Context = ExecutionContext,
39+
>(): Promise<CloudflareContext<CfProperties, Context>> {
40+
const cloudflareContext = (
41+
globalThis as unknown as {
42+
[cloudflareContextSymbol]: CloudflareContext<CfProperties, Context> | undefined;
43+
}
44+
)[cloudflareContextSymbol];
45+
46+
if (!cloudflareContext) {
47+
// TODO: cloudflareContext should always be present in production/preview, if not it means that this
48+
// is running under `next dev`, in this case use `getPlatformProxy` to return local proxies
49+
throw new Error("Cloudflare context is not defined!");
50+
}
51+
52+
return cloudflareContext;
53+
}
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
export * from "./get-cloudflare-context";

packages/cloudflare/src/build/patches/investigated/copy-package.ts

Lines changed: 0 additions & 11 deletions
This file was deleted.

0 commit comments

Comments
 (0)