Skip to content

Commit fc2e172

Browse files
committed
simple tag cache filter
1 parent 0361672 commit fc2e172

File tree

2 files changed

+182
-0
lines changed

2 files changed

+182
-0
lines changed
Lines changed: 123 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,123 @@
1+
import { NextModeTagCache } from "@opennextjs/aws/types/overrides";
2+
import {beforeEach, describe, expect, it, vi } from "vitest";
3+
4+
import { softTagFilter,withFilter } from "./tag-cache-filter";
5+
6+
const mockedTagCache = {
7+
name: "mocked",
8+
mode: "nextMode",
9+
getPathsByTags: vi.fn(),
10+
hasBeenRevalidated: vi.fn(),
11+
writeTags: vi.fn(),
12+
} satisfies NextModeTagCache;
13+
14+
const filterFn = (tag: string) => tag.startsWith("valid_");
15+
16+
describe("withFilter", () => {
17+
beforeEach(() => {
18+
vi.clearAllMocks();
19+
});
20+
21+
it("should filter out tags based on writeTags", async () => {
22+
const tagCache = withFilter({
23+
originalTagCache: mockedTagCache,
24+
filterFn,
25+
});
26+
27+
const tags = ["valid_tag", "invalid_tag"];
28+
29+
await tagCache.writeTags(tags);
30+
expect(mockedTagCache.writeTags).toHaveBeenCalledWith(["valid_tag"]);
31+
})
32+
33+
it('should not call writeTags if no tags are valid', async () => {
34+
const tagCache = withFilter({
35+
originalTagCache: mockedTagCache,
36+
filterFn,
37+
});
38+
const tags = ["invalid_tag"];
39+
await tagCache.writeTags(tags);
40+
expect(mockedTagCache.writeTags).not.toHaveBeenCalled();
41+
})
42+
43+
it("should filter out tags based on hasBeenRevalidated", async () => {
44+
const tagCache = withFilter({
45+
originalTagCache: mockedTagCache,
46+
filterFn,
47+
});
48+
49+
const tags = ["valid_tag", "invalid_tag"];
50+
const lastModified = Date.now();
51+
52+
await tagCache.hasBeenRevalidated(tags, lastModified);
53+
expect(mockedTagCache.hasBeenRevalidated).toHaveBeenCalledWith(["valid_tag"], lastModified);
54+
}
55+
)
56+
57+
it('should not call hasBeenRevalidated if no tags are valid', async () => {
58+
const tagCache = withFilter({
59+
originalTagCache: mockedTagCache,
60+
filterFn,
61+
});
62+
const tags = ["invalid_tag"];
63+
const lastModified = Date.now();
64+
await tagCache.hasBeenRevalidated(tags, lastModified);
65+
expect(mockedTagCache.hasBeenRevalidated).not.toHaveBeenCalled();
66+
})
67+
68+
it("should filter out tags based on getPathsByTags", async () => {
69+
const tagCache = withFilter({
70+
originalTagCache: mockedTagCache,
71+
filterFn,
72+
});
73+
74+
const tags = ["valid_tag", "invalid_tag"];
75+
76+
await tagCache.getPathsByTags?.(tags);
77+
expect(mockedTagCache.getPathsByTags).toHaveBeenCalledWith(["valid_tag"]);
78+
}
79+
)
80+
81+
it('should not call getPathsByTags if no tags are valid', async () => {
82+
const tagCache = withFilter({
83+
originalTagCache: mockedTagCache,
84+
filterFn,
85+
});
86+
const tags = ["invalid_tag"];
87+
await tagCache.getPathsByTags?.(tags);
88+
expect(mockedTagCache.getPathsByTags).not.toHaveBeenCalled();
89+
})
90+
91+
it('should return the correct name', () => {
92+
const tagCache = withFilter({
93+
originalTagCache: mockedTagCache,
94+
filterFn,
95+
});
96+
97+
expect(tagCache.name).toBe('filtered-mocked');
98+
}
99+
)
100+
101+
it("should not create a function if getPathsByTags is not defined", async () => {
102+
const tagCache = withFilter({
103+
originalTagCache: {
104+
...mockedTagCache,
105+
getPathsByTags: undefined,
106+
},
107+
filterFn,
108+
});
109+
110+
expect(tagCache.getPathsByTags).toBeUndefined();
111+
}
112+
)
113+
114+
it("should properly filter soft tags", () => {
115+
const tagCache = withFilter({
116+
originalTagCache: mockedTagCache,
117+
filterFn: softTagFilter,
118+
});
119+
120+
tagCache.writeTags(["valid_tag", "_N_T_/", "_N_T_/test", "_N_T_/layout"]);
121+
expect(mockedTagCache.writeTags).toHaveBeenCalledWith(["valid_tag"]);
122+
})
123+
});
Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
import { NextModeTagCache } from "@opennextjs/aws/types/overrides";
2+
3+
interface WithFilterOptions {
4+
/**
5+
* The original tag cache.
6+
* Call to this will receive only the filtered tags.
7+
*/
8+
originalTagCache: NextModeTagCache;
9+
/**
10+
* The function to filter tags.
11+
* @param tag The tag to filter.
12+
* @returns true if the tag should be forwarde, false otherwise.
13+
*/
14+
filterFn: (tag: string) => boolean;
15+
}
16+
17+
/**
18+
* Creates a new tag cache that filters tags based on the provided filter function.
19+
* This is usefult to remove tags that are not used by the app, this could reduce the number of request to the underlying tag cache.
20+
*/
21+
export function withFilter({
22+
originalTagCache,
23+
filterFn,
24+
}: WithFilterOptions): NextModeTagCache {
25+
return {
26+
name: `filtered-${originalTagCache.name}`,
27+
mode: "nextMode",
28+
getPathsByTags: originalTagCache.getPathsByTags ? async (tags) => {
29+
const filteredTags = tags.filter(filterFn);
30+
if (filteredTags.length === 0) {
31+
return [];
32+
}
33+
return originalTagCache.getPathsByTags!(filteredTags)
34+
} : undefined,
35+
hasBeenRevalidated: async (tags, lastModified) => {
36+
const filteredTags = tags.filter(filterFn);
37+
if (filteredTags.length === 0) {
38+
return false;
39+
}
40+
return originalTagCache.hasBeenRevalidated(filteredTags, lastModified);
41+
},
42+
writeTags: async (tags) => {
43+
const filteredTags = tags.filter(filterFn);
44+
if (filteredTags.length === 0) {
45+
return;
46+
}
47+
return originalTagCache.writeTags(filteredTags);
48+
},
49+
};
50+
}
51+
52+
/**
53+
* Filter function to exclude tags that start with "_N_T_".
54+
* This is used to filter out internal soft tags.
55+
* Can be used if `revalidatePath` is not used.
56+
*/
57+
export function softTagFilter(tag: string): boolean {
58+
return !tag.startsWith("_N_T_");
59+
}

0 commit comments

Comments
 (0)