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>;
}