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
-
+
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