diff --git a/.changeset/young-parents-take.md b/.changeset/young-parents-take.md new file mode 100644 index 000000000..bf18c4022 --- /dev/null +++ b/.changeset/young-parents-take.md @@ -0,0 +1,5 @@ +--- +"@opennextjs/aws": patch +--- + +Next 16 takes an array of tags in ComposableCacheHandler#getExpiration diff --git a/packages/open-next/src/adapters/composable-cache.ts b/packages/open-next/src/adapters/composable-cache.ts index 5ed51478e..6415bed02 100644 --- a/packages/open-next/src/adapters/composable-cache.ts +++ b/packages/open-next/src/adapters/composable-cache.ts @@ -99,14 +99,25 @@ export default { // We don't do anything for now, do we want to do something here ??? return; }, - async getExpiration(...tags: string[]) { + + /** + * The signature has changed in Next.js 16 + * - Before Next.js 16, the method takes `...tags: string[]` + * - From Next.js 16, the method takes `tags: string[]` + */ + async getExpiration(...tags: string[] | string[][]) { if (globalThis.tagCache.mode === "nextMode") { - return globalThis.tagCache.getLastRevalidated(tags); + // Use `.flat()` to accommodate both signatures + return globalThis.tagCache.getLastRevalidated(tags.flat()); } // We always return 0 here, original tag cache are handled directly in the get part // TODO: We need to test this more, i'm not entirely sure that this is working as expected return 0; }, + + /** + * This method is only used before Next.js 16 + */ async expireTags(...tags: string[]) { if (globalThis.tagCache.mode === "nextMode") { return writeTags(tags); diff --git a/packages/open-next/src/types/cache.ts b/packages/open-next/src/types/cache.ts index 26b5b396e..ddaeb9dec 100644 --- a/packages/open-next/src/types/cache.ts +++ b/packages/open-next/src/types/cache.ts @@ -162,7 +162,13 @@ export interface ComposableCacheHandler { pendingEntry: Promise, ): Promise; refreshTags(): Promise; - getExpiration(...tags: string[]): Promise; + /** + * Next 16 takes an array of tags instead of variadic arguments + */ + getExpiration(...tags: string[] | string[][]): Promise; + /** + * Removed from Next.js 16 + */ expireTags(...tags: string[]): Promise; /** * This function is only there for older versions and do nothing diff --git a/packages/tests-unit/tests/adapters/composable-cache.test.ts b/packages/tests-unit/tests/adapters/composable-cache.test.ts index 7d110ce0c..3f622c1d1 100644 --- a/packages/tests-unit/tests/adapters/composable-cache.test.ts +++ b/packages/tests-unit/tests/adapters/composable-cache.test.ts @@ -326,7 +326,7 @@ describe("Composable cache handler", () => { }); }); - describe("getExpiration", () => { + describe("getExpiration (Next 15)", () => { it("should return last revalidated time in nextMode", async () => { tagCache.mode = "nextMode"; tagCache.getLastRevalidated.mockResolvedValueOnce(123456); @@ -357,6 +357,37 @@ describe("Composable cache handler", () => { }); }); + describe("getExpiration (Next 16)", () => { + it("should return last revalidated time in nextMode", async () => { + tagCache.mode = "nextMode"; + tagCache.getLastRevalidated.mockResolvedValueOnce(123456); + + const result = await ComposableCache.getExpiration(["tag1", "tag2"]); + + expect(tagCache.getLastRevalidated).toHaveBeenCalledWith([ + "tag1", + "tag2", + ]); + expect(result).toBe(123456); + }); + + it("should return 0 in original mode", async () => { + tagCache.mode = "original"; + + const result = await ComposableCache.getExpiration(["tag1", "tag2"]); + + expect(result).toBe(0); + }); + + it("should return 0 when mode is undefined", async () => { + tagCache.mode = undefined; + + const result = await ComposableCache.getExpiration(["tag1", "tag2"]); + + expect(result).toBe(0); + }); + }); + describe("expireTags", () => { beforeEach(() => { writtenTags.clear();