@opennextjs/aws 3.6.5 → 3.7.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (36) hide show
  1. package/README.md +1 -1
  2. package/dist/adapters/cache.js +4 -4
  3. package/dist/adapters/composable-cache.js +14 -4
  4. package/dist/adapters/edge-adapter.js +5 -1
  5. package/dist/adapters/middleware.js +18 -8
  6. package/dist/build/copyTracedFiles.js +10 -5
  7. package/dist/build/createAssets.js +55 -51
  8. package/dist/build/createServerBundle.js +0 -1
  9. package/dist/build/patch/patches/index.d.ts +0 -1
  10. package/dist/build/patch/patches/index.js +0 -1
  11. package/dist/build/patch/patches/patchNextServer.d.ts +13 -3
  12. package/dist/build/patch/patches/patchNextServer.js +82 -44
  13. package/dist/core/createMainHandler.js +4 -1
  14. package/dist/core/requestHandler.js +11 -2
  15. package/dist/core/resolve.d.ts +5 -0
  16. package/dist/core/resolve.js +11 -0
  17. package/dist/core/routing/cacheInterceptor.js +13 -1
  18. package/dist/core/routing/matcher.d.ts +5 -0
  19. package/dist/core/routing/matcher.js +9 -3
  20. package/dist/core/routing/util.js +2 -0
  21. package/dist/core/routingHandler.d.ts +5 -1
  22. package/dist/core/routingHandler.js +75 -52
  23. package/dist/overrides/assetResolver/dummy.d.ts +8 -0
  24. package/dist/overrides/assetResolver/dummy.js +9 -0
  25. package/dist/overrides/converters/edge.js +7 -1
  26. package/dist/types/global.d.ts +9 -1
  27. package/dist/types/next-types.d.ts +2 -0
  28. package/dist/types/open-next.d.ts +8 -1
  29. package/dist/types/overrides.d.ts +19 -5
  30. package/dist/utils/cache.d.ts +2 -1
  31. package/dist/utils/cache.js +32 -0
  32. package/dist/utils/promise.d.ts +2 -1
  33. package/dist/utils/promise.js +3 -2
  34. package/package.json +1 -1
  35. package/dist/build/patch/patches/dropBabel.d.ts +0 -20
  36. package/dist/build/patch/patches/dropBabel.js +0 -72
@@ -35,6 +35,11 @@ export declare function resolveImageLoader(imageLoader: RemoveUndefined<OpenNext
35
35
  * @__PURE__
36
36
  */
37
37
  export declare function resolveOriginResolver(originResolver: RemoveUndefined<OpenNextConfig["middleware"]>["originResolver"]): Promise<import("../types/overrides").OriginResolver>;
38
+ /**
39
+ * @returns
40
+ * @__PURE__
41
+ */
42
+ export declare function resolveAssetResolver(assetResolver: RemoveUndefined<OpenNextConfig["middleware"]>["assetResolver"]): Promise<import("../types/overrides").AssetResolver>;
38
43
  /**
39
44
  * @__PURE__
40
45
  */
@@ -78,6 +78,17 @@ export async function resolveOriginResolver(originResolver) {
78
78
  const m_1 = await import("../overrides/originResolver/pattern-env.js");
79
79
  return m_1.default;
80
80
  }
81
+ /**
82
+ * @returns
83
+ * @__PURE__
84
+ */
85
+ export async function resolveAssetResolver(assetResolver) {
86
+ if (typeof assetResolver === "function") {
87
+ return assetResolver();
88
+ }
89
+ const m_1 = await import("../overrides/assetResolver/dummy.js");
90
+ return m_1.default;
91
+ }
81
92
  /**
82
93
  * @__PURE__
83
94
  */
@@ -7,6 +7,16 @@ import { localizePath } from "./i18n";
7
7
  import { generateMessageGroupId } from "./queue";
8
8
  const CACHE_ONE_YEAR = 60 * 60 * 24 * 365;
9
9
  const CACHE_ONE_MONTH = 60 * 60 * 24 * 30;
10
+ /*
11
+ * We use this header to prevent Firefox (and possibly some CDNs) from incorrectly reusing the RSC responses during caching.
12
+ * This can especially happen when there's a redirect in the middleware as the `_rsc` query parameter is not visible there.
13
+ * So it will get dropped during the redirect, which results in the RSC response being cached instead of the actual HTML on the path `/`.
14
+ * This value can be found in the routes manifest, under `rsc.varyHeader`.
15
+ * They recompute it here in Next:
16
+ * https://github.com/vercel/next.js/blob/c5bf5bb4c8b01b1befbbfa7ad97a97476ee9d0d7/packages/next/src/server/base-server.ts#L2011
17
+ * Also see this PR: https://github.com/vercel/next.js/pull/79426
18
+ */
19
+ const VARY_HEADER = "RSC, Next-Router-State-Tree, Next-Router-Prefetch, Next-Router-Segment-Prefetch, Next-Url";
10
20
  async function computeCacheControl(path, body, host, revalidate, lastModified) {
11
21
  let finalRevalidate = CACHE_ONE_YEAR;
12
22
  const existingRoute = Object.entries(PrerenderManifest.routes).find((p) => p[0] === path)?.[1];
@@ -92,13 +102,15 @@ async function generateResult(event, localizedPath, cachedValue, lastModified) {
92
102
  const cacheControl = await computeCacheControl(localizedPath, body, event.headers.host, cachedValue.revalidate, lastModified);
93
103
  return {
94
104
  type: "core",
95
- statusCode: 200,
105
+ // sometimes other status codes can be cached, like 404. For these cases, we should return the correct status code
106
+ statusCode: cachedValue.meta?.status ?? 200,
96
107
  body: toReadableStream(body, false),
97
108
  isBase64Encoded: false,
98
109
  headers: {
99
110
  ...cacheControl,
100
111
  "content-type": type,
101
112
  ...cachedValue.meta?.headers,
113
+ vary: VARY_HEADER,
102
114
  },
103
115
  };
104
116
  }
@@ -1,6 +1,11 @@
1
1
  import type { Header, PrerenderManifest, RedirectDefinition, RewriteDefinition } from "../../types/next-types";
2
2
  import type { InternalEvent, InternalResult } from "../../types/open-next";
3
3
  export declare function getNextConfigHeaders(event: InternalEvent, configHeaders?: Header[] | undefined): Record<string, string | undefined>;
4
+ /**
5
+ * TODO: This method currently only check for the first match.
6
+ * It should check for all matches for `beforeFiles` and `afterFiles` rewrite
7
+ * See https://nextjs.org/docs/app/api-reference/config/next-config-js/rewrites
8
+ */
4
9
  export declare function handleRewrites<T extends RewriteDefinition>(event: InternalEvent, rewrites: T[]): {
5
10
  internalEvent: {
6
11
  query: Record<string, string | string[]>;
@@ -97,6 +97,11 @@ export function getNextConfigHeaders(event, configHeaders) {
97
97
  }
98
98
  return requestHeaders;
99
99
  }
100
+ /**
101
+ * TODO: This method currently only check for the first match.
102
+ * It should check for all matches for `beforeFiles` and `afterFiles` rewrite
103
+ * See https://nextjs.org/docs/app/api-reference/config/next-config-js/rewrites
104
+ */
100
105
  export function handleRewrites(event, rewrites) {
101
106
  const { rawPath, headers, query, cookies, url } = event;
102
107
  const localizedRawPath = localizePath(event);
@@ -259,7 +264,8 @@ export function handleRedirects(event, redirects) {
259
264
  }
260
265
  export function fixDataPage(internalEvent, buildId) {
261
266
  const { rawPath, query } = internalEvent;
262
- const dataPattern = `${NextConfig.basePath ?? ""}/_next/data/${buildId}`;
267
+ const basePath = NextConfig.basePath ?? "";
268
+ const dataPattern = `${basePath}/_next/data/${buildId}`;
263
269
  // Return 404 for data requests that don't match the buildId
264
270
  if (rawPath.startsWith("/_next/data") && !rawPath.startsWith(dataPattern)) {
265
271
  return {
@@ -273,9 +279,9 @@ export function fixDataPage(internalEvent, buildId) {
273
279
  };
274
280
  }
275
281
  if (rawPath.startsWith(dataPattern) && rawPath.endsWith(".json")) {
276
- const newPath = rawPath
282
+ const newPath = `${basePath}${rawPath
277
283
  .slice(dataPattern.length, -".json".length)
278
- .replace(/^\/index$/, "/");
284
+ .replace(/^\/index$/, "/")}`;
279
285
  query.__nextDataReq = "1";
280
286
  return {
281
287
  ...internalEvent,
@@ -233,6 +233,8 @@ export function addOpenNextHeader(headers) {
233
233
  }
234
234
  if (globalThis.openNextDebug) {
235
235
  headers["X-OpenNext-Version"] = globalThis.openNextVersion;
236
+ }
237
+ if (process.env.OPEN_NEXT_REQUEST_ID_HEADER || globalThis.openNextDebug) {
236
238
  headers["X-OpenNext-RequestId"] =
237
239
  globalThis.__openNextAls.getStore()?.requestId;
238
240
  }
@@ -1,8 +1,12 @@
1
1
  import type { InternalEvent, InternalResult, RoutingResult } from "../types/open-next";
2
+ import type { AssetResolver } from "../types/overrides";
2
3
  export declare const MIDDLEWARE_HEADER_PREFIX = "x-middleware-response-";
3
4
  export declare const MIDDLEWARE_HEADER_PREFIX_LEN: number;
4
5
  export declare const INTERNAL_HEADER_PREFIX = "x-opennext-";
5
6
  export declare const INTERNAL_HEADER_INITIAL_URL = "x-opennext-initial-url";
6
7
  export declare const INTERNAL_HEADER_LOCALE = "x-opennext-locale";
7
8
  export declare const INTERNAL_HEADER_RESOLVED_ROUTES = "x-opennext-resolved-routes";
8
- export default function routingHandler(event: InternalEvent): Promise<InternalResult | RoutingResult>;
9
+ export declare const INTERNAL_EVENT_REQUEST_ID = "x-opennext-request-id";
10
+ export default function routingHandler(event: InternalEvent, { assetResolver }: {
11
+ assetResolver?: AssetResolver;
12
+ }): Promise<InternalResult | RoutingResult>;
@@ -12,6 +12,7 @@ export const INTERNAL_HEADER_PREFIX = "x-opennext-";
12
12
  export const INTERNAL_HEADER_INITIAL_URL = `${INTERNAL_HEADER_PREFIX}initial-url`;
13
13
  export const INTERNAL_HEADER_LOCALE = `${INTERNAL_HEADER_PREFIX}locale`;
14
14
  export const INTERNAL_HEADER_RESOLVED_ROUTES = `${INTERNAL_HEADER_PREFIX}resolved-routes`;
15
+ export const INTERNAL_EVENT_REQUEST_ID = `${INTERNAL_HEADER_PREFIX}request-id`;
15
16
  // Geolocation headers starting from Nextjs 15
16
17
  // See https://github.com/vercel/vercel/blob/7714b1c/packages/functions/src/headers.ts
17
18
  const geoHeaderToNextHeader = {
@@ -21,17 +22,25 @@ const geoHeaderToNextHeader = {
21
22
  "x-open-next-latitude": "x-vercel-ip-latitude",
22
23
  "x-open-next-longitude": "x-vercel-ip-longitude",
23
24
  };
24
- function applyMiddlewareHeaders(eventHeaders, middlewareHeaders, setPrefix = true) {
25
- const keyPrefix = setPrefix ? MIDDLEWARE_HEADER_PREFIX : "";
25
+ /**
26
+ * Adds the middleware headers to an event or result.
27
+ *
28
+ * @param eventOrResult
29
+ * @param middlewareHeaders
30
+ */
31
+ function applyMiddlewareHeaders(eventOrResult, middlewareHeaders) {
32
+ // Use the `MIDDLEWARE_HEADER_PREFIX` prefix for events, they will be processed by the request handler later.
33
+ // Results do not go through the request handler and should not be prefixed.
34
+ const isResult = isInternalResult(eventOrResult);
35
+ const headers = eventOrResult.headers;
36
+ const keyPrefix = isResult ? "" : MIDDLEWARE_HEADER_PREFIX;
26
37
  Object.entries(middlewareHeaders).forEach(([key, value]) => {
27
38
  if (value) {
28
- eventHeaders[keyPrefix + key] = Array.isArray(value)
29
- ? value.join(",")
30
- : value;
39
+ headers[keyPrefix + key] = Array.isArray(value) ? value.join(",") : value;
31
40
  }
32
41
  });
33
42
  }
34
- export default async function routingHandler(event) {
43
+ export default async function routingHandler(event, { assetResolver }) {
35
44
  try {
36
45
  // Add Next geo headers
37
46
  for (const [openNextGeoName, nextGeoName] of Object.entries(geoHeaderToNextHeader)) {
@@ -48,12 +57,13 @@ export default async function routingHandler(event) {
48
57
  delete event.headers[key];
49
58
  }
50
59
  }
51
- const nextHeaders = getNextConfigHeaders(event, ConfigHeaders);
52
- let internalEvent = fixDataPage(event, BuildId);
53
- if ("statusCode" in internalEvent) {
54
- return internalEvent;
60
+ // Headers from the Next config and middleware (the later are applied further down).
61
+ let headers = getNextConfigHeaders(event, ConfigHeaders);
62
+ let eventOrResult = fixDataPage(event, BuildId);
63
+ if (isInternalResult(eventOrResult)) {
64
+ return eventOrResult;
55
65
  }
56
- const redirect = handleRedirects(internalEvent, RoutesManifest.redirects);
66
+ const redirect = handleRedirects(eventOrResult, RoutesManifest.redirects);
57
67
  if (redirect) {
58
68
  // We need to encode the value in the Location header to make sure it is valid according to RFC
59
69
  // https://stackoverflow.com/a/7654605/16587222
@@ -61,99 +71,105 @@ export default async function routingHandler(event) {
61
71
  debug("redirect", redirect);
62
72
  return redirect;
63
73
  }
64
- const eventOrResult = await handleMiddleware(internalEvent,
74
+ const middlewareEventOrResult = await handleMiddleware(eventOrResult,
65
75
  // We need to pass the initial search without any decoding
66
76
  // TODO: we'd need to refactor InternalEvent to include the initial querystring directly
67
77
  // Should be done in another PR because it is a breaking change
68
78
  new URL(event.url).search);
69
- const isResult = "statusCode" in eventOrResult;
70
- if (isResult) {
71
- return eventOrResult;
79
+ if (isInternalResult(middlewareEventOrResult)) {
80
+ return middlewareEventOrResult;
72
81
  }
73
- const middlewareResponseHeaders = eventOrResult.responseHeaders;
74
- let isExternalRewrite = eventOrResult.isExternalRewrite ?? false;
75
- // internalEvent is `InternalEvent | MiddlewareEvent`
76
- internalEvent = eventOrResult;
82
+ headers = {
83
+ ...middlewareEventOrResult.responseHeaders,
84
+ ...headers,
85
+ };
86
+ let isExternalRewrite = middlewareEventOrResult.isExternalRewrite ?? false;
87
+ eventOrResult = middlewareEventOrResult;
77
88
  if (!isExternalRewrite) {
78
89
  // First rewrite to be applied
79
- const beforeRewrites = handleRewrites(internalEvent, RoutesManifest.rewrites.beforeFiles);
80
- internalEvent = beforeRewrites.internalEvent;
81
- isExternalRewrite = beforeRewrites.isExternalRewrite;
90
+ const beforeRewrite = handleRewrites(eventOrResult, RoutesManifest.rewrites.beforeFiles);
91
+ eventOrResult = beforeRewrite.internalEvent;
92
+ isExternalRewrite = beforeRewrite.isExternalRewrite;
93
+ // Check for matching public files after `beforeFiles` rewrites
94
+ // See:
95
+ // - https://nextjs.org/docs/app/api-reference/file-conventions/middleware#execution-order
96
+ // - https://nextjs.org/docs/app/api-reference/config/next-config-js/rewrites
97
+ if (!isExternalRewrite) {
98
+ const assetResult = await assetResolver?.maybeGetAssetResult?.(eventOrResult);
99
+ if (assetResult) {
100
+ applyMiddlewareHeaders(assetResult, headers);
101
+ return assetResult;
102
+ }
103
+ }
82
104
  }
83
- const foundStaticRoute = staticRouteMatcher(internalEvent.rawPath);
105
+ const foundStaticRoute = staticRouteMatcher(eventOrResult.rawPath);
84
106
  const isStaticRoute = !isExternalRewrite && foundStaticRoute.length > 0;
85
107
  if (!(isStaticRoute || isExternalRewrite)) {
86
108
  // Second rewrite to be applied
87
- const afterRewrites = handleRewrites(internalEvent, RoutesManifest.rewrites.afterFiles);
88
- internalEvent = afterRewrites.internalEvent;
89
- isExternalRewrite = afterRewrites.isExternalRewrite;
109
+ const afterRewrite = handleRewrites(eventOrResult, RoutesManifest.rewrites.afterFiles);
110
+ eventOrResult = afterRewrite.internalEvent;
111
+ isExternalRewrite = afterRewrite.isExternalRewrite;
90
112
  }
91
113
  let isISR = false;
92
114
  // We want to run this just before the dynamic route check
93
115
  // We can skip it if its an external rewrite
94
116
  if (!isExternalRewrite) {
95
- const fallbackResult = handleFallbackFalse(internalEvent, PrerenderManifest);
96
- internalEvent = fallbackResult.event;
117
+ const fallbackResult = handleFallbackFalse(eventOrResult, PrerenderManifest);
118
+ eventOrResult = fallbackResult.event;
97
119
  isISR = fallbackResult.isISR;
98
120
  }
99
- const foundDynamicRoute = dynamicRouteMatcher(internalEvent.rawPath);
121
+ const foundDynamicRoute = dynamicRouteMatcher(eventOrResult.rawPath);
100
122
  const isDynamicRoute = !isExternalRewrite && foundDynamicRoute.length > 0;
101
123
  if (!(isDynamicRoute || isStaticRoute || isExternalRewrite)) {
102
124
  // Fallback rewrite to be applied
103
- const fallbackRewrites = handleRewrites(internalEvent, RoutesManifest.rewrites.fallback);
104
- internalEvent = fallbackRewrites.internalEvent;
125
+ const fallbackRewrites = handleRewrites(eventOrResult, RoutesManifest.rewrites.fallback);
126
+ eventOrResult = fallbackRewrites.internalEvent;
105
127
  isExternalRewrite = fallbackRewrites.isExternalRewrite;
106
128
  }
107
- const isNextImageRoute = internalEvent.rawPath.startsWith("/_next/image");
129
+ const isNextImageRoute = eventOrResult.rawPath.startsWith("/_next/image");
108
130
  const isRouteFoundBeforeAllRewrites = isStaticRoute || isDynamicRoute || isExternalRewrite;
109
131
  // If we still haven't found a route, we show the 404 page
110
132
  // We need to ensure that rewrites are applied before showing the 404 page
111
133
  if (!(isRouteFoundBeforeAllRewrites ||
112
134
  isNextImageRoute ||
113
135
  // We need to check again once all rewrites have been applied
114
- staticRouteMatcher(internalEvent.rawPath).length > 0 ||
115
- dynamicRouteMatcher(internalEvent.rawPath).length > 0)) {
116
- internalEvent = {
117
- ...internalEvent,
136
+ staticRouteMatcher(eventOrResult.rawPath).length > 0 ||
137
+ dynamicRouteMatcher(eventOrResult.rawPath).length > 0)) {
138
+ eventOrResult = {
139
+ ...eventOrResult,
118
140
  rawPath: "/404",
119
- url: constructNextUrl(internalEvent.url, "/404"),
141
+ url: constructNextUrl(eventOrResult.url, "/404"),
120
142
  headers: {
121
- ...internalEvent.headers,
143
+ ...eventOrResult.headers,
122
144
  "x-middleware-response-cache-control": "private, no-cache, no-store, max-age=0, must-revalidate",
123
145
  },
124
146
  };
125
147
  }
126
148
  if (globalThis.openNextConfig.dangerous?.enableCacheInterception &&
127
- !("statusCode" in internalEvent)) {
149
+ !isInternalResult(eventOrResult)) {
128
150
  debug("Cache interception enabled");
129
- internalEvent = await cacheInterceptor(internalEvent);
130
- if ("statusCode" in internalEvent) {
131
- applyMiddlewareHeaders(internalEvent.headers, {
132
- ...middlewareResponseHeaders,
133
- ...nextHeaders,
134
- }, false);
135
- return internalEvent;
151
+ eventOrResult = await cacheInterceptor(eventOrResult);
152
+ if (isInternalResult(eventOrResult)) {
153
+ applyMiddlewareHeaders(eventOrResult, headers);
154
+ return eventOrResult;
136
155
  }
137
156
  }
138
157
  // We apply the headers from the middleware response last
139
- applyMiddlewareHeaders(internalEvent.headers, {
140
- ...middlewareResponseHeaders,
141
- ...nextHeaders,
142
- });
158
+ applyMiddlewareHeaders(eventOrResult, headers);
143
159
  const resolvedRoutes = [
144
160
  ...foundStaticRoute,
145
161
  ...foundDynamicRoute,
146
162
  ];
147
163
  debug("resolvedRoutes", resolvedRoutes);
148
164
  return {
149
- internalEvent,
165
+ internalEvent: eventOrResult,
150
166
  isExternalRewrite,
151
167
  origin: false,
152
168
  isISR,
153
169
  resolvedRoutes,
154
170
  initialURL: event.url,
155
171
  locale: NextConfig.i18n
156
- ? detectLocale(internalEvent, NextConfig.i18n)
172
+ ? detectLocale(eventOrResult, NextConfig.i18n)
157
173
  : undefined,
158
174
  };
159
175
  }
@@ -184,3 +200,10 @@ export default async function routingHandler(event) {
184
200
  };
185
201
  }
186
202
  }
203
+ /**
204
+ * @param eventOrResult
205
+ * @returns Whether the event is an instance of `InternalResult`
206
+ */
207
+ function isInternalResult(eventOrResult) {
208
+ return eventOrResult != null && "statusCode" in eventOrResult;
209
+ }
@@ -0,0 +1,8 @@
1
+ import type { AssetResolver } from "../../types/overrides";
2
+ /**
3
+ * A dummy asset resolver.
4
+ *
5
+ * It never overrides the result with an asset.
6
+ */
7
+ declare const resolver: AssetResolver;
8
+ export default resolver;
@@ -0,0 +1,9 @@
1
+ /**
2
+ * A dummy asset resolver.
3
+ *
4
+ * It never overrides the result with an asset.
5
+ */
6
+ const resolver = {
7
+ name: "dummy",
8
+ };
9
+ export default resolver;
@@ -2,6 +2,8 @@ import { Buffer } from "node:buffer";
2
2
  import cookieParser from "cookie";
3
3
  import { parseSetCookieHeader } from "../../http/util";
4
4
  import { getQueryFromSearchParams } from "./utils.js";
5
+ // https://fetch.spec.whatwg.org/#statuses
6
+ const NULL_BODY_STATUSES = new Set([101, 103, 204, 205, 304]);
5
7
  const converter = {
6
8
  convertFrom: async (event) => {
7
9
  const url = new URL(event.url);
@@ -77,7 +79,11 @@ const converter = {
77
79
  headers.set(key, value);
78
80
  }
79
81
  }
80
- return new Response(result.body, {
82
+ // We should not return a body for statusCode's that doesn't allow bodies
83
+ const body = NULL_BODY_STATUSES.has(result.statusCode)
84
+ ? null
85
+ : result.body;
86
+ return new Response(body, {
81
87
  status: result.statusCode,
82
88
  headers,
83
89
  });
@@ -1,6 +1,6 @@
1
1
  import type { AsyncLocalStorage } from "node:async_hooks";
2
2
  import type { OutgoingHttpHeaders } from "node:http";
3
- import type { CDNInvalidationHandler, IncrementalCache, ProxyExternalRequest, Queue, TagCache } from "./overrides";
3
+ import type { AssetResolver, CDNInvalidationHandler, IncrementalCache, ProxyExternalRequest, Queue, TagCache } from "./overrides";
4
4
  import type { DetachedPromiseRunner } from "../utils/promise";
5
5
  import type { OpenNextConfig, WaitUntil } from "./open-next";
6
6
  export interface RequestData {
@@ -53,6 +53,8 @@ interface OpenNextRequestContext {
53
53
  mergeHeadersPriority?: "middleware" | "handler";
54
54
  lastModified?: number;
55
55
  waitUntil?: WaitUntil;
56
+ /** We use this to deduplicate write of the tags*/
57
+ writtenTags: Set<string>;
56
58
  }
57
59
  declare global {
58
60
  /**
@@ -179,6 +181,12 @@ declare global {
179
181
  * Defined in `createMainHandler`
180
182
  */
181
183
  var cdnInvalidationHandler: CDNInvalidationHandler;
184
+ /**
185
+ * The function called to resolve assets.
186
+ * Available in main functions
187
+ * Defined in `createMainHandler` when the middleware is internal
188
+ */
189
+ var assetResolver: AssetResolver | undefined;
182
190
  /**
183
191
  * A function to preload the routes.
184
192
  * This needs to be defined on globalThis because it can be used by custom overrides.
@@ -72,10 +72,12 @@ export interface NextConfig {
72
72
  experimental: {
73
73
  serverActions?: boolean;
74
74
  appDir?: boolean;
75
+ optimizeCss?: boolean;
75
76
  };
76
77
  images: ImageConfig;
77
78
  poweredByHeader?: boolean;
78
79
  serverExternalPackages?: string[];
80
+ deploymentId?: string;
79
81
  }
80
82
  export interface RouteDefinition {
81
83
  page: string;
@@ -1,7 +1,7 @@
1
1
  import type { ReadableStream } from "node:stream/web";
2
2
  import type { Writable } from "node:stream";
3
3
  import type { WarmerEvent, WarmerResponse } from "../adapters/warmer-function";
4
- import type { CDNInvalidationHandler, Converter, ImageLoader, IncrementalCache, OriginResolver, ProxyExternalRequest, Queue, TagCache, Warmer, Wrapper } from "./overrides";
4
+ import type { AssetResolver, CDNInvalidationHandler, Converter, ImageLoader, IncrementalCache, OriginResolver, ProxyExternalRequest, Queue, TagCache, Warmer, Wrapper } from "./overrides";
5
5
  export type BaseEventOrResult<T extends string = string> = {
6
6
  type: T;
7
7
  };
@@ -103,6 +103,7 @@ export type IncludedOriginResolver = "pattern-env" | "dummy";
103
103
  export type IncludedWarmer = "aws-lambda" | "dummy";
104
104
  export type IncludedProxyExternalRequest = "node" | "fetch" | "dummy";
105
105
  export type IncludedCDNInvalidationHandler = "cloudfront" | "dummy";
106
+ export type IncludedAssetResolver = "dummy";
106
107
  export interface DefaultOverrideOptions<E extends BaseEventOrResult = InternalEvent, R extends BaseEventOrResult = InternalResult> {
107
108
  /**
108
109
  * This is the main entrypoint of your app.
@@ -273,6 +274,12 @@ export interface OpenNextConfig {
273
274
  * @default "pattern-env"
274
275
  */
275
276
  originResolver?: IncludedOriginResolver | LazyLoadedOverride<OriginResolver>;
277
+ /**
278
+ * The assetResolver is used to resolve assets in the routing layer.
279
+ *
280
+ * @default "dummy"
281
+ */
282
+ assetResolver?: IncludedAssetResolver | LazyLoadedOverride<AssetResolver>;
276
283
  };
277
284
  /**
278
285
  * Override the default warmer
@@ -15,6 +15,19 @@ export interface Queue {
15
15
  send(message: QueueMessage): Promise<void>;
16
16
  name: string;
17
17
  }
18
+ /**
19
+ * Resolves assets in the routing layer.
20
+ */
21
+ export interface AssetResolver {
22
+ name: string;
23
+ /**
24
+ * Called by the routing layer to check for a matching static asset.
25
+ *
26
+ * @param event
27
+ * @returns an `InternalResult` when an asset is found a the path from the event, undefined otherwise.
28
+ */
29
+ maybeGetAssetResult?: (event: InternalEvent) => Promise<InternalResult | undefined> | undefined;
30
+ }
18
31
  export type CachedFile = {
19
32
  type: "redirect";
20
33
  props?: Object;
@@ -95,6 +108,11 @@ export type NextModeTagCache = BaseTagCache & {
95
108
  writeTags(tags: string[]): Promise<void>;
96
109
  getPathsByTags?: (tags: string[]) => Promise<string[]>;
97
110
  };
111
+ export interface OriginalTagCacheWriteInput {
112
+ tag: string;
113
+ path: string;
114
+ revalidatedAt?: number;
115
+ }
98
116
  /**
99
117
  * On get :
100
118
  We just check for the cache key in the tag cache. If it has been revalidated we just return null, otherwise we continue
@@ -119,11 +137,7 @@ export type OriginalTagCache = BaseTagCache & {
119
137
  getByTag(tag: string): Promise<string[]>;
120
138
  getByPath(path: string): Promise<string[]>;
121
139
  getLastModified(path: string, lastModified?: number): Promise<number>;
122
- writeTags(tags: {
123
- tag: string;
124
- path: string;
125
- revalidatedAt?: number;
126
- }[]): Promise<void>;
140
+ writeTags(tags: OriginalTagCacheWriteInput[]): Promise<void>;
127
141
  };
128
142
  export type TagCache = NextModeTagCache | OriginalTagCache;
129
143
  export type WrapperHandler<E extends BaseEventOrResult = InternalEvent, R extends BaseEventOrResult = InternalResult> = (handler: OpenNextHandler<E, R>, converter: Converter<E, R>) => Promise<(...args: any[]) => any>;
@@ -1,3 +1,4 @@
1
- import type { CacheValue, WithLastModified } from "../types/overrides";
1
+ import type { CacheValue, OriginalTagCacheWriteInput, WithLastModified } from "../types/overrides";
2
2
  export declare function hasBeenRevalidated(key: string, tags: string[], cacheEntry: WithLastModified<CacheValue<any>>): Promise<boolean>;
3
3
  export declare function getTagsFromValue(value?: CacheValue<"cache">): string[];
4
+ export declare function writeTags(tags: (string | OriginalTagCacheWriteInput)[]): Promise<void>;
@@ -1,3 +1,4 @@
1
+ import { debug } from "../adapters/logger";
1
2
  export async function hasBeenRevalidated(key, tags, cacheEntry) {
2
3
  if (globalThis.openNextConfig.dangerous?.disableTagCache) {
3
4
  return false;
@@ -30,3 +31,34 @@ export function getTagsFromValue(value) {
30
31
  return [];
31
32
  }
32
33
  }
34
+ function getTagKey(tag) {
35
+ if (typeof tag === "string") {
36
+ return tag;
37
+ }
38
+ return JSON.stringify({
39
+ tag: tag.tag,
40
+ path: tag.path,
41
+ });
42
+ }
43
+ export async function writeTags(tags) {
44
+ const store = globalThis.__openNextAls.getStore();
45
+ debug("Writing tags", tags, store);
46
+ if (!store || globalThis.openNextConfig.dangerous?.disableTagCache) {
47
+ return;
48
+ }
49
+ const tagsToWrite = tags.filter((t) => {
50
+ const tagKey = getTagKey(t);
51
+ const shouldWrite = !store.writtenTags.has(tagKey);
52
+ // We preemptively add the tag to the writtenTags set
53
+ // to avoid writing the same tag multiple times in the same request
54
+ if (shouldWrite) {
55
+ store.writtenTags.add(tagKey);
56
+ }
57
+ return shouldWrite;
58
+ });
59
+ if (tagsToWrite.length === 0) {
60
+ return;
61
+ }
62
+ // Here we know that we have the correct type
63
+ await globalThis.tagCache.writeTags(tagsToWrite);
64
+ }
@@ -17,7 +17,8 @@ export declare class DetachedPromiseRunner {
17
17
  add<T>(promise: Promise<T>): void;
18
18
  await(): Promise<void>;
19
19
  }
20
- export declare function runWithOpenNextRequestContext<T>({ isISRRevalidation, waitUntil, }: {
20
+ export declare function runWithOpenNextRequestContext<T>({ isISRRevalidation, waitUntil, requestId, }: {
21
21
  isISRRevalidation: boolean;
22
22
  waitUntil?: WaitUntil;
23
+ requestId?: string;
23
24
  }, fn: () => Promise<T>): Promise<T>;
@@ -77,12 +77,13 @@ function provideNextAfterProvider() {
77
77
  globalThis[VERCEL_REQUEST_CONTEXT_SYMBOL] = nextAfterContext;
78
78
  }
79
79
  }
80
- export function runWithOpenNextRequestContext({ isISRRevalidation, waitUntil, }, fn) {
80
+ export function runWithOpenNextRequestContext({ isISRRevalidation, waitUntil, requestId = Math.random().toString(36), }, fn) {
81
81
  return globalThis.__openNextAls.run({
82
- requestId: Math.random().toString(36),
82
+ requestId,
83
83
  pendingPromiseRunner: new DetachedPromiseRunner(),
84
84
  isISRRevalidation,
85
85
  waitUntil,
86
+ writtenTags: new Set(),
86
87
  }, async () => {
87
88
  provideNextAfterProvider();
88
89
  let result;
package/package.json CHANGED
@@ -3,7 +3,7 @@
3
3
  "access": "public"
4
4
  },
5
5
  "name": "@opennextjs/aws",
6
- "version": "3.6.5",
6
+ "version": "3.7.0",
7
7
  "bin": {
8
8
  "open-next": "./dist/index.js"
9
9
  },
@@ -1,20 +0,0 @@
1
- /**
2
- * Patches to avoid pulling babel (~4MB).
3
- *
4
- * Details:
5
- * - empty `NextServer#runMiddleware` and `NextServer#runEdgeFunction` that are not used
6
- * - drop `next/dist/server/node-environment-extensions/error-inspect.js`
7
- */
8
- import type { CodePatcher } from "../codePatcher.js";
9
- export declare const patchDropBabel: CodePatcher;
10
- /**
11
- * Swaps the body for a throwing implementation
12
- *
13
- * @param methodName The name of the method
14
- * @returns A rule to replace the body with a `throw`
15
- */
16
- export declare function createEmptyBodyRule(methodName: string): string;
17
- /**
18
- * Drops `require("./node-environment-extensions/error-inspect");`
19
- */
20
- export declare const errorInspectRule = "\nrule:\n pattern: require(\"./node-environment-extensions/error-inspect\");\nfix: |-\n // Removed by OpenNext\n // require(\"./node-environment-extensions/error-inspect\");\n";