From 9b1772493969b33bd24e539232093995e070f781 Mon Sep 17 00:00:00 2001 From: Victor Berchet Date: Mon, 10 Feb 2025 07:10:39 +0100 Subject: [PATCH 1/4] refactor: store WaitUntil in ALS --- .../open-next/src/adapters/edge-adapter.ts | 5 ++-- .../adapters/image-optimization-adapter.ts | 7 +++--- packages/open-next/src/adapters/middleware.ts | 7 +++++- packages/open-next/src/core/requestHandler.ts | 17 +++++++++---- .../wrappers/aws-lambda-streaming.ts | 2 +- .../src/overrides/wrappers/aws-lambda.ts | 4 ++- .../src/overrides/wrappers/cloudflare-edge.ts | 5 ++-- .../src/overrides/wrappers/cloudflare-node.ts | 8 ++++-- .../open-next/src/overrides/wrappers/dummy.ts | 9 ++++--- .../src/overrides/wrappers/express-dev.ts | 8 +++--- .../open-next/src/overrides/wrappers/node.ts | 4 +-- packages/open-next/src/types/global.ts | 10 ++------ packages/open-next/src/types/open-next.ts | 1 + packages/open-next/src/types/overrides.ts | 6 ++++- packages/open-next/src/utils/promise.ts | 25 +++++++++++-------- 15 files changed, 73 insertions(+), 45 deletions(-) diff --git a/packages/open-next/src/adapters/edge-adapter.ts b/packages/open-next/src/adapters/edge-adapter.ts index 7cce4e751..d836cbb35 100644 --- a/packages/open-next/src/adapters/edge-adapter.ts +++ b/packages/open-next/src/adapters/edge-adapter.ts @@ -1,6 +1,6 @@ import type { ReadableStream } from "node:stream/web"; -import type { InternalEvent, InternalResult } from "types/open-next"; +import type { InternalEvent, InternalResult, WaitUntil } from "types/open-next"; import { runWithOpenNextRequestContext } from "utils/promise"; import { emptyReadableStream } from "utils/stream"; @@ -16,12 +16,13 @@ globalThis.__openNextAls = new AsyncLocalStorage(); const defaultHandler = async ( internalEvent: InternalEvent, + options?: { waitUntil?: WaitUntil }, ): Promise => { globalThis.isEdgeRuntime = true; // We run everything in the async local storage context so that it is available in edge runtime functions return runWithOpenNextRequestContext( - { isISRRevalidation: false }, + { isISRRevalidation: false, waitUntil: options?.waitUntil }, async () => { const host = internalEvent.headers.host ? `https://${internalEvent.headers.host}` diff --git a/packages/open-next/src/adapters/image-optimization-adapter.ts b/packages/open-next/src/adapters/image-optimization-adapter.ts index 5ee98a7c5..268f263bc 100644 --- a/packages/open-next/src/adapters/image-optimization-adapter.ts +++ b/packages/open-next/src/adapters/image-optimization-adapter.ts @@ -22,6 +22,7 @@ import type { InternalEvent, InternalResult, StreamCreator, + WaitUntil, } from "types/open-next.js"; import { emptyReadableStream, toReadableStream } from "utils/stream.js"; @@ -58,7 +59,7 @@ export const handler = await createGenericHandler({ export async function defaultHandler( event: InternalEvent, - streamCreator?: StreamCreator, + options?: { streamCreator?: StreamCreator; waitUntil?: WaitUntil }, ): Promise { // Images are handled via header and query param information. debug("handler event", event); @@ -99,9 +100,9 @@ export async function defaultHandler( downloadHandler, ); - return buildSuccessResponse(result, streamCreator, etag); + return buildSuccessResponse(result, options?.streamCreator, etag); } catch (e: any) { - return buildFailureResponse(e, streamCreator); + return buildFailureResponse(e, options?.streamCreator); } } diff --git a/packages/open-next/src/adapters/middleware.ts b/packages/open-next/src/adapters/middleware.ts index 363ce4b21..11dc76dce 100644 --- a/packages/open-next/src/adapters/middleware.ts +++ b/packages/open-next/src/adapters/middleware.ts @@ -2,6 +2,7 @@ import type { InternalEvent, InternalResult, MiddlewareResult, + WaitUntil, } from "types/open-next"; import { runWithOpenNextRequestContext } from "utils/promise"; @@ -24,6 +25,7 @@ globalThis.__openNextAls = new AsyncLocalStorage(); const defaultHandler = async ( internalEvent: InternalEvent, + options?: { waitUntil?: WaitUntil }, ): Promise => { const originResolver = await resolveOriginResolver( globalThis.openNextConfig.middleware?.originResolver, @@ -49,7 +51,10 @@ const defaultHandler = async ( // We run everything in the async local storage context so that it is available in the external middleware return runWithOpenNextRequestContext( - { isISRRevalidation: internalEvent.headers["x-isr"] === "1" }, + { + isISRRevalidation: internalEvent.headers["x-isr"] === "1", + waitUntil: options?.waitUntil, + }, async () => { const result = await routingHandler(internalEvent); if ("internalEvent" in result) { diff --git a/packages/open-next/src/core/requestHandler.ts b/packages/open-next/src/core/requestHandler.ts index 92823d6d5..34982651a 100644 --- a/packages/open-next/src/core/requestHandler.ts +++ b/packages/open-next/src/core/requestHandler.ts @@ -8,6 +8,7 @@ import type { ResolvedRoute, RoutingResult, StreamCreator, + WaitUntil, } from "types/open-next"; import { runWithOpenNextRequestContext } from "utils/promise"; @@ -29,12 +30,18 @@ patchAsyncStorage(); export async function openNextHandler( internalEvent: InternalEvent, - responseStreaming?: StreamCreator, + options?: { + streamCreator?: StreamCreator; + waitUntil?: WaitUntil; + }, ): Promise { const initialHeaders = internalEvent.headers; // We run everything in the async local storage context so that it is available in the middleware as well as in NextServer return runWithOpenNextRequestContext( - { isISRRevalidation: initialHeaders["x-isr"] === "1" }, + { + isISRRevalidation: initialHeaders["x-isr"] === "1", + waitUntil: options?.waitUntil, + }, async () => { if (initialHeaders["x-forwarded-host"]) { initialHeaders.host = initialHeaders["x-forwarded-host"]; @@ -116,7 +123,7 @@ export async function openNextHandler( if ("type" in routingResult) { // response is used only in the streaming case - if (responseStreaming) { + if (options?.streamCreator) { const response = createServerResponse( { internalEvent, @@ -127,7 +134,7 @@ export async function openNextHandler( initialPath: internalEvent.rawPath, }, headers, - responseStreaming, + options.streamCreator, ); response.statusCode = routingResult.statusCode; response.flushHeaders(); @@ -171,7 +178,7 @@ export async function openNextHandler( const res = createServerResponse( routingResult, overwrittenResponseHeaders, - responseStreaming, + options?.streamCreator, ); await processRequest(req, res, preprocessedEvent); diff --git a/packages/open-next/src/overrides/wrappers/aws-lambda-streaming.ts b/packages/open-next/src/overrides/wrappers/aws-lambda-streaming.ts index bb48ba714..e8243d2a4 100644 --- a/packages/open-next/src/overrides/wrappers/aws-lambda-streaming.ts +++ b/packages/open-next/src/overrides/wrappers/aws-lambda-streaming.ts @@ -94,7 +94,7 @@ const handler: WrapperHandler = async (handler, converter) => }, }; - const response = await handler(internalEvent, streamCreator); + const response = await handler(internalEvent, { streamCreator }); const isUsingEdge = globalThis.isEdgeRuntime ?? false; if (isUsingEdge) { diff --git a/packages/open-next/src/overrides/wrappers/aws-lambda.ts b/packages/open-next/src/overrides/wrappers/aws-lambda.ts index e23135747..6c45ee07c 100644 --- a/packages/open-next/src/overrides/wrappers/aws-lambda.ts +++ b/packages/open-next/src/overrides/wrappers/aws-lambda.ts @@ -61,7 +61,9 @@ const handler: WrapperHandler = }, }; - const response = await handler(internalEvent, fakeStream); + const response = await handler(internalEvent, { + streamCreator: fakeStream, + }); return converter.convertTo(response, event); }; diff --git a/packages/open-next/src/overrides/wrappers/cloudflare-edge.ts b/packages/open-next/src/overrides/wrappers/cloudflare-edge.ts index 3c26f4f44..b767cac9f 100644 --- a/packages/open-next/src/overrides/wrappers/cloudflare-edge.ts +++ b/packages/open-next/src/overrides/wrappers/cloudflare-edge.ts @@ -33,7 +33,6 @@ const handler: WrapperHandler< ctx: WorkerContext, ): Promise => { globalThis.process = process; - globalThis.openNextWaitUntil = ctx.waitUntil.bind(ctx); // Set the environment variables // Cloudflare suggests to not override the process.env object but instead apply the values to it @@ -63,7 +62,9 @@ const handler: WrapperHandler< } } - const response = await handler(internalEvent); + const response = await handler(internalEvent, { + waitUntil: ctx.waitUntil.bind(ctx), + }); const result: Response = await converter.convertTo(response); diff --git a/packages/open-next/src/overrides/wrappers/cloudflare-node.ts b/packages/open-next/src/overrides/wrappers/cloudflare-node.ts index 973467cb7..51c82ace9 100644 --- a/packages/open-next/src/overrides/wrappers/cloudflare-node.ts +++ b/packages/open-next/src/overrides/wrappers/cloudflare-node.ts @@ -18,7 +18,6 @@ const handler: WrapperHandler = ctx: any, ): Promise => { globalThis.process = process; - globalThis.openNextWaitUntil = ctx.waitUntil.bind(ctx); // Set the environment variables // Cloudflare suggests to not override the process.env object but instead apply the values to it @@ -75,7 +74,12 @@ const handler: WrapperHandler = }, }; - ctx.waitUntil(handler(internalEvent, streamCreator)); + ctx.waitUntil( + handler(internalEvent, { + streamCreator, + waitUntil: ctx.waitUntil.bind(ctx), + }), + ); return promiseResponse; }; diff --git a/packages/open-next/src/overrides/wrappers/dummy.ts b/packages/open-next/src/overrides/wrappers/dummy.ts index 11b4d8066..be78e2839 100644 --- a/packages/open-next/src/overrides/wrappers/dummy.ts +++ b/packages/open-next/src/overrides/wrappers/dummy.ts @@ -1,9 +1,12 @@ -import type { InternalEvent, StreamCreator } from "types/open-next"; +import type { InternalEvent, StreamCreator, WaitUntil } from "types/open-next"; import type { Wrapper, WrapperHandler } from "types/overrides"; const dummyWrapper: WrapperHandler = async (handler, converter) => { - return async (event: InternalEvent, responseStream?: StreamCreator) => { - return await handler(event, responseStream); + return async ( + event: InternalEvent, + options?: { streamCreator?: StreamCreator; waitUntil?: WaitUntil }, + ) => { + return await handler(event, options); }; }; diff --git a/packages/open-next/src/overrides/wrappers/express-dev.ts b/packages/open-next/src/overrides/wrappers/express-dev.ts index fdd457e1a..7f861c462 100644 --- a/packages/open-next/src/overrides/wrappers/express-dev.ts +++ b/packages/open-next/src/overrides/wrappers/express-dev.ts @@ -13,25 +13,25 @@ const wrapper: WrapperHandler = async (handler, converter) => { app.all("/_next/image", async (req, res) => { const internalEvent = await converter.convertFrom(req); - const _res: StreamCreator = { + const streamCreator: StreamCreator = { writeHeaders: (prelude) => { res.writeHead(prelude.statusCode, prelude.headers); return res; }, }; - await imageHandler(internalEvent, _res); + await imageHandler(internalEvent, { streamCreator }); }); app.all("*paths", async (req, res) => { const internalEvent = await converter.convertFrom(req); - const _res: StreamCreator = { + const streamCreator: StreamCreator = { writeHeaders: (prelude) => { res.writeHead(prelude.statusCode, prelude.headers); return res; }, onFinish: () => {}, }; - await handler(internalEvent, _res); + await handler(internalEvent, { streamCreator }); }); const server = app.listen( diff --git a/packages/open-next/src/overrides/wrappers/node.ts b/packages/open-next/src/overrides/wrappers/node.ts index 71768daae..ecfbe1d1a 100644 --- a/packages/open-next/src/overrides/wrappers/node.ts +++ b/packages/open-next/src/overrides/wrappers/node.ts @@ -8,7 +8,7 @@ import { debug, error } from "../../adapters/logger"; const wrapper: WrapperHandler = async (handler, converter) => { const server = createServer(async (req, res) => { const internalEvent = await converter.convertFrom(req); - const _res: StreamCreator = { + const streamCreator: StreamCreator = { writeHeaders: (prelude) => { res.setHeader("Set-Cookie", prelude.cookies); res.writeHead(prelude.statusCode, prelude.headers); @@ -23,7 +23,7 @@ const wrapper: WrapperHandler = async (handler, converter) => { }); res.end("OK"); } else { - await handler(internalEvent, _res); + await handler(internalEvent, { streamCreator }); } }); diff --git a/packages/open-next/src/types/global.ts b/packages/open-next/src/types/global.ts index 5a89b5e22..63f221b87 100644 --- a/packages/open-next/src/types/global.ts +++ b/packages/open-next/src/types/global.ts @@ -10,7 +10,7 @@ import type { } from "types/overrides"; import type { DetachedPromiseRunner } from "../utils/promise"; -import type { OpenNextConfig } from "./open-next"; +import type { OpenNextConfig, WaitUntil } from "./open-next"; export interface RequestData { geo?: { @@ -59,6 +59,7 @@ interface OpenNextRequestContext { pendingPromiseRunner: DetachedPromiseRunner; isISRRevalidation?: boolean; mergeHeadersPriority?: "middleware" | "handler"; + waitUntil?: WaitUntil; } declare global { @@ -152,13 +153,6 @@ declare global { */ var __openNextAls: AsyncLocalStorage; - /** - * The function that is used to run background tasks even after the response has been sent. - * This one is defined by the wrapper function as most of them don't need or support this feature. - * If not present, all the awaiting promises will be resolved before sending the response. - */ - var openNextWaitUntil: ((promise: Promise) => void) | undefined; - /** * The entries object that contains the functions that are available in the function. * Only available in edge runtime functions. diff --git a/packages/open-next/src/types/open-next.ts b/packages/open-next/src/types/open-next.ts index 114f92f72..e7ba2cd87 100644 --- a/packages/open-next/src/types/open-next.ts +++ b/packages/open-next/src/types/open-next.ts @@ -48,6 +48,7 @@ export interface StreamCreator { onFinish?: (length: number) => void; } +export type WaitUntil = (promise: Promise) => void; export interface DangerousOptions { /** * The tag cache is used for revalidateTags and revalidatePath. diff --git a/packages/open-next/src/types/overrides.ts b/packages/open-next/src/types/overrides.ts index 717694f8a..734e0ddcd 100644 --- a/packages/open-next/src/types/overrides.ts +++ b/packages/open-next/src/types/overrides.ts @@ -10,6 +10,7 @@ import type { Origin, ResolvedRoute, StreamCreator, + WaitUntil, } from "./open-next"; // Queue @@ -125,7 +126,10 @@ export type Wrapper< export type OpenNextHandler< E extends BaseEventOrResult = InternalEvent, R extends BaseEventOrResult = InternalResult, -> = (event: E, responseStream?: StreamCreator) => Promise; +> = ( + event: E, + options?: { streamCreator?: StreamCreator; waitUntil?: WaitUntil }, +) => Promise; export type Converter< E extends BaseEventOrResult = InternalEvent, diff --git a/packages/open-next/src/utils/promise.ts b/packages/open-next/src/utils/promise.ts index e80f83acd..856fb8e9d 100644 --- a/packages/open-next/src/utils/promise.ts +++ b/packages/open-next/src/utils/promise.ts @@ -1,3 +1,4 @@ +import type { WaitUntil } from "types/open-next"; import { debug, error } from "../adapters/logger"; /** @@ -60,30 +61,30 @@ export class DetachedPromiseRunner { } async function awaitAllDetachedPromise() { + const store = globalThis.__openNextAls.getStore(); + const promisesToAwait = - globalThis.__openNextAls.getStore()?.pendingPromiseRunner.await() ?? - Promise.resolve(); - if (globalThis.openNextWaitUntil) { - globalThis.openNextWaitUntil(promisesToAwait); + store?.pendingPromiseRunner.await() ?? Promise.resolve(); + if (store?.waitUntil) { + store.waitUntil(promisesToAwait); return; } await promisesToAwait; } function provideNextAfterProvider() { - /** This should be considered unstable until `unstable_after` is stablized. */ + /** This should be considered unstable until `unstable_after` is stabilized. */ const NEXT_REQUEST_CONTEXT_SYMBOL = Symbol.for("@next/request-context"); // This is needed by some lib that relies on the vercel request context to properly await stuff. // Remove this when vercel builder is updated to provide '@next/request-context'. const VERCEL_REQUEST_CONTEXT_SYMBOL = Symbol.for("@vercel/request-context"); - const openNextStoreContext = globalThis.__openNextAls.getStore(); + const store = globalThis.__openNextAls.getStore(); const waitUntil = - globalThis.openNextWaitUntil ?? - ((promise: Promise) => - openNextStoreContext?.pendingPromiseRunner.add(promise)); + store?.waitUntil ?? + ((promise: Promise) => store?.pendingPromiseRunner.add(promise)); const nextAfterContext = { get: () => ({ @@ -102,7 +103,10 @@ function provideNextAfterProvider() { } export function runWithOpenNextRequestContext( - { isISRRevalidation }: { isISRRevalidation: boolean }, + { + isISRRevalidation, + waitUntil, + }: { isISRRevalidation: boolean; waitUntil?: WaitUntil }, fn: () => Promise, ): Promise { return globalThis.__openNextAls.run( @@ -110,6 +114,7 @@ export function runWithOpenNextRequestContext( requestId: Math.random().toString(36), pendingPromiseRunner: new DetachedPromiseRunner(), isISRRevalidation, + waitUntil, }, async () => { provideNextAfterProvider(); From ea044836344c18ba8e3b83ac950d303e86c40764 Mon Sep 17 00:00:00 2001 From: Victor Berchet Date: Mon, 10 Feb 2025 14:56:19 +0100 Subject: [PATCH 2/4] fixup! +OpenNextHandlerOptions +changeset --- .changeset/pink-papayas-smoke.md | 25 +++++++++++++++++++ .../open-next/src/adapters/edge-adapter.ts | 3 ++- .../adapters/image-optimization-adapter.ts | 4 +-- packages/open-next/src/adapters/middleware.ts | 4 +-- packages/open-next/src/core/requestHandler.ts | 8 ++---- .../open-next/src/overrides/wrappers/dummy.ts | 13 +++++----- packages/open-next/src/types/overrides.ts | 12 ++++++--- packages/open-next/src/utils/promise.ts | 7 +++++- 8 files changed, 54 insertions(+), 22 deletions(-) create mode 100644 .changeset/pink-papayas-smoke.md diff --git a/.changeset/pink-papayas-smoke.md b/.changeset/pink-papayas-smoke.md new file mode 100644 index 000000000..2b108686c --- /dev/null +++ b/.changeset/pink-papayas-smoke.md @@ -0,0 +1,25 @@ +--- +"@opennextjs/aws": minor +--- + +refactor: `waitUntil` passed around via ALS. + +BREAKING CHANGE: `waitUntil` is passed around via ALS to fix #713. + +`globalThis.openNextWaitUntil` is no more available, you can access `waitUntil` +on the ALS context: `globalThis.__openNextAls.getStore()` + +The `OpenNextHandler` signature has changed: the second parameter was a `StreamCreator`. +It was changed to be of type `OpenNextHandlerOptions` which has both a `streamCreator` key +and a `waitUntil` key. + +If you use a custom wrapper, you need to update the call to the handler as follow: + +```ts +// before +globalThis.openNextWaitUntil = myWaitUntil; +handler(internalEvent, myStreamCreator); + +// after +handler(internalEvent, { streamCreator: myStreamCreator, waitUntil: myWaitUntil }); +``` diff --git a/packages/open-next/src/adapters/edge-adapter.ts b/packages/open-next/src/adapters/edge-adapter.ts index d836cbb35..c1574a9aa 100644 --- a/packages/open-next/src/adapters/edge-adapter.ts +++ b/packages/open-next/src/adapters/edge-adapter.ts @@ -11,12 +11,13 @@ import { convertBodyToReadableStream, convertToQueryString, } from "../core/routing/util"; +import type { OpenNextHandlerOptions } from "types/overrides"; globalThis.__openNextAls = new AsyncLocalStorage(); const defaultHandler = async ( internalEvent: InternalEvent, - options?: { waitUntil?: WaitUntil }, + options?: OpenNextHandlerOptions, ): Promise => { globalThis.isEdgeRuntime = true; diff --git a/packages/open-next/src/adapters/image-optimization-adapter.ts b/packages/open-next/src/adapters/image-optimization-adapter.ts index 268f263bc..923aebf82 100644 --- a/packages/open-next/src/adapters/image-optimization-adapter.ts +++ b/packages/open-next/src/adapters/image-optimization-adapter.ts @@ -22,10 +22,10 @@ import type { InternalEvent, InternalResult, StreamCreator, - WaitUntil, } from "types/open-next.js"; import { emptyReadableStream, toReadableStream } from "utils/stream.js"; +import type { OpenNextHandlerOptions } from "types/overrides.js"; import { createGenericHandler } from "../core/createGenericHandler.js"; import { resolveImageLoader } from "../core/resolve.js"; import { debug, error } from "./logger.js"; @@ -59,7 +59,7 @@ export const handler = await createGenericHandler({ export async function defaultHandler( event: InternalEvent, - options?: { streamCreator?: StreamCreator; waitUntil?: WaitUntil }, + options?: OpenNextHandlerOptions, ): Promise { // Images are handled via header and query param information. debug("handler event", event); diff --git a/packages/open-next/src/adapters/middleware.ts b/packages/open-next/src/adapters/middleware.ts index 11dc76dce..cb2729685 100644 --- a/packages/open-next/src/adapters/middleware.ts +++ b/packages/open-next/src/adapters/middleware.ts @@ -2,10 +2,10 @@ import type { InternalEvent, InternalResult, MiddlewareResult, - WaitUntil, } from "types/open-next"; import { runWithOpenNextRequestContext } from "utils/promise"; +import type { OpenNextHandlerOptions } from "types/overrides"; import { debug, error } from "../adapters/logger"; import { createGenericHandler } from "../core/createGenericHandler"; import { @@ -25,7 +25,7 @@ globalThis.__openNextAls = new AsyncLocalStorage(); const defaultHandler = async ( internalEvent: InternalEvent, - options?: { waitUntil?: WaitUntil }, + options?: OpenNextHandlerOptions, ): Promise => { const originResolver = await resolveOriginResolver( globalThis.openNextConfig.middleware?.originResolver, diff --git a/packages/open-next/src/core/requestHandler.ts b/packages/open-next/src/core/requestHandler.ts index 34982651a..2ff95aaee 100644 --- a/packages/open-next/src/core/requestHandler.ts +++ b/packages/open-next/src/core/requestHandler.ts @@ -7,11 +7,10 @@ import type { InternalResult, ResolvedRoute, RoutingResult, - StreamCreator, - WaitUntil, } from "types/open-next"; import { runWithOpenNextRequestContext } from "utils/promise"; +import type { OpenNextHandlerOptions } from "types/overrides"; import { debug, error, warn } from "../adapters/logger"; import { patchAsyncStorage } from "./patchAsyncStorage"; import { convertRes, createServerResponse } from "./routing/util"; @@ -30,10 +29,7 @@ patchAsyncStorage(); export async function openNextHandler( internalEvent: InternalEvent, - options?: { - streamCreator?: StreamCreator; - waitUntil?: WaitUntil; - }, + options?: OpenNextHandlerOptions, ): Promise { const initialHeaders = internalEvent.headers; // We run everything in the async local storage context so that it is available in the middleware as well as in NextServer diff --git a/packages/open-next/src/overrides/wrappers/dummy.ts b/packages/open-next/src/overrides/wrappers/dummy.ts index be78e2839..431fdf6f3 100644 --- a/packages/open-next/src/overrides/wrappers/dummy.ts +++ b/packages/open-next/src/overrides/wrappers/dummy.ts @@ -1,11 +1,12 @@ -import type { InternalEvent, StreamCreator, WaitUntil } from "types/open-next"; -import type { Wrapper, WrapperHandler } from "types/overrides"; +import type { InternalEvent } from "types/open-next"; +import type { + OpenNextHandlerOptions, + Wrapper, + WrapperHandler, +} from "types/overrides"; const dummyWrapper: WrapperHandler = async (handler, converter) => { - return async ( - event: InternalEvent, - options?: { streamCreator?: StreamCreator; waitUntil?: WaitUntil }, - ) => { + return async (event: InternalEvent, options?: OpenNextHandlerOptions) => { return await handler(event, options); }; }; diff --git a/packages/open-next/src/types/overrides.ts b/packages/open-next/src/types/overrides.ts index 734e0ddcd..8a77e588a 100644 --- a/packages/open-next/src/types/overrides.ts +++ b/packages/open-next/src/types/overrides.ts @@ -123,13 +123,17 @@ export type Wrapper< edgeRuntime?: boolean; }; +export type OpenNextHandlerOptions = { + // Create a `Writeable` for streaming responses. + streamCreator?: StreamCreator; + // Extends the liftetime of the runtime after the response is returned. + waitUntil?: WaitUntil; +}; + export type OpenNextHandler< E extends BaseEventOrResult = InternalEvent, R extends BaseEventOrResult = InternalResult, -> = ( - event: E, - options?: { streamCreator?: StreamCreator; waitUntil?: WaitUntil }, -) => Promise; +> = (event: E, options?: OpenNextHandlerOptions) => Promise; export type Converter< E extends BaseEventOrResult = InternalEvent, diff --git a/packages/open-next/src/utils/promise.ts b/packages/open-next/src/utils/promise.ts index 856fb8e9d..ca527eea1 100644 --- a/packages/open-next/src/utils/promise.ts +++ b/packages/open-next/src/utils/promise.ts @@ -106,7 +106,12 @@ export function runWithOpenNextRequestContext( { isISRRevalidation, waitUntil, - }: { isISRRevalidation: boolean; waitUntil?: WaitUntil }, + }: { + // Whether we are in ISR revalidation + isISRRevalidation: boolean; + // Extends the liftetime of the runtime after the response is returned. + waitUntil?: WaitUntil; + }, fn: () => Promise, ): Promise { return globalThis.__openNextAls.run( From d6db773c692353eb951b39fc56fedfa4b7ef7c8f Mon Sep 17 00:00:00 2001 From: Victor Berchet Date: Mon, 10 Feb 2025 15:30:05 +0100 Subject: [PATCH 3/4] Update .changeset/pink-papayas-smoke.md Co-authored-by: conico974 --- .changeset/pink-papayas-smoke.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.changeset/pink-papayas-smoke.md b/.changeset/pink-papayas-smoke.md index 2b108686c..ea64d97cd 100644 --- a/.changeset/pink-papayas-smoke.md +++ b/.changeset/pink-papayas-smoke.md @@ -2,7 +2,7 @@ "@opennextjs/aws": minor --- -refactor: `waitUntil` passed around via ALS. +refactor: `waitUntil` passed around via ALS and `OpenNextHandler` signature has changed BREAKING CHANGE: `waitUntil` is passed around via ALS to fix #713. From 4f7c2109f162f97083ca70389363f52b0bbf898b Mon Sep 17 00:00:00 2001 From: Victor Berchet Date: Mon, 10 Feb 2025 15:31:07 +0100 Subject: [PATCH 4/4] fixup! --- .changeset/pink-papayas-smoke.md | 2 +- packages/open-next/src/adapters/edge-adapter.ts | 4 ++-- packages/open-next/src/utils/promise.ts | 1 - 3 files changed, 3 insertions(+), 4 deletions(-) diff --git a/.changeset/pink-papayas-smoke.md b/.changeset/pink-papayas-smoke.md index ea64d97cd..a5ba78556 100644 --- a/.changeset/pink-papayas-smoke.md +++ b/.changeset/pink-papayas-smoke.md @@ -2,7 +2,7 @@ "@opennextjs/aws": minor --- -refactor: `waitUntil` passed around via ALS and `OpenNextHandler` signature has changed +refactor: `waitUntil` passed around via ALS and `OpenNextHandler` signature has changed BREAKING CHANGE: `waitUntil` is passed around via ALS to fix #713. diff --git a/packages/open-next/src/adapters/edge-adapter.ts b/packages/open-next/src/adapters/edge-adapter.ts index c1574a9aa..e4cc718cf 100644 --- a/packages/open-next/src/adapters/edge-adapter.ts +++ b/packages/open-next/src/adapters/edge-adapter.ts @@ -1,9 +1,10 @@ import type { ReadableStream } from "node:stream/web"; -import type { InternalEvent, InternalResult, WaitUntil } from "types/open-next"; +import type { InternalEvent, InternalResult } from "types/open-next"; import { runWithOpenNextRequestContext } from "utils/promise"; import { emptyReadableStream } from "utils/stream"; +import type { OpenNextHandlerOptions } from "types/overrides"; // We import it like that so that the edge plugin can replace it import { NextConfig } from "../adapters/config"; import { createGenericHandler } from "../core/createGenericHandler"; @@ -11,7 +12,6 @@ import { convertBodyToReadableStream, convertToQueryString, } from "../core/routing/util"; -import type { OpenNextHandlerOptions } from "types/overrides"; globalThis.__openNextAls = new AsyncLocalStorage(); diff --git a/packages/open-next/src/utils/promise.ts b/packages/open-next/src/utils/promise.ts index ca527eea1..b2688cefb 100644 --- a/packages/open-next/src/utils/promise.ts +++ b/packages/open-next/src/utils/promise.ts @@ -73,7 +73,6 @@ async function awaitAllDetachedPromise() { } function provideNextAfterProvider() { - /** This should be considered unstable until `unstable_after` is stabilized. */ const NEXT_REQUEST_CONTEXT_SYMBOL = Symbol.for("@next/request-context"); // This is needed by some lib that relies on the vercel request context to properly await stuff.