// `AccountStore` is the session-wide cache for the caller's // `user.account.get` aggregate. The lobby shell and every post-login // screen read the identity (display name, immutable user_name, time // zone, …) from the same rune, so navigating between Overview and // Profile does not refetch and does not flash the // `lobby.account_loading` placeholder. // // `ensure(client)` fetches once on first call, dedupes concurrent // callers onto a single in-flight promise, and resolves immediately // from the cache thereafter. `set(account)` is the write-through // path used by Profile after `user.profile.update` / // `user.settings.update` succeeds — both the shell and the screen // pick up the change without an extra round-trip. `clear()` resets // the cache on logout so a different user signing in on the same // browser does not briefly see the previous identity. // // The store is intentionally narrow: it caches one struct, never // retries on failure (the caller decides), and exposes no error // state of its own. Callers that need a tighter error surface (the // Profile form) catch the rejection from `ensure(client)` directly. import type { GalaxyClient } from "../api/galaxy-client"; import { getMyAccount, type Account } from "../api/account"; class AccountStore { current: Account | null = $state(null); #inFlight: Promise | null = null; /** * ensure returns the cached `Account` when present, otherwise issues * `user.account.get` through the supplied client and caches the * result. Concurrent callers during the first fetch share the same * in-flight promise so the gateway only sees one request per * session. */ ensure(client: GalaxyClient): Promise { if (this.current !== null) { return Promise.resolve(this.current); } if (this.#inFlight !== null) { return this.#inFlight; } const pending = getMyAccount(client) .then((account) => { this.current = account; return account; }) .finally(() => { this.#inFlight = null; }); this.#inFlight = pending; return pending; } /** * set replaces the cached `Account` with the supplied value. Used * by the Profile screen after a successful save so both the form * and the shell identity strip pick up the new fields without a * second round-trip. */ set(next: Account): void { this.current = next; } /** * clear resets the cache. Called on logout so a different user * signing in on the same browser does not briefly see the * previous identity through the rune. */ clear(): void { this.current = null; this.#inFlight = null; } } export const account = new AccountStore();