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<string, number>;
}