Skip to content

Commit acd22df

Browse files
committed
edge, middleware and server all have access to __als
1 parent 00ff01e commit acd22df

File tree

6 files changed

+204
-166
lines changed

6 files changed

+204
-166
lines changed

packages/open-next/src/adapters/edge-adapter.ts

Lines changed: 54 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -10,55 +10,70 @@ import {
1010
convertBodyToReadableStream,
1111
convertToQueryString,
1212
} from "../core/routing/util";
13+
import { generateOpenNextRequestContext } from "./util";
14+
15+
globalThis.__als = new AsyncLocalStorage();
1316

1417
const defaultHandler = async (
1518
internalEvent: InternalEvent,
1619
): Promise<InternalResult> => {
1720
globalThis.isEdgeRuntime = true;
1821

19-
const host = internalEvent.headers.host
20-
? `https://${internalEvent.headers.host}`
21-
: "http://localhost:3000";
22-
const initialUrl = new URL(internalEvent.rawPath, host);
23-
initialUrl.search = convertToQueryString(internalEvent.query);
24-
const url = initialUrl.toString();
22+
const { requestId, pendingPromiseRunner, isISRRevalidation } =
23+
generateOpenNextRequestContext();
2524

26-
// @ts-expect-error - This is bundled
27-
const handler = await import(`./middleware.mjs`);
25+
// We run everything in the async local storage context so that it is available in edge runtime functions
26+
return globalThis.__als.run(
27+
{ requestId, pendingPromiseRunner, isISRRevalidation },
28+
async () => {
29+
const host = internalEvent.headers.host
30+
? `https://${internalEvent.headers.host}`
31+
: "http://localhost:3000";
32+
const initialUrl = new URL(internalEvent.rawPath, host);
33+
initialUrl.search = convertToQueryString(internalEvent.query);
34+
const url = initialUrl.toString();
2835

29-
const response: Response = await handler.default({
30-
headers: internalEvent.headers,
31-
method: internalEvent.method || "GET",
32-
nextConfig: {
33-
basePath: NextConfig.basePath,
34-
i18n: NextConfig.i18n,
35-
trailingSlash: NextConfig.trailingSlash,
36-
},
37-
url,
38-
body: convertBodyToReadableStream(internalEvent.method, internalEvent.body),
39-
});
40-
const responseHeaders: Record<string, string | string[]> = {};
41-
response.headers.forEach((value, key) => {
42-
if (key.toLowerCase() === "set-cookie") {
43-
responseHeaders[key] = responseHeaders[key]
44-
? [...responseHeaders[key], value]
45-
: [value];
46-
} else {
47-
responseHeaders[key] = value;
48-
}
49-
});
36+
// @ts-expect-error - This is bundled
37+
const handler = await import(`./middleware.mjs`);
5038

51-
const body =
52-
(response.body as ReadableStream<Uint8Array>) ?? emptyReadableStream();
39+
const response: Response = await handler.default({
40+
headers: internalEvent.headers,
41+
method: internalEvent.method || "GET",
42+
nextConfig: {
43+
basePath: NextConfig.basePath,
44+
i18n: NextConfig.i18n,
45+
trailingSlash: NextConfig.trailingSlash,
46+
},
47+
url,
48+
body: convertBodyToReadableStream(
49+
internalEvent.method,
50+
internalEvent.body,
51+
),
52+
});
53+
const responseHeaders: Record<string, string | string[]> = {};
54+
response.headers.forEach((value, key) => {
55+
if (key.toLowerCase() === "set-cookie") {
56+
responseHeaders[key] = responseHeaders[key]
57+
? [...responseHeaders[key], value]
58+
: [value];
59+
} else {
60+
responseHeaders[key] = value;
61+
}
62+
});
5363

54-
return {
55-
type: "core",
56-
statusCode: response.status,
57-
headers: responseHeaders,
58-
body: body,
59-
// Do we need to handle base64 encoded response?
60-
isBase64Encoded: false,
61-
};
64+
const body =
65+
(response.body as ReadableStream<Uint8Array>) ?? emptyReadableStream();
66+
67+
return {
68+
type: "core",
69+
statusCode: response.status,
70+
headers: responseHeaders,
71+
body: body,
72+
// Do we need to handle base64 encoded response?
73+
isBase64Encoded: false,
74+
};
75+
},
76+
);
6277
};
6378

6479
export const handler = await createGenericHandler({

packages/open-next/src/adapters/middleware.ts

Lines changed: 33 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -9,8 +9,10 @@ import {
99
resolveTagCache,
1010
} from "../core/resolve";
1111
import routingHandler from "../core/routingHandler";
12+
import { generateOpenNextRequestContext } from "./util";
1213

1314
globalThis.internalFetch = fetch;
15+
globalThis.__als = new AsyncLocalStorage();
1416

1517
const defaultHandler = async (internalEvent: InternalEvent) => {
1618
const originResolver = await resolveOriginResolver(
@@ -31,24 +33,37 @@ const defaultHandler = async (internalEvent: InternalEvent) => {
3133
);
3234
//#endOverride
3335

34-
const result = await routingHandler(internalEvent);
35-
if ("internalEvent" in result) {
36-
debug("Middleware intercepted event", internalEvent);
37-
let origin: Origin | false = false;
38-
if (!result.isExternalRewrite) {
39-
origin = await originResolver.resolve(result.internalEvent.rawPath);
40-
}
41-
return {
42-
type: "middleware",
43-
internalEvent: result.internalEvent,
44-
isExternalRewrite: result.isExternalRewrite,
45-
origin,
46-
isISR: result.isISR,
47-
};
48-
}
49-
50-
debug("Middleware response", result);
51-
return result;
36+
const { requestId, pendingPromiseRunner, isISRRevalidation } =
37+
generateOpenNextRequestContext(internalEvent.headers["x-isr"] === "1");
38+
39+
// We run everything in the async local storage context so that it is available in the external middleware
40+
return globalThis.__als.run(
41+
{
42+
requestId,
43+
pendingPromiseRunner,
44+
isISRRevalidation,
45+
},
46+
async () => {
47+
const result = await routingHandler(internalEvent);
48+
if ("internalEvent" in result) {
49+
debug("Middleware intercepted event", internalEvent);
50+
let origin: Origin | false = false;
51+
if (!result.isExternalRewrite) {
52+
origin = await originResolver.resolve(result.internalEvent.rawPath);
53+
}
54+
return {
55+
type: "middleware",
56+
internalEvent: result.internalEvent,
57+
isExternalRewrite: result.isExternalRewrite,
58+
origin,
59+
isISR: result.isISR,
60+
};
61+
}
62+
63+
debug("Middleware response", result);
64+
return result;
65+
},
66+
);
5267
};
5368

5469
export const handler = await createGenericHandler({

packages/open-next/src/adapters/util.ts

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
//TODO: We should probably move all the utils to a separate location
22

3+
import { DetachedPromiseRunner } from "utils/promise";
4+
35
export function setNodeEnv() {
46
process.env.NODE_ENV = process.env.NODE_ENV ?? "production";
57
}
@@ -8,6 +10,14 @@ export function generateUniqueId() {
810
return Math.random().toString(36).slice(2, 8);
911
}
1012

13+
export function generateOpenNextRequestContext(isISRRevalidation = false) {
14+
return {
15+
requestId: Math.random().toString(36),
16+
pendingPromiseRunner: new DetachedPromiseRunner(),
17+
isISRRevalidation,
18+
};
19+
}
20+
1121
/**
1222
* Create an array of arrays of size `chunkSize` from `items`
1323
* @param items Array of T

packages/open-next/src/core/createMainHandler.ts

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,4 @@
11
import type { OpenNextConfig } from "types/open-next";
2-
import type { IncrementalCache, Queue } from "types/overrides";
3-
import type { DetachedPromiseRunner } from "utils/promise";
42

53
import { debug } from "../adapters/logger";
64
import { generateUniqueId } from "../adapters/util";

0 commit comments

Comments
 (0)