Fixed resetting page cache when some special collections are changed

This commit is contained in:
DrMint 2024-07-28 08:39:30 +02:00
parent 90266abc91
commit 6b797a42f8
1 changed files with 107 additions and 72 deletions

179
src/cache/pageCache.ts vendored
View File

@ -26,6 +26,25 @@ export class PageCache {
private readonly contextCache: ContextCache private readonly contextCache: ContextCache
) {} ) {}
get(url: string): Response | undefined {
if (import.meta.env.PAGE_CACHING !== "true") return;
const cachedPage = this.responseCache.get(url);
if (cachedPage) {
this.logger.log("Retrieved cached page for", url);
return cachedPage?.clone();
}
return;
}
set(url: string, response: Response) {
if (import.meta.env.PAGE_CACHING !== "true") return;
this.responseCache.set(url, response.clone());
this.logger.log("Cached response for", url);
if (this.initialized) {
this.scheduleSave();
}
}
async init() { async init() {
if (this.initialized) return; if (this.initialized) return;
@ -39,10 +58,7 @@ export class PageCache {
private async precache() { private async precache() {
if (import.meta.env.DATA_CACHING !== "true") return; if (import.meta.env.DATA_CACHING !== "true") return;
// Get all pages urls from CMS const allPageUrls = await this.getAllUrls();
const allDocs = (await this.uncachedPayload.getAll()).data;
const allPageUrls = allDocs.flatMap((doc) => this.getUrlFromEndpointChange(doc));
// TODO: Add static pages likes "/" and "/settings"
// Load cache from disk if available // Load cache from disk if available
if (existsSync(ON_DISK_RESPONSE_CACHE_FILE)) { if (existsSync(ON_DISK_RESPONSE_CACHE_FILE)) {
@ -56,7 +72,7 @@ export class PageCache {
]); ]);
// Do not include cache where the key is no longer in the CMS // Do not include cache where the key is no longer in the CMS
deserializedData = deserializedData.filter(([key]) => allPageUrls.includes(key)); deserializedData = deserializedData.filter(([key]) => allPageUrls.has(key));
this.responseCache = new Map(deserializedData); this.responseCache = new Map(deserializedData);
} }
@ -80,25 +96,98 @@ export class PageCache {
this.logger.log("Precaching completed!", this.responseCache.size, "responses cached"); this.logger.log("Precaching completed!", this.responseCache.size, "responses cached");
} }
get(url: string): Response | undefined { async invalidate(changes: EndpointChange[]) {
if (import.meta.env.PAGE_CACHING !== "true") return; if (import.meta.env.PAGE_CACHING !== "true") return;
const cachedPage = this.responseCache.get(url);
if (cachedPage) {
this.logger.log("Retrieved cached page for", url);
return cachedPage?.clone();
}
return;
}
set(url: string, response: Response) { if (
if (import.meta.env.PAGE_CACHING !== "true") return; changes.some(({ type }) =>
this.responseCache.set(url, response.clone()); [
this.logger.log("Cached response for", url); SDKEndpointNames.getWebsiteConfig,
SDKEndpointNames.getLanguages,
SDKEndpointNames.getCurrencies,
SDKEndpointNames.getWordings,
].includes(type)
)
) {
await this.resetAllUrls();
return;
}
const pagesToInvalidate = new Set<string>(
changes.flatMap((change) => this.getUrlFromEndpointChange(change))
);
for (const url of pagesToInvalidate) {
this.responseCache.delete(url);
this.logger.log("Invalidated cache for", url);
try {
await fetch(`http://${import.meta.env.ASTRO_HOST}:${import.meta.env.ASTRO_PORT}${url}`);
} catch (e) {
this.logger.log("Revalidation fails for", url);
}
}
this.logger.log("There are currently", this.responseCache.size, "responses in cache.");
if (this.initialized) { if (this.initialized) {
this.scheduleSave(); this.scheduleSave();
} }
} }
private async resetAllUrls() {
const allUrls = await this.getAllUrls();
this.responseCache.clear();
for (const url of allUrls) {
try {
await fetch(`http://${import.meta.env.ASTRO_HOST}:${import.meta.env.ASTRO_PORT}${url}`);
} catch (e) {
this.logger.warn("Reset failed for page", url);
}
}
if (this.initialized) {
this.scheduleSave();
}
}
private scheduleSave() {
if (this.scheduleSaveTimeout) {
clearTimeout(this.scheduleSaveTimeout);
}
this.scheduleSaveTimeout = setTimeout(() => {
this.save();
}, 10_000);
}
private async save() {
if (!existsSync(ON_DISK_ROOT)) {
await mkdir(ON_DISK_ROOT, { recursive: true });
}
const serializedResponses = await Promise.all(
[...this.responseCache].map(async ([key, value]) => [key, await serializeResponse(value)])
);
const serializedResponseCache = JSON.stringify(serializedResponses);
await writeFile(ON_DISK_RESPONSE_CACHE_FILE, serializedResponseCache, {
encoding: "utf-8",
});
this.logger.log("Saved", ON_DISK_RESPONSE_CACHE_FILE);
}
/* -------------------------------------------------------------------------------------------- */
private getLocalizedUrlsFromUrl = (urls: string[]) => {
return urls.flatMap((url) => this.contextCache.locales.map((id) => `/${id}${url}`));
};
private async getAllUrls() {
const allChanges = (await this.uncachedPayload.getAll()).data;
return new Set<string>([
...this.getLocalizedUrlsFromUrl(["", "/settings", "/search"]),
...allChanges.flatMap((change) => this.getUrlFromEndpointChange(change)),
]);
}
private getUrlFromEndpointChange(change: EndpointChange): string[] { private getUrlFromEndpointChange(change: EndpointChange): string[] {
const getUnlocalizedUrl = (): string[] => { const getUnlocalizedUrl = (): string[] => {
switch (change.type) { switch (change.type) {
@ -142,65 +231,11 @@ export class PageCache {
case SDKEndpointNames.getChronologyEventByID: case SDKEndpointNames.getChronologyEventByID:
return [`/timeline`]; return [`/timeline`];
case SDKEndpointNames.getWebsiteConfig:
case SDKEndpointNames.getLanguages:
case SDKEndpointNames.getCurrencies:
case SDKEndpointNames.getWordings:
return [...this.responseCache.keys()];
default: default:
return []; return [];
} }
}; };
return getUnlocalizedUrl().flatMap((url) => return this.getLocalizedUrlsFromUrl(getUnlocalizedUrl());
this.contextCache.locales.map((id) => `/${id}${url}`)
);
}
async invalidate(changes: EndpointChange[]) {
if (import.meta.env.PAGE_CACHING !== "true") return;
const pagesToInvalidate = new Set<string>(
changes.flatMap((change) => this.getUrlFromEndpointChange(change))
);
for (const url of pagesToInvalidate) {
this.responseCache.delete(url);
this.logger.log("Invalidated cache for", url);
try {
await fetch(`http://${import.meta.env.ASTRO_HOST}:${import.meta.env.ASTRO_PORT}${url}`);
} catch (e) {
this.logger.log("Revalidation fails for", url);
}
}
this.logger.log("There are currently", this.responseCache.size, "responses in cache.");
if (this.initialized) {
this.scheduleSave();
}
}
private scheduleSave() {
if (this.scheduleSaveTimeout) {
clearTimeout(this.scheduleSaveTimeout);
}
this.scheduleSaveTimeout = setTimeout(() => {
this.save();
}, 10_000);
}
private async save() {
if (!existsSync(ON_DISK_ROOT)) {
await mkdir(ON_DISK_ROOT, { recursive: true });
}
const serializedResponses = await Promise.all(
[...this.responseCache].map(async ([key, value]) => [key, await serializeResponse(value)])
);
const serializedResponseCache = JSON.stringify(serializedResponses);
await writeFile(ON_DISK_RESPONSE_CACHE_FILE, serializedResponseCache, {
encoding: "utf-8",
});
this.logger.log("Saved", ON_DISK_RESPONSE_CACHE_FILE);
} }
} }