@opennextjs/aws 3.1.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.
- package/LICENSE +21 -0
- package/README.md +94 -0
- package/dist/adapters/binary.d.ts +1 -0
- package/dist/adapters/binary.js +63 -0
- package/dist/adapters/cache.d.ts +96 -0
- package/dist/adapters/cache.js +278 -0
- package/dist/adapters/config/index.d.ts +27 -0
- package/dist/adapters/config/index.js +21 -0
- package/dist/adapters/config/util.d.ts +28 -0
- package/dist/adapters/config/util.js +84 -0
- package/dist/adapters/dynamo-provider.d.ts +8 -0
- package/dist/adapters/dynamo-provider.js +42 -0
- package/dist/adapters/edge-adapter.d.ts +8 -0
- package/dist/adapters/edge-adapter.js +54 -0
- package/dist/adapters/image-optimization-adapter.d.ts +4 -0
- package/dist/adapters/image-optimization-adapter.js +193 -0
- package/dist/adapters/logger.d.ts +13 -0
- package/dist/adapters/logger.js +51 -0
- package/dist/adapters/middleware.d.ts +5 -0
- package/dist/adapters/middleware.js +84 -0
- package/dist/adapters/plugins/14.1/util.d.ts +1 -0
- package/dist/adapters/plugins/14.1/util.js +29 -0
- package/dist/adapters/plugins/image-optimization/image-optimization.d.ts +11 -0
- package/dist/adapters/plugins/image-optimization/image-optimization.js +14 -0
- package/dist/adapters/plugins/image-optimization/image-optimization.replacement.d.ts +11 -0
- package/dist/adapters/plugins/image-optimization/image-optimization.replacement.js +22 -0
- package/dist/adapters/plugins/without-routing/requestHandler.d.ts +2 -0
- package/dist/adapters/plugins/without-routing/requestHandler.js +10 -0
- package/dist/adapters/revalidate.d.ts +9 -0
- package/dist/adapters/revalidate.js +71 -0
- package/dist/adapters/server-adapter.d.ts +4 -0
- package/dist/adapters/server-adapter.js +27 -0
- package/dist/adapters/util.d.ts +10 -0
- package/dist/adapters/util.js +28 -0
- package/dist/adapters/warmer-function.d.ts +12 -0
- package/dist/adapters/warmer-function.js +80 -0
- package/dist/build/bundleNextServer.d.ts +1 -0
- package/dist/build/bundleNextServer.js +87 -0
- package/dist/build/compileConfig.d.ts +3 -0
- package/dist/build/compileConfig.js +64 -0
- package/dist/build/copyTracedFiles.d.ts +1 -0
- package/dist/build/copyTracedFiles.js +207 -0
- package/dist/build/createServerBundle.d.ts +3 -0
- package/dist/build/createServerBundle.js +228 -0
- package/dist/build/edge/createEdgeBundle.d.ts +20 -0
- package/dist/build/edge/createEdgeBundle.js +149 -0
- package/dist/build/generateOutput.d.ts +2 -0
- package/dist/build/generateOutput.js +239 -0
- package/dist/build/helper.d.ts +33 -0
- package/dist/build/helper.js +193 -0
- package/dist/build/patch/patchedAsyncStorage.d.ts +5 -0
- package/dist/build/patch/patchedAsyncStorage.js +16 -0
- package/dist/build/validateConfig.d.ts +2 -0
- package/dist/build/validateConfig.js +76 -0
- package/dist/build.d.ts +7 -0
- package/dist/build.js +533 -0
- package/dist/cache/incremental/s3-lite.d.ts +3 -0
- package/dist/cache/incremental/s3-lite.js +72 -0
- package/dist/cache/incremental/s3.d.ts +3 -0
- package/dist/cache/incremental/s3.js +44 -0
- package/dist/cache/incremental/types.d.ts +34 -0
- package/dist/cache/incremental/types.js +1 -0
- package/dist/cache/next-types.d.ts +62 -0
- package/dist/cache/next-types.js +1 -0
- package/dist/cache/tag/constants.d.ts +2 -0
- package/dist/cache/tag/constants.js +17 -0
- package/dist/cache/tag/dynamodb-lite.d.ts +3 -0
- package/dist/cache/tag/dynamodb-lite.js +174 -0
- package/dist/cache/tag/dynamodb.d.ts +3 -0
- package/dist/cache/tag/dynamodb.js +129 -0
- package/dist/cache/tag/types.d.ts +11 -0
- package/dist/cache/tag/types.js +1 -0
- package/dist/converters/aws-apigw-v1.d.ts +3 -0
- package/dist/converters/aws-apigw-v1.js +89 -0
- package/dist/converters/aws-apigw-v2.d.ts +3 -0
- package/dist/converters/aws-apigw-v2.js +99 -0
- package/dist/converters/aws-cloudfront.d.ts +3 -0
- package/dist/converters/aws-cloudfront.js +170 -0
- package/dist/converters/dummy.d.ts +7 -0
- package/dist/converters/dummy.js +16 -0
- package/dist/converters/edge.d.ts +6 -0
- package/dist/converters/edge.js +88 -0
- package/dist/converters/node.d.ts +3 -0
- package/dist/converters/node.js +45 -0
- package/dist/converters/sqs-revalidate.d.ts +4 -0
- package/dist/converters/sqs-revalidate.js +22 -0
- package/dist/converters/utils.d.ts +1 -0
- package/dist/converters/utils.js +9 -0
- package/dist/core/createGenericHandler.d.ts +11 -0
- package/dist/core/createGenericHandler.js +16 -0
- package/dist/core/createMainHandler.d.ts +18 -0
- package/dist/core/createMainHandler.js +24 -0
- package/dist/core/edgeFunctionHandler.d.ts +54 -0
- package/dist/core/edgeFunctionHandler.js +21 -0
- package/dist/core/patchAsyncStorage.d.ts +1 -0
- package/dist/core/patchAsyncStorage.js +16 -0
- package/dist/core/requestHandler.d.ts +3 -0
- package/dist/core/requestHandler.js +162 -0
- package/dist/core/require-hooks.d.ts +3 -0
- package/dist/core/require-hooks.js +102 -0
- package/dist/core/resolve.d.ts +31 -0
- package/dist/core/resolve.js +81 -0
- package/dist/core/routing/cacheInterceptor.d.ts +2 -0
- package/dist/core/routing/cacheInterceptor.js +162 -0
- package/dist/core/routing/i18n/accept-header.d.ts +1 -0
- package/dist/core/routing/i18n/accept-header.js +101 -0
- package/dist/core/routing/i18n/index.d.ts +2 -0
- package/dist/core/routing/i18n/index.js +36 -0
- package/dist/core/routing/matcher.d.ts +25 -0
- package/dist/core/routing/matcher.js +294 -0
- package/dist/core/routing/middleware.d.ts +7 -0
- package/dist/core/routing/middleware.js +167 -0
- package/dist/core/routing/util.d.ts +105 -0
- package/dist/core/routing/util.js +415 -0
- package/dist/core/routingHandler.d.ts +8 -0
- package/dist/core/routingHandler.js +127 -0
- package/dist/core/util.d.ts +5 -0
- package/dist/core/util.js +84 -0
- package/dist/helpers/withCloudflare.d.ts +65 -0
- package/dist/helpers/withCloudflare.js +58 -0
- package/dist/helpers/withSST.d.ts +54 -0
- package/dist/helpers/withSST.js +28 -0
- package/dist/http/index.d.ts +2 -0
- package/dist/http/index.js +4 -0
- package/dist/http/openNextResponse.d.ts +78 -0
- package/dist/http/openNextResponse.js +281 -0
- package/dist/http/request.d.ts +12 -0
- package/dist/http/request.js +35 -0
- package/dist/http/util.d.ts +4 -0
- package/dist/http/util.js +34 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +37 -0
- package/dist/logger.d.ts +12 -0
- package/dist/logger.js +13 -0
- package/dist/minimize-js.d.ts +1 -0
- package/dist/minimize-js.js +101 -0
- package/dist/overrides/imageLoader/host.d.ts +3 -0
- package/dist/overrides/imageLoader/host.js +29 -0
- package/dist/overrides/imageLoader/s3.d.ts +3 -0
- package/dist/overrides/imageLoader/s3.js +33 -0
- package/dist/plugins/edge.d.ts +15 -0
- package/dist/plugins/edge.js +157 -0
- package/dist/plugins/replacement.d.ts +42 -0
- package/dist/plugins/replacement.js +74 -0
- package/dist/plugins/resolve.d.ts +18 -0
- package/dist/plugins/resolve.js +45 -0
- package/dist/queue/sqs-lite.d.ts +3 -0
- package/dist/queue/sqs-lite.js +52 -0
- package/dist/queue/sqs.d.ts +3 -0
- package/dist/queue/sqs.js +20 -0
- package/dist/queue/types.d.ts +12 -0
- package/dist/queue/types.js +1 -0
- package/dist/types/aws-lambda.d.ts +22 -0
- package/dist/types/aws-lambda.js +1 -0
- package/dist/types/next-types.d.ts +155 -0
- package/dist/types/next-types.js +2 -0
- package/dist/types/open-next.d.ts +303 -0
- package/dist/types/open-next.js +1 -0
- package/dist/utils/error.d.ts +23 -0
- package/dist/utils/error.js +32 -0
- package/dist/utils/fetch.d.ts +2 -0
- package/dist/utils/fetch.js +23 -0
- package/dist/utils/promise.d.ts +18 -0
- package/dist/utils/promise.js +48 -0
- package/dist/utils/stream.d.ts +5 -0
- package/dist/utils/stream.js +30 -0
- package/dist/wrappers/aws-lambda-streaming.d.ts +7 -0
- package/dist/wrappers/aws-lambda-streaming.js +88 -0
- package/dist/wrappers/aws-lambda.d.ts +7 -0
- package/dist/wrappers/aws-lambda.js +39 -0
- package/dist/wrappers/cloudflare.d.ts +11 -0
- package/dist/wrappers/cloudflare.js +20 -0
- package/dist/wrappers/node.d.ts +7 -0
- package/dist/wrappers/node.js +57 -0
- package/package.json +66 -0
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
import fs from "fs";
|
|
2
|
+
import path from "path";
|
|
3
|
+
export function loadConfig(nextDir) {
|
|
4
|
+
const filePath = path.join(nextDir, "required-server-files.json");
|
|
5
|
+
const json = fs.readFileSync(filePath, "utf-8");
|
|
6
|
+
const { config } = JSON.parse(json);
|
|
7
|
+
return config;
|
|
8
|
+
}
|
|
9
|
+
export function loadBuildId(nextDir) {
|
|
10
|
+
const filePath = path.join(nextDir, "BUILD_ID");
|
|
11
|
+
return fs.readFileSync(filePath, "utf-8").trim();
|
|
12
|
+
}
|
|
13
|
+
export function loadHtmlPages(nextDir) {
|
|
14
|
+
const filePath = path.join(nextDir, "server", "pages-manifest.json");
|
|
15
|
+
const json = fs.readFileSync(filePath, "utf-8");
|
|
16
|
+
return Object.entries(JSON.parse(json))
|
|
17
|
+
.filter(([_, value]) => value.endsWith(".html"))
|
|
18
|
+
.map(([key]) => key);
|
|
19
|
+
}
|
|
20
|
+
export function loadPublicAssets(openNextDir) {
|
|
21
|
+
const filePath = path.join(openNextDir, "public-files.json");
|
|
22
|
+
const json = fs.readFileSync(filePath, "utf-8");
|
|
23
|
+
return JSON.parse(json);
|
|
24
|
+
}
|
|
25
|
+
export function loadRoutesManifest(nextDir) {
|
|
26
|
+
const filePath = path.join(nextDir, "routes-manifest.json");
|
|
27
|
+
const json = fs.readFileSync(filePath, "utf-8");
|
|
28
|
+
const routesManifest = JSON.parse(json);
|
|
29
|
+
const _dataRoutes = routesManifest.dataRoutes ?? [];
|
|
30
|
+
const dataRoutes = {
|
|
31
|
+
static: _dataRoutes.filter((r) => r.routeKeys === undefined),
|
|
32
|
+
dynamic: _dataRoutes.filter((r) => r.routeKeys !== undefined),
|
|
33
|
+
};
|
|
34
|
+
return {
|
|
35
|
+
basePath: routesManifest.basePath,
|
|
36
|
+
rewrites: Array.isArray(routesManifest.rewrites)
|
|
37
|
+
? { beforeFiles: [], afterFiles: routesManifest.rewrites, fallback: [] }
|
|
38
|
+
: {
|
|
39
|
+
beforeFiles: routesManifest.rewrites.beforeFiles ?? [],
|
|
40
|
+
afterFiles: routesManifest.rewrites.afterFiles ?? [],
|
|
41
|
+
fallback: routesManifest.rewrites.fallback ?? [],
|
|
42
|
+
},
|
|
43
|
+
redirects: routesManifest.redirects ?? [],
|
|
44
|
+
routes: {
|
|
45
|
+
static: routesManifest.staticRoutes ?? [],
|
|
46
|
+
dynamic: routesManifest.dynamicRoutes ?? [],
|
|
47
|
+
data: dataRoutes,
|
|
48
|
+
},
|
|
49
|
+
locales: routesManifest.i18n?.locales ?? [],
|
|
50
|
+
};
|
|
51
|
+
}
|
|
52
|
+
export function loadConfigHeaders(nextDir) {
|
|
53
|
+
const filePath = path.join(nextDir, "routes-manifest.json");
|
|
54
|
+
const json = fs.readFileSync(filePath, "utf-8");
|
|
55
|
+
const routesManifest = JSON.parse(json);
|
|
56
|
+
return routesManifest.headers;
|
|
57
|
+
}
|
|
58
|
+
export function loadPrerenderManifest(nextDir) {
|
|
59
|
+
const filePath = path.join(nextDir, "prerender-manifest.json");
|
|
60
|
+
const json = fs.readFileSync(filePath, "utf-8");
|
|
61
|
+
return JSON.parse(json);
|
|
62
|
+
}
|
|
63
|
+
export function loadAppPathsManifestKeys(nextDir) {
|
|
64
|
+
const appPathsManifestPath = path.join(nextDir, "server", "app-paths-manifest.json");
|
|
65
|
+
const appPathsManifestJson = fs.existsSync(appPathsManifestPath)
|
|
66
|
+
? fs.readFileSync(appPathsManifestPath, "utf-8")
|
|
67
|
+
: "{}";
|
|
68
|
+
const appPathsManifest = JSON.parse(appPathsManifestJson);
|
|
69
|
+
return Object.keys(appPathsManifest).map((key) => {
|
|
70
|
+
// Remove parallel route
|
|
71
|
+
let cleanedKey = key.replace(/\/@[^\/]+/g, "");
|
|
72
|
+
// Remove group routes
|
|
73
|
+
cleanedKey = cleanedKey.replace(/\/\((?!\.)[^\)]*\)/g, "");
|
|
74
|
+
// Remove /page suffix
|
|
75
|
+
cleanedKey = cleanedKey.replace(/\/page$/g, "");
|
|
76
|
+
// We need to check if the cleaned key is empty because it means it's the root path
|
|
77
|
+
return cleanedKey === "" ? "/" : cleanedKey;
|
|
78
|
+
});
|
|
79
|
+
}
|
|
80
|
+
export function loadMiddlewareManifest(nextDir) {
|
|
81
|
+
const filePath = path.join(nextDir, "server", "middleware-manifest.json");
|
|
82
|
+
const json = fs.readFileSync(filePath, "utf-8");
|
|
83
|
+
return JSON.parse(json);
|
|
84
|
+
}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
declare const PHYSICAL_RESOURCE_ID: "dynamodb-cache";
|
|
2
|
+
export interface InitializationFunctionEvent {
|
|
3
|
+
type: "initializationFunction";
|
|
4
|
+
requestType: "create" | "update" | "delete";
|
|
5
|
+
resourceId: typeof PHYSICAL_RESOURCE_ID;
|
|
6
|
+
}
|
|
7
|
+
export declare const handler: (...args: any[]) => any;
|
|
8
|
+
export {};
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import { readFileSync } from "fs";
|
|
2
|
+
import { createGenericHandler } from "../core/createGenericHandler.js";
|
|
3
|
+
import { resolveTagCache } from "../core/resolve.js";
|
|
4
|
+
const PHYSICAL_RESOURCE_ID = "dynamodb-cache";
|
|
5
|
+
const tagCache = await resolveTagCache(globalThis.openNextConfig?.initializationFunction?.tagCache);
|
|
6
|
+
export const handler = await createGenericHandler({
|
|
7
|
+
handler: defaultHandler,
|
|
8
|
+
type: "initializationFunction",
|
|
9
|
+
});
|
|
10
|
+
async function defaultHandler(event) {
|
|
11
|
+
switch (event.requestType) {
|
|
12
|
+
case "delete":
|
|
13
|
+
return remove();
|
|
14
|
+
case "create":
|
|
15
|
+
case "update":
|
|
16
|
+
default:
|
|
17
|
+
return insert(event.requestType);
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
async function insert(requestType) {
|
|
21
|
+
const file = readFileSync(`dynamodb-cache.json`, "utf8");
|
|
22
|
+
const data = JSON.parse(file);
|
|
23
|
+
const parsedData = data.map((item) => ({
|
|
24
|
+
tag: item.tag.S,
|
|
25
|
+
path: item.path.S,
|
|
26
|
+
revalidatedAt: parseInt(item.revalidatedAt.N),
|
|
27
|
+
}));
|
|
28
|
+
await tagCache.writeTags(parsedData);
|
|
29
|
+
return {
|
|
30
|
+
type: "initializationFunction",
|
|
31
|
+
requestType,
|
|
32
|
+
resourceId: PHYSICAL_RESOURCE_ID,
|
|
33
|
+
};
|
|
34
|
+
}
|
|
35
|
+
async function remove() {
|
|
36
|
+
// Do we want to actually delete anything here?
|
|
37
|
+
return {
|
|
38
|
+
type: "initializationFunction",
|
|
39
|
+
requestType: "delete",
|
|
40
|
+
resourceId: PHYSICAL_RESOURCE_ID,
|
|
41
|
+
};
|
|
42
|
+
}
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
import { emptyReadableStream } from "../utils/stream";
|
|
2
|
+
// We import it like that so that the edge plugin can replace it
|
|
3
|
+
import { NextConfig } from "../adapters/config";
|
|
4
|
+
import { createGenericHandler } from "../core/createGenericHandler";
|
|
5
|
+
import { convertBodyToReadableStream, convertToQueryString, } from "../core/routing/util";
|
|
6
|
+
const defaultHandler = async (internalEvent) => {
|
|
7
|
+
globalThis.isEdgeRuntime = true;
|
|
8
|
+
const host = internalEvent.headers.host
|
|
9
|
+
? `https://${internalEvent.headers.host}`
|
|
10
|
+
: "http://localhost:3000";
|
|
11
|
+
const initialUrl = new URL(internalEvent.rawPath, host);
|
|
12
|
+
initialUrl.search = convertToQueryString(internalEvent.query);
|
|
13
|
+
const url = initialUrl.toString();
|
|
14
|
+
// @ts-expect-error - This is bundled
|
|
15
|
+
const handler = await import(`./middleware.mjs`);
|
|
16
|
+
const response = await handler.default({
|
|
17
|
+
headers: internalEvent.headers,
|
|
18
|
+
method: internalEvent.method || "GET",
|
|
19
|
+
nextConfig: {
|
|
20
|
+
basePath: NextConfig.basePath,
|
|
21
|
+
i18n: NextConfig.i18n,
|
|
22
|
+
trailingSlash: NextConfig.trailingSlash,
|
|
23
|
+
},
|
|
24
|
+
url,
|
|
25
|
+
body: convertBodyToReadableStream(internalEvent.method, internalEvent.body),
|
|
26
|
+
});
|
|
27
|
+
const responseHeaders = {};
|
|
28
|
+
response.headers.forEach((value, key) => {
|
|
29
|
+
if (key.toLowerCase() === "set-cookie") {
|
|
30
|
+
responseHeaders[key] = responseHeaders[key]
|
|
31
|
+
? [...responseHeaders[key], value]
|
|
32
|
+
: [value];
|
|
33
|
+
}
|
|
34
|
+
else {
|
|
35
|
+
responseHeaders[key] = value;
|
|
36
|
+
}
|
|
37
|
+
});
|
|
38
|
+
const body = response.body ?? emptyReadableStream();
|
|
39
|
+
return {
|
|
40
|
+
type: "core",
|
|
41
|
+
statusCode: response.status,
|
|
42
|
+
headers: responseHeaders,
|
|
43
|
+
body: body,
|
|
44
|
+
// Do we need to handle base64 encoded response?
|
|
45
|
+
isBase64Encoded: false,
|
|
46
|
+
};
|
|
47
|
+
};
|
|
48
|
+
export const handler = await createGenericHandler({
|
|
49
|
+
handler: defaultHandler,
|
|
50
|
+
type: "middleware",
|
|
51
|
+
});
|
|
52
|
+
export default {
|
|
53
|
+
fetch: handler,
|
|
54
|
+
};
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
import { StreamCreator } from "../http/openNextResponse.js";
|
|
2
|
+
import { InternalEvent, InternalResult } from "../types/open-next.js";
|
|
3
|
+
export declare const handler: (...args: any[]) => any;
|
|
4
|
+
export declare function defaultHandler(event: InternalEvent, streamCreator?: StreamCreator): Promise<InternalResult>;
|
|
@@ -0,0 +1,193 @@
|
|
|
1
|
+
import { createHash } from "node:crypto";
|
|
2
|
+
import https from "node:https";
|
|
3
|
+
import path from "node:path";
|
|
4
|
+
import { loadBuildId, loadConfig } from "./config/util.js";
|
|
5
|
+
import { OpenNextNodeResponse } from "../http/openNextResponse.js";
|
|
6
|
+
// @ts-ignore
|
|
7
|
+
import { defaultConfig } from "next/dist/server/config-shared";
|
|
8
|
+
import { ImageOptimizerCache,
|
|
9
|
+
// @ts-ignore
|
|
10
|
+
} from "next/dist/server/image-optimizer";
|
|
11
|
+
import { emptyReadableStream, toReadableStream } from "../utils/stream.js";
|
|
12
|
+
import { createGenericHandler } from "../core/createGenericHandler.js";
|
|
13
|
+
import { resolveImageLoader } from "../core/resolve.js";
|
|
14
|
+
import { debug, error } from "./logger.js";
|
|
15
|
+
import { optimizeImage } from "./plugins/image-optimization/image-optimization.js";
|
|
16
|
+
import { setNodeEnv } from "./util.js";
|
|
17
|
+
setNodeEnv();
|
|
18
|
+
const nextDir = path.join(__dirname, ".next");
|
|
19
|
+
const config = loadConfig(nextDir);
|
|
20
|
+
const buildId = loadBuildId(nextDir);
|
|
21
|
+
const nextConfig = {
|
|
22
|
+
...defaultConfig,
|
|
23
|
+
images: {
|
|
24
|
+
...defaultConfig.images,
|
|
25
|
+
...config.images,
|
|
26
|
+
},
|
|
27
|
+
};
|
|
28
|
+
debug("Init config", {
|
|
29
|
+
nextDir,
|
|
30
|
+
nextConfig,
|
|
31
|
+
});
|
|
32
|
+
/////////////
|
|
33
|
+
// Handler //
|
|
34
|
+
/////////////
|
|
35
|
+
export const handler = await createGenericHandler({
|
|
36
|
+
handler: defaultHandler,
|
|
37
|
+
type: "imageOptimization",
|
|
38
|
+
});
|
|
39
|
+
export async function defaultHandler(event, streamCreator) {
|
|
40
|
+
// Images are handled via header and query param information.
|
|
41
|
+
debug("handler event", event);
|
|
42
|
+
const { headers, query: queryString } = event;
|
|
43
|
+
try {
|
|
44
|
+
// Set the HOST environment variable to the host header if it is not set
|
|
45
|
+
// If it is set it is assumed to be set by the user and should be used instead
|
|
46
|
+
// It might be useful for cases where the user wants to use a different host than the one in the request
|
|
47
|
+
// It could even allow to have multiple hosts for the image optimization by setting the HOST environment variable in the wrapper for example
|
|
48
|
+
if (!process.env.HOST) {
|
|
49
|
+
const headersHost = headers["x-forwarded-host"] || headers["host"];
|
|
50
|
+
process.env.HOST = headersHost;
|
|
51
|
+
}
|
|
52
|
+
const imageParams = validateImageParams(headers, queryString === null ? undefined : queryString);
|
|
53
|
+
let etag;
|
|
54
|
+
// We don't cache any images, so in order to be able to return 304 responses, we compute an ETag from what is assumed to be static
|
|
55
|
+
if (process.env.OPENNEXT_STATIC_ETAG) {
|
|
56
|
+
etag = computeEtag(imageParams);
|
|
57
|
+
}
|
|
58
|
+
if (etag && headers["if-none-match"] === etag) {
|
|
59
|
+
return {
|
|
60
|
+
statusCode: 304,
|
|
61
|
+
headers: {},
|
|
62
|
+
body: emptyReadableStream(),
|
|
63
|
+
isBase64Encoded: false,
|
|
64
|
+
type: "core",
|
|
65
|
+
};
|
|
66
|
+
}
|
|
67
|
+
const result = await optimizeImage(headers, imageParams, nextConfig, downloadHandler);
|
|
68
|
+
return buildSuccessResponse(result, streamCreator, etag);
|
|
69
|
+
}
|
|
70
|
+
catch (e) {
|
|
71
|
+
return buildFailureResponse(e, streamCreator);
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
//////////////////////
|
|
75
|
+
// Helper functions //
|
|
76
|
+
//////////////////////
|
|
77
|
+
function validateImageParams(headers, query) {
|
|
78
|
+
// Next.js checks if external image URL matches the
|
|
79
|
+
// `images.remotePatterns`
|
|
80
|
+
const imageParams = ImageOptimizerCache.validateParams(
|
|
81
|
+
// @ts-ignore
|
|
82
|
+
{ headers }, query, nextConfig, false);
|
|
83
|
+
debug("image params", imageParams);
|
|
84
|
+
if ("errorMessage" in imageParams) {
|
|
85
|
+
throw new Error(imageParams.errorMessage);
|
|
86
|
+
}
|
|
87
|
+
return imageParams;
|
|
88
|
+
}
|
|
89
|
+
function computeEtag(imageParams) {
|
|
90
|
+
return createHash("sha1")
|
|
91
|
+
.update(JSON.stringify({
|
|
92
|
+
href: imageParams.href,
|
|
93
|
+
width: imageParams.width,
|
|
94
|
+
quality: imageParams.quality,
|
|
95
|
+
buildId,
|
|
96
|
+
}))
|
|
97
|
+
.digest("base64");
|
|
98
|
+
}
|
|
99
|
+
function buildSuccessResponse(result, streamCreator, etag) {
|
|
100
|
+
const headers = {
|
|
101
|
+
Vary: "Accept",
|
|
102
|
+
"Content-Type": result.contentType,
|
|
103
|
+
"Cache-Control": `public,max-age=${result.maxAge},immutable`,
|
|
104
|
+
};
|
|
105
|
+
debug("result", result);
|
|
106
|
+
if (etag) {
|
|
107
|
+
headers["ETag"] = etag;
|
|
108
|
+
}
|
|
109
|
+
if (streamCreator) {
|
|
110
|
+
const response = new OpenNextNodeResponse(() => void 0, async () => void 0, streamCreator);
|
|
111
|
+
response.writeHead(200, headers);
|
|
112
|
+
response.end(result.buffer);
|
|
113
|
+
}
|
|
114
|
+
return {
|
|
115
|
+
type: "core",
|
|
116
|
+
statusCode: 200,
|
|
117
|
+
body: toReadableStream(result.buffer, true),
|
|
118
|
+
isBase64Encoded: true,
|
|
119
|
+
headers,
|
|
120
|
+
};
|
|
121
|
+
}
|
|
122
|
+
function buildFailureResponse(e, streamCreator) {
|
|
123
|
+
debug(e);
|
|
124
|
+
if (streamCreator) {
|
|
125
|
+
const response = new OpenNextNodeResponse(() => void 0, async () => void 0, streamCreator);
|
|
126
|
+
response.writeHead(500, {
|
|
127
|
+
Vary: "Accept",
|
|
128
|
+
"Cache-Control": `public,max-age=60,immutable`,
|
|
129
|
+
"Content-Type": "application/json",
|
|
130
|
+
});
|
|
131
|
+
response.end(e?.message || e?.toString() || "An error occurred");
|
|
132
|
+
}
|
|
133
|
+
return {
|
|
134
|
+
type: "core",
|
|
135
|
+
isBase64Encoded: false,
|
|
136
|
+
statusCode: 500,
|
|
137
|
+
headers: {
|
|
138
|
+
Vary: "Accept",
|
|
139
|
+
// For failed images, allow client to retry after 1 minute.
|
|
140
|
+
"Cache-Control": `public,max-age=60,immutable`,
|
|
141
|
+
"Content-Type": "application/json",
|
|
142
|
+
},
|
|
143
|
+
body: toReadableStream(e?.message || e?.toString() || "An error occurred"),
|
|
144
|
+
};
|
|
145
|
+
}
|
|
146
|
+
const loader = await resolveImageLoader(globalThis.openNextConfig.imageOptimization?.loader ?? "s3");
|
|
147
|
+
async function downloadHandler(_req, res, url) {
|
|
148
|
+
// downloadHandler is called by Next.js. We don't call this function
|
|
149
|
+
// directly.
|
|
150
|
+
debug("downloadHandler url", url);
|
|
151
|
+
// Reads the output from the Writable and writes to the response
|
|
152
|
+
const pipeRes = (w, res) => {
|
|
153
|
+
w.pipe(res)
|
|
154
|
+
.once("close", () => {
|
|
155
|
+
res.statusCode = 200;
|
|
156
|
+
res.end();
|
|
157
|
+
})
|
|
158
|
+
.once("error", (err) => {
|
|
159
|
+
error("Failed to get image", err);
|
|
160
|
+
res.statusCode = 400;
|
|
161
|
+
res.end();
|
|
162
|
+
});
|
|
163
|
+
};
|
|
164
|
+
try {
|
|
165
|
+
// Case 1: remote image URL => download the image from the URL
|
|
166
|
+
if (url.href.toLowerCase().match(/^https?:\/\//)) {
|
|
167
|
+
pipeRes(https.get(url), res);
|
|
168
|
+
}
|
|
169
|
+
// Case 2: local image => download the image from S3
|
|
170
|
+
else {
|
|
171
|
+
// Download image from S3
|
|
172
|
+
// note: S3 expects keys without leading `/`
|
|
173
|
+
const response = await loader.load(url.href);
|
|
174
|
+
if (!response.body) {
|
|
175
|
+
throw new Error("Empty response body from the S3 request.");
|
|
176
|
+
}
|
|
177
|
+
// @ts-ignore
|
|
178
|
+
pipeRes(response.body, res);
|
|
179
|
+
// Respect the bucket file's content-type and cache-control
|
|
180
|
+
// imageOptimizer will use this to set the results.maxAge
|
|
181
|
+
if (response.contentType) {
|
|
182
|
+
res.setHeader("Content-Type", response.contentType);
|
|
183
|
+
}
|
|
184
|
+
if (response.cacheControl) {
|
|
185
|
+
res.setHeader("Cache-Control", response.cacheControl);
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
catch (e) {
|
|
190
|
+
error("Failed to download image", e);
|
|
191
|
+
throw e;
|
|
192
|
+
}
|
|
193
|
+
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
declare global {
|
|
2
|
+
var openNextDebug: boolean;
|
|
3
|
+
}
|
|
4
|
+
export declare function debug(...args: any[]): void;
|
|
5
|
+
export declare function warn(...args: any[]): void;
|
|
6
|
+
export declare function error(...args: any[]): void;
|
|
7
|
+
export declare const awsLogger: {
|
|
8
|
+
trace: () => void;
|
|
9
|
+
debug: () => void;
|
|
10
|
+
info: typeof debug;
|
|
11
|
+
warn: typeof warn;
|
|
12
|
+
error: typeof error;
|
|
13
|
+
};
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
export function debug(...args) {
|
|
2
|
+
if (globalThis.openNextDebug) {
|
|
3
|
+
console.log(...args);
|
|
4
|
+
}
|
|
5
|
+
}
|
|
6
|
+
export function warn(...args) {
|
|
7
|
+
console.warn(...args);
|
|
8
|
+
}
|
|
9
|
+
const DOWNPLAYED_ERROR_LOGS = [
|
|
10
|
+
{
|
|
11
|
+
clientName: "S3Client",
|
|
12
|
+
commandName: "GetObjectCommand",
|
|
13
|
+
errorName: "NoSuchKey",
|
|
14
|
+
},
|
|
15
|
+
];
|
|
16
|
+
const isDownplayedErrorLog = (errorLog) => DOWNPLAYED_ERROR_LOGS.some((downplayedInput) => downplayedInput.clientName === errorLog?.clientName &&
|
|
17
|
+
downplayedInput.commandName === errorLog?.commandName &&
|
|
18
|
+
(downplayedInput.errorName === errorLog?.error?.name ||
|
|
19
|
+
downplayedInput.errorName === errorLog?.error?.Code));
|
|
20
|
+
export function error(...args) {
|
|
21
|
+
// we try to catch errors from the aws-sdk client and downplay some of them
|
|
22
|
+
if (args.some((arg) => isDownplayedErrorLog(arg))) {
|
|
23
|
+
debug(...args);
|
|
24
|
+
}
|
|
25
|
+
else if (args.some((arg) => arg.__openNextInternal)) {
|
|
26
|
+
// In case of an internal error, we log it with the appropriate log level
|
|
27
|
+
const error = args.find((arg) => arg.__openNextInternal);
|
|
28
|
+
if (error.logLevel === 0) {
|
|
29
|
+
debug(...args);
|
|
30
|
+
return;
|
|
31
|
+
}
|
|
32
|
+
else if (error.logLevel === 1) {
|
|
33
|
+
warn(...args);
|
|
34
|
+
return;
|
|
35
|
+
}
|
|
36
|
+
else {
|
|
37
|
+
console.error(...args);
|
|
38
|
+
return;
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
else {
|
|
42
|
+
console.error(...args);
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
export const awsLogger = {
|
|
46
|
+
trace: () => { },
|
|
47
|
+
debug: () => { },
|
|
48
|
+
info: debug,
|
|
49
|
+
warn,
|
|
50
|
+
error,
|
|
51
|
+
};
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
import { debug, error } from "../adapters/logger";
|
|
2
|
+
import { createGenericHandler } from "../core/createGenericHandler";
|
|
3
|
+
import { resolveIncrementalCache, resolveQueue, resolveTagCache, } from "../core/resolve";
|
|
4
|
+
import routingHandler from "../core/routingHandler";
|
|
5
|
+
const resolveOriginResolver = () => {
|
|
6
|
+
const openNextParams = globalThis.openNextConfig.middleware;
|
|
7
|
+
if (typeof openNextParams?.originResolver === "function") {
|
|
8
|
+
return openNextParams.originResolver();
|
|
9
|
+
}
|
|
10
|
+
else {
|
|
11
|
+
return Promise.resolve({
|
|
12
|
+
name: "env",
|
|
13
|
+
resolve: async (_path) => {
|
|
14
|
+
try {
|
|
15
|
+
const origin = JSON.parse(process.env.OPEN_NEXT_ORIGIN ?? "{}");
|
|
16
|
+
for (const [key, value] of Object.entries(globalThis.openNextConfig.functions ?? {}).filter(([key]) => key !== "default")) {
|
|
17
|
+
if (value.patterns.some((pattern) => {
|
|
18
|
+
// Convert cloudfront pattern to regex
|
|
19
|
+
return new RegExp(
|
|
20
|
+
// transform glob pattern to regex
|
|
21
|
+
"/" +
|
|
22
|
+
pattern
|
|
23
|
+
.replace(/\*\*/g, "(.*)")
|
|
24
|
+
.replace(/\*/g, "([^/]*)")
|
|
25
|
+
.replace(/\//g, "\\/")
|
|
26
|
+
.replace(/\?/g, ".")).test(_path);
|
|
27
|
+
})) {
|
|
28
|
+
debug("Using origin", key, value.patterns);
|
|
29
|
+
return origin[key];
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
if (_path.startsWith("/_next/image") && origin["imageOptimizer"]) {
|
|
33
|
+
debug("Using origin", "imageOptimizer", _path);
|
|
34
|
+
return origin["imageOptimizer"];
|
|
35
|
+
}
|
|
36
|
+
if (origin["default"]) {
|
|
37
|
+
debug("Using default origin", origin["default"], _path);
|
|
38
|
+
return origin["default"];
|
|
39
|
+
}
|
|
40
|
+
return false;
|
|
41
|
+
}
|
|
42
|
+
catch (e) {
|
|
43
|
+
error("Error while resolving origin", e);
|
|
44
|
+
return false;
|
|
45
|
+
}
|
|
46
|
+
},
|
|
47
|
+
});
|
|
48
|
+
}
|
|
49
|
+
};
|
|
50
|
+
globalThis.internalFetch = fetch;
|
|
51
|
+
const defaultHandler = async (internalEvent) => {
|
|
52
|
+
const originResolver = await resolveOriginResolver();
|
|
53
|
+
//#override includeCacheInMiddleware
|
|
54
|
+
globalThis.tagCache = await resolveTagCache(globalThis.openNextConfig.middleware?.override?.tagCache);
|
|
55
|
+
globalThis.queue = await resolveQueue(globalThis.openNextConfig.middleware?.override?.queue);
|
|
56
|
+
globalThis.incrementalCache = await resolveIncrementalCache(globalThis.openNextConfig.middleware?.override?.incrementalCache);
|
|
57
|
+
//#endOverride
|
|
58
|
+
const result = await routingHandler(internalEvent);
|
|
59
|
+
if ("internalEvent" in result) {
|
|
60
|
+
debug("Middleware intercepted event", internalEvent);
|
|
61
|
+
let origin = false;
|
|
62
|
+
if (!result.isExternalRewrite) {
|
|
63
|
+
origin = await originResolver.resolve(result.internalEvent.rawPath);
|
|
64
|
+
}
|
|
65
|
+
return {
|
|
66
|
+
type: "middleware",
|
|
67
|
+
internalEvent: result.internalEvent,
|
|
68
|
+
isExternalRewrite: result.isExternalRewrite,
|
|
69
|
+
origin,
|
|
70
|
+
isISR: result.isISR,
|
|
71
|
+
};
|
|
72
|
+
}
|
|
73
|
+
else {
|
|
74
|
+
debug("Middleware response", result);
|
|
75
|
+
return result;
|
|
76
|
+
}
|
|
77
|
+
};
|
|
78
|
+
export const handler = await createGenericHandler({
|
|
79
|
+
handler: defaultHandler,
|
|
80
|
+
type: "middleware",
|
|
81
|
+
});
|
|
82
|
+
export default {
|
|
83
|
+
fetch: handler,
|
|
84
|
+
};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare const requestHandler: any;
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import { NextConfig } from "../../config";
|
|
2
|
+
import { debug } from "../../logger.js";
|
|
3
|
+
//#override requestHandler
|
|
4
|
+
// @ts-ignore
|
|
5
|
+
export const requestHandler = new NextServer.default({
|
|
6
|
+
conf: {
|
|
7
|
+
...NextConfig,
|
|
8
|
+
// Next.js compression should be disabled because of a bug in the bundled
|
|
9
|
+
// `compression` package — https://github.com/vercel/next.js/issues/11669
|
|
10
|
+
compress: false,
|
|
11
|
+
// By default, Next.js uses local disk to store ISR cache. We will use
|
|
12
|
+
// our own cache handler to store the cache on S3.
|
|
13
|
+
cacheHandler: `${process.env.LAMBDA_TASK_ROOT}/cache.cjs`,
|
|
14
|
+
cacheMaxMemorySize: 0,
|
|
15
|
+
experimental: {
|
|
16
|
+
...NextConfig.experimental,
|
|
17
|
+
// This uses the request.headers.host as the URL
|
|
18
|
+
// https://github.com/vercel/next.js/blob/canary/packages/next/src/server/next-server.ts#L1749-L1754
|
|
19
|
+
trustHostHeader: true,
|
|
20
|
+
},
|
|
21
|
+
},
|
|
22
|
+
customServer: false,
|
|
23
|
+
dev: false,
|
|
24
|
+
dir: __dirname,
|
|
25
|
+
}).getRequestHandler();
|
|
26
|
+
//#endOverride
|
|
27
|
+
//#override requireHooks
|
|
28
|
+
debug("No need to override require hooks with next 13.4.20+");
|
|
29
|
+
//#endOverride
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
/// <reference types="node" />
|
|
2
|
+
/// <reference types="node" />
|
|
3
|
+
import { IncomingMessage, ServerResponse } from "node:http";
|
|
4
|
+
import { APIGatewayProxyEventHeaders } from "aws-lambda";
|
|
5
|
+
import { NextConfig } from "next/dist/server/config-shared";
|
|
6
|
+
import { NextUrlWithParsedQuery } from "next/dist/server/request-meta";
|
|
7
|
+
export declare function optimizeImage(headers: APIGatewayProxyEventHeaders, imageParams: any, nextConfig: NextConfig, handleRequest: (newReq: IncomingMessage, newRes: ServerResponse, newParsedUrl: NextUrlWithParsedQuery) => Promise<void>): Promise<{
|
|
8
|
+
buffer: Buffer;
|
|
9
|
+
contentType: string;
|
|
10
|
+
maxAge: number;
|
|
11
|
+
}>;
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
//#override imports
|
|
2
|
+
import { imageOptimizer } from "next/dist/server/image-optimizer";
|
|
3
|
+
import { debug } from "../../logger.js";
|
|
4
|
+
//#override optimizeImage
|
|
5
|
+
export async function optimizeImage(headers, imageParams, nextConfig, handleRequest) {
|
|
6
|
+
const result = await imageOptimizer(
|
|
7
|
+
// @ts-ignore
|
|
8
|
+
{ headers }, {}, // res object is not necessary as it's not actually used.
|
|
9
|
+
imageParams, nextConfig, false, // not in dev mode
|
|
10
|
+
handleRequest);
|
|
11
|
+
debug("optimized result", result);
|
|
12
|
+
return result;
|
|
13
|
+
}
|
|
14
|
+
//#endOverride
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
/// <reference types="node" />
|
|
2
|
+
/// <reference types="node" />
|
|
3
|
+
import type { IncomingMessage, ServerResponse } from "node:http";
|
|
4
|
+
import type { APIGatewayProxyEventHeaders } from "aws-lambda";
|
|
5
|
+
import type { NextConfig } from "next/dist/server/config-shared";
|
|
6
|
+
import type { NextUrlWithParsedQuery } from "next/dist/server/request-meta";
|
|
7
|
+
export declare function optimizeImage(headers: APIGatewayProxyEventHeaders, imageParams: any, nextConfig: NextConfig, handleRequest: (newReq: IncomingMessage, newRes: ServerResponse, newParsedUrl?: NextUrlWithParsedQuery) => Promise<void>): Promise<{
|
|
8
|
+
buffer: Buffer;
|
|
9
|
+
contentType: string;
|
|
10
|
+
maxAge: number;
|
|
11
|
+
}>;
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
//#override imports
|
|
2
|
+
import {
|
|
3
|
+
// @ts-ignore
|
|
4
|
+
fetchExternalImage,
|
|
5
|
+
// @ts-ignore
|
|
6
|
+
fetchInternalImage, imageOptimizer, } from "next/dist/server/image-optimizer";
|
|
7
|
+
import { debug } from "../../logger.js";
|
|
8
|
+
//#override optimizeImage
|
|
9
|
+
export async function optimizeImage(headers, imageParams, nextConfig, handleRequest) {
|
|
10
|
+
const { isAbsolute, href } = imageParams;
|
|
11
|
+
const imageUpstream = isAbsolute
|
|
12
|
+
? await fetchExternalImage(href)
|
|
13
|
+
: await fetchInternalImage(href,
|
|
14
|
+
// @ts-ignore
|
|
15
|
+
{ headers }, {}, // res object is not necessary as it's not actually used.
|
|
16
|
+
handleRequest);
|
|
17
|
+
// @ts-ignore
|
|
18
|
+
const result = await imageOptimizer(imageUpstream, imageParams, nextConfig, false);
|
|
19
|
+
debug("optimized result", result);
|
|
20
|
+
return result;
|
|
21
|
+
}
|
|
22
|
+
//#endOverride
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
//#override withRouting
|
|
2
|
+
const preprocessResult = {
|
|
3
|
+
internalEvent: internalEvent,
|
|
4
|
+
isExternalRewrite: false,
|
|
5
|
+
origin: false,
|
|
6
|
+
isISR: false,
|
|
7
|
+
};
|
|
8
|
+
//#endOverride
|
|
9
|
+
// We need to export something otherwise when compiled in js it creates an empty export {} inside the override
|
|
10
|
+
export default {};
|