From 2cacccae86f649028c4f1435848c7659bbce56e5 Mon Sep 17 00:00:00 2001 From: DrMint <29893320+DrMint@users.noreply.github.com> Date: Thu, 27 Jun 2024 22:04:08 +0200 Subject: [PATCH] Organized caching + groundwork for page caching --- TODO.md | 1 - src/cache/contextCache.ts | 58 ++++ src/cache/dataCache.ts | 84 ++++++ .../AppLayout/components/Html.astro | 14 +- .../Topbar/components/CurrencySelector.astro | 7 +- .../Topbar/components/LanguageSelector.astro | 7 +- .../Topbar/components/ThemeSelector.astro | 24 +- src/env.d.ts | 1 - src/i18n/i18n.ts | 4 +- src/middleware.ts | 47 ++- .../[locale]/_components/LibraryGrid.astro | 4 +- .../api/hooks/collection-operation.ts | 19 +- src/pages/[locale]/api/on-startup.ts | 4 +- .../_components/ColorShowcase.astro | 2 +- src/pages/[locale]/index.astro | 8 +- src/pages/[locale]/settings/index.astro | 36 +-- .../timeline/_components/TimelineYear.astro | 4 +- src/pages/[locale]/timeline/index.astro | 6 +- src/shared/analytics/analytics.ts | 5 +- src/shared/openExchange/rates.json | 282 +++++++++--------- src/utils/format.ts | 4 +- src/utils/logger.ts | 6 + src/utils/payload.ts | 116 +------ 23 files changed, 392 insertions(+), 351 deletions(-) create mode 100644 src/cache/contextCache.ts create mode 100644 src/cache/dataCache.ts create mode 100644 src/utils/logger.ts diff --git a/TODO.md b/TODO.md index 90e0ef1..4563b82 100644 --- a/TODO.md +++ b/TODO.md @@ -10,7 +10,6 @@ ## Short term - [Bugs] On android Chrome, the setting button in the header flashes for a few ms when the page is loading -- [Feat] [caching] Use getURLs for precaching + precache everything - [Bugs] Make sure uploads name are slug-like and with an extension. - [Bugs] Nyupun can't upload subtitles files - [Bugs] https://v3.accords-library.com/en/collectibles/dod-original-soundtrack/scans obi is way too big diff --git a/src/cache/contextCache.ts b/src/cache/contextCache.ts new file mode 100644 index 0000000..25ce04d --- /dev/null +++ b/src/cache/contextCache.ts @@ -0,0 +1,58 @@ +import type { + EndpointWebsiteConfig, + EndpointWording, + Language, +} from "src/shared/payload/payload-sdk"; +import { getLogger } from "src/utils/logger"; +import { payload } from "src/utils/payload"; + +class ContextCache { + private initialized = false; + private logger = getLogger("[ContextCache]"); + + locales: Language[] = []; + currencies: string[] = []; + wordings: EndpointWording[] = []; + config: EndpointWebsiteConfig = { + home: { folders: [] }, + timeline: { breaks: [], eras: [], eventCount: 0 }, + }; + + async init() { + if (this.initialized) return; + await this.refreshAll(); + this.initialized = true; + this.logger.log("Init complete"); + } + + private async refreshAll() { + await this.refreshCurrencies(); + await this.refreshLocales(); + await this.refreshWebsiteConfig(); + await this.refreshWordings(); + } + + async refreshWordings() { + this.wordings = await payload.getWordings(); + this.logger.log("Wordings refreshed"); + } + + async refreshCurrencies() { + contextCache.currencies = (await payload.getCurrencies()).map(({ id }) => id); + this.logger.log("Currencies refreshed"); + } + + async refreshLocales() { + contextCache.locales = await payload.getLanguages(); + this.logger.log("Locales refreshed"); + } + + async refreshWebsiteConfig() { + contextCache.config = await payload.getConfig(); + this.logger.log("WebsiteConfig refreshed"); + } +} + +const contextCache = new ContextCache(); +await contextCache.init(); +export { contextCache }; diff --git a/src/cache/dataCache.ts b/src/cache/dataCache.ts new file mode 100644 index 0000000..6e2052d --- /dev/null +++ b/src/cache/dataCache.ts @@ -0,0 +1,84 @@ +import { getLogger } from "src/utils/logger"; +import { payload } from "src/utils/payload"; + +class DataCache { + private readonly logger = getLogger("[DataCache]"); + private initialized = false; + + private readonly responseCache = new Map(); + private readonly idsCacheMap = new Map>(); + + async init() { + if (this.initialized) return; + + if (import.meta.env.ENABLE_PRECACHING === "true") { + await this.precache(); + } + + this.initialized = true; + } + + private async precache() { + const { urls } = await payload.getAllSdkUrls(); + for (const url of urls) { + try { + await payload.request(url); + } catch { + this.logger.warn("Precaching failed for url", url); + } + } + this.logger.log("Precaching completed!", this.responseCache.size, "responses cached"); + } + + get(url: string) { + const cachedResponse = this.responseCache.get(url); + if (cachedResponse) { + this.logger.log("Retrieved cached response for", url); + return structuredClone(cachedResponse); + } + } + + set(url: string, response: any) { + const stringData = JSON.stringify(response); + const regex = /[a-f0-9]{24}/g; + const ids = [...stringData.matchAll(regex)].map((match) => match[0]); + const uniqueIds = [...new Set(ids)]; + + uniqueIds.forEach((id) => { + const current = this.idsCacheMap.get(id); + if (current) { + current.add(url); + } else { + this.idsCacheMap.set(id, new Set([url])); + } + }); + + this.responseCache.set(url, response); + this.logger.log("Cached response for", url); + } + + async invalidate(ids: string[], urls: string[]) { + const urlsToInvalidate = new Set(urls); + + ids.forEach((id) => { + const urlsForThisId = this.idsCacheMap.get(id); + if (!urlsForThisId) return; + this.idsCacheMap.delete(id); + [...urlsForThisId].forEach((url) => urlsToInvalidate.add(url)); + }); + + for (const url of urlsToInvalidate) { + this.responseCache.delete(url); + this.logger.log("Invalidated cache for", url); + try { + await payload.request(url); + } catch (e) { + this.logger.log("Revalidation fails for", url); + } + } + + this.logger.log("There are currently", this.responseCache.size, "responses in cache."); + } +} + +export const dataCache = new DataCache(); diff --git a/src/components/AppLayout/components/Html.astro b/src/components/AppLayout/components/Html.astro index 7fd6732..594b4eb 100644 --- a/src/components/AppLayout/components/Html.astro +++ b/src/components/AppLayout/components/Html.astro @@ -7,7 +7,7 @@ import type { EndpointPayloadImage, EndpointVideo, } from "src/shared/payload/payload-sdk"; -import { cache } from "src/utils/payload"; +import { contextCache } from "src/cache/contextCache"; interface Props { openGraph?: @@ -28,7 +28,9 @@ const { openGraph = {} } = Astro.props; const { description = t("global.meta.description"), audio, video } = openGraph; const thumbnail = - openGraph.thumbnail?.openGraph ?? openGraph.thumbnail ?? cache.config.defaultOpenGraphImage; + openGraph.thumbnail?.openGraph ?? + openGraph.thumbnail ?? + contextCache.config.defaultOpenGraphImage; const title = openGraph.title ? `${openGraph.title} – ${t("global.siteName")}` @@ -38,8 +40,6 @@ const userAgent = Astro.request.headers.get("user-agent") ?? ""; const parser = new UAParser(userAgent); const isIOS = parser.getOS().name === "iOS"; -const { currentTheme } = Astro.locals; - /* Keep that separator here or else it breaks the HTML ----------------------------------------------- HTML -------------------------------------------- */ --- @@ -47,9 +47,7 @@ const { currentTheme } = Astro.locals;
{ - cache.currencies.map((id) => ( + contextCache.currencies.map((id) => ( + href={`?action-currency=${id}`}> {`${id} (${formatCurrency(id)})`} )) diff --git a/src/components/AppLayout/components/Topbar/components/LanguageSelector.astro b/src/components/AppLayout/components/Topbar/components/LanguageSelector.astro index f3008b5..7df9894 100644 --- a/src/components/AppLayout/components/Topbar/components/LanguageSelector.astro +++ b/src/components/AppLayout/components/Topbar/components/LanguageSelector.astro @@ -2,7 +2,7 @@ import Button from "components/Button.astro"; import Tooltip from "components/Tooltip.astro"; import { getI18n } from "src/i18n/i18n"; -import { cache } from "src/utils/payload"; +import { contextCache } from "src/cache/contextCache"; import { formatLocale } from "src/utils/format"; interface Props { @@ -21,11 +21,10 @@ const { t } = await getI18n(currentLocale);
{ - cache.locales.map(({ id }) => ( + contextCache.locales.map(({ id }) => ( + href={`?action-lang=${id}`}> {formatLocale(id)} )) diff --git a/src/components/AppLayout/components/Topbar/components/ThemeSelector.astro b/src/components/AppLayout/components/Topbar/components/ThemeSelector.astro index 5ff11b9..c9c3b9f 100644 --- a/src/components/AppLayout/components/Topbar/components/ThemeSelector.astro +++ b/src/components/AppLayout/components/Topbar/components/ThemeSelector.astro @@ -3,7 +3,7 @@ import Button from "components/Button.astro"; import Tooltip from "components/Tooltip.astro"; import { getI18n } from "src/i18n/i18n"; -const { currentLocale, currentTheme } = Astro.locals; +const { currentLocale } = Astro.locals; const { t } = await getI18n(currentLocale); --- @@ -11,19 +11,13 @@ const { t } = await getI18n(currentLocale); @@ -45,10 +39,12 @@ const { t } = await getI18n(currentLocale); #content { display: grid; gap: 0.5em; + } - & > .current { - color: var(--color-base-750); - text-decoration: underline 0.08em var(--color-base-650); - } + :global(html.light-theme) a.underline-when-light, + :global(html.dark-theme) a.underline-when-dark, + :global(html:not(.light-theme, .dark-theme)) a.underline-when-auto { + color: var(--color-base-750); + text-decoration: underline 0.08em var(--color-base-650); } diff --git a/src/env.d.ts b/src/env.d.ts index e4c4c87..59b8d4d 100644 --- a/src/env.d.ts +++ b/src/env.d.ts @@ -4,7 +4,6 @@ declare namespace App { interface Locals { currentLocale: string; - currentTheme: "dark" | "auto" | "light"; currentCurrency: string; notFound: boolean; } diff --git a/src/i18n/i18n.ts b/src/i18n/i18n.ts index 6c16183..1c3c95d 100644 --- a/src/i18n/i18n.ts +++ b/src/i18n/i18n.ts @@ -1,6 +1,6 @@ import type { WordingKey } from "src/i18n/wordings-keys"; import type { ChronologyEvent, EndpointSource } from "src/shared/payload/payload-sdk"; -import { cache } from "src/utils/payload"; +import { contextCache } from "src/cache/contextCache"; import { capitalize, formatInlineTitle } from "src/utils/format"; export const defaultLocale = "en"; @@ -113,7 +113,7 @@ export const getI18n = async (locale: string) => { options[0]!; // We will consider that there will always be at least one option. const t = (key: WordingKey, values: Record = {}): string => { - const wording = cache.wordings.find(({ name }) => name === key); + const wording = contextCache.wordings.find(({ name }) => name === key); const fallbackString = `«${key}»`; if (!wording) { diff --git a/src/middleware.ts b/src/middleware.ts index 2ba454f..3caa591 100644 --- a/src/middleware.ts +++ b/src/middleware.ts @@ -1,10 +1,10 @@ import { defineMiddleware, sequence } from "astro:middleware"; -import { cache } from "src/utils/payload"; import acceptLanguage from "accept-language"; import type { AstroCookies } from "astro"; import { z } from "astro:content"; import { trackRequest, trackEvent } from "src/shared/analytics/analytics"; import { defaultLocale } from "src/i18n/i18n"; +import { contextCache } from "src/cache/contextCache"; const ninetyDaysInSeconds = 60 * 60 * 24 * 90; @@ -141,20 +141,22 @@ const refreshCookiesMaxAge = defineMiddleware(async ({ cookies }, next) => { return response; }); -const addContentLanguageResponseHeader = defineMiddleware(async ({ url }, next) => { +const addCommonHeaders = defineMiddleware(async ({ url }, next) => { const currentLocale = getCurrentLocale(url.pathname); const response = await next(); - if (response.status === 200 && currentLocale) { + if (response.ok && currentLocale) { response.headers.set("Content-Language", currentLocale); } + + response.headers.set("Vary", "Cookie"); + return response; }); const provideLocalsToRequest = defineMiddleware(async ({ url, locals, cookies }, next) => { locals.currentLocale = getCurrentLocale(url.pathname) ?? "en"; locals.currentCurrency = getCookieCurrency(cookies) ?? "USD"; - locals.currentTheme = getCookieTheme(cookies) ?? "auto"; return next(); }); @@ -165,19 +167,40 @@ const analytics = defineMiddleware(async (context, next) => { return response; }); +const postProcess = defineMiddleware(async ({ cookies }, next) => { + const response = await next(); + if (!response.ok) { + return response; + } + + let html = await response.text(); + + const currentTheme = getCookieTheme(cookies) ?? "auto"; + html = html.replace( + "POST_PROCESS_HTML_CLASS", + currentTheme === "dark" ? "dark-theme" : currentTheme === "light" ? "light-theme" : "" + ); + return new Response(html, response); +}); + export const onRequest = sequence( - addContentLanguageResponseHeader, + // Possible redirect handleActionsSearchParams, - refreshCookiesMaxAge, localeNegotiator, + + addCommonHeaders, + + // Get a response + analytics, + refreshCookiesMaxAge, provideLocalsToRequest, - analytics + postProcess ); /* LOCALE */ const getCurrentLocale = (pathname: string): string | undefined => { - for (const locale of cache.locales) { + for (const locale of contextCache.locales) { if (pathname.split("/")[1] === locale.id) { return locale.id; } @@ -189,7 +212,7 @@ const getBestAcceptedLanguage = (request: Request): string | undefined => { const header = request.headers.get("Accept-Language"); if (!header) return; - acceptLanguage.languages(cache.locales.map(({ id }) => id)); + acceptLanguage.languages(contextCache.locales.map(({ id }) => id)); return acceptLanguage.get(request.headers.get("Accept-Language")) ?? undefined; }; @@ -220,10 +243,12 @@ export const getCookieTheme = (cookies: AstroCookies): z.infer - currency !== null && currency != undefined && cache.currencies.includes(currency); + currency !== null && currency != undefined && contextCache.currencies.includes(currency); export const isValidLocale = (locale: string | null | undefined): locale is string => - locale !== null && locale != undefined && cache.locales.map(({ id }) => id).includes(locale); + locale !== null && + locale != undefined && + contextCache.locales.map(({ id }) => id).includes(locale); export const isValidTheme = ( theme: string | null | undefined diff --git a/src/pages/[locale]/_components/LibraryGrid.astro b/src/pages/[locale]/_components/LibraryGrid.astro index d100e8b..a7a6d3c 100644 --- a/src/pages/[locale]/_components/LibraryGrid.astro +++ b/src/pages/[locale]/_components/LibraryGrid.astro @@ -1,7 +1,7 @@ --- import LibraryCard from "./LibraryCard.astro"; import { getI18n } from "src/i18n/i18n"; -import { cache } from "src/utils/payload"; +import { contextCache } from "src/cache/contextCache"; const { getLocalizedUrl, getLocalizedMatch } = await getI18n(Astro.locals.currentLocale); --- @@ -9,7 +9,7 @@ const { getLocalizedUrl, getLocalizedMatch } = await getI18n(Astro.locals.curren {/* ------------------------------------------- HTML ------------------------------------------- */} { - cache.config.home.folders.map(({ slug, translations, darkThumbnail, lightThumbnail }) => ( + contextCache.config.home.folders.map(({ slug, translations, darkThumbnail, lightThumbnail }) => ( { const auth = request.headers.get("Authorization"); @@ -30,23 +25,23 @@ const handleWebHookMessage = async ({ urls, id, }: AfterOperationWebHookMessage) => { - await invalidateDataCache([...(id ? [id] : []), ...addedDependantIds], urls); + await dataCache.invalidate([...(id ? [id] : []), ...addedDependantIds], urls); switch (collection) { case Collections.Wordings: - await refreshWordings(); + await contextCache.refreshWordings(); break; case Collections.Currencies: - await refreshCurrencies(); + await contextCache.refreshCurrencies(); break; case Collections.Languages: - await refreshLocales(); + await contextCache.refreshLocales(); break; case Collections.WebsiteConfig: - await refreshWebsiteConfig(); + await contextCache.refreshWebsiteConfig(); break; } }; diff --git a/src/pages/[locale]/api/on-startup.ts b/src/pages/[locale]/api/on-startup.ts index a120fc5..7cce4d3 100644 --- a/src/pages/[locale]/api/on-startup.ts +++ b/src/pages/[locale]/api/on-startup.ts @@ -1,7 +1,7 @@ import type { APIRoute } from "astro"; -import { initPayload } from "src/utils/payload"; +import { dataCache } from "src/cache/dataCache"; export const GET: APIRoute = async () => { - await initPayload(); + await dataCache.init(); return new Response(null, { status: 200, statusText: "Ok" }); }; diff --git a/src/pages/[locale]/dev/design-system/_components/ColorShowcase.astro b/src/pages/[locale]/dev/design-system/_components/ColorShowcase.astro index 9695371..3795015 100644 --- a/src/pages/[locale]/dev/design-system/_components/ColorShowcase.astro +++ b/src/pages/[locale]/dev/design-system/_components/ColorShowcase.astro @@ -9,7 +9,7 @@ interface Props { const { baseColors, theme } = Astro.props; --- -
+

Base colors

{ diff --git a/src/pages/[locale]/index.astro b/src/pages/[locale]/index.astro index fcaca43..f9886de 100644 --- a/src/pages/[locale]/index.astro +++ b/src/pages/[locale]/index.astro @@ -3,7 +3,7 @@ import Button from "components/Button.astro"; import LibraryGrid from "./_components/LibraryGrid.astro"; import LinkCard from "./_components/LinkCard.astro"; import { getI18n } from "src/i18n/i18n"; -import { cache } from "src/utils/payload"; +import { contextCache } from "src/cache/contextCache"; import AppLayout from "components/AppLayout/AppLayout.astro"; import HomeTitle from "./_components/HomeTitle.astro"; @@ -14,7 +14,7 @@ const { t, getLocalizedUrl } = await getI18n(Astro.locals.currentLocale); @@ -77,8 +77,8 @@ const { t, getLocalizedUrl } = await getI18n(Astro.locals.currentLocale); icon="material-symbols:calendar-month" title={t("footer.links.timeline.title")} subtitle={t("footer.links.timeline.subtitle", { - eraCount: cache.config.timeline.eras.length, - eventCount: cache.config.timeline.eventCount, + eraCount: contextCache.config.timeline.eras.length, + eventCount: contextCache.config.timeline.eventCount, })} href={getLocalizedUrl("/timeline")} /> diff --git a/src/pages/[locale]/settings/index.astro b/src/pages/[locale]/settings/index.astro index 198abc4..1f05e4f 100644 --- a/src/pages/[locale]/settings/index.astro +++ b/src/pages/[locale]/settings/index.astro @@ -2,11 +2,11 @@ import AppLayout from "components/AppLayout/AppLayout.astro"; import AppLayoutTitle from "components/AppLayout/components/AppLayoutTitle.astro"; import { getI18n } from "src/i18n/i18n"; -import { cache } from "src/utils/payload"; +import { contextCache } from "src/cache/contextCache"; import { formatCurrency } from "src/utils/currencies"; import { formatLocale } from "src/utils/format"; -const { currentLocale, currentTheme, currentCurrency } = Astro.locals; +const { currentLocale, currentCurrency } = Astro.locals; const { t } = await getI18n(currentLocale); --- @@ -20,11 +20,10 @@ const { t } = await getI18n(currentLocale);

{t("settings.language.title")}

{t("settings.language.description")}


{ - cache.locales.map(({ id }) => ( + contextCache.locales.map(({ id }) => ( + href={`?action-lang=${id}`}> {formatLocale(id)} )) @@ -34,22 +33,13 @@ const { t } = await getI18n(currentLocale);

{t("settings.theme.title")}

{t("settings.theme.description")}


- + {t("global.theme.dark")} - + {t("global.theme.auto")} - + {t("global.theme.light")}
@@ -58,11 +48,10 @@ const { t } = await getI18n(currentLocale);

{t("settings.currency.title")}

{t("settings.currency.description")}


{ - cache.currencies.map((id) => ( + contextCache.currencies.map((id) => ( + href={`?action-currency=${id}`}> {`${id} (${formatCurrency(id)})`} )) @@ -86,6 +75,13 @@ const { t } = await getI18n(currentLocale); } } + :global(html.light-theme) a.underline-when-light, + :global(html.dark-theme) a.underline-when-dark, + :global(html:not(.light-theme, .dark-theme)) a.underline-when-auto { + color: var(--color-base-750); + text-decoration: underline 0.08em var(--color-base-650); + } + #main { display: grid; grid-template-columns: repeat(auto-fit, minmax(250px, 1fr)); diff --git a/src/pages/[locale]/timeline/_components/TimelineYear.astro b/src/pages/[locale]/timeline/_components/TimelineYear.astro index 01f0527..968adc9 100644 --- a/src/pages/[locale]/timeline/_components/TimelineYear.astro +++ b/src/pages/[locale]/timeline/_components/TimelineYear.astro @@ -2,7 +2,7 @@ import type { EndpointChronologyEvent } from "src/shared/payload/payload-sdk"; import TimelineEvent from "./TimelineEvent.astro"; import { getI18n } from "src/i18n/i18n"; -import { cache } from "src/utils/payload"; +import { contextCache } from "src/cache/contextCache"; interface Props { year: number; @@ -25,7 +25,7 @@ if (year === 856) { {/* ------------------------------------------- HTML ------------------------------------------- */} -{cache.config.timeline.breaks.includes(year) &&
} +{contextCache.config.timeline.breaks.includes(year) &&
}

diff --git a/src/pages/[locale]/timeline/index.astro b/src/pages/[locale]/timeline/index.astro index 83c2b74..1d1a163 100644 --- a/src/pages/[locale]/timeline/index.astro +++ b/src/pages/[locale]/timeline/index.astro @@ -6,7 +6,7 @@ import AppLayout from "components/AppLayout/AppLayout.astro"; import AppLayoutTitle from "components/AppLayout/components/AppLayoutTitle.astro"; import Card from "components/Card.astro"; import { getI18n } from "src/i18n/i18n"; -import { cache } from "src/utils/payload"; +import { contextCache } from "src/cache/contextCache"; import type { WordingKey } from "src/i18n/wordings-keys"; const events = await payload.getChronologyEvents(); @@ -16,7 +16,7 @@ const { getLocalizedUrl, t, formatTimelineDate } = await getI18n(Astro.locals.cu {/* ------------------------------------------- HTML ------------------------------------------- */} - +

@@ -54,7 +54,7 @@ const { getLocalizedUrl, t, formatTimelineDate } = await getI18n(Astro.locals.cu

{t("timeline.jumpTo")}

{ - cache.config.timeline.eras.map(({ name, startingYear, endingYear }) => ( + contextCache.config.timeline.eras.map(({ name, startingYear, endingYear }) => (

${formatTimelineDate({ year: startingYear })}`, diff --git a/src/shared/analytics/analytics.ts b/src/shared/analytics/analytics.ts index bff9356..2d7e63c 100644 --- a/src/shared/analytics/analytics.ts +++ b/src/shared/analytics/analytics.ts @@ -1,7 +1,7 @@ -import { cache } from "src/utils/payload"; +import { contextCache } from "src/cache/contextCache"; const getUnlocalizedPathname = (pathname: string): string => { - for (const locale of cache.locales) { + for (const locale of contextCache.locales) { if (pathname.startsWith(`/${locale.id}`)) { return pathname.substring(`/${locale.id}`.length) || "/"; } @@ -30,7 +30,6 @@ export const trackRequest = (request: Request, { clientAddress, locals }: TrackR attributes: { locale: locals.currentLocale, currency: locals.currentCurrency, - theme: locals.currentTheme, }, }, request: { diff --git a/src/shared/openExchange/rates.json b/src/shared/openExchange/rates.json index 8ee72f9..7379078 100644 --- a/src/shared/openExchange/rates.json +++ b/src/shared/openExchange/rates.json @@ -1,177 +1,177 @@ { "disclaimer": "Usage subject to terms: https://openexchangerates.org/terms", "license": "https://openexchangerates.org/license", - "timestamp": 1719374401, + "timestamp": 1719518400, "base": "USD", "rates": { "AED": 3.673, - "AFN": 70.737389, - "ALL": 93.591883, - "AMD": 388.464511, - "ANG": 1.804398, - "AOA": 855.209667, - "ARS": 909.2076, - "AUD": 1.498775, + "AFN": 70, + "ALL": 93.656243, + "AMD": 388.12, + "ANG": 1.803057, + "AOA": 853.629, + "ARS": 911.0001, + "AUD": 1.504506, "AWG": 1.8025, "AZN": 1.7, - "BAM": 1.825905, + "BAM": 1.827765, "BBD": 2, - "BDT": 117.631657, - "BGN": 1.8261, - "BHD": 0.376925, - "BIF": 2879.142593, + "BDT": 117.53659, + "BGN": 1.826865, + "BHD": 0.376928, + "BIF": 2882.5, "BMD": 1, - "BND": 1.354432, - "BOB": 6.917863, - "BRL": 5.452199, + "BND": 1.357361, + "BOB": 6.912666, + "BRL": 5.5062, "BSD": 1, - "BTC": 0.000016147327, - "BTN": 83.525391, - "BWP": 13.566127, - "BYN": 3.276229, - "BZD": 2.017943, - "CAD": 1.365775, - "CDF": 2845.344278, - "CHF": 0.895078, - "CLF": 0.034108, - "CLP": 943.396226, - "CNH": 7.292088, - "CNY": 7.2661, - "COP": 4092.688822, - "CRC": 524.018922, + "BTC": 0.000016286712, + "BTN": 83.510338, + "BWP": 13.649322, + "BYN": 3.274029, + "BZD": 2.016506, + "CAD": 1.36937, + "CDF": 2860, + "CHF": 0.898769, + "CLF": 0.034588, + "CLP": 954.24, + "CNH": 7.303225, + "CNY": 7.2683, + "COP": 4147.064273, + "CRC": 523.036765, "CUC": 1, "CUP": 25.75, - "CVE": 102.941746, - "CZK": 23.187699, - "DJF": 178.249371, - "DKK": 6.9642, - "DOP": 59.114309, - "DZD": 134.5516, - "EGP": 48.377851, + "CVE": 103.063904, + "CZK": 23.432, + "DJF": 177.5, + "DKK": 6.969109, + "DOP": 59.2, + "DZD": 134.481574, + "EGP": 48.0279, "ERN": 15, - "ETB": 57.353303, - "EUR": 0.933624, - "FJD": 2.2362, - "FKP": 0.788219, - "GBP": 0.788219, - "GEL": 2.81, - "GGP": 0.788219, - "GHS": 15.216498, - "GIP": 0.788219, - "GMD": 67.75, - "GNF": 8617.038799, - "GTQ": 7.774894, - "GYD": 209.365764, - "HKD": 7.809496, - "HNL": 24.774423, - "HRK": 7.034221, - "HTG": 132.791853, - "HUF": 369.47, - "IDR": 16421.779805, - "ILS": 3.74785, - "IMP": 0.788219, - "INR": 83.447347, - "IQD": 1311.401658, - "IRR": 42087.5, - "ISK": 139.21, - "JEP": 0.788219, - "JMD": 156.444628, + "ETB": 57.750301, + "EUR": 0.934324, + "FJD": 2.24125, + "FKP": 0.791234, + "GBP": 0.791234, + "GEL": 2.8, + "GGP": 0.791234, + "GHS": 15.25, + "GIP": 0.791234, + "GMD": 67.775, + "GNF": 8595, + "GTQ": 7.773841, + "GYD": 209.310316, + "HKD": 7.808725, + "HNL": 24.762821, + "HRK": 7.039709, + "HTG": 132.614267, + "HUF": 370.35232, + "IDR": 16384.008966, + "ILS": 3.75783, + "IMP": 0.791234, + "INR": 83.466142, + "IQD": 1310.629281, + "IRR": 42100, + "ISK": 139.14, + "JEP": 0.791234, + "JMD": 156.065666, "JOD": 0.7087, - "JPY": 159.8375, - "KES": 129.646953, - "KGS": 86.5868, - "KHR": 4119.876268, - "KMF": 460.124977, + "JPY": 160.819, + "KES": 129, + "KGS": 86.45, + "KHR": 4116, + "KMF": 460.04988, "KPW": 900, - "KRW": 1390.097786, - "KWD": 0.306622, - "KYD": 0.834247, - "KZT": 467.84526, - "LAK": 22038.352227, - "LBP": 89647.700512, - "LKR": 305.644882, - "LRD": 194.349995, - "LSL": 18.152071, - "LYD": 4.855, - "MAD": 9.950426, - "MDL": 17.920244, - "MGA": 4473.169164, - "MKD": 57.439335, + "KRW": 1387.206613, + "KWD": 0.306775, + "KYD": 0.833746, + "KZT": 466.751615, + "LAK": 22077.44273, + "LBP": 89576.348385, + "LKR": 306.055904, + "LRD": 194.492735, + "LSL": 18.359053, + "LYD": 4.87349, + "MAD": 9.939151, + "MDL": 17.849263, + "MGA": 4476.966706, + "MKD": 57.476359, "MMK": 2481.91, "MNT": 3450, - "MOP": 8.051048, - "MRU": 39.352282, - "MUR": 46.929999, + "MOP": 8.047221, + "MRU": 39.452362, + "MUR": 46.899999, "MVR": 15.405, - "MWK": 1735.964257, - "MXN": 18.104717, - "MYR": 4.71, + "MWK": 1734.657401, + "MXN": 18.41087, + "MYR": 4.7195, "MZN": 63.850001, - "NAD": 18.152071, - "NGN": 1518.12, - "NIO": 36.848246, - "NOK": 10.607752, - "NPR": 133.640175, - "NZD": 1.635653, - "OMR": 0.384952, + "NAD": 18.359053, + "NGN": 1515.9, + "NIO": 36.825702, + "NOK": 10.666237, + "NPR": 133.611854, + "NZD": 1.643791, + "OMR": 0.384955, "PAB": 1, - "PEN": 3.815747, - "PGK": 3.906082, - "PHP": 58.837749, - "PKR": 278.808073, - "PLN": 4.011559, - "PYG": 7549.795422, - "QAR": 3.651064, - "RON": 4.644, - "RSD": 109.292759, - "RUB": 87.502462, - "RWF": 1326.548635, - "SAR": 3.751926, + "PEN": 3.823237, + "PGK": 3.904308, + "PHP": 58.639498, + "PKR": 278.503056, + "PLN": 4.029254, + "PYG": 7540.098869, + "QAR": 3.649042, + "RON": 4.6507, + "RSD": 109.379523, + "RUB": 84.998456, + "RWF": 1306.142519, + "SAR": 3.751634, "SBD": 8.43942, - "SCR": 13.861839, + "SCR": 13.796937, "SDG": 601, - "SEK": 10.509533, - "SGD": 1.354893, - "SHP": 0.788219, + "SEK": 10.62955, + "SGD": 1.358205, + "SHP": 0.791234, "SLL": 20969.5, - "SOS": 572.148966, - "SRD": 31.0905, + "SOS": 571.751991, + "SRD": 30.623, "SSP": 130.26, "STD": 22281.8, - "STN": 22.872934, - "SVC": 8.759785, + "STN": 22.899229, + "SVC": 8.754335, "SYP": 2512.53, - "SZL": 18.159157, - "THB": 36.7755, - "TJS": 10.696778, + "SZL": 18.178413, + "THB": 36.7685, + "TJS": 10.65474, "TMT": 3.51, - "TND": 3.133858, - "TOP": 2.359259, - "TRY": 33.0006, - "TTD": 6.801133, - "TWD": 32.533, - "TZS": 2638.159, - "UAH": 40.666635, - "UGX": 3709.12772, + "TND": 3.136769, + "TOP": 2.36092, + "TRY": 32.836478, + "TTD": 6.7978, + "TWD": 32.560001, + "TZS": 2626.295328, + "UAH": 40.51595, + "UGX": 3711.539326, "USD": 1, - "UYU": 39.322323, - "UZS": 12613.6, - "VES": 36.320372, - "VND": 25461.48991, + "UYU": 39.578963, + "UZS": 12585.732694, + "VES": 36.35908, + "VND": 25455.008685, "VUV": 118.722, "WST": 2.8, - "XAF": 612.41733, - "XAG": 0.03457994, - "XAU": 0.00043156, + "XAF": 612.876514, + "XAG": 0.0345453, + "XAU": 0.00042988, "XCD": 2.70255, - "XDR": 0.761402, - "XOF": 612.41733, - "XPD": 0.00107841, - "XPF": 111.411003, - "XPT": 0.00101239, + "XDR": 0.759718, + "XOF": 612.876514, + "XPD": 0.00108638, + "XPF": 111.494537, + "XPT": 0.00101314, "YER": 250.399984, - "ZAR": 18.227543, - "ZMW": 25.80335, + "ZAR": 18.4643, + "ZMW": 25.735569, "ZWL": 322 } } \ No newline at end of file diff --git a/src/utils/format.ts b/src/utils/format.ts index 5c8eaa8..3e77bcd 100644 --- a/src/utils/format.ts +++ b/src/utils/format.ts @@ -6,10 +6,10 @@ import { type RichTextContent, type RichTextNode, } from "src/shared/payload/payload-sdk"; -import { cache } from "src/utils/payload"; +import { contextCache } from "src/cache/contextCache"; export const formatLocale = (code: string): string => - cache.locales.find(({ id }) => id === code)?.name ?? code; + contextCache.locales.find(({ id }) => id === code)?.name ?? code; export const formatInlineTitle = ({ pretitle, diff --git a/src/utils/logger.ts b/src/utils/logger.ts new file mode 100644 index 0000000..509acf2 --- /dev/null +++ b/src/utils/logger.ts @@ -0,0 +1,6 @@ +export const getLogger = (prefix: string): Pick => ({ + debug: (...message) => console.debug(prefix, ...message), + log: (...message) => console.log(prefix, ...message), + warn: (...message) => console.warn(prefix, ...message), + error: (...message) => console.error(prefix, ...message), +}); diff --git a/src/utils/payload.ts b/src/utils/payload.ts index 7f075bd..acf7c07 100644 --- a/src/utils/payload.ts +++ b/src/utils/payload.ts @@ -1,18 +1,9 @@ -import { - type Language, - type EndpointWording, - type EndpointWebsiteConfig, -} from "src/shared/payload/payload-sdk"; +import { dataCache } from "src/cache/dataCache"; import { getPayloadSDK } from "src/shared/payload/payload-sdk"; let token: string | undefined = undefined; let expiration: number | undefined = undefined; -const responseCache = new Map(); -const idsCacheMap = new Map>(); - -const isPrecachingEnabled = import.meta.env.ENABLE_PRECACHING === "true"; - export const payload = getPayloadSDK({ apiURL: import.meta.env.PAYLOAD_API_URL, email: import.meta.env.PAYLOAD_USER, @@ -33,108 +24,5 @@ export const payload = getPayloadSDK({ console.log("[PayloadSDK] New token set. TTL is", diffInMinutes, "minutes."); }, }, - responseCache: { - get: (url) => { - const cachedResponse = responseCache.get(url); - if (cachedResponse) { - console.log("[ResponseCaching] Retrieved cache response for", url); - return structuredClone(cachedResponse); - } - }, - set: (url, response) => { - const stringData = JSON.stringify(response); - const regex = /[a-f0-9]{24}/g; - const ids = [...stringData.matchAll(regex)].map((match) => match[0]); - const uniqueIds = [...new Set(ids)]; - - uniqueIds.forEach((id) => { - const current = idsCacheMap.get(id); - if (current) { - current.add(url); - } else { - idsCacheMap.set(id, new Set([url])); - } - }); - - console.log("[ResponseCaching] Caching response for", url); - responseCache.set(url, response); - }, - }, + responseCache: dataCache, }); - -export const invalidateDataCache = async (ids: string[], urls: string[]) => { - const urlsToInvalidate = new Set(urls); - - ids.forEach((id) => { - const urlsForThisId = idsCacheMap.get(id); - if (!urlsForThisId) return; - idsCacheMap.delete(id); - [...urlsForThisId].forEach((url) => urlsToInvalidate.add(url)); - }); - - for (const url of urlsToInvalidate) { - responseCache.delete(url); - console.log("[ResponseCaching][Invalidation] Deleted cache for", url); - try { - await payload.request(url); - } catch (e) { - console.log("[ResponseCaching][Revalidation] Failure for", url); - } - } - - console.log("[ResponseCaching] There are currently", responseCache.size, "responses in cache."); -}; - -type Cache = { - locales: Language[]; - currencies: string[]; - wordings: EndpointWording[]; - config: EndpointWebsiteConfig; -}; - -const fetchNewData = async (): Promise => ({ - locales: await payload.getLanguages(), - currencies: (await payload.getCurrencies()).map(({ id }) => id), - wordings: await payload.getWordings(), - config: await payload.getConfig(), -}); - -export let cache = await fetchNewData(); - -export const refreshWordings = async () => { - cache.wordings = await payload.getWordings(); -}; - -export const refreshCurrencies = async () => { - cache.currencies = (await payload.getCurrencies()).map(({ id }) => id); -}; - -export const refreshLocales = async () => { - cache.locales = await payload.getLanguages(); -}; - -export const refreshWebsiteConfig = async () => { - cache.config = await payload.getConfig(); -}; - -let payloadInitialized = false; -export const initPayload = async () => { - if (!payloadInitialized) { - if (!isPrecachingEnabled) { - payloadInitialized = true; - return; - } - - const { urls } = await payload.getAllSdkUrls(); - for (const url of urls) { - try { - await payload.request(url); - } catch { - console.warn("[ResponseCaching] Precaching failed for url", url); - } - } - console.log("[ResponseCaching] Precaching completed!", responseCache.size, "responses cached"); - - payloadInitialized = true; - } -};