From 4a9a64d34558b4f2b80f7d1dd301ead7d80a2a45 Mon Sep 17 00:00:00 2001 From: Janka Uryga Date: Tue, 3 Dec 2024 00:55:38 +0100 Subject: [PATCH] wip: onAfterError in edge --- .../loaders/next-edge-ssr-loader/render.ts | 28 +++++++++++++++++-- packages/next/src/server/next-server.ts | 2 ++ packages/next/src/server/web/adapter.ts | 16 ++++++++--- .../server/web/edge-route-module-wrapper.ts | 9 +++--- packages/next/src/server/web/types.ts | 2 ++ 5 files changed, 47 insertions(+), 10 deletions(-) diff --git a/packages/next/src/build/webpack/loaders/next-edge-ssr-loader/render.ts b/packages/next/src/build/webpack/loaders/next-edge-ssr-loader/render.ts index f4b589571931f..0b2ce3b369cc5 100644 --- a/packages/next/src/build/webpack/loaders/next-edge-ssr-loader/render.ts +++ b/packages/next/src/build/webpack/loaders/next-edge-ssr-loader/render.ts @@ -21,7 +21,11 @@ import { normalizeAppPath } from '../../../../shared/lib/router/utils/app-paths' import type { SizeLimit } from '../../../../types' import { internal_getCurrentFunctionWaitUntil } from '../../../../server/web/internal-edge-wait-until' import type { PAGE_TYPES } from '../../../../lib/page-types' -import type { NextRequestHint } from '../../../../server/web/adapter' +import type { + HandlerExtraOpts, + NextRequestHint, +} from '../../../../server/web/adapter' +import { InvariantError } from '../../../../shared/lib/invariant-error' export function getRender({ dev, @@ -157,11 +161,31 @@ export function getRender({ return async function render( request: NextRequestHint, - event?: NextFetchEvent + event?: NextFetchEvent, + extraOpts?: HandlerExtraOpts ) { const extendedReq = new WebNextRequest(request) const extendedRes = new WebNextResponse(undefined) + if (dev) { + if (extraOpts) { + const { onAfterTaskError } = extraOpts + // in practice `onAfterTaskError` should be constant, so it's fine to set it like this + // but double check that it's not changing across request to prevent future bugs + if ( + server['afterTaskErrorHandler'] && + onAfterTaskError && + server['afterTaskErrorHandler'] !== onAfterTaskError + ) { + throw new InvariantError( + 'Expected `extraOpts.afterTaskErrorHandler` to not change' + ) + } + + server['afterTaskErrorHandler'] = onAfterTaskError + } + } + handler(extendedReq, extendedRes) const result = await extendedRes.toResponse() request.fetchMetrics = extendedReq.fetchMetrics diff --git a/packages/next/src/server/next-server.ts b/packages/next/src/server/next-server.ts index 6fa79f387436a..37d91e96b6784 100644 --- a/packages/next/src/server/next-server.ts +++ b/packages/next/src/server/next-server.ts @@ -1494,6 +1494,7 @@ export default class NextNodeServer extends BaseServer< body: getRequestMeta(params.request, 'clonableBody'), signal: signalFromNodeResponse(params.response.originalResponse), waitUntil: this.getWaitUntil(), + onAfterTaskError: this.afterTaskErrorHandler, }, useCache: true, onWarning: params.onWarning, @@ -1798,6 +1799,7 @@ export default class NextNodeServer extends BaseServer< body: getRequestMeta(params.req, 'clonableBody'), signal: signalFromNodeResponse(params.res.originalResponse), waitUntil: this.getWaitUntil(), + onAfterTaskError: this.afterTaskErrorHandler, }, useCache: true, onError: params.onError, diff --git a/packages/next/src/server/web/adapter.ts b/packages/next/src/server/web/adapter.ts index 3f1edd45378f8..2c7ff276f8922 100644 --- a/packages/next/src/server/web/adapter.ts +++ b/packages/next/src/server/web/adapter.ts @@ -57,8 +57,13 @@ const headersGetter: TextMapGetter = { get: (headers, key) => headers.get(key) ?? undefined, } +export type HandlerExtraOpts = { onAfterTaskError?: (error: unknown) => void } export type AdapterOptions = { - handler: (req: NextRequestHint, event: NextFetchEvent) => Promise + handler: ( + req: NextRequestHint, + event: NextFetchEvent, + extraOpts?: HandlerExtraOpts + ) => Promise page: string request: RequestData IncrementalCache?: typeof import('../lib/incremental-cache').IncrementalCache @@ -212,6 +217,8 @@ export async function adapter( let response let cookiesFromResponse + const onAfterTaskError = params.request.onAfterTaskError + response = await propagator(request, () => { // we only care to make async storage available for middleware const isMiddleware = @@ -265,7 +272,7 @@ export async function adapter( supportsDynamicResponse: true, waitUntil, onClose: closeController.onClose.bind(closeController), - onAfterTaskError: undefined, + onAfterTaskError, }, requestEndedState: { ended: false }, isPrefetchRequest: request.headers.has( @@ -278,7 +285,8 @@ export async function adapter( requestStore, params.handler, request, - event + event, + { onAfterTaskError } ) ) } finally { @@ -293,7 +301,7 @@ export async function adapter( } ) } - return params.handler(request, event) + return params.handler(request, event, { onAfterTaskError }) }) // check if response is a Response object diff --git a/packages/next/src/server/web/edge-route-module-wrapper.ts b/packages/next/src/server/web/edge-route-module-wrapper.ts index 7959059a4c192..8aedbad647078 100644 --- a/packages/next/src/server/web/edge-route-module-wrapper.ts +++ b/packages/next/src/server/web/edge-route-module-wrapper.ts @@ -6,7 +6,7 @@ import type { import './globals' -import { adapter, type AdapterOptions } from './adapter' +import { adapter, type AdapterOptions, type HandlerExtraOpts } from './adapter' import { IncrementalCache } from '../lib/incremental-cache' import { RouteMatcher } from '../route-matchers/route-matcher' import type { NextFetchEvent } from './spec-extension/fetch-event' @@ -57,7 +57,7 @@ export class EdgeRouteModuleWrapper { const wrapper = new EdgeRouteModuleWrapper(routeModule, options.nextConfig) // Return the wrapping function. - return (opts: AdapterOptions) => { + return (opts: Omit) => { return adapter({ ...opts, IncrementalCache, @@ -69,7 +69,8 @@ export class EdgeRouteModuleWrapper { private async handler( request: NextRequest, - evt: NextFetchEvent + evt: NextFetchEvent, + extraOpts?: HandlerExtraOpts ): Promise { const utils = getUtils({ pageIsDynamic: this.matcher.isDynamic, @@ -105,7 +106,7 @@ export class EdgeRouteModuleWrapper { supportsDynamicResponse: true, waitUntil, onClose: closeController.onClose.bind(closeController), - onAfterTaskError: undefined, + onAfterTaskError: extraOpts?.onAfterTaskError, experimental: { dynamicIO: !!process.env.__NEXT_DYNAMIC_IO, authInterrupts: !!process.env.__NEXT_EXPERIMENTAL_AUTH_INTERRUPTS, diff --git a/packages/next/src/server/web/types.ts b/packages/next/src/server/web/types.ts index dce815cc2a03d..f3566d283e67f 100644 --- a/packages/next/src/server/web/types.ts +++ b/packages/next/src/server/web/types.ts @@ -26,6 +26,8 @@ export interface RequestData { signal: AbortSignal /** passed in when running in edge runtime sandbox */ waitUntil?: (promise: Promise) => void + /** passed in when running in edge runtime sandbox */ + onAfterTaskError?: (error: unknown) => void } export type NodejsRequestData = Omit & {