Skip to content

Commit 0a341db

Browse files
committed
add support for a global waitUntil
1 parent cbd1b1b commit 0a341db

File tree

6 files changed

+37
-4
lines changed

6 files changed

+37
-4
lines changed

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

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import type { ReadableStream } from "node:stream/web";
22

33
import type { InternalEvent, InternalResult } from "types/open-next";
4+
import { awaitAllDetachedPromise } from "utils/promise";
45
import { emptyReadableStream } from "utils/stream";
56

67
// We import it like that so that the edge plugin can replace it
@@ -64,6 +65,8 @@ const defaultHandler = async (
6465
const body =
6566
(response.body as ReadableStream<Uint8Array>) ?? emptyReadableStream();
6667

68+
await awaitAllDetachedPromise();
69+
6770
return {
6871
type: "core",
6972
statusCode: response.status,

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

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import type { InternalEvent, Origin } from "types/open-next";
2+
import { awaitAllDetachedPromise } from "utils/promise";
23

34
import { debug } from "../adapters/logger";
45
import { createGenericHandler } from "../core/createGenericHandler";
@@ -51,6 +52,7 @@ const defaultHandler = async (internalEvent: InternalEvent) => {
5152
if (!result.isExternalRewrite) {
5253
origin = await originResolver.resolve(result.internalEvent.rawPath);
5354
}
55+
await awaitAllDetachedPromise();
5456
return {
5557
type: "middleware",
5658
internalEvent: result.internalEvent,

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

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ import { AsyncLocalStorage } from "node:async_hooks";
33
import type { OpenNextNodeResponse, StreamCreator } from "http/index.js";
44
import { IncomingMessage } from "http/index.js";
55
import type { InternalEvent, InternalResult } from "types/open-next";
6+
import { awaitAllDetachedPromise } from "utils/promise";
67

78
import { debug, error, warn } from "../adapters/logger";
89
import { generateOpenNextRequestContext } from "../adapters/util";
@@ -150,7 +151,7 @@ export async function openNextHandler(
150151
// reset lastModified. We need to do this to avoid memory leaks
151152
delete globalThis.lastModified[requestId];
152153

153-
await pendingPromiseRunner.await();
154+
await awaitAllDetachedPromise();
154155

155156
return internalResult;
156157
},

packages/open-next/src/overrides/wrappers/cloudflare.ts

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,13 +11,22 @@ const cfPropNameToHeaderName = {
1111
longitude: "x-open-next-longitude",
1212
};
1313

14+
interface WorkerContext {
15+
waitUntil: (promise: Promise<unknown>) => void;
16+
}
17+
1418
const handler: WrapperHandler<
1519
InternalEvent,
1620
InternalResult | ({ type: "middleware" } & MiddlewareOutputEvent)
1721
> =
1822
async (handler, converter) =>
19-
async (request: Request, env: Record<string, string>): Promise<Response> => {
23+
async (
24+
request: Request,
25+
env: Record<string, string>,
26+
ctx: WorkerContext,
27+
): Promise<Response> => {
2028
globalThis.process = process;
29+
globalThis.openNextWaitUntil = ctx.waitUntil;
2130

2231
// Set the environment variables
2332
// Cloudflare suggests to not override the process.env object but instead apply the values to it

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

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -138,12 +138,19 @@ declare global {
138138

139139
/**
140140
* The AsyncLocalStorage instance that is used to store the request context.
141-
* Only available in main functions.
141+
* Only available in main, middleware and edge functions.
142142
* TODO: should be available everywhere in the future.
143-
* Defined in `requestHandler.ts`.
143+
* Defined in `requestHandler.ts`, `middleware.ts` and `edge-adapter.ts`.
144144
*/
145145
var __als: AsyncLocalStorage<OpenNextRequestContext>;
146146

147+
/**
148+
* The function that is used to run background tasks even after the response has been sent.
149+
* This one is defined by the wrapper function as most of them don't need or support this feature.
150+
* If not present, all the awaiting promises will be resolved before sending the response.
151+
*/
152+
var openNextWaitUntil: ((promise: Promise<void>) => void) | undefined;
153+
147154
/**
148155
* The entries object that contains the functions that are available in the function.
149156
* Only available in edge runtime functions.

packages/open-next/src/utils/promise.ts

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -58,3 +58,14 @@ export class DetachedPromiseRunner {
5858
});
5959
}
6060
}
61+
62+
export async function awaitAllDetachedPromise() {
63+
const promisesToAwait =
64+
globalThis.__als.getStore()?.pendingPromiseRunner.await() ??
65+
Promise.resolve();
66+
if (globalThis.openNextWaitUntil) {
67+
globalThis.openNextWaitUntil(promisesToAwait);
68+
return;
69+
}
70+
await promisesToAwait;
71+
}

0 commit comments

Comments
 (0)