# 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:

```ts
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.

```ts
const cache = new DisplayValueCacheService();
await cache.init();
```

You can check whether the cache is backed by IndexedDB after initialization:

```ts
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}`.

```ts
import type { DisplayCacheKey } from "@smallstack/client";

const key: DisplayCacheKey = {
  collectionName: "products",
  entityId: "abc123",
  schemaHash: "d4f9a1",
  language: "en"
};
```

## Reading from the Cache

### Single Entry

```ts
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).

```ts
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

```ts
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:

```ts
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):

```ts
import { deleteDisplayCacheDB } from "@smallstack/client";

await deleteDisplayCacheDB();
```

## Type Reference

### `DisplayCacheKey`

```ts
interface DisplayCacheKey {
  collectionName: string;
  entityId: string;
  schemaHash: string;
  language: string;
}
```

### `CachedDisplayValueEntry`

Extends `DisplayValueEntry` with metadata stored alongside each entry in IndexedDB:

```ts
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:

```ts
interface DisplayCacheMetadata {
  totalEntries: number;
  entriesByCollection: Record<string, number>;
}
```
