Skip to content

Commit 1e9aa86

Browse files
committed
feat: add a cloudflare-streaming wrapper
1 parent 041b9e8 commit 1e9aa86

File tree

4 files changed

+84
-0
lines changed

4 files changed

+84
-0
lines changed

.changeset/ten-trainers-boil.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
"@opennextjs/aws": minor
3+
---
4+
5+
feat: add a cloudflare-streaming wrapper

packages/open-next/src/build/validateConfig.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ const compatibilityMatrix: Record<IncludedWrapper, IncludedConverter[]> = {
1717
],
1818
"aws-lambda-streaming": ["aws-apigw-v2"],
1919
cloudflare: ["edge"],
20+
"cloudflare-streaming": ["edge"],
2021
node: ["node"],
2122
dummy: [],
2223
};
Lines changed: 77 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,77 @@
1+
import type { InternalEvent, InternalResult } from "types/open-next";
2+
import type { WrapperHandler } from "types/overrides";
3+
4+
import { Writable } from "node:stream";
5+
import type { StreamCreator } from "http/index";
6+
import type { MiddlewareOutputEvent } from "../../core/routingHandler";
7+
8+
const handler: WrapperHandler<
9+
InternalEvent,
10+
InternalResult | ({ type: "middleware" } & MiddlewareOutputEvent)
11+
> =
12+
async (handler, converter) =>
13+
async (
14+
request: Request,
15+
env: Record<string, string>,
16+
ctx: any,
17+
): Promise<Response> => {
18+
globalThis.process = process;
19+
globalThis.openNextWaitUntil = ctx.waitUntil.bind(ctx);
20+
21+
// Set the environment variables
22+
// Cloudflare suggests to not override the process.env object but instead apply the values to it
23+
for (const [key, value] of Object.entries(env)) {
24+
if (typeof value === "string") {
25+
process.env[key] = value;
26+
}
27+
}
28+
29+
const internalEvent = await converter.convertFrom(request);
30+
31+
// TODO:
32+
// The edge converter populate event.url with the url including the origin.
33+
// This is required for middleware to keep track of the protocol (i.e. http with wrangler dev).
34+
// However the server expects that the origin is not included.
35+
const url = new URL(internalEvent.url);
36+
(internalEvent.url as string) = url.href.slice(url.origin.length);
37+
38+
const { promise: promiseResponse, resolve: resolveResponse } =
39+
Promise.withResolvers<Response>();
40+
41+
const streamCreator: StreamCreator = {
42+
writeHeaders(prelude: {
43+
statusCode: number;
44+
cookies: string[];
45+
headers: Record<string, string>;
46+
}): Writable {
47+
const { statusCode, cookies, headers } = prelude;
48+
49+
const responseHeaders = new Headers(headers);
50+
for (const cookie of cookies) {
51+
responseHeaders.append("Set-Cookie", cookie);
52+
}
53+
54+
const { readable, writable } = new TransformStream();
55+
const response = new Response(readable, {
56+
status: statusCode,
57+
headers: responseHeaders,
58+
});
59+
resolveResponse(response);
60+
61+
return Writable.fromWeb(writable);
62+
},
63+
onWrite: () => {},
64+
onFinish: (_length: number) => {},
65+
};
66+
67+
ctx.waitUntil(handler(internalEvent, streamCreator));
68+
69+
return promiseResponse;
70+
};
71+
72+
export default {
73+
wrapper: handler,
74+
name: "cloudflare-streaming",
75+
supportStreaming: true,
76+
edgeRuntime: true,
77+
};

packages/open-next/src/types/open-next.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -81,6 +81,7 @@ export type IncludedWrapper =
8181
| "aws-lambda-streaming"
8282
| "node"
8383
| "cloudflare"
84+
| "cloudflare-streaming"
8485
| "dummy";
8586

8687
export type IncludedConverter =

0 commit comments

Comments
 (0)