Title: Display Value Cache Service --- The DisplayValueCacheService provides a persistent cache for DisplayValueEntry objects, backed by IndexedDB with automatic fallback to in-memory storage when IndexedDB is unavailable. Overview When rendering data views, resolved display values (human-readable representations of entity fields) are expensive to recompute. The cache stores these resolved values keyed by collection, entity ID, schema version, and language — so repeated lookups avoid redundant server round-trips or recomputation. Persistence: Cached entries survive page reloads when IndexedDB is available. Fallback: When IndexedDB is unavailable (e.g. Safari private browsing, storage quota exceeded, non-browser environments), the service transparently falls back to an in-memory Map. The API is identical in both modes; only persistence is lost. Installation The service is exported from the @smallstack/client package: import { DisplayValueCacheService } from "@smallstack/client"; Initialization Call init() before performing any cache operations. It is safe to call multiple times — subsequent calls are no-ops. const cache = new DisplayValueCacheService(); await cache.init(); You can check whether the cache is backed by IndexedDB after initialization: if (cache.isPersistent) { console.log("Cache is persisted via IndexedDB."); } else { console.log("Cache is running in memory-only mode."); } Cache Key Structure Every entry is addressed by a DisplayCacheKey, a composite of four fields: | Field | Type | Description | |------------------|----------|--------------------------------------------------| | collectionName | string | The name of the data collection (e.g. "users") | | entityId | string | The unique ID of the entity | | schemaHash | string | A hash of the schema version used for rendering | | language | string | The language/locale of the display value | These four fields are combined into a single composite primary key stored in IndexedDB as ${collectionName}:${entityId}:${schemaHash}:${language}. import type { DisplayCacheKey } from "@smallstack/client"; const key: DisplayCacheKey = { collectionName: "products", entityId: "abc123", schemaHash: "d4f9a1", language: "en" }; Reading from the Cache Single Entry const entry = await cache.getCacheEntry(key); if (entry) { console.log("Cache hit:", entry); } else { console.log("Cache miss — fetch from source."); } Batch Read Retrieve multiple entries in a single operation. Returns a Map keyed by entityId containing only the entries that were found (cache misses are omitted). const keys: DisplayCacheKey[] = [ { collectionName: "products", entityId: "abc123", schemaHash: "d4f9a1", language: "en" }, { collectionName: "products", entityId: "xyz789", schemaHash: "d4f9a1", language: "en" } ]; const found = await cache.getCacheEntriesMany(keys); for (const [entityId, entry] of found) { console.log(entityId, entry); } Writing to the Cache Single Entry import type { DisplayValueEntry } from "@smallstack/shared"; const entry: DisplayValueEntry = { / ... / }; await cache.setCacheEntry(key, entry); Batch Write Write multiple entries in a single IndexedDB transaction for better performance: await cache.setCacheEntriesMany([ { key: key1, entry: entry1 }, { key: key2, entry: entry2 } ]); Fallback Behavior The service handles IndexedDB unavailability gracefully: | Scenario | Behavior | |---------------------------------|-------------------------------------------------------| | IndexedDB not available | Falls back to in-memory Map; isPersistent is false | | Safari private browsing | Falls back to in-memory Map | | Storage quota exceeded | Falls back to in-memory Map | | Non-browser environment (SSR) | Falls back to in-memory Map | A warning is logged to the console when the fallback is activated: DisplayCache: IndexedDB unavailable, falling back to in-memory mode In-memory mode is fully functional for the lifetime of the page session. All reads and writes behave identically — entries simply will not survive a page reload. Database Management The underlying IndexedDB database is named smallstack-display-cache and uses the object store display-entries. These are managed internally and you should not need to interact with them directly under normal usage. To fully delete the cache database (useful in tests or for a hard cache reset): import { deleteDisplayCacheDB } from "@smallstack/client"; await deleteDisplayCacheDB(); Type Reference DisplayCacheKey interface DisplayCacheKey { collectionName: string; entityId: string; schemaHash: string; language: string; } CachedDisplayValueEntry Extends DisplayValueEntry with metadata stored alongside each entry in IndexedDB: interface CachedDisplayValueEntry extends DisplayValueEntry { / Composite primary key: ${collectionName}:${entityId}:${schemaHash}:${language} */ cacheKey: string; collectionName: string; schemaHash: string; language: string; } DisplayCacheMetadata Describes the current state of the cache: interface DisplayCacheMetadata { totalEntries: number; entriesByCollection: Record; }