diff --git a/README.md b/README.md index 448c8231..10cd47c5 100644 --- a/README.md +++ b/README.md @@ -40,7 +40,7 @@ Please refer to [CONTRIBUTING.md](CONTRIBUTING.md) and [HACKING.md](HACKING.md) ### Sponsoring - + Gno.land logo diff --git a/web/src/components/layout/Header/Header.tsx b/web/src/components/layout/Header/Header.tsx index 440e53c4..29b75f9a 100644 --- a/web/src/components/layout/Header/Header.tsx +++ b/web/src/components/layout/Header/Header.tsx @@ -1,4 +1,5 @@ import React from 'react' +import { addDays } from 'date-fns' import { CommandBar, type ICommandBarItemProps, Stack } from '@fluentui/react' import type { Snippet } from '~/services/examples' @@ -11,6 +12,7 @@ import { ExamplesModal } from '~/components/features/examples/ExamplesModal' import { RunTargetSelector } from '~/components/elements/inputs/RunTargetSelector' import { SharePopup } from '~/components/utils/SharePopup' +import { keyValue } from '~/services/storage' import { dispatchTerminalSettingsChange } from '~/store/terminal' import { dispatchFormatFile, @@ -35,6 +37,12 @@ import './Header.css' */ const BTN_SHARE_CLASSNAME = 'Header__btn--share' +const goVersionsCacheEntry = { + key: 'api.go.versions', + ttl: () => addDays(new Date(), 7), + getInitialValue: async () => await apiClient.getBackendVersions(), +} + interface HeaderState { showSettings?: boolean showAbout?: boolean @@ -67,8 +75,8 @@ class HeaderContainer extends ThemeableComponent { } componentDidMount(): void { - apiClient - .getBackendVersions() + keyValue + .getOrInsert(goVersionsCacheEntry) .then((rsp) => { this.setState({ goVersions: rsp, diff --git a/web/src/services/storage/kv.ts b/web/src/services/storage/kv.ts index 689c9ee9..c77b3f36 100644 --- a/web/src/services/storage/kv.ts +++ b/web/src/services/storage/kv.ts @@ -4,6 +4,13 @@ import type { CacheEntry, CacheStorage } from './types' type RecordValidator = (entry: CacheEntry) => boolean +export interface CachedValueDescriptor { + key: string + ttl: () => Date + validate?: (entry: CacheEntry) => boolean + getInitialValue: () => Promise +} + export class KeyValueStore implements CacheStorage { constructor(private readonly db: DatabaseStorage) {} @@ -22,6 +29,28 @@ export class KeyValueStore implements CacheStorage { return entry?.value as T | undefined } + async getOrInsert({ ttl, key, validate, getInitialValue }: CachedValueDescriptor) { + let cachedVal: T | undefined + try { + cachedVal = await this.getItem(key, validate) + } catch (err) { + console.error(`Failed to get cached record "${key}": ${err}, falling back to default value.`) + } + + if (typeof cachedVal !== 'undefined') { + return cachedVal + } + + const initVal = await getInitialValue() + try { + await this.setItem(key, initVal, ttl()) + } catch (err) { + console.error(`Failed to save cached record "${key}": ${err}`) + } + + return initVal + } + async deleteItem(key: string) { const n = await this.db.keyValue.where({ key }).delete() return n > 0