diff --git a/app/constants.ts b/app/constants.ts index 5e8f3cd53..f2b499453 100644 --- a/app/constants.ts +++ b/app/constants.ts @@ -71,3 +71,6 @@ export const LOHI_TOKEN_HEADER_NAME = "Lohi-Token"; export const CUSTOMIZED_CSS_VARS_NAME = "css"; export const MAX_AP = 57; + +export const ONE_HOUR_IN_MS = 60 * 60 * 1000; +export const TWELVE_HOURS_IN_MS = 12 * ONE_HOUR_IN_MS; diff --git a/app/entry.server.tsx b/app/entry.server.tsx index 64e4ecb0a..b782b92c4 100644 --- a/app/entry.server.tsx +++ b/app/entry.server.tsx @@ -45,7 +45,6 @@ const handleBotRequest = ( void i18Instance(request, remixContext).then((i18n) => { const { pipe, abort } = renderToPipeableStream( - {/* @ts-expect-error TODO: fix since probably a real issue not just types? https://github.com/remix-run/remix/issues/5073#issuecomment-1380380695 */} , { @@ -90,7 +89,6 @@ const handleBrowserRequest = ( void i18Instance(request, remixContext).then((i18n) => { const { pipe, abort } = renderToPipeableStream( - {/* @ts-expect-error TODO: fix */} , { diff --git a/app/features/build-stats/routes/builds.$slug.popular.tsx b/app/features/build-stats/routes/builds.$slug.popular.tsx index 51637d2d8..4e729c672 100644 --- a/app/features/build-stats/routes/builds.$slug.popular.tsx +++ b/app/features/build-stats/routes/builds.$slug.popular.tsx @@ -1,5 +1,6 @@ import type { LoaderArgs, MetaFunction, SerializeFrom } from "@remix-run/node"; import { useLoaderData } from "@remix-run/react"; +import { cachified } from "cachified"; import clsx from "clsx"; import { Ability } from "~/components/Ability"; import { Main } from "~/components/Main"; @@ -16,6 +17,8 @@ import { } from "~/utils/urls"; import { popularBuilds } from "../build-stats-utils"; import { abilitiesByWeaponId } from "../queries/abilitiesByWeaponId.server"; +import { cache } from "~/utils/cache.server"; +import { ONE_HOUR_IN_MS, TWELVE_HOURS_IN_MS } from "~/constants"; export const meta: MetaFunction = (args) => { const data = args.data as SerializeFrom | null; @@ -58,12 +61,22 @@ export const loader = async ({ params, request }: LoaderArgs) => { const t = await i18next.getFixedT(request, ["builds", "weapons", "common"]); const slug = params["slug"]; const weaponId = notFoundIfFalsy(weaponNameSlugToId(slug)); - const abilities = abilitiesByWeaponId(weaponId); const weaponName = t(`weapons:MAIN_${weaponId}`); + const cachedPopularBuilds = await cachified({ + key: `popular-builds-${weaponId}`, + cache, + ttl: ONE_HOUR_IN_MS, + staleWhileRevalidate: TWELVE_HOURS_IN_MS, + // eslint-disable-next-line @typescript-eslint/require-await + async getFreshValue() { + return popularBuilds(abilitiesByWeaponId(weaponId)); + }, + }); + return { - popularBuilds: popularBuilds(abilities), + popularBuilds: cachedPopularBuilds, meta: { weaponId, slug: slug!, diff --git a/app/features/build-stats/routes/builds.$slug.stats.tsx b/app/features/build-stats/routes/builds.$slug.stats.tsx index c20d158d8..77f526c3f 100644 --- a/app/features/build-stats/routes/builds.$slug.stats.tsx +++ b/app/features/build-stats/routes/builds.$slug.stats.tsx @@ -14,7 +14,7 @@ import styles from "../build-stats.css"; import { WeaponImage } from "~/components/Image"; import type { SendouRouteHandle } from "~/utils/remix"; import { notFoundIfFalsy } from "~/utils/remix"; -import { MAX_AP } from "~/constants"; +import { MAX_AP, ONE_HOUR_IN_MS, TWELVE_HOURS_IN_MS } from "~/constants"; import { useTranslation } from "~/hooks/useTranslation"; import { BUILDS_PAGE, @@ -24,6 +24,8 @@ import { } from "~/utils/urls"; import { i18next } from "~/modules/i18n"; import { makeTitle } from "~/utils/strings"; +import { cache } from "~/utils/cache.server"; +import { cachified } from "cachified"; export const meta: MetaFunction = (args) => { const data = args.data as SerializeFrom | null; @@ -72,11 +74,22 @@ export const loader = async ({ params, request }: LoaderArgs) => { const weaponName = t(`weapons:MAIN_${weaponId}`); + const cachedStats = await cachified({ + key: `build-stats-${weaponId}`, + cache, + ttl: ONE_HOUR_IN_MS, + staleWhileRevalidate: TWELVE_HOURS_IN_MS, + // eslint-disable-next-line @typescript-eslint/require-await + async getFreshValue() { + return abilityPointCountsToAverages({ + allAbilities: averageAbilityPoints(), + weaponAbilities: averageAbilityPoints(weaponId), + }); + }, + }); + return { - stats: abilityPointCountsToAverages({ - allAbilities: averageAbilityPoints(), - weaponAbilities: averageAbilityPoints(weaponId), - }), + stats: cachedStats, weaponId, meta: { slug: params["slug"]!, diff --git a/app/utils/cache.server.ts b/app/utils/cache.server.ts new file mode 100644 index 000000000..0554bf189 --- /dev/null +++ b/app/utils/cache.server.ts @@ -0,0 +1,12 @@ +import type { CacheEntry } from "cachified"; +import LRUCache from "lru-cache"; + +declare global { + // This preserves the LRU cache during development + // eslint-disable-next-line + var __lruCache: LRUCache> | undefined; +} + +export const cache = (global.__lruCache = global.__lruCache + ? global.__lruCache + : new LRUCache>({ max: 5000 })); diff --git a/e2e/user-page.spec.ts b/e2e/user-page.spec.ts index be0628db7..30ff3fb23 100644 --- a/e2e/user-page.spec.ts +++ b/e2e/user-page.spec.ts @@ -52,7 +52,7 @@ test.describe("User page", () => { window.getComputedStyle(element).getPropertyValue("--bg").trim() ); - await expect(bodyColor()).resolves.toMatch("#ebebf0"); + await expect(bodyColor()).resolves.toMatch(/#ebebf0/); await goToEditPage(page); @@ -61,7 +61,7 @@ test.describe("User page", () => { // got redirected await expect(page).not.toHaveURL(/edit/); - await expect(bodyColor()).resolves.toMatch("#4a412a"); + await expect(bodyColor()).resolves.toMatch(/#4a412a/); }); test("has redirecting custom url", async ({ page }) => { diff --git a/package-lock.json b/package-lock.json index cf75aed7a..2498d16d7 100644 --- a/package-lock.json +++ b/package-lock.json @@ -9,7 +9,7 @@ "version": "3.0.0", "dependencies": { "@faker-js/faker": "^7.6.0", - "@headlessui/react": "^1.7.13", + "@headlessui/react": "1.7.10", "@popperjs/core": "^2.11.6", "@remix-run/node": "^1.14.1", "@remix-run/react": "^1.14.1", @@ -17,6 +17,7 @@ "@tldraw/tldraw": "^1.28.0", "aws-sdk": "^2.1333.0", "better-sqlite3": "^8.2.0", + "cachified": "^3.1.0", "clsx": "^1.2.1", "compressorjs": "^1.2.1", "countries-list": "^2.6.1", @@ -32,6 +33,7 @@ "just-clone": "^6.2.0", "just-random-integer": "^4.2.0", "just-shuffle": "^4.2.0", + "lru-cache": "7.18.3", "markdown-to-jsx": "^7.1.9", "nanoid": "~3.3.4", "node-cron": "3.0.2", @@ -2207,9 +2209,9 @@ "license": "MIT" }, "node_modules/@headlessui/react": { - "version": "1.7.13", - "resolved": "https://registry.npmjs.org/@headlessui/react/-/react-1.7.13.tgz", - "integrity": "sha512-9n+EQKRtD9266xIHXdY5MfiXPDfYwl7zBM7KOx2Ae3Gdgxy8QML1FkCMjq6AsOf0l6N9uvI4HcFtuFlenaldKg==", + "version": "1.7.10", + "resolved": "https://registry.npmjs.org/@headlessui/react/-/react-1.7.10.tgz", + "integrity": "sha512-1m66h/5eayTEZVT2PI13/2PG3EVC7a9XalmUtVSC8X76pcyKYMuyX1XAL2RUtCr8WhoMa/KrDEyoeU5v+kSQOw==", "dependencies": { "client-only": "^0.0.1" }, @@ -3307,14 +3309,6 @@ "@esbuild/win32-x64": "0.16.3" } }, - "node_modules/@remix-run/dev/node_modules/lru-cache": { - "version": "7.14.1", - "dev": true, - "license": "ISC", - "engines": { - "node": ">=12" - } - }, "node_modules/@remix-run/dev/node_modules/prettier": { "version": "2.7.1", "dev": true, @@ -5032,6 +5026,18 @@ "node": ">= 10" } }, + "node_modules/cacache/node_modules/lru-cache": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", + "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", + "dev": true, + "dependencies": { + "yallist": "^4.0.0" + }, + "engines": { + "node": ">=10" + } + }, "node_modules/cacheable-lookup": { "version": "5.0.4", "dev": true, @@ -5080,6 +5086,11 @@ "once": "^1.3.1" } }, + "node_modules/cachified": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/cachified/-/cachified-3.1.0.tgz", + "integrity": "sha512-5yyRHcGF2DY+ZQ8zVw+Ge3rTHI+EbPXsrE1ZybQ2P8Nh/hqQLDPz/89j/ZuFggRy0aDLYPhuQ/QdTFtHgd/2Xw==" + }, "node_modules/call-bind": { "version": "1.0.2", "license": "MIT", @@ -9147,13 +9158,11 @@ } }, "node_modules/lru-cache": { - "version": "6.0.0", - "license": "ISC", - "dependencies": { - "yallist": "^4.0.0" - }, + "version": "7.18.3", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-7.18.3.tgz", + "integrity": "sha512-jumlc0BIUrS3qJGgIkWZsyfAM7NCWiBcCDhnd+3NNM5KbBmLTgHVfWBcg6W+rLUsIpzpERPsvwUP7CckAQSOoA==", "engines": { - "node": ">=10" + "node": ">=12" } }, "node_modules/lz-string": { @@ -12085,13 +12094,6 @@ "react-i18next": "^11.13.0" } }, - "node_modules/remix-i18next/node_modules/lru-cache": { - "version": "7.13.1", - "license": "ISC", - "engines": { - "node": ">=12" - } - }, "node_modules/require-from-string": { "version": "2.0.2", "dev": true, @@ -12367,6 +12369,17 @@ "node": ">=10" } }, + "node_modules/semver/node_modules/lru-cache": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", + "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", + "dependencies": { + "yallist": "^4.0.0" + }, + "engines": { + "node": ">=10" + } + }, "node_modules/send": { "version": "0.18.0", "resolved": "https://registry.npmjs.org/send/-/send-0.18.0.tgz", @@ -13160,6 +13173,18 @@ "node": ">=0.10.0" } }, + "node_modules/stylelint/node_modules/lru-cache": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", + "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", + "dev": true, + "dependencies": { + "yallist": "^4.0.0" + }, + "engines": { + "node": ">=10" + } + }, "node_modules/stylelint/node_modules/meow": { "version": "9.0.0", "dev": true, @@ -15871,9 +15896,9 @@ "dev": true }, "@headlessui/react": { - "version": "1.7.13", - "resolved": "https://registry.npmjs.org/@headlessui/react/-/react-1.7.13.tgz", - "integrity": "sha512-9n+EQKRtD9266xIHXdY5MfiXPDfYwl7zBM7KOx2Ae3Gdgxy8QML1FkCMjq6AsOf0l6N9uvI4HcFtuFlenaldKg==", + "version": "1.7.10", + "resolved": "https://registry.npmjs.org/@headlessui/react/-/react-1.7.10.tgz", + "integrity": "sha512-1m66h/5eayTEZVT2PI13/2PG3EVC7a9XalmUtVSC8X76pcyKYMuyX1XAL2RUtCr8WhoMa/KrDEyoeU5v+kSQOw==", "requires": { "client-only": "^0.0.1" } @@ -16625,10 +16650,6 @@ "@esbuild/win32-x64": "0.16.3" } }, - "lru-cache": { - "version": "7.14.1", - "dev": true - }, "prettier": { "version": "2.7.1", "dev": true @@ -17810,6 +17831,17 @@ "ssri": "^8.0.1", "tar": "^6.0.2", "unique-filename": "^1.1.1" + }, + "dependencies": { + "lru-cache": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", + "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", + "dev": true, + "requires": { + "yallist": "^4.0.0" + } + } } }, "cacheable-lookup": { @@ -17846,6 +17878,11 @@ } } }, + "cachified": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/cachified/-/cachified-3.1.0.tgz", + "integrity": "sha512-5yyRHcGF2DY+ZQ8zVw+Ge3rTHI+EbPXsrE1ZybQ2P8Nh/hqQLDPz/89j/ZuFggRy0aDLYPhuQ/QdTFtHgd/2Xw==" + }, "call-bind": { "version": "1.0.2", "requires": { @@ -20413,10 +20450,9 @@ "dev": true }, "lru-cache": { - "version": "6.0.0", - "requires": { - "yallist": "^4.0.0" - } + "version": "7.18.3", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-7.18.3.tgz", + "integrity": "sha512-jumlc0BIUrS3qJGgIkWZsyfAM7NCWiBcCDhnd+3NNM5KbBmLTgHVfWBcg6W+rLUsIpzpERPsvwUP7CckAQSOoA==" }, "lz-string": { "version": "1.4.4", @@ -22173,11 +22209,6 @@ "intl-parse-accept-language": "^1.0.0", "lru-cache": "^7.10.0", "use-consistent-value": "^1.0.0" - }, - "dependencies": { - "lru-cache": { - "version": "7.13.1" - } } }, "require-from-string": { @@ -22341,6 +22372,16 @@ "version": "7.3.7", "requires": { "lru-cache": "^6.0.0" + }, + "dependencies": { + "lru-cache": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", + "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", + "requires": { + "yallist": "^4.0.0" + } + } } }, "send": { @@ -22822,6 +22863,15 @@ "version": "5.0.0", "dev": true }, + "lru-cache": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", + "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", + "dev": true, + "requires": { + "yallist": "^4.0.0" + } + }, "meow": { "version": "9.0.0", "dev": true, diff --git a/package.json b/package.json index 584dab422..1e41d6221 100644 --- a/package.json +++ b/package.json @@ -34,7 +34,7 @@ }, "dependencies": { "@faker-js/faker": "^7.6.0", - "@headlessui/react": "^1.7.13", + "@headlessui/react": "1.7.10", "@popperjs/core": "^2.11.6", "@remix-run/node": "^1.14.1", "@remix-run/react": "^1.14.1", @@ -42,6 +42,7 @@ "@tldraw/tldraw": "^1.28.0", "aws-sdk": "^2.1333.0", "better-sqlite3": "^8.2.0", + "cachified": "^3.1.0", "clsx": "^1.2.1", "compressorjs": "^1.2.1", "countries-list": "^2.6.1", @@ -57,6 +58,7 @@ "just-clone": "^6.2.0", "just-random-integer": "^4.2.0", "just-shuffle": "^4.2.0", + "lru-cache": "7.18.3", "markdown-to-jsx": "^7.1.9", "nanoid": "~3.3.4", "node-cron": "3.0.2",