diff --git a/package-lock.json b/package-lock.json index 9d92a12e9..43a733568 100644 --- a/package-lock.json +++ b/package-lock.json @@ -22,6 +22,7 @@ "hast-util-to-dom": "^4.0.0", "hast-util-to-html": "^9.0.1", "lodash": "^4.17.21", + "lru-cache": "^11.0.0", "marked": "^14.0.0", "marked-emoji": "^1.4.2", "marked-footnote": "^1.2.2", @@ -3377,6 +3378,14 @@ "get-func-name": "^2.0.1" } }, + "node_modules/lru-cache": { + "version": "11.0.0", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-11.0.0.tgz", + "integrity": "sha512-Qv32eSV1RSCfhY3fpPE2GNZ8jgM9X7rdAfemLWqTUxwiyIC4jJ6Sy0fZ8H+oLWevO6i4/bizg7c8d8i6bxrzbA==", + "engines": { + "node": "20 || >=22" + } + }, "node_modules/magic-string": { "version": "0.30.11", "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.11.tgz", diff --git a/package.json b/package.json index 08269156b..c18be704d 100644 --- a/package.json +++ b/package.json @@ -68,6 +68,7 @@ "hast-util-to-dom": "^4.0.0", "hast-util-to-html": "^9.0.1", "lodash": "^4.17.21", + "lru-cache": "^11.0.0", "marked": "^14.0.0", "marked-emoji": "^1.4.2", "marked-footnote": "^1.2.2", diff --git a/src/lib/cache.ts b/src/lib/cache.ts new file mode 100644 index 000000000..4be20b98f --- /dev/null +++ b/src/lib/cache.ts @@ -0,0 +1,21 @@ +import { LRUCache } from "lru-cache"; + +export function cached( + f: (...args: Args) => Promise, + makeKey: (...args: Args) => string, + options?: LRUCache.Options, +): (...args: Args) => Promise { + const cache = new LRUCache(options || { max: 500 }); + return async function (...args: Args): Promise { + const key = makeKey(...args); + const cached = cache.get(key); + + if (cached === undefined) { + const value = await f(...args); + cache.set(key, { value }); + return value; + } else { + return cached.value; + } + }; +} diff --git a/src/views/projects/Cob/Revision.svelte b/src/views/projects/Cob/Revision.svelte index c28b2f0af..68e432aa8 100644 --- a/src/views/projects/Cob/Revision.svelte +++ b/src/views/projects/Cob/Revision.svelte @@ -11,6 +11,7 @@ import * as utils from "@app/lib/utils"; import { HttpdClient } from "@http-client"; + import { cachedGetDiff } from "@app/views/projects/router"; import { onMount } from "svelte"; import CobCommitTeaser from "@app/views/projects/Cob/CobCommitTeaser.svelte"; @@ -104,7 +105,12 @@ onMount(async () => { try { loading = true; - response = await api.project.getDiff(projectId, fromCommit, revisionOid); + response = await cachedGetDiff( + api.baseUrl, + projectId, + fromCommit, + revisionOid, + ); // eslint-disable-next-line @typescript-eslint/no-explicit-any } catch (err: any) { error = err; diff --git a/src/views/projects/DiffStatBadgeLoader.svelte b/src/views/projects/DiffStatBadgeLoader.svelte index 6603a6f82..3db848c46 100644 --- a/src/views/projects/DiffStatBadgeLoader.svelte +++ b/src/views/projects/DiffStatBadgeLoader.svelte @@ -1,7 +1,7 @@ -{#await diffPromise} +{#await cachedGetDiff(baseUrl, projectId, latestRevision.base, latestRevision.oid)} {:then { diff }} { + const api = new HttpdClient(baseUrl); + return await api.project.getDiff(rid, base, oid); + }, + (...args) => JSON.stringify(args), + { max: 200 }, +); + function parseRevisionToOid( revision: string | undefined, defaultBranch: string, @@ -291,7 +303,7 @@ export async function loadProjectRoute( } else if (route.resource === "project.issue") { return await loadIssueView(route); } else if (route.resource === "project.patch") { - return await loadPatchView(route); + return await loadPatchView(route, previousLoaded); } else if (route.resource === "project.issues") { return await loadIssuesView(route); } else if (route.resource === "project.patches") { @@ -633,6 +645,7 @@ async function loadIssueView( async function loadPatchView( route: ProjectPatchRoute, + previousLoaded: LoadedRoute, ): Promise { const api = new HttpdClient(route.node); const rawPath = (commit?: string) => @@ -640,14 +653,40 @@ async function loadPatchView( route.project }${commit ? `/${commit}` : ""}`; - const [project, patch, seedingPolicy, node] = await Promise.all([ - api.project.getById(route.project), - api.project.getPatchById(route.project, route.patch), - api.getPolicyById(route.project), - api.getNode(), + let projectPromise: Promise; + let patchPromise: Promise; + let nodePromise: Promise>; + let seedingPolicyPromise: Promise; + + if ( + previousLoaded.resource === "project.patch" && + previousLoaded.params.project.id === route.project && + previousLoaded.params.patch.id === route.patch + ) { + projectPromise = Promise.resolve(previousLoaded.params.project); + patchPromise = Promise.resolve(previousLoaded.params.patch); + seedingPolicyPromise = Promise.resolve(previousLoaded.params.seedingPolicy); + nodePromise = Promise.resolve({ + avatarUrl: previousLoaded.params.nodeAvatarUrl, + }); + } else { + projectPromise = api.project.getById(route.project); + patchPromise = api.project.getPatchById(route.project, route.patch); + seedingPolicyPromise = api.getPolicyById(route.project); + nodePromise = api.getNode(); + } + const [project, patch, seedingPolicy, { avatarUrl }] = await Promise.all([ + projectPromise, + patchPromise, + seedingPolicyPromise, + nodePromise, ]); - const latestRevision = patch.revisions[patch.revisions.length - 1]; - const { diff } = await api.project.getDiff( + + const latestRevision = patch.revisions.at(-1) as Revision; + const { + diff: { stats }, + } = await cachedGetDiff( + api.baseUrl, route.project, latestRevision.base, latestRevision.oid, @@ -669,7 +708,8 @@ async function loadPatchView( `revision ${revisionId} of patch ${route.patch} not found`, ); } - const { diff, commits, files } = await api.project.getDiff( + const { diff, commits, files } = await cachedGetDiff( + api.baseUrl, route.project, revision.base, revision.oid, @@ -686,7 +726,8 @@ async function loadPatchView( } case "diff": { const { fromCommit, toCommit } = route.view; - const { diff, files } = await api.project.getDiff( + const { diff, files } = await cachedGetDiff( + api.baseUrl, route.project, fromCommit, toCommit, @@ -704,9 +745,9 @@ async function loadPatchView( project, rawPath, patch, - stats: diff.stats, + stats, view, - nodeAvatarUrl: node.avatarUrl, + nodeAvatarUrl: avatarUrl, }, }; }