diff --git a/.changeset/nasty-geckos-sniff.md b/.changeset/nasty-geckos-sniff.md new file mode 100644 index 000000000..ea166e6c1 --- /dev/null +++ b/.changeset/nasty-geckos-sniff.md @@ -0,0 +1,5 @@ +--- +"@opennextjs/aws": minor +--- + +Add support for the node middleware diff --git a/packages/open-next/src/adapters/config/index.ts b/packages/open-next/src/adapters/config/index.ts index 12adfb939..8fafbc1fb 100644 --- a/packages/open-next/src/adapters/config/index.ts +++ b/packages/open-next/src/adapters/config/index.ts @@ -8,6 +8,7 @@ import { loadBuildId, loadConfig, loadConfigHeaders, + loadFunctionsConfigManifest, loadHtmlPages, loadMiddlewareManifest, loadPrerenderManifest, @@ -35,3 +36,6 @@ export const MiddlewareManifest = export const AppPathsManifest = /* @__PURE__ */ loadAppPathsManifest(NEXT_DIR); export const AppPathRoutesManifest = /* @__PURE__ */ loadAppPathRoutesManifest(NEXT_DIR); + +export const FunctionsConfigManifest = + /* @__PURE__ */ loadFunctionsConfigManifest(NEXT_DIR); diff --git a/packages/open-next/src/adapters/config/util.ts b/packages/open-next/src/adapters/config/util.ts index 501a76501..8c083fc21 100644 --- a/packages/open-next/src/adapters/config/util.ts +++ b/packages/open-next/src/adapters/config/util.ts @@ -1,6 +1,7 @@ import fs from "node:fs"; import path from "node:path"; import type { + FunctionsConfigManifest, MiddlewareManifest, NextConfig, PrerenderManifest, @@ -123,3 +124,13 @@ export function loadMiddlewareManifest(nextDir: string) { const json = fs.readFileSync(filePath, "utf-8"); return JSON.parse(json) as MiddlewareManifest; } + +export function loadFunctionsConfigManifest(nextDir: string) { + const filePath = path.join(nextDir, "server/functions-config-manifest.json"); + try { + const json = fs.readFileSync(filePath, "utf-8"); + return JSON.parse(json) as FunctionsConfigManifest; + } catch (e) { + return { functions: {}, version: 1 }; + } +} diff --git a/packages/open-next/src/build/compileConfig.ts b/packages/open-next/src/build/compileConfig.ts index bb5c24631..bc9657a36 100644 --- a/packages/open-next/src/build/compileConfig.ts +++ b/packages/open-next/src/build/compileConfig.ts @@ -50,7 +50,7 @@ export async function compileOpenNextConfig( // We need to check if the config uses the edge runtime at any point // If it does, we need to compile it with the edge runtime const usesEdgeRuntime = - config.middleware?.external || + (config.middleware?.external && config.middleware.runtime !== "node") || Object.values(config.functions || {}).some((fn) => fn.runtime === "edge"); if (!usesEdgeRuntime) { logger.debug( diff --git a/packages/open-next/src/build/constant.ts b/packages/open-next/src/build/constant.ts new file mode 100644 index 000000000..b2f0d1651 --- /dev/null +++ b/packages/open-next/src/build/constant.ts @@ -0,0 +1,2 @@ +//TODO: Move all other manifest path here as well +export const MIDDLEWARE_TRACE_FILE = "server/middleware.js.nft.json"; diff --git a/packages/open-next/src/build/copyTracedFiles.ts b/packages/open-next/src/build/copyTracedFiles.ts index eaef22c00..65588b438 100644 --- a/packages/open-next/src/build/copyTracedFiles.ts +++ b/packages/open-next/src/build/copyTracedFiles.ts @@ -15,6 +15,7 @@ import path from "node:path"; import type { NextConfig, PrerenderManifest } from "types/next-types"; import logger from "../logger.js"; +import { MIDDLEWARE_TRACE_FILE } from "./constant.js"; const __dirname = url.fileURLToPath(new URL(".", import.meta.url)); @@ -24,14 +25,24 @@ function copyPatchFile(outputDir: string) { copyFileSync(patchFile, outputPatchFile); } +interface CopyTracedFilesOptions { + buildOutputPath: string; + packagePath: string; + outputDir: string; + routes: string[]; + bundledNextServer: boolean; + skipServerFiles?: boolean; +} + // eslint-disable-next-line sonarjs/cognitive-complexity -export async function copyTracedFiles( - buildOutputPath: string, - packagePath: string, - outputDir: string, - routes: string[], - bundledNextServer: boolean, -) { +export async function copyTracedFiles({ + buildOutputPath, + packagePath, + outputDir, + routes, + bundledNextServer, + skipServerFiles, +}: CopyTracedFilesOptions) { const tsStart = Date.now(); const dotNextDir = path.join(buildOutputPath, ".next"); const standaloneDir = path.join(dotNextDir, "standalone"); @@ -58,10 +69,11 @@ export async function copyTracedFiles( const filesToCopy = new Map(); // Files necessary by the server - extractFiles(requiredServerFiles.files).forEach((f) => { - filesToCopy.set(f, f.replace(standaloneDir, outputDir)); - }); - + if (!skipServerFiles) { + extractFiles(requiredServerFiles.files).forEach((f) => { + filesToCopy.set(f, f.replace(standaloneDir, outputDir)); + }); + } // create directory for pages if (existsSync(path.join(standaloneDir, ".next/server/pages"))) { mkdirSync(path.join(outputNextDir, "server/pages"), { @@ -141,6 +153,15 @@ File ${fullFilePath} does not exist } }; + if (existsSync(path.join(dotNextDir, MIDDLEWARE_TRACE_FILE))) { + // We still need to copy the nft.json file so that computeCopyFilesForPage doesn't throw + copyFileSync( + path.join(dotNextDir, MIDDLEWARE_TRACE_FILE), + path.join(standaloneNextDir, MIDDLEWARE_TRACE_FILE), + ); + computeCopyFilesForPage("middleware"); + } + const hasPageDir = routes.some((route) => route.startsWith("pages/")); const hasAppDir = routes.some((route) => route.startsWith("app/")); diff --git a/packages/open-next/src/build/createMiddleware.ts b/packages/open-next/src/build/createMiddleware.ts index b6e8cdcd2..5f4856be0 100644 --- a/packages/open-next/src/build/createMiddleware.ts +++ b/packages/open-next/src/build/createMiddleware.ts @@ -1,6 +1,7 @@ import fs from "node:fs"; import path from "node:path"; +import { loadFunctionsConfigManifest } from "config/util.js"; import logger from "../logger.js"; import type { MiddlewareInfo, @@ -9,6 +10,10 @@ import type { import { buildEdgeBundle } from "./edge/createEdgeBundle.js"; import * as buildHelper from "./helper.js"; import { installDependencies } from "./installDeps.js"; +import { + buildBundledNodeMiddleware, + buildExternalNodeMiddleware, +} from "./middleware/buildNodeMiddleware.js"; /** * Compiles the middleware bundle. @@ -32,10 +37,24 @@ export async function createMiddleware( ), ) as MiddlewareManifest; - const middlewareInfo = middlewareManifest.middleware["/"] as + const edgeMiddlewareInfo = middlewareManifest.middleware["/"] as | MiddlewareInfo | undefined; + if (!edgeMiddlewareInfo) { + // If there is no middleware info, it might be a node middleware + const functionsConfigManifest = loadFunctionsConfigManifest( + path.join(appBuildOutputPath, ".next"), + ); + + if (functionsConfigManifest?.functions["/_middleware"]) { + await (config.middleware?.external + ? buildExternalNodeMiddleware(options) + : buildBundledNodeMiddleware(options)); + return; + } + } + if (config.middleware?.external) { const outputPath = path.join(outputDir, "middleware"); fs.mkdirSync(outputPath, { recursive: true }); @@ -55,7 +74,7 @@ export async function createMiddleware( "middleware.js", ), outfile: path.join(outputPath, "handler.mjs"), - middlewareInfo, + middlewareInfo: edgeMiddlewareInfo, options, overrides: { ...config.middleware.override, @@ -77,7 +96,7 @@ export async function createMiddleware( "edgeFunctionHandler.js", ), outfile: path.join(options.buildDir, "middleware.mjs"), - middlewareInfo, + middlewareInfo: edgeMiddlewareInfo, options, onlyBuildOnce: true, name: "middleware", diff --git a/packages/open-next/src/build/createServerBundle.ts b/packages/open-next/src/build/createServerBundle.ts index 18a1a1db4..6b4a3747d 100644 --- a/packages/open-next/src/build/createServerBundle.ts +++ b/packages/open-next/src/build/createServerBundle.ts @@ -153,13 +153,13 @@ async function generateBundle( buildHelper.copyEnvFile(appBuildOutputPath, packagePath, outputPath); // Copy all necessary traced files - await copyTracedFiles( - appBuildOutputPath, + await copyTracedFiles({ + buildOutputPath: appBuildOutputPath, packagePath, - outputPath, - fnOptions.routes ?? ["app/page.tsx"], - isBundled, - ); + outputDir: outputPath, + routes: fnOptions.routes ?? ["app/page.tsx"], + bundledNextServer: isBundled, + }); // Build Lambda code // note: bundle in OpenNext package b/c the adapter relies on the diff --git a/packages/open-next/src/build/edge/createEdgeBundle.ts b/packages/open-next/src/build/edge/createEdgeBundle.ts index a3664116d..22a7b4efc 100644 --- a/packages/open-next/src/build/edge/createEdgeBundle.ts +++ b/packages/open-next/src/build/edge/createEdgeBundle.ts @@ -16,6 +16,7 @@ import type { import type { OriginResolver } from "types/overrides.js"; import logger from "../../logger.js"; import { openNextEdgePlugins } from "../../plugins/edge.js"; +import { openNextExternalMiddlewarePlugin } from "../../plugins/externalMiddleware.js"; import { openNextReplacementPlugin } from "../../plugins/replacement.js"; import { openNextResolvePlugin } from "../../plugins/resolve.js"; import { getCrossPlatformPathRegex } from "../../utils/regex.js"; @@ -88,14 +89,12 @@ export async function buildEdgeBundle({ target: getCrossPlatformPathRegex("adapters/middleware.js"), deletes: includeCache ? [] : ["includeCacheInMiddleware"], }), + openNextExternalMiddlewarePlugin( + path.join(options.openNextDistDir, "core/edgeFunctionHandler.js"), + ), openNextEdgePlugins({ middlewareInfo, nextDir: path.join(options.appBuildOutputPath, ".next"), - edgeFunctionHandlerPath: path.join( - options.openNextDistDir, - "core", - "edgeFunctionHandler.js", - ), isInCloudfare, }), ], diff --git a/packages/open-next/src/build/middleware/buildNodeMiddleware.ts b/packages/open-next/src/build/middleware/buildNodeMiddleware.ts new file mode 100644 index 000000000..8cf95f003 --- /dev/null +++ b/packages/open-next/src/build/middleware/buildNodeMiddleware.ts @@ -0,0 +1,138 @@ +import fs from "node:fs"; +import path from "node:path"; + +import type { + IncludedOriginResolver, + LazyLoadedOverride, + OverrideOptions, +} from "types/open-next.js"; +import type { OriginResolver } from "types/overrides.js"; +import { getCrossPlatformPathRegex } from "utils/regex.js"; +import { openNextExternalMiddlewarePlugin } from "../../plugins/externalMiddleware.js"; +import { openNextReplacementPlugin } from "../../plugins/replacement.js"; +import { openNextResolvePlugin } from "../../plugins/resolve.js"; +import { copyTracedFiles } from "../copyTracedFiles.js"; +import * as buildHelper from "../helper.js"; +import { installDependencies } from "../installDeps.js"; + +type Override = OverrideOptions & { + originResolver?: LazyLoadedOverride | IncludedOriginResolver; +}; + +export async function buildExternalNodeMiddleware( + options: buildHelper.BuildOptions, +) { + const { appBuildOutputPath, config, outputDir } = options; + if (!config.middleware?.external) { + throw new Error( + "This function should only be called for external middleware", + ); + } + const outputPath = path.join(outputDir, "middleware"); + fs.mkdirSync(outputPath, { recursive: true }); + + // Copy open-next.config.mjs + buildHelper.copyOpenNextConfig( + options.buildDir, + outputPath, + await buildHelper.isEdgeRuntime(config.middleware.override), + ); + const overrides = { + ...config.middleware.override, + originResolver: config.middleware.originResolver, + }; + const includeCache = config.dangerous?.enableCacheInterception; + const packagePath = buildHelper.getPackagePath(options); + + // TODO: change this so that we don't copy unnecessary files + await copyTracedFiles({ + buildOutputPath: appBuildOutputPath, + packagePath, + outputDir: outputPath, + routes: [], + bundledNextServer: false, + skipServerFiles: true, + }); + + function override(target: T) { + return typeof overrides?.[target] === "string" + ? overrides[target] + : undefined; + } + + // Bundle middleware + await buildHelper.esbuildAsync( + { + entryPoints: [ + path.join(options.openNextDistDir, "adapters", "middleware.js"), + ], + outfile: path.join(outputPath, "handler.mjs"), + external: ["./.next/*"], + platform: "node", + plugins: [ + openNextResolvePlugin({ + overrides: { + wrapper: override("wrapper") ?? "aws-lambda", + converter: override("converter") ?? "aws-cloudfront", + ...(includeCache + ? { + tagCache: override("tagCache") ?? "dynamodb-lite", + incrementalCache: override("incrementalCache") ?? "s3-lite", + queue: override("queue") ?? "sqs-lite", + } + : {}), + originResolver: override("originResolver") ?? "pattern-env", + proxyExternalRequest: override("proxyExternalRequest") ?? "node", + }, + fnName: "middleware", + }), + openNextReplacementPlugin({ + name: "externalMiddlewareOverrides", + target: getCrossPlatformPathRegex("adapters/middleware.js"), + deletes: includeCache ? [] : ["includeCacheInMiddleware"], + }), + openNextExternalMiddlewarePlugin( + path.join( + options.openNextDistDir, + "core", + "nodeMiddlewareHandler.js", + ), + ), + ], + banner: { + js: [ + `globalThis.monorepoPackagePath = '${packagePath}';`, + "import process from 'node:process';", + "import { Buffer } from 'node:buffer';", + "import { AsyncLocalStorage } from 'node:async_hooks';", + "import { createRequire as topLevelCreateRequire } from 'module';", + "const require = topLevelCreateRequire(import.meta.url);", + "import bannerUrl from 'url';", + "const __dirname = bannerUrl.fileURLToPath(new URL('.', import.meta.url));", + ].join(""), + }, + }, + options, + ); + + // Do we need to copy or do something with env file here? + + installDependencies(outputPath, config.middleware?.install); +} + +export async function buildBundledNodeMiddleware( + options: buildHelper.BuildOptions, +) { + await buildHelper.esbuildAsync( + { + entryPoints: [ + path.join(options.openNextDistDir, "core/nodeMiddlewareHandler.js"), + ], + external: ["./.next/*"], + outfile: path.join(options.buildDir, "middleware.mjs"), + bundle: true, + platform: "node", + }, + options, + ); +} diff --git a/packages/open-next/src/core/edgeFunctionHandler.ts b/packages/open-next/src/core/edgeFunctionHandler.ts index 90d215661..51e864807 100644 --- a/packages/open-next/src/core/edgeFunctionHandler.ts +++ b/packages/open-next/src/core/edgeFunctionHandler.ts @@ -28,7 +28,9 @@ export default async function edgeFunctionHandler( }, }, }); - await result.waitUntil; + globalThis.__openNextAls + .getStore() + ?.pendingPromiseRunner.add(result.waitUntil); const response = result.response; return response; } diff --git a/packages/open-next/src/core/nodeMiddlewareHandler.ts b/packages/open-next/src/core/nodeMiddlewareHandler.ts new file mode 100644 index 000000000..73c86d140 --- /dev/null +++ b/packages/open-next/src/core/nodeMiddlewareHandler.ts @@ -0,0 +1,46 @@ +import type { RequestData } from "types/global"; + +type EdgeRequest = Omit; + +// Do we need Buffer here? +import { Buffer } from "node:buffer"; +globalThis.Buffer = Buffer; + +// AsyncLocalStorage is needed to be defined globally +import { AsyncLocalStorage } from "node:async_hooks"; +globalThis.AsyncLocalStorage = AsyncLocalStorage; + +interface NodeMiddleware { + default: (req: { + handler: any; + request: EdgeRequest; + page: "middleware"; + }) => Promise<{ + response: Response; + waitUntil: Promise; + }>; + middleware: any; +} + +let _module: NodeMiddleware | undefined; + +export default async function middlewareHandler( + request: EdgeRequest, +): Promise { + if (!_module) { + // We use await import here so that we are sure that it is loaded after AsyncLocalStorage is defined on globalThis + // We need both await here, same way as in https://github.com/opennextjs/opennextjs-aws/pull/704 + //@ts-expect-error - This file should be bundled with esbuild + _module = await (await import("./.next/server/middleware.js")).default; + } + const adapterFn = _module!.default || _module; + const result = await adapterFn({ + handler: _module!.middleware || _module, + request: request, + page: "middleware", + }); + globalThis.__openNextAls + .getStore() + ?.pendingPromiseRunner.add(result.waitUntil); + return result.response; +} diff --git a/packages/open-next/src/core/routing/middleware.ts b/packages/open-next/src/core/routing/middleware.ts index cca12a7b2..b0aa2b4cf 100644 --- a/packages/open-next/src/core/routing/middleware.ts +++ b/packages/open-next/src/core/routing/middleware.ts @@ -1,6 +1,10 @@ import type { ReadableStream } from "node:stream/web"; -import { MiddlewareManifest, NextConfig } from "config/index.js"; +import { + FunctionsConfigManifest, + MiddlewareManifest, + NextConfig, +} from "config/index.js"; import type { InternalEvent, InternalResult } from "types/open-next.js"; import { emptyReadableStream } from "utils/stream.js"; @@ -20,8 +24,12 @@ import { } from "./util.js"; const middlewareManifest = MiddlewareManifest; +const functionsConfigManifest = FunctionsConfigManifest; -const middleMatch = getMiddlewareMatch(middlewareManifest); +const middleMatch = getMiddlewareMatch( + middlewareManifest, + functionsConfigManifest, +); type MiddlewareEvent = InternalEvent & { responseHeaders?: Record; diff --git a/packages/open-next/src/core/routing/util.ts b/packages/open-next/src/core/routing/util.ts index 0056bf6df..35d5029d4 100644 --- a/packages/open-next/src/core/routing/util.ts +++ b/packages/open-next/src/core/routing/util.ts @@ -6,7 +6,10 @@ import { BuildId, HtmlPages, NextConfig } from "config/index.js"; import type { IncomingMessage } from "http/index.js"; import { OpenNextNodeResponse } from "http/openNextResponse.js"; import { parseHeaders } from "http/util.js"; -import type { MiddlewareManifest } from "types/next-types"; +import type { + FunctionsConfigManifest, + MiddlewareManifest, +} from "types/next-types"; import type { InternalEvent, InternalResult, @@ -158,7 +161,17 @@ export function convertToQuery(querystring: string) { * * @__PURE__ */ -export function getMiddlewareMatch(middlewareManifest: MiddlewareManifest) { +export function getMiddlewareMatch( + middlewareManifest: MiddlewareManifest, + functionsManifest?: FunctionsConfigManifest, +) { + if (functionsManifest?.functions?.["/_middleware"]) { + return ( + functionsManifest.functions["/_middleware"].matchers?.map( + ({ regexp }) => new RegExp(regexp), + ) ?? [/.*/] + ); + } const rootMiddleware = middlewareManifest.middleware["/"]; if (!rootMiddleware?.matchers) return []; return rootMiddleware.matchers.map(({ regexp }) => new RegExp(regexp)); diff --git a/packages/open-next/src/core/util.ts b/packages/open-next/src/core/util.ts index a4a466b86..a337ad510 100644 --- a/packages/open-next/src/core/util.ts +++ b/packages/open-next/src/core/util.ts @@ -1,6 +1,3 @@ -import fs from "node:fs"; -import path from "node:path"; - import { AppPathsManifestKeys, NextConfig, @@ -8,7 +5,6 @@ import { } from "config/index.js"; // @ts-ignore import NextServer from "next/dist/server/next-server.js"; -import type { MiddlewareManifest } from "types/next-types.js"; import { debug } from "../adapters/logger.js"; import { @@ -63,18 +59,6 @@ export const requestHandler = new NextServer.default({ dir: __dirname, }).getRequestHandler(); -export function getMiddlewareMatch(middlewareManifest: MiddlewareManifest) { - const rootMiddleware = middlewareManifest.middleware["/"]; - if (!rootMiddleware?.matchers) return []; - return rootMiddleware.matchers.map(({ regexp }) => new RegExp(regexp)); -} - -export function loadMiddlewareManifest(nextDir: string) { - const filePath = path.join(nextDir, "server", "middleware-manifest.json"); - const json = fs.readFileSync(filePath, "utf-8"); - return JSON.parse(json) as MiddlewareManifest; -} - //#override setNextjsPrebundledReact export function setNextjsPrebundledReact(rawPath: string) { // WORKAROUND: Set `__NEXT_PRIVATE_PREBUNDLED_REACT` to use prebundled React diff --git a/packages/open-next/src/plugins/edge.ts b/packages/open-next/src/plugins/edge.ts index 284b8aa32..29761eb20 100644 --- a/packages/open-next/src/plugins/edge.ts +++ b/packages/open-next/src/plugins/edge.ts @@ -12,6 +12,7 @@ import { loadBuildId, loadConfig, loadConfigHeaders, + loadFunctionsConfigManifest, loadHtmlPages, loadMiddlewareManifest, loadPrerenderManifest, @@ -22,21 +23,18 @@ import { getCrossPlatformPathRegex } from "../utils/regex.js"; export interface IPluginSettings { nextDir: string; - edgeFunctionHandlerPath?: string; middlewareInfo?: MiddlewareInfo; isInCloudfare?: boolean; } /** * @param opts.nextDir - The path to the .next directory - * @param opts.edgeFunctionHandlerPath - The path to the edgeFunctionHandler.js file that we'll use to bundle the routing * @param opts.middlewareInfo - Information about the middleware * @param opts.isInCloudfare - Whether the code runs on the cloudflare runtime * @returns */ export function openNextEdgePlugins({ nextDir, - edgeFunctionHandlerPath, middlewareInfo, isInCloudfare, }: IPluginSettings): Plugin { @@ -57,17 +55,6 @@ export function openNextEdgePlugins({ name: "opennext-edge", setup(build) { logger.debug(chalk.blue("OpenNext Edge plugin")); - if (edgeFunctionHandlerPath) { - // If we bundle the routing, we need to resolve the middleware - build.onResolve( - { filter: getCrossPlatformPathRegex("./middleware.mjs") }, - () => { - return { - path: edgeFunctionHandlerPath, - }; - }, - ); - } build.onResolve({ filter: /\.(mjs|wasm)$/g }, () => { return { @@ -180,6 +167,7 @@ ${contents} const MiddlewareManifest = loadMiddlewareManifest(nextDir); const AppPathsManifest = loadAppPathsManifest(nextDir); const AppPathRoutesManifest = loadAppPathRoutesManifest(nextDir); + const FunctionsConfigManifest = loadFunctionsConfigManifest(nextDir); const contents = ` import path from "node:path"; @@ -203,6 +191,7 @@ ${contents} export const MiddlewareManifest = ${JSON.stringify(MiddlewareManifest)}; export const AppPathsManifest = ${JSON.stringify(AppPathsManifest)}; export const AppPathRoutesManifest = ${JSON.stringify(AppPathRoutesManifest)}; + export const FunctionsConfigManifest = ${JSON.stringify(FunctionsConfigManifest)}; process.env.NEXT_BUILD_ID = BuildId; diff --git a/packages/open-next/src/plugins/externalMiddleware.ts b/packages/open-next/src/plugins/externalMiddleware.ts new file mode 100644 index 000000000..5a9517b48 --- /dev/null +++ b/packages/open-next/src/plugins/externalMiddleware.ts @@ -0,0 +1,15 @@ +import type { Plugin } from "esbuild"; +import { getCrossPlatformPathRegex } from "utils/regex.js"; + +export function openNextExternalMiddlewarePlugin(functionPath: string): Plugin { + return { + name: "open-next-external-node-middleware", + setup(build) { + // If we bundle the routing, we need to resolve the middleware + build.onResolve( + { filter: getCrossPlatformPathRegex("./middleware.mjs") }, + () => ({ path: functionPath }), + ); + }, + }; +} diff --git a/packages/open-next/src/types/next-types.ts b/packages/open-next/src/types/next-types.ts index 1608dda65..4d0803e05 100644 --- a/packages/open-next/src/types/next-types.ts +++ b/packages/open-next/src/types/next-types.ts @@ -178,3 +178,20 @@ export type PluginHandler = ( res: OpenNextNodeResponse, options: Options, ) => Promise; + +export interface FunctionsConfigManifest { + version: number; + functions: Record< + string, + { + maxDuration?: number | undefined; + runtime?: "nodejs"; + matchers?: Array<{ + regexp: string; + originalSource: string; + has?: Rewrite["has"]; + missing?: Rewrite["has"]; + }>; + } + >; +} diff --git a/packages/open-next/src/types/open-next.ts b/packages/open-next/src/types/open-next.ts index 5ecc8b06e..83afb0f0e 100644 --- a/packages/open-next/src/types/open-next.ts +++ b/packages/open-next/src/types/open-next.ts @@ -353,6 +353,12 @@ export interface OpenNextConfig { //We force the middleware to be a function external: true; + /** + * The runtime used by next for the middleware. + * @default "edge" + */ + runtime?: "node" | "edge"; + /** * The override options for the middleware. * By default the lite override are used (.i.e. s3-lite, dynamodb-lite, sqs-lite) diff --git a/packages/open-next/src/utils/promise.ts b/packages/open-next/src/utils/promise.ts index b2688cefb..9fde876cb 100644 --- a/packages/open-next/src/utils/promise.ts +++ b/packages/open-next/src/utils/promise.ts @@ -122,8 +122,14 @@ export function runWithOpenNextRequestContext( }, async () => { provideNextAfterProvider(); - const result = await fn(); - await awaitAllDetachedPromise(); + let result: T; + try { + result = await fn(); + // We always await all detached promises before returning the result + // However we don't want to catch errors here, we want to let the parent handle it + } finally { + await awaitAllDetachedPromise(); + } return result; }, ); diff --git a/packages/tests-unit/tests/core/routing/middleware.test.ts b/packages/tests-unit/tests/core/routing/middleware.test.ts index 8229e0b28..066d1a711 100644 --- a/packages/tests-unit/tests/core/routing/middleware.test.ts +++ b/packages/tests-unit/tests/core/routing/middleware.test.ts @@ -32,6 +32,7 @@ vi.mock("@opennextjs/aws/adapters/config/index.js", () => ({ functions: {}, version: 2, }, + FunctionsConfigManifest: undefined, })); vi.mock("@opennextjs/aws/core/routing/i18n/index.js", () => ({