# Runtime Config Utility

Shared runtime loader and update adapter used by Theme and Translation. It loads JSON at boot, checks updates, and stores last-known-good configs in IndexedDB (Dexie).

This doc covers:

- RuntimeConfigLoader flow
- Cache and schema versioning
- Update adapters (SSE/stream)
- RuntimeHttpClient behavior
- Examples for custom resources

---

## Core Concepts

### RuntimeConfigLoader

Responsibilities:

- Load a JSON config at boot (cache-first)
- Store last-known-good in Dexie
- Compare hash to detect updates
- Refresh on demand

### RuntimeConfigStore

Dexie-backed store that persists last-known-good config entries.

### RuntimeUpdateAdapter

Optional SSE/Observable wiring that signals when updates are available.

---

## Basic Usage

```ts
import { RuntimeConfigLoader, RuntimeConfigStore } from '@sixbell-telco/sdk/utils/runtime-config';

const store = new RuntimeConfigStore<MyConfig>('my-runtime-config', 'my-app');
const loader = new RuntimeConfigLoader<MyConfig>('/assets/config.json', {
  fallbackData: { enabled: true },
  resource: 'theme',
  store,
  schemaVersion: '1',
});

const result = await loader.loadLatest();
const check = await loader.checkForUpdates(result.hash);
if (check.updated) {
  // show a "Refresh" CTA
}
const refreshed = await loader.refresh();
```

---

## Mermaid: Load Flow

```mermaid
sequenceDiagram
  participant Loader
  participant Store as RuntimeConfigStore
  participant Net as Network

  Loader->>Store: get(cacheKey)
  alt cached & schema ok
    Store-->>Loader: cached
    Loader-->>Loader: return cached
  else cache miss / schema mismatch
    Loader->>Net: fetch(configPath)
    Net-->>Loader: JSON
    Loader->>Store: set(cacheKey)
    Loader-->>Loader: return network
  end
```

---

## Schema Versioning

If cached JSON is not compatible with the current code, set a schema version:

```ts
const loader = new RuntimeConfigLoader<MyConfig>(path, {
  fallbackData,
  resource: 'theme',
  store,
  schemaVersion: '2',
});
```

Add the version in JSON:

```json
{
  "meta": {
    "schemaVersion": "2"
  }
}
```

If the schema version mismatches, cached entries are removed and a fresh network fetch occurs.

Hash requirements:

- `meta.hash` is mandatory and used for update detection.
- If `meta.hash` is missing, the loader treats the config as invalid and falls back.

Cache keys also include the schema version when provided, so new schema versions always
create a fresh cache namespace.

---

## RuntimeUpdateAdapter (SSE/Stream)

### SSE

````ts
import { RuntimeUpdateAdapter } from '@sixbell-telco/sdk/utils/runtime-config';

const adapter = new RuntimeUpdateAdapter(async (event) => {
  if (event.resource !== 'theme' && event.resource !== 'both') return;
  const check = await loader.checkForUpdates(currentHash);
  if (check.updated) {
    // show CTA
  }
});

adapter.connectSse('/api/updates', {
  resource: 'both',
  eventType: 'message',
});

### Update payload requirements

Translation updates must include `lang` so clients can refresh the affected locale only.

```json
{
  "resource": "translation",
  "version": "translation-update",
  "hash": "translations-1700000000000",
  "url": "/api/runtime/translation/config",
  "lang": "es"
}
````

```json
{
  "resource": "theme",
  "version": "theme-update",
  "hash": "theme-1700000000000",
  "url": "/api/runtime/theme/config",
  "theme": "sixbell_telco"
}
```

If `lang` is missing, the update is rejected to avoid stale caches.

For theme updates, `theme` is required so clients can refresh the affected theme only.

````

### Observable

```ts
adapter.connectStream(update$);
````

### Observable from a polling endpoint

```ts
import { from, timer } from 'rxjs';
import { switchMap } from 'rxjs/operators';

const update$ = timer(0, 8000).pipe(
  switchMap(() => from(fetch('http://localhost:4000/api/runtime/updates/poll'))),
  switchMap((response) => from(response.json())),
);

adapter.connectStream(update$);
```

---

## RuntimeHttpClient

Built-in fetch with retries and timeouts:

```ts
const client = new RuntimeHttpClient({
  retries: 2,
  retryDelayMs: 400,
  timeoutMs: 8000,
});

const response = await client.fetch('/assets/config.json', { cache: 'no-store' });
```

---

## Resource Loader Example (Custom Resource)

```ts
type FeatureFlags = {
  meta?: { hash?: string; schemaVersion?: string };
  flags: Record<string, boolean>;
};

const store = new RuntimeConfigStore<FeatureFlags>('flags-runtime-config', 'host');
const loader = new RuntimeConfigLoader<FeatureFlags>('/assets/flags.json', {
  fallbackData: { flags: {} },
  resource: 'both',
  store,
  schemaVersion: '1',
});

const result = await loader.loadLatest();
const flags = result.data.flags;
```

---

## Notes

- Fetch uses `cache: 'no-store'` to avoid stale responses.
- Dexie cache is last-known-good only, not an aggressive cache.
- `meta.hash` is required. If it is missing, the loader treats the config as invalid and falls back.

## Related Utilities

- `@sixbell-telco/sdk/utils/theme` - Runtime theme system
- `@sixbell-telco/sdk/utils/translation` - Runtime translation system
