# Services Overview

This section documents the client-side services available in the Business Platform client package. Services encapsulate reusable logic for data access, caching, and communication with platform APIs.

## Available Services

| Service | Description |
|---|---|
| [DisplayValueCacheService](./display-value-cache.md) | Persistent cache for display value entries, backed by IndexedDB with automatic in-memory fallback. |

---

## DisplayValueCacheService

**Module:** `@smallstack/client/services/dataview`

The `DisplayValueCacheService` provides a persistent, IndexedDB-backed cache for `DisplayValueEntry` objects. It is designed to reduce redundant network requests when resolving human-readable display values for entities across collections.

When IndexedDB is unavailable (for example, in Safari private browsing mode or when storage quota is exceeded), the service automatically falls back to an in-memory cache for the duration of the session.

### Key Features

- **Persistent storage** via IndexedDB (`smallstack-display-cache` database)
- **Automatic fallback** to in-memory caching when IndexedDB is unavailable
- **Batch read and write** operations for efficient multi-entry access
- **Composite cache keys** combining `collectionName`, `entityId`, `schemaHash`, and `language` to ensure correctness across schema versions and locales
- **Indexed columns** for efficient querying by collection, entity, schema hash, or language

### Cache Key Structure

Each cache entry is identified by a composite key with the following structure:

```
{collectionName}:{entityId}:{schemaHash}:{language}
```

This ensures that cached display values are invalidated automatically when the underlying schema changes (`schemaHash`) or when a different locale is requested (`language`).

### Basic Usage

```typescript
import { DisplayValueCacheService } from "@smallstack/client/services/dataview";

const cache = new DisplayValueCacheService();

// Initialize the cache (safe to call multiple times)
await cache.init();

// Check whether IndexedDB is available
console.log(cache.isPersistent); // true if IndexedDB is active

// Read a single entry
const entry = await cache.getCacheEntry({
  collectionName: "products",
  entityId: "abc123",
  schemaHash: "d4f9e1",
  language: "en"
});

// Write a single entry
await cache.setCacheEntry(
  { collectionName: "products", entityId: "abc123", schemaHash: "d4f9e1", language: "en" },
  { displayValue: "Widget Pro", ... }
);
```

### Batch Operations

Use batch methods when resolving display values for multiple entities at once to minimize transaction overhead:

```typescript
// Batch read — returns only the entries that exist in the cache
const entries = await cache.getCacheEntriesMany([
  { collectionName: "products", entityId: "abc123", schemaHash: "d4f9e1", language: "en" },
  { collectionName: "products", entityId: "xyz789", schemaHash: "d4f9e1", language: "en" }
]);
// entries: Map<entityId, DisplayValueEntry>

// Batch write — executes in a single IndexedDB transaction
await cache.setCacheEntriesMany([
  { key: { collectionName: "products", entityId: "abc123", schemaHash: "d4f9e1", language: "en" }, entry: { ... } },
  { key: { collectionName: "products", entityId: "xyz789", schemaHash: "d4f9e1", language: "en" }, entry: { ... } }
]);
```

### Resetting the Cache

To fully delete the underlying IndexedDB database (for example, in tests or after a major schema migration):

```typescript
import { deleteDisplayCacheDB } from "@smallstack/client/services/dataview";

await deleteDisplayCacheDB();
```

### Type Reference

#### `DisplayCacheKey`

| Field | Type | Description |
|---|---|---|
| `collectionName` | `string` | The name of the entity collection. |
| `entityId` | `string` | The unique identifier of the entity. |
| `schemaHash` | `string` | A hash of the schema version used to generate the display value. |
| `language` | `string` | The locale/language code for the display value. |

#### `CachedDisplayValueEntry`

Extends `DisplayValueEntry` with the following additional fields:

| Field | Type | Description |
|---|---|---|
| `cacheKey` | `string` | Composite primary key stored in IndexedDB. |
| `collectionName` | `string` | The collection this entry belongs to. |
| `schemaHash` | `string` | Schema version hash at the time of caching. |
| `language` | `string` | Language code for this entry. |

#### `DisplayCacheMetadata`

| Field | Type | Description |
|---|---|---|
| `totalEntries` | `number` | Total number of entries currently in the cache. |
| `entriesByCollection` | `Record<string, number>` | Entry count broken down by collection name. |

### Fallback Behavior

The service handles IndexedDB unavailability transparently:

- If `indexedDB` is not defined in the current environment, the service resolves with `null` for the database handle and operates purely in memory.
- If `indexedDB.open()` fails (e.g. quota exceeded, permissions denied), a warning is logged to the console and the service continues in in-memory mode.
- The `isPersistent` getter can be checked at runtime to determine which mode is active.

> **Note:** In-memory cache entries are lost when the page is reloaded. For session-scoped caching without persistence requirements this is acceptable, but long-lived applications should ensure IndexedDB is available for best performance.
