diff --git a/.changeset/fuzzy-jeans-draw.md b/.changeset/fuzzy-jeans-draw.md
new file mode 100644
index 000000000..3312d39fe
--- /dev/null
+++ b/.changeset/fuzzy-jeans-draw.md
@@ -0,0 +1,5 @@
+---
+"@opennextjs/aws": minor
+---
+
+Refactor overrides
diff --git a/examples/app-router/app/image-optimization/page.tsx b/examples/app-router/app/image-optimization/page.tsx
index baba473bc..05e171396 100644
--- a/examples/app-router/app/image-optimization/page.tsx
+++ b/examples/app-router/app/image-optimization/page.tsx
@@ -4,7 +4,7 @@ export default function ImageOptimization() {
return (
{
- const openNextParams = globalThis.openNextConfig.middleware;
- if (typeof openNextParams?.originResolver === "function") {
- return openNextParams.originResolver();
- }
-
- return Promise.resolve({
- name: "env",
- resolve: async (_path: string) => {
- try {
- const origin = JSON.parse(
- process.env.OPEN_NEXT_ORIGIN ?? "{}",
- ) as Record;
- for (const [key, value] of Object.entries(
- globalThis.openNextConfig.functions ?? {},
- ).filter(([key]) => key !== "default")) {
- if (
- value.patterns.some((pattern) => {
- // Convert cloudfront pattern to regex
- return new RegExp(
- // transform glob pattern to regex
- "/" +
- pattern
- .replace(/\*\*/g, "(.*)")
- .replace(/\*/g, "([^/]*)")
- .replace(/\//g, "\\/")
- .replace(/\?/g, "."),
- ).test(_path);
- })
- ) {
- debug("Using origin", key, value.patterns);
- return origin[key];
- }
- }
- if (_path.startsWith("/_next/image") && origin["imageOptimizer"]) {
- debug("Using origin", "imageOptimizer", _path);
- return origin["imageOptimizer"];
- }
- if (origin["default"]) {
- debug("Using default origin", origin["default"], _path);
- return origin["default"];
- }
- return false as const;
- } catch (e) {
- error("Error while resolving origin", e);
- return false as const;
- }
- },
- });
-};
-
globalThis.internalFetch = fetch;
const defaultHandler = async (internalEvent: InternalEvent) => {
- const originResolver = await resolveOriginResolver();
+ const originResolver = await resolveOriginResolver(
+ globalThis.openNextConfig.middleware?.originResolver,
+ );
//#override includeCacheInMiddleware
globalThis.tagCache = await resolveTagCache(
diff --git a/packages/open-next/src/adapters/warmer-function.ts b/packages/open-next/src/adapters/warmer-function.ts
index 00addca4b..7d6cf067a 100644
--- a/packages/open-next/src/adapters/warmer-function.ts
+++ b/packages/open-next/src/adapters/warmer-function.ts
@@ -1,7 +1,5 @@
-import { Warmer } from "types/open-next.js";
-
import { createGenericHandler } from "../core/createGenericHandler.js";
-import { debug, error } from "./logger.js";
+import { resolveWarmerInvoke } from "../core/resolve.js";
import { generateUniqueId } from "./util.js";
export interface WarmerEvent {
@@ -17,88 +15,6 @@ export interface WarmerResponse {
serverId: string;
}
-const resolveWarmerInvoke = async () => {
- const openNextParams = globalThis.openNextConfig.warmer!;
- if (typeof openNextParams?.invokeFunction === "function") {
- return await openNextParams.invokeFunction();
- } else {
- return Promise.resolve({
- name: "aws-invoke",
- invoke: async (warmerId: string) => {
- const { InvokeCommand, LambdaClient } = await import(
- "@aws-sdk/client-lambda"
- );
- const lambda = new LambdaClient({});
- const warmParams = JSON.parse(process.env.WARM_PARAMS!) as {
- concurrency: number;
- function: string;
- }[];
-
- for (const warmParam of warmParams) {
- const { concurrency: CONCURRENCY, function: FUNCTION_NAME } =
- warmParam;
- debug({
- event: "warmer invoked",
- functionName: FUNCTION_NAME,
- concurrency: CONCURRENCY,
- warmerId,
- });
- const ret = await Promise.all(
- Array.from({ length: CONCURRENCY }, (_v, i) => i).map((i) => {
- try {
- return lambda.send(
- new InvokeCommand({
- FunctionName: FUNCTION_NAME,
- InvocationType: "RequestResponse",
- Payload: Buffer.from(
- JSON.stringify({
- type: "warmer",
- warmerId,
- index: i,
- concurrency: CONCURRENCY,
- delay: 75,
- } satisfies WarmerEvent),
- ),
- }),
- );
- } catch (e) {
- error(`failed to warm up #${i}`, e);
- // ignore error
- }
- }),
- );
-
- // Print status
-
- const warmedServerIds = ret
- .map((r, i) => {
- if (r?.StatusCode !== 200 || !r?.Payload) {
- error(`failed to warm up #${i}:`, r?.Payload?.toString());
- return;
- }
- const payload = JSON.parse(
- Buffer.from(r.Payload).toString(),
- ) as WarmerResponse;
- return {
- statusCode: r.StatusCode,
- payload,
- type: "warmer" as const,
- };
- })
- .filter((r): r is Exclude => !!r);
-
- debug({
- event: "warmer result",
- sent: CONCURRENCY,
- success: warmedServerIds.length,
- uniqueServersWarmed: [...new Set(warmedServerIds)].length,
- });
- }
- },
- });
- }
-};
-
export const handler = await createGenericHandler({
handler: defaultHandler,
type: "warmer",
@@ -107,7 +23,9 @@ export const handler = await createGenericHandler({
async function defaultHandler() {
const warmerId = `warmer-${generateUniqueId()}`;
- const invokeFn = await resolveWarmerInvoke();
+ const invokeFn = await resolveWarmerInvoke(
+ globalThis.openNextConfig.warmer?.invokeFunction,
+ );
await invokeFn.invoke(warmerId);
diff --git a/packages/open-next/src/core/createMainHandler.ts b/packages/open-next/src/core/createMainHandler.ts
index deb5ecda9..e9a1f378d 100644
--- a/packages/open-next/src/core/createMainHandler.ts
+++ b/packages/open-next/src/core/createMainHandler.ts
@@ -5,8 +5,8 @@ import { DetachedPromiseRunner } from "utils/promise";
import { debug } from "../adapters/logger";
import { generateUniqueId } from "../adapters/util";
-import type { IncrementalCache } from "../cache/incremental/types";
-import type { Queue } from "../queue/types";
+import type { IncrementalCache } from "../overrides/incrementalCache/types";
+import type { Queue } from "../overrides/queue/types";
import { openNextHandler } from "./requestHandler.js";
import {
resolveConverter,
diff --git a/packages/open-next/src/core/resolve.ts b/packages/open-next/src/core/resolve.ts
index aac5a8a54..28c73c4a0 100644
--- a/packages/open-next/src/core/resolve.ts
+++ b/packages/open-next/src/core/resolve.ts
@@ -6,11 +6,13 @@ import {
InternalEvent,
InternalResult,
LazyLoadedOverride,
+ OriginResolver,
OverrideOptions,
+ Warmer,
Wrapper,
} from "types/open-next.js";
-import { TagCache } from "../cache/tag/types.js";
+import { TagCache } from "../overrides/tagCache/types.js";
export async function resolveConverter<
E extends BaseEventOrResult = InternalEvent,
@@ -21,7 +23,7 @@ export async function resolveConverter<
if (typeof converter === "function") {
return converter();
} else {
- const m_1 = await import(`../converters/aws-apigw-v2.js`);
+ const m_1 = await import(`../overrides/converters/aws-apigw-v2.js`);
// @ts-expect-error
return m_1.default;
}
@@ -35,7 +37,7 @@ export async function resolveWrapper<
return wrapper();
} else {
// This will be replaced by the bundler
- const m_1 = await import("../wrappers/aws-lambda.js");
+ const m_1 = await import("../overrides/wrappers/aws-lambda.js");
// @ts-expect-error
return m_1.default;
}
@@ -54,7 +56,7 @@ export async function resolveTagCache(
return tagCache();
} else {
// This will be replaced by the bundler
- const m_1 = await import("../cache/tag/dynamodb.js");
+ const m_1 = await import("../overrides/tagCache/dynamodb.js");
return m_1.default;
}
}
@@ -69,7 +71,7 @@ export async function resolveQueue(queue: OverrideOptions["queue"]) {
if (typeof queue === "function") {
return queue();
} else {
- const m_1 = await import("../queue/sqs.js");
+ const m_1 = await import("../overrides/queue/sqs.js");
return m_1.default;
}
}
@@ -86,7 +88,7 @@ export async function resolveIncrementalCache(
if (typeof incrementalCache === "function") {
return incrementalCache();
} else {
- const m_1 = await import("../cache/incremental/s3.js");
+ const m_1 = await import("../overrides/incrementalCache/s3.js");
return m_1.default;
}
}
@@ -106,3 +108,32 @@ export async function resolveImageLoader(
return m_1.default;
}
}
+
+/**
+ * @returns
+ * @__PURE__
+ */
+export async function resolveOriginResolver(
+ originResolver?: LazyLoadedOverride | string,
+) {
+ if (typeof originResolver === "function") {
+ return originResolver();
+ } else {
+ const m_1 = await import("../overrides/originResolver/pattern-env.js");
+ return m_1.default;
+ }
+}
+
+/**
+ * @__PURE__
+ */
+export async function resolveWarmerInvoke(
+ warmer?: LazyLoadedOverride | "aws-lambda",
+) {
+ if (typeof warmer === "function") {
+ return warmer();
+ } else {
+ const m_1 = await import("../overrides/warmer/aws-lambda.js");
+ return m_1.default;
+ }
+}
diff --git a/packages/open-next/src/core/routing/cacheInterceptor.ts b/packages/open-next/src/core/routing/cacheInterceptor.ts
index a3f83f0cc..29848753c 100644
--- a/packages/open-next/src/core/routing/cacheInterceptor.ts
+++ b/packages/open-next/src/core/routing/cacheInterceptor.ts
@@ -5,7 +5,7 @@ import { InternalEvent, InternalResult } from "types/open-next";
import { emptyReadableStream, toReadableStream } from "utils/stream";
import { debug } from "../../adapters/logger";
-import { CacheValue } from "../../cache/incremental/types";
+import { CacheValue } from "../../overrides/incrementalCache/types";
import { localizePath } from "./i18n";
import { generateMessageGroupId } from "./util";
diff --git a/packages/open-next/src/converters/aws-apigw-v1.ts b/packages/open-next/src/overrides/converters/aws-apigw-v1.ts
similarity index 96%
rename from packages/open-next/src/converters/aws-apigw-v1.ts
rename to packages/open-next/src/overrides/converters/aws-apigw-v1.ts
index c5e1fcfa6..4dbc1e2ec 100644
--- a/packages/open-next/src/converters/aws-apigw-v1.ts
+++ b/packages/open-next/src/overrides/converters/aws-apigw-v1.ts
@@ -1,8 +1,8 @@
-import { APIGatewayProxyEvent, APIGatewayProxyResult } from "aws-lambda";
+import type { APIGatewayProxyEvent, APIGatewayProxyResult } from "aws-lambda";
import type { Converter, InternalEvent, InternalResult } from "types/open-next";
import { fromReadableStream } from "utils/stream";
-import { debug } from "../adapters/logger";
+import { debug } from "../../adapters/logger";
import { removeUndefinedFromQuery } from "./utils";
function normalizeAPIGatewayProxyEventHeaders(
diff --git a/packages/open-next/src/converters/aws-apigw-v2.ts b/packages/open-next/src/overrides/converters/aws-apigw-v2.ts
similarity index 94%
rename from packages/open-next/src/converters/aws-apigw-v2.ts
rename to packages/open-next/src/overrides/converters/aws-apigw-v2.ts
index 8bc443cc7..e9eefe794 100644
--- a/packages/open-next/src/converters/aws-apigw-v2.ts
+++ b/packages/open-next/src/overrides/converters/aws-apigw-v2.ts
@@ -1,10 +1,13 @@
-import { APIGatewayProxyEventV2, APIGatewayProxyResultV2 } from "aws-lambda";
+import type {
+ APIGatewayProxyEventV2,
+ APIGatewayProxyResultV2,
+} from "aws-lambda";
import { parseCookies } from "http/util";
import type { Converter, InternalEvent, InternalResult } from "types/open-next";
import { fromReadableStream } from "utils/stream";
-import { debug } from "../adapters/logger";
-import { convertToQuery } from "../core/routing/util";
+import { debug } from "../../adapters/logger";
+import { convertToQuery } from "../../core/routing/util";
import { removeUndefinedFromQuery } from "./utils";
// Not sure which one is reallly needed as this is not documented anywhere but server actions redirect are not working without this, it causes a 500 error from cloudfront itself with a 'x-amzErrortype: InternalFailure' header
diff --git a/packages/open-next/src/converters/aws-cloudfront.ts b/packages/open-next/src/overrides/converters/aws-cloudfront.ts
similarity index 96%
rename from packages/open-next/src/converters/aws-cloudfront.ts
rename to packages/open-next/src/overrides/converters/aws-cloudfront.ts
index c00f89d94..08d490a60 100644
--- a/packages/open-next/src/converters/aws-cloudfront.ts
+++ b/packages/open-next/src/overrides/converters/aws-cloudfront.ts
@@ -1,24 +1,25 @@
-import {
+import type { OutgoingHttpHeader } from "node:http";
+
+import type {
CloudFrontCustomOrigin,
CloudFrontHeaders,
CloudFrontRequest,
CloudFrontRequestEvent,
CloudFrontRequestResult,
} from "aws-lambda";
-import { OutgoingHttpHeader } from "http";
import { parseCookies } from "http/util";
import type { Converter, InternalEvent, InternalResult } from "types/open-next";
import { fromReadableStream } from "utils/stream";
-import { debug } from "../adapters/logger";
+import { debug } from "../../adapters/logger";
import {
convertRes,
convertToQuery,
convertToQueryString,
createServerResponse,
proxyRequest,
-} from "../core/routing/util";
-import { MiddlewareOutputEvent } from "../core/routingHandler";
+} from "../../core/routing/util";
+import type { MiddlewareOutputEvent } from "../../core/routingHandler";
const CloudFrontBlacklistedHeaders = [
// Disallowed headers, see: https://docs.aws.amazon.com/AmazonCloudFront/latest/DeveloperGuide/edge-function-restrictions-all.html#function-restrictions-disallowed-headers
diff --git a/packages/open-next/src/converters/dummy.ts b/packages/open-next/src/overrides/converters/dummy.ts
similarity index 89%
rename from packages/open-next/src/converters/dummy.ts
rename to packages/open-next/src/overrides/converters/dummy.ts
index 917b50e13..dda76c670 100644
--- a/packages/open-next/src/converters/dummy.ts
+++ b/packages/open-next/src/overrides/converters/dummy.ts
@@ -1,4 +1,4 @@
-import { Converter } from "types/open-next";
+import type { Converter } from "types/open-next";
type DummyEventOrResult = {
type: "dummy";
diff --git a/packages/open-next/src/converters/edge.ts b/packages/open-next/src/overrides/converters/edge.ts
similarity index 95%
rename from packages/open-next/src/converters/edge.ts
rename to packages/open-next/src/overrides/converters/edge.ts
index 7341c7486..140f621e2 100644
--- a/packages/open-next/src/converters/edge.ts
+++ b/packages/open-next/src/overrides/converters/edge.ts
@@ -1,9 +1,9 @@
import { Buffer } from "node:buffer";
import { parseCookies } from "http/util";
-import { Converter, InternalEvent, InternalResult } from "types/open-next";
+import type { Converter, InternalEvent, InternalResult } from "types/open-next";
-import { MiddlewareOutputEvent } from "../core/routingHandler";
+import { MiddlewareOutputEvent } from "../../core/routingHandler";
const converter: Converter<
InternalEvent,
diff --git a/packages/open-next/src/converters/node.ts b/packages/open-next/src/overrides/converters/node.ts
similarity index 97%
rename from packages/open-next/src/converters/node.ts
rename to packages/open-next/src/overrides/converters/node.ts
index ec3e28641..50251d0f2 100644
--- a/packages/open-next/src/converters/node.ts
+++ b/packages/open-next/src/overrides/converters/node.ts
@@ -1,4 +1,4 @@
-import { IncomingMessage } from "http";
+import type { IncomingMessage } from "http";
import { parseCookies } from "http/util";
import type { Converter, InternalResult } from "types/open-next";
diff --git a/packages/open-next/src/converters/sqs-revalidate.ts b/packages/open-next/src/overrides/converters/sqs-revalidate.ts
similarity index 79%
rename from packages/open-next/src/converters/sqs-revalidate.ts
rename to packages/open-next/src/overrides/converters/sqs-revalidate.ts
index 0d18d93da..c8c29c02b 100644
--- a/packages/open-next/src/converters/sqs-revalidate.ts
+++ b/packages/open-next/src/overrides/converters/sqs-revalidate.ts
@@ -1,7 +1,7 @@
-import { SQSEvent } from "aws-lambda";
-import { Converter } from "types/open-next";
+import type { SQSEvent } from "aws-lambda";
+import type { Converter } from "types/open-next";
-import { RevalidateEvent } from "../adapters/revalidate";
+import type { RevalidateEvent } from "../../adapters/revalidate";
const converter: Converter = {
convertFrom(event: SQSEvent) {
diff --git a/packages/open-next/src/converters/utils.ts b/packages/open-next/src/overrides/converters/utils.ts
similarity index 100%
rename from packages/open-next/src/converters/utils.ts
rename to packages/open-next/src/overrides/converters/utils.ts
diff --git a/packages/open-next/src/overrides/imageLoader/dummy.ts b/packages/open-next/src/overrides/imageLoader/dummy.ts
new file mode 100644
index 000000000..aa013a3ae
--- /dev/null
+++ b/packages/open-next/src/overrides/imageLoader/dummy.ts
@@ -0,0 +1,11 @@
+import { ImageLoader } from "types/open-next";
+import { FatalError } from "utils/error";
+
+const dummyLoader: ImageLoader = {
+ name: "dummy",
+ load: async (_: string) => {
+ throw new FatalError("Dummy loader is not implemented");
+ },
+};
+
+export default dummyLoader;
diff --git a/packages/open-next/src/overrides/incrementalCache/dummy.ts b/packages/open-next/src/overrides/incrementalCache/dummy.ts
new file mode 100644
index 000000000..51580a775
--- /dev/null
+++ b/packages/open-next/src/overrides/incrementalCache/dummy.ts
@@ -0,0 +1,16 @@
+import { IncrementalCache } from "./types";
+
+const dummyIncrementalCache: IncrementalCache = {
+ name: "dummy",
+ get: async () => {
+ throw new Error("Dummy cache is not implemented");
+ },
+ set: async () => {
+ throw new Error("Dummy cache is not implemented");
+ },
+ delete: async () => {
+ throw new Error("Dummy cache is not implemented");
+ },
+};
+
+export default dummyIncrementalCache;
diff --git a/packages/open-next/src/cache/incremental/s3-lite.ts b/packages/open-next/src/overrides/incrementalCache/s3-lite.ts
similarity index 98%
rename from packages/open-next/src/cache/incremental/s3-lite.ts
rename to packages/open-next/src/overrides/incrementalCache/s3-lite.ts
index 37fda1be4..25d24ba61 100644
--- a/packages/open-next/src/cache/incremental/s3-lite.ts
+++ b/packages/open-next/src/overrides/incrementalCache/s3-lite.ts
@@ -1,11 +1,11 @@
/* eslint-disable @typescript-eslint/no-non-null-assertion */
import { AwsClient } from "aws4fetch";
import path from "path";
+import { Extension } from "types/cache";
import { IgnorableError, RecoverableError } from "utils/error";
import { customFetchClient } from "utils/fetch";
import { parseNumberFromEnv } from "../../adapters/util";
-import { Extension } from "../next-types";
import { IncrementalCache } from "./types";
let awsClient: AwsClient | null = null;
diff --git a/packages/open-next/src/cache/incremental/s3.ts b/packages/open-next/src/overrides/incrementalCache/s3.ts
similarity index 97%
rename from packages/open-next/src/cache/incremental/s3.ts
rename to packages/open-next/src/overrides/incrementalCache/s3.ts
index 2e277ba19..cc1cdb3e1 100644
--- a/packages/open-next/src/cache/incremental/s3.ts
+++ b/packages/open-next/src/overrides/incrementalCache/s3.ts
@@ -6,10 +6,10 @@ import {
S3ClientConfig,
} from "@aws-sdk/client-s3";
import path from "path";
+import { Extension } from "types/cache";
import { awsLogger } from "../../adapters/logger";
import { parseNumberFromEnv } from "../../adapters/util";
-import { Extension } from "../next-types";
import { IncrementalCache } from "./types";
const {
diff --git a/packages/open-next/src/cache/incremental/types.ts b/packages/open-next/src/overrides/incrementalCache/types.ts
similarity index 96%
rename from packages/open-next/src/cache/incremental/types.ts
rename to packages/open-next/src/overrides/incrementalCache/types.ts
index 81030eed1..71948665c 100644
--- a/packages/open-next/src/cache/incremental/types.ts
+++ b/packages/open-next/src/overrides/incrementalCache/types.ts
@@ -1,4 +1,4 @@
-import { Meta } from "../next-types";
+import { Meta } from "types/cache";
export type S3CachedFile =
| {
diff --git a/packages/open-next/src/overrides/originResolver/dummy.ts b/packages/open-next/src/overrides/originResolver/dummy.ts
new file mode 100644
index 000000000..89aef325e
--- /dev/null
+++ b/packages/open-next/src/overrides/originResolver/dummy.ts
@@ -0,0 +1,10 @@
+import { OriginResolver } from "types/open-next";
+
+const dummyOriginResolver: OriginResolver = {
+ name: "dummy",
+ resolve: async (_path: string) => {
+ return false as const;
+ },
+};
+
+export default dummyOriginResolver;
diff --git a/packages/open-next/src/overrides/originResolver/pattern-env.ts b/packages/open-next/src/overrides/originResolver/pattern-env.ts
new file mode 100644
index 000000000..73b142158
--- /dev/null
+++ b/packages/open-next/src/overrides/originResolver/pattern-env.ts
@@ -0,0 +1,50 @@
+import { Origin, OriginResolver } from "types/open-next";
+
+import { debug, error } from "../../adapters/logger";
+
+const envLoader: OriginResolver = {
+ name: "env",
+ resolve: async (_path: string) => {
+ try {
+ const origin = JSON.parse(process.env.OPEN_NEXT_ORIGIN ?? "{}") as Record<
+ string,
+ Origin
+ >;
+ for (const [key, value] of Object.entries(
+ globalThis.openNextConfig.functions ?? {},
+ ).filter(([key]) => key !== "default")) {
+ if (
+ value.patterns.some((pattern) => {
+ // Convert cloudfront pattern to regex
+ return new RegExp(
+ // transform glob pattern to regex
+ "/" +
+ pattern
+ .replace(/\*\*/g, "(.*)")
+ .replace(/\*/g, "([^/]*)")
+ .replace(/\//g, "\\/")
+ .replace(/\?/g, "."),
+ ).test(_path);
+ })
+ ) {
+ debug("Using origin", key, value.patterns);
+ return origin[key];
+ }
+ }
+ if (_path.startsWith("/_next/image") && origin["imageOptimizer"]) {
+ debug("Using origin", "imageOptimizer", _path);
+ return origin["imageOptimizer"];
+ }
+ if (origin["default"]) {
+ debug("Using default origin", origin["default"], _path);
+ return origin["default"];
+ }
+ return false as const;
+ } catch (e) {
+ error("Error while resolving origin", e);
+ return false as const;
+ }
+ },
+};
+
+export default envLoader;
diff --git a/packages/open-next/src/overrides/queue/dummy.ts b/packages/open-next/src/overrides/queue/dummy.ts
new file mode 100644
index 000000000..a1c2e579c
--- /dev/null
+++ b/packages/open-next/src/overrides/queue/dummy.ts
@@ -0,0 +1,10 @@
+import { Queue } from "./types";
+
+const dummyQueue: Queue = {
+ name: "dummy",
+ send: async () => {
+ throw new Error("Dummy queue is not implemented");
+ },
+};
+
+export default dummyQueue;
diff --git a/packages/open-next/src/queue/sqs-lite.ts b/packages/open-next/src/overrides/queue/sqs-lite.ts
similarity index 94%
rename from packages/open-next/src/queue/sqs-lite.ts
rename to packages/open-next/src/overrides/queue/sqs-lite.ts
index dfb97581b..813a71a6f 100644
--- a/packages/open-next/src/queue/sqs-lite.ts
+++ b/packages/open-next/src/overrides/queue/sqs-lite.ts
@@ -2,8 +2,8 @@ import { AwsClient } from "aws4fetch";
import { RecoverableError } from "utils/error";
import { customFetchClient } from "utils/fetch";
-import { error } from "../adapters/logger";
-import { Queue } from "./types";
+import { error } from "../../adapters/logger";
+import type { Queue } from "./types";
let awsClient: AwsClient | null = null;
diff --git a/packages/open-next/src/queue/sqs.ts b/packages/open-next/src/overrides/queue/sqs.ts
similarity index 87%
rename from packages/open-next/src/queue/sqs.ts
rename to packages/open-next/src/overrides/queue/sqs.ts
index cbc2bdfec..43cc320bf 100644
--- a/packages/open-next/src/queue/sqs.ts
+++ b/packages/open-next/src/overrides/queue/sqs.ts
@@ -1,7 +1,7 @@
import { SendMessageCommand, SQSClient } from "@aws-sdk/client-sqs";
-import { awsLogger } from "../adapters/logger";
-import { Queue } from "./types";
+import { awsLogger } from "../../adapters/logger";
+import type { Queue } from "./types";
// Expected environment variables
const { REVALIDATION_QUEUE_REGION, REVALIDATION_QUEUE_URL } = process.env;
diff --git a/packages/open-next/src/queue/types.ts b/packages/open-next/src/overrides/queue/types.ts
similarity index 100%
rename from packages/open-next/src/queue/types.ts
rename to packages/open-next/src/overrides/queue/types.ts
diff --git a/packages/open-next/src/cache/tag/constants.ts b/packages/open-next/src/overrides/tagCache/constants.ts
similarity index 100%
rename from packages/open-next/src/cache/tag/constants.ts
rename to packages/open-next/src/overrides/tagCache/constants.ts
diff --git a/packages/open-next/src/overrides/tagCache/dummy.ts b/packages/open-next/src/overrides/tagCache/dummy.ts
new file mode 100644
index 000000000..0b4b52db2
--- /dev/null
+++ b/packages/open-next/src/overrides/tagCache/dummy.ts
@@ -0,0 +1,20 @@
+import { TagCache } from "./types";
+
+// We don't want to throw error on this one because we might use it when we don't need tag cache
+const dummyTagCache: TagCache = {
+ name: "dummy",
+ getByPath: async () => {
+ return [];
+ },
+ getByTag: async () => {
+ return [];
+ },
+ getLastModified: async (_: string, lastModified) => {
+ return lastModified ?? Date.now();
+ },
+ writeTags: async () => {
+ return;
+ },
+};
+
+export default dummyTagCache;
diff --git a/packages/open-next/src/cache/tag/dynamodb-lite.ts b/packages/open-next/src/overrides/tagCache/dynamodb-lite.ts
similarity index 99%
rename from packages/open-next/src/cache/tag/dynamodb-lite.ts
rename to packages/open-next/src/overrides/tagCache/dynamodb-lite.ts
index 8dcef36a6..b0c1543e2 100644
--- a/packages/open-next/src/cache/tag/dynamodb-lite.ts
+++ b/packages/open-next/src/overrides/tagCache/dynamodb-lite.ts
@@ -10,7 +10,7 @@ import {
getDynamoBatchWriteCommandConcurrency,
MAX_DYNAMO_BATCH_WRITE_ITEM_COUNT,
} from "./constants";
-import { TagCache } from "./types";
+import type { TagCache } from "./types";
let awsClient: AwsClient | null = null;
diff --git a/packages/open-next/src/cache/tag/dynamodb.ts b/packages/open-next/src/overrides/tagCache/dynamodb.ts
similarity index 99%
rename from packages/open-next/src/cache/tag/dynamodb.ts
rename to packages/open-next/src/overrides/tagCache/dynamodb.ts
index 454b9800b..8d9ec89c1 100644
--- a/packages/open-next/src/cache/tag/dynamodb.ts
+++ b/packages/open-next/src/overrides/tagCache/dynamodb.ts
@@ -12,7 +12,7 @@ import {
getDynamoBatchWriteCommandConcurrency,
MAX_DYNAMO_BATCH_WRITE_ITEM_COUNT,
} from "./constants";
-import { TagCache } from "./types";
+import type { TagCache } from "./types";
const { CACHE_BUCKET_REGION, CACHE_DYNAMO_TABLE, NEXT_BUILD_ID } = process.env;
diff --git a/packages/open-next/src/cache/tag/types.ts b/packages/open-next/src/overrides/tagCache/types.ts
similarity index 100%
rename from packages/open-next/src/cache/tag/types.ts
rename to packages/open-next/src/overrides/tagCache/types.ts
diff --git a/packages/open-next/src/overrides/warmer/aws-lambda.ts b/packages/open-next/src/overrides/warmer/aws-lambda.ts
new file mode 100644
index 000000000..d7da9bea7
--- /dev/null
+++ b/packages/open-next/src/overrides/warmer/aws-lambda.ts
@@ -0,0 +1,83 @@
+import { Warmer } from "types/open-next";
+
+import { debug, error } from "../../adapters/logger";
+import type {
+ WarmerEvent,
+ WarmerResponse,
+} from "../../adapters/warmer-function";
+
+const lambdaWarmerInvoke: Warmer = {
+ name: "aws-invoke",
+ invoke: async (warmerId: string) => {
+ const { InvokeCommand, LambdaClient } = await import(
+ "@aws-sdk/client-lambda"
+ );
+ const lambda = new LambdaClient({});
+ const warmParams = JSON.parse(process.env.WARM_PARAMS!) as {
+ concurrency: number;
+ function: string;
+ }[];
+
+ for (const warmParam of warmParams) {
+ const { concurrency: CONCURRENCY, function: FUNCTION_NAME } = warmParam;
+ debug({
+ event: "warmer invoked",
+ functionName: FUNCTION_NAME,
+ concurrency: CONCURRENCY,
+ warmerId,
+ });
+ const ret = await Promise.all(
+ Array.from({ length: CONCURRENCY }, (_v, i) => i).map((i) => {
+ try {
+ return lambda.send(
+ new InvokeCommand({
+ FunctionName: FUNCTION_NAME,
+ InvocationType: "RequestResponse",
+ Payload: Buffer.from(
+ JSON.stringify({
+ type: "warmer",
+ warmerId,
+ index: i,
+ concurrency: CONCURRENCY,
+ delay: 75,
+ } satisfies WarmerEvent),
+ ),
+ }),
+ );
+ } catch (e) {
+ error(`failed to warm up #${i}`, e);
+ // ignore error
+ }
+ }),
+ );
+
+ // Print status
+
+ const warmedServerIds = ret
+ .map((r, i) => {
+ if (r?.StatusCode !== 200 || !r?.Payload) {
+ error(`failed to warm up #${i}:`, r?.Payload?.toString());
+ return;
+ }
+ const payload = JSON.parse(
+ Buffer.from(r.Payload).toString(),
+ ) as WarmerResponse;
+ return {
+ statusCode: r.StatusCode,
+ payload,
+ type: "warmer" as const,
+ };
+ })
+ .filter((r): r is Exclude => !!r);
+
+ debug({
+ event: "warmer result",
+ sent: CONCURRENCY,
+ success: warmedServerIds.length,
+ uniqueServersWarmed: [...new Set(warmedServerIds)].length,
+ });
+ }
+ },
+};
+
+export default lambdaWarmerInvoke;
diff --git a/packages/open-next/src/overrides/warmer/dummy.ts b/packages/open-next/src/overrides/warmer/dummy.ts
new file mode 100644
index 000000000..01112f5b1
--- /dev/null
+++ b/packages/open-next/src/overrides/warmer/dummy.ts
@@ -0,0 +1,10 @@
+import type { Warmer } from "types/open-next";
+
+const dummyWarmer: Warmer = {
+ name: "dummy",
+ invoke: async (_: string) => {
+ throw new Error("Dummy warmer is not implemented");
+ },
+};
+
+export default dummyWarmer;
diff --git a/packages/open-next/src/wrappers/aws-lambda-streaming.ts b/packages/open-next/src/overrides/wrappers/aws-lambda-streaming.ts
similarity index 90%
rename from packages/open-next/src/wrappers/aws-lambda-streaming.ts
rename to packages/open-next/src/overrides/wrappers/aws-lambda-streaming.ts
index d953eb2bd..8a6e57f25 100644
--- a/packages/open-next/src/wrappers/aws-lambda-streaming.ts
+++ b/packages/open-next/src/overrides/wrappers/aws-lambda-streaming.ts
@@ -1,12 +1,15 @@
-import { Readable, Writable } from "node:stream";
+import { Readable, type Writable } from "node:stream";
import zlib from "node:zlib";
-import { APIGatewayProxyEventV2 } from "aws-lambda";
-import { StreamCreator } from "http/index.js";
-import { WrapperHandler } from "types/open-next";
+import type { APIGatewayProxyEventV2 } from "aws-lambda";
+import type { StreamCreator } from "http/index.js";
+import type { WrapperHandler } from "types/open-next";
-import { debug, error } from "../adapters/logger";
-import { WarmerEvent, WarmerResponse } from "../adapters/warmer-function";
+import { debug, error } from "../../adapters/logger";
+import type {
+ WarmerEvent,
+ WarmerResponse,
+} from "../../adapters/warmer-function";
type AwsLambdaEvent = APIGatewayProxyEventV2 | WarmerEvent;
diff --git a/packages/open-next/src/wrappers/aws-lambda.ts b/packages/open-next/src/overrides/wrappers/aws-lambda.ts
similarity index 93%
rename from packages/open-next/src/wrappers/aws-lambda.ts
rename to packages/open-next/src/overrides/wrappers/aws-lambda.ts
index e4fa8afc8..833951b3b 100644
--- a/packages/open-next/src/wrappers/aws-lambda.ts
+++ b/packages/open-next/src/overrides/wrappers/aws-lambda.ts
@@ -8,10 +8,13 @@ import type {
CloudFrontRequestEvent,
CloudFrontRequestResult,
} from "aws-lambda";
-import { StreamCreator } from "http/openNextResponse";
+import type { StreamCreator } from "http/openNextResponse";
import type { WrapperHandler } from "types/open-next";
-import { WarmerEvent, WarmerResponse } from "../adapters/warmer-function";
+import type {
+ WarmerEvent,
+ WarmerResponse,
+} from "../../adapters/warmer-function";
type AwsLambdaEvent =
| APIGatewayProxyEventV2
diff --git a/packages/open-next/src/wrappers/cloudflare.ts b/packages/open-next/src/overrides/wrappers/cloudflare.ts
similarity index 92%
rename from packages/open-next/src/wrappers/cloudflare.ts
rename to packages/open-next/src/overrides/wrappers/cloudflare.ts
index bfeea939e..55a217246 100644
--- a/packages/open-next/src/wrappers/cloudflare.ts
+++ b/packages/open-next/src/overrides/wrappers/cloudflare.ts
@@ -4,7 +4,7 @@ import type {
WrapperHandler,
} from "types/open-next";
-import { MiddlewareOutputEvent } from "../core/routingHandler";
+import type { MiddlewareOutputEvent } from "../../core/routingHandler";
const handler: WrapperHandler<
InternalEvent,
diff --git a/packages/open-next/src/overrides/wrappers/dummy.ts b/packages/open-next/src/overrides/wrappers/dummy.ts
new file mode 100644
index 000000000..0f66667fa
--- /dev/null
+++ b/packages/open-next/src/overrides/wrappers/dummy.ts
@@ -0,0 +1,9 @@
+import { WrapperHandler } from "types/open-next";
+
+const dummyWrapper: WrapperHandler = async () => async () => undefined;
+
+export default {
+ name: "dummy",
+ handler: dummyWrapper,
+ supportStreaming: false,
+};
diff --git a/packages/open-next/src/wrappers/node.ts b/packages/open-next/src/overrides/wrappers/node.ts
similarity index 93%
rename from packages/open-next/src/wrappers/node.ts
rename to packages/open-next/src/overrides/wrappers/node.ts
index 978cd0aef..bc1b69c5c 100644
--- a/packages/open-next/src/wrappers/node.ts
+++ b/packages/open-next/src/overrides/wrappers/node.ts
@@ -1,9 +1,9 @@
import { createServer } from "node:http";
-import { StreamCreator } from "http/index.js";
+import type { StreamCreator } from "http/index.js";
import type { WrapperHandler } from "types/open-next";
-import { debug, error } from "../adapters/logger";
+import { debug, error } from "../../adapters/logger";
const wrapper: WrapperHandler = async (handler, converter) => {
const server = createServer(async (req, res) => {
diff --git a/packages/open-next/src/plugins/resolve.ts b/packages/open-next/src/plugins/resolve.ts
index 48eb2f3a0..de28f8c63 100644
--- a/packages/open-next/src/plugins/resolve.ts
+++ b/packages/open-next/src/plugins/resolve.ts
@@ -5,8 +5,12 @@ import type {
DefaultOverrideOptions,
ImageLoader,
IncludedImageLoader,
+ IncludedOriginResolver,
+ IncludedWarmer,
LazyLoadedOverride,
+ OriginResolver,
OverrideOptions,
+ Warmer,
} from "types/open-next";
import logger from "../logger.js";
@@ -19,19 +23,47 @@ export interface IPluginSettings {
queue?: OverrideOptions["queue"];
incrementalCache?: OverrideOptions["incrementalCache"];
imageLoader?: LazyLoadedOverride | IncludedImageLoader;
+ originResolver?:
+ | LazyLoadedOverride
+ | IncludedOriginResolver;
+ warmer?: LazyLoadedOverride | IncludedWarmer;
};
fnName?: string;
}
-function getOverrideOrDefault<
- Override extends string | LazyLoadedOverride,
->(override: Override, defaultOverride: string) {
+function getOverrideOrDummy>(
+ override: Override,
+) {
if (typeof override === "string") {
return override;
}
- return defaultOverride;
+ // We can return dummy here because if it's not a string, it's a LazyLoadedOverride
+ return "dummy";
}
+// This could be useful in the future to map overrides to nested folders
+const nameToFolder = {
+ wrapper: "wrappers",
+ converter: "converters",
+ tagCache: "tagCache",
+ queue: "queue",
+ incrementalCache: "incrementalCache",
+ imageLoader: "imageLoader",
+ originResolver: "originResolver",
+ warmer: "warmer",
+};
+
+const defaultOverrides = {
+ wrapper: "aws-lambda",
+ converter: "aws-apigw-v2",
+ tagCache: "dynamodb",
+ queue: "sqs",
+ incrementalCache: "s3",
+ imageLoader: "s3",
+ originResolver: "pattern-env",
+ warmer: "aws-lambda",
+};
+
/**
* @param opts.overrides - The name of the overrides to use
* @returns
@@ -46,56 +78,18 @@ export function openNextResolvePlugin({
logger.debug(`OpenNext Resolve plugin for ${fnName}`);
build.onLoad({ filter: /core(\/|\\)resolve\.js/g }, async (args) => {
let contents = readFileSync(args.path, "utf-8");
- //TODO: refactor this. Every override should be at the same place so we can generate this dynamically
- if (overrides?.wrapper) {
- contents = contents.replace(
- "../wrappers/aws-lambda.js",
- `../wrappers/${getOverrideOrDefault(
- overrides.wrapper,
- "aws-lambda",
- )}.js`,
- );
- }
- if (overrides?.converter) {
- contents = contents.replace(
- "../converters/aws-apigw-v2.js",
- `../converters/${getOverrideOrDefault(
- overrides.converter,
- "dummy",
- )}.js`,
- );
- }
- if (overrides?.tagCache) {
- contents = contents.replace(
- "../cache/tag/dynamodb.js",
- `../cache/tag/${getOverrideOrDefault(
- overrides.tagCache,
- "dynamodb-lite",
- )}.js`,
- );
- }
- if (overrides?.queue) {
- contents = contents.replace(
- "../queue/sqs.js",
- `../queue/${getOverrideOrDefault(overrides.queue, "sqs-lite")}.js`,
- );
- }
- if (overrides?.incrementalCache) {
- contents = contents.replace(
- "../cache/incremental/s3.js",
- `../cache/incremental/${getOverrideOrDefault(
- overrides.incrementalCache,
- "s3-lite",
- )}.js`,
- );
- }
- if (overrides?.imageLoader) {
+ const overridesEntries = Object.entries(overrides ?? {});
+ for (const [overrideName, overrideValue] of overridesEntries) {
+ if (!overrideValue) {
+ continue;
+ }
+ const folder =
+ nameToFolder[overrideName as keyof typeof nameToFolder];
+ const defaultOverride =
+ defaultOverrides[overrideName as keyof typeof defaultOverrides];
contents = contents.replace(
- "../overrides/imageLoader/s3.js",
- `../overrides/imageLoader/${getOverrideOrDefault(
- overrides.imageLoader,
- "s3",
- )}.js`,
+ `../overrides/${folder}/${defaultOverride}.js`,
+ `../overrides/${folder}/${getOverrideOrDummy(overrideValue)}.js`,
);
}
return {
diff --git a/packages/open-next/src/cache/next-types.ts b/packages/open-next/src/types/cache.ts
similarity index 100%
rename from packages/open-next/src/cache/next-types.ts
rename to packages/open-next/src/types/cache.ts
diff --git a/packages/open-next/src/types/open-next.ts b/packages/open-next/src/types/open-next.ts
index 5efb27de5..dbc8f9239 100644
--- a/packages/open-next/src/types/open-next.ts
+++ b/packages/open-next/src/types/open-next.ts
@@ -4,9 +4,9 @@ import type { ReadableStream } from "node:stream/web";
import type { StreamCreator } from "http/index.js";
import type { WarmerEvent, WarmerResponse } from "../adapters/warmer-function";
-import type { IncrementalCache } from "../cache/incremental/types";
-import type { TagCache } from "../cache/tag/types";
-import type { Queue } from "../queue/types";
+import type { IncrementalCache } from "../overrides/incrementalCache/types";
+import type { Queue } from "../overrides/queue/types";
+import type { TagCache } from "../overrides/tagCache/types";
export type BaseEventOrResult = {
type: T;
@@ -139,6 +139,10 @@ export type IncludedTagCache = "dynamodb" | "dynamodb-lite";
export type IncludedImageLoader = "s3" | "host";
+export type IncludedOriginResolver = "pattern-env";
+
+export type IncludedWarmer = "aws-lambda";
+
export interface DefaultOverrideOptions<
E extends BaseEventOrResult = InternalEvent,
R extends BaseEventOrResult = InternalResult,
@@ -284,7 +288,9 @@ export interface OpenNextConfig {
* OPEN_NEXT_ORIGIN should be a json stringified object with the key of the splitted function as key and the origin as value
* @default "pattern-env"
*/
- originResolver?: "pattern-env" | LazyLoadedOverride;
+ originResolver?:
+ | IncludedOriginResolver
+ | LazyLoadedOverride;
};
/**
@@ -294,7 +300,7 @@ export interface OpenNextConfig {
* @default undefined
*/
warmer?: DefaultFunctionOptions & {
- invokeFunction: "aws-lambda" | LazyLoadedOverride;
+ invokeFunction: IncludedWarmer | LazyLoadedOverride;
};
/**
diff --git a/packages/tests-unit/tests/converters/aws-apigw-v1.test.ts b/packages/tests-unit/tests/converters/aws-apigw-v1.test.ts
index 03234bb3f..343ef72ed 100644
--- a/packages/tests-unit/tests/converters/aws-apigw-v1.test.ts
+++ b/packages/tests-unit/tests/converters/aws-apigw-v1.test.ts
@@ -1,5 +1,5 @@
/* eslint-disable sonarjs/no-duplicate-string */
-import converter from "@opennextjs/aws/converters/aws-apigw-v1.js";
+import converter from "@opennextjs/aws/overrides/converters/aws-apigw-v1.js";
import { APIGatewayProxyEvent, APIGatewayProxyResult } from "aws-lambda";
import { Readable } from "stream";
diff --git a/packages/tests-unit/tests/converters/aws-apigw-v2.test.ts b/packages/tests-unit/tests/converters/aws-apigw-v2.test.ts
index ab9e4f505..420e41e87 100644
--- a/packages/tests-unit/tests/converters/aws-apigw-v2.test.ts
+++ b/packages/tests-unit/tests/converters/aws-apigw-v2.test.ts
@@ -1,4 +1,4 @@
-import converter from "@opennextjs/aws/converters/aws-apigw-v2.js";
+import converter from "@opennextjs/aws/overrides/converters/aws-apigw-v2.js";
import { APIGatewayProxyEventV2 } from "aws-lambda";
import { Readable } from "stream";
import { vi } from "vitest";
diff --git a/packages/tests-unit/tests/converters/aws-cloudfront.test.ts b/packages/tests-unit/tests/converters/aws-cloudfront.test.ts
index bed3ed744..81749a558 100644
--- a/packages/tests-unit/tests/converters/aws-cloudfront.test.ts
+++ b/packages/tests-unit/tests/converters/aws-cloudfront.test.ts
@@ -1,4 +1,4 @@
-import converter from "@opennextjs/aws/converters/aws-cloudfront.js";
+import converter from "@opennextjs/aws/overrides/converters/aws-cloudfront.js";
import { CloudFrontRequestEvent, CloudFrontRequestResult } from "aws-lambda";
import { Readable } from "stream";
import { vi } from "vitest";
diff --git a/packages/tests-unit/tests/converters/node.test.ts b/packages/tests-unit/tests/converters/node.test.ts
index da0c07757..d77fd708f 100644
--- a/packages/tests-unit/tests/converters/node.test.ts
+++ b/packages/tests-unit/tests/converters/node.test.ts
@@ -1,5 +1,5 @@
-import converter from "@opennextjs/aws/converters/node.js";
import { IncomingMessage } from "@opennextjs/aws/http/request.js";
+import converter from "@opennextjs/aws/overrides/converters/node.js";
describe("convertFrom", () => {
it("should convert GET request", async () => {
diff --git a/packages/tests-unit/tests/converters/utils.test.ts b/packages/tests-unit/tests/converters/utils.test.ts
index 796fc6d31..9ca7b71bb 100644
--- a/packages/tests-unit/tests/converters/utils.test.ts
+++ b/packages/tests-unit/tests/converters/utils.test.ts
@@ -1,4 +1,4 @@
-import { removeUndefinedFromQuery } from "@opennextjs/aws/converters/utils.js";
+import { removeUndefinedFromQuery } from "@opennextjs/aws/overrides/converters/utils.js";
describe("removeUndefinedFromQuery", () => {
it("should remove undefined from query", () => {