@opennextjs/aws 3.8.2 → 3.8.4

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.
@@ -63,8 +63,8 @@ function getStaticAPIRoutes() {
63
63
  .map(createRouteDefinition);
64
64
  // We filter out both static API and page routes from the app paths manifest
65
65
  const appPathsStaticAPIRoutes = Object.values(AppPathRoutesManifest)
66
- .filter((route) => route.startsWith("/api/") ||
67
- (route === "/api" && !dynamicRoutePages.has(route)))
66
+ .filter((route) => (route.startsWith("/api/") || route === "/api") &&
67
+ !dynamicRoutePages.has(route))
68
68
  .map(createRouteDefinition);
69
69
  return [...pagesStaticAPIRoutes, ...appPathsStaticAPIRoutes];
70
70
  }
@@ -126,6 +126,8 @@ export function convertToQueryString(query) {
126
126
  * @__PURE__
127
127
  */
128
128
  export function convertToQuery(querystring) {
129
+ if (!querystring)
130
+ return {};
129
131
  const query = new URLSearchParams(querystring);
130
132
  const queryObject = {};
131
133
  for (const key of query.keys()) {
@@ -47,8 +47,10 @@ function normalizeAPIGatewayProxyEventV2Headers(event) {
47
47
  if (Array.isArray(cookies)) {
48
48
  headers.cookie = cookies.join("; ");
49
49
  }
50
- for (const [key, value] of Object.entries(rawHeaders || {})) {
51
- headers[key.toLowerCase()] = value;
50
+ if (rawHeaders) {
51
+ for (const [key, value] of Object.entries(rawHeaders)) {
52
+ headers[key.toLowerCase()] = value;
53
+ }
52
54
  }
53
55
  return headers;
54
56
  }
@@ -48,9 +48,10 @@ const cloudfrontReadOnlyHeaders = [
48
48
  function normalizeCloudFrontRequestEventHeaders(rawHeaders) {
49
49
  const headers = {};
50
50
  for (const [key, values] of Object.entries(rawHeaders)) {
51
+ const lowerKey = key.toLowerCase();
51
52
  for (const { value } of values) {
52
53
  if (value) {
53
- headers[key.toLowerCase()] = value;
54
+ headers[lowerKey] = value;
54
55
  }
55
56
  }
56
57
  }
@@ -9,8 +9,6 @@ const converter = {
9
9
  const url = new URL(event.url);
10
10
  const searchParams = url.searchParams;
11
11
  const query = getQueryFromSearchParams(searchParams);
12
- // Transform body into Buffer
13
- const body = await event.arrayBuffer();
14
12
  const headers = {};
15
13
  event.headers.forEach((value, key) => {
16
14
  headers[key] = value;
@@ -18,6 +16,10 @@ const converter = {
18
16
  const rawPath = url.pathname;
19
17
  const method = event.method;
20
18
  const shouldHaveBody = method !== "GET" && method !== "HEAD";
19
+ // Only read body for methods that should have one
20
+ const body = shouldHaveBody
21
+ ? Buffer.from(await event.arrayBuffer())
22
+ : undefined;
21
23
  const cookieHeader = event.headers.get("cookie");
22
24
  const cookies = cookieHeader
23
25
  ? cookieParser.parse(cookieHeader)
@@ -27,7 +29,7 @@ const converter = {
27
29
  method,
28
30
  rawPath,
29
31
  url: event.url,
30
- body: shouldHaveBody ? Buffer.from(body) : undefined,
32
+ body,
31
33
  headers,
32
34
  remoteAddress: event.headers.get("x-forwarded-for") ?? "::1",
33
35
  query,
@@ -1,31 +1,61 @@
1
1
  import { debug, error } from "../../adapters/logger";
2
+ // Cache parsed origins and compiled patterns at module level
3
+ let cachedOrigins;
4
+ const cachedPatterns = [];
5
+ let initialized = false;
6
+ /**
7
+ * Initializes the cached values on the first execution
8
+ */
9
+ function initializeOnce() {
10
+ if (initialized)
11
+ return;
12
+ // Parse origin JSON once
13
+ cachedOrigins = JSON.parse(process.env.OPEN_NEXT_ORIGIN ?? "{}");
14
+ // Pre-compile all regex patterns
15
+ const functions = globalThis.openNextConfig.functions ?? {};
16
+ for (const key in functions) {
17
+ if (key !== "default") {
18
+ const value = functions[key];
19
+ const regexes = [];
20
+ for (const pattern of value.patterns) {
21
+ // Convert cloudfront pattern to regex
22
+ const regexPattern = `/${pattern
23
+ .replace(/\*\*/g, "(.*)")
24
+ .replace(/\*/g, "([^/]*)")
25
+ .replace(/\//g, "\\/")
26
+ .replace(/\?/g, ".")}`;
27
+ regexes.push(new RegExp(regexPattern));
28
+ }
29
+ cachedPatterns.push({
30
+ key,
31
+ patterns: value.patterns,
32
+ regexes,
33
+ });
34
+ }
35
+ }
36
+ initialized = true;
37
+ }
2
38
  const envLoader = {
3
39
  name: "env",
4
40
  resolve: async (_path) => {
5
41
  try {
6
- const origin = JSON.parse(process.env.OPEN_NEXT_ORIGIN ?? "{}");
7
- for (const [key, value] of Object.entries(globalThis.openNextConfig.functions ?? {}).filter(([key]) => key !== "default")) {
8
- if (value.patterns.some((pattern) => {
9
- // Convert cloudfront pattern to regex
10
- return new RegExp(
11
- // transform glob pattern to regex
12
- `/${pattern
13
- .replace(/\*\*/g, "(.*)")
14
- .replace(/\*/g, "([^/]*)")
15
- .replace(/\//g, "\\/")
16
- .replace(/\?/g, ".")}`).test(_path);
17
- })) {
18
- debug("Using origin", key, value.patterns);
19
- return origin[key];
42
+ initializeOnce();
43
+ // Test against pre-compiled patterns
44
+ for (const { key, patterns, regexes } of cachedPatterns) {
45
+ for (const regex of regexes) {
46
+ if (regex.test(_path)) {
47
+ debug("Using origin", key, patterns);
48
+ return cachedOrigins[key];
49
+ }
20
50
  }
21
51
  }
22
- if (_path.startsWith("/_next/image") && origin.imageOptimizer) {
52
+ if (_path.startsWith("/_next/image") && cachedOrigins.imageOptimizer) {
23
53
  debug("Using origin", "imageOptimizer", _path);
24
- return origin.imageOptimizer;
54
+ return cachedOrigins.imageOptimizer;
25
55
  }
26
- if (origin.default) {
27
- debug("Using default origin", origin.default, _path);
28
- return origin.default;
56
+ if (cachedOrigins.default) {
57
+ debug("Using default origin", cachedOrigins.default, _path);
58
+ return cachedOrigins.default;
29
59
  }
30
60
  return false;
31
61
  }
@@ -25,18 +25,55 @@ const handler = async (handler, converter) => async (request, env, ctx, abortSig
25
25
  if (url.hostname === "localhost") {
26
26
  responseHeaders.set("Content-Encoding", "identity");
27
27
  }
28
- const { readable, writable } = new TransformStream({
29
- transform(chunk, controller) {
30
- controller.enqueue(Uint8Array.from(chunk.chunk ?? chunk));
28
+ // Optimize: skip ReadableStream creation for null body statuses
29
+ if (NULL_BODY_STATUSES.has(statusCode)) {
30
+ const response = new Response(null, {
31
+ status: statusCode,
32
+ headers: responseHeaders,
33
+ });
34
+ resolveResponse(response);
35
+ // Return a no-op Writable that discards all data
36
+ return new Writable({
37
+ write(chunk, encoding, callback) {
38
+ callback();
39
+ },
40
+ });
41
+ }
42
+ let controller;
43
+ const readable = new ReadableStream({
44
+ start(c) {
45
+ controller = c;
31
46
  },
32
47
  });
33
- const body = NULL_BODY_STATUSES.has(statusCode) ? null : readable;
34
- const response = new Response(body, {
48
+ const response = new Response(readable, {
35
49
  status: statusCode,
36
50
  headers: responseHeaders,
37
51
  });
38
52
  resolveResponse(response);
39
- return Writable.fromWeb(writable);
53
+ return new Writable({
54
+ write(chunk, encoding, callback) {
55
+ controller.enqueue(chunk);
56
+ callback();
57
+ },
58
+ final(callback) {
59
+ controller.close();
60
+ callback();
61
+ },
62
+ destroy(error, callback) {
63
+ if (error) {
64
+ controller.error(error);
65
+ }
66
+ else {
67
+ try {
68
+ controller.close();
69
+ }
70
+ catch {
71
+ // Ignore "This ReadableStream is closed" error
72
+ }
73
+ }
74
+ callback(error);
75
+ },
76
+ });
40
77
  },
41
78
  // This is for passing along the original abort signal from the initial Request you retrieve in your worker
42
79
  // Ensures that the response we pass to NextServer is aborted if the request is aborted
package/package.json CHANGED
@@ -3,7 +3,7 @@
3
3
  "access": "public"
4
4
  },
5
5
  "name": "@opennextjs/aws",
6
- "version": "3.8.2",
6
+ "version": "3.8.4",
7
7
  "bin": {
8
8
  "open-next": "./dist/index.js"
9
9
  },