# Contract: BestiarySourceCache Port **Feature**: 029-on-demand-bestiary **Layer**: Application (port interface, implemented by web adapter) ## Purpose Defines the interface for caching and retrieving full bestiary source data. The application layer uses this port to look up full creature stat blocks. The web adapter implements it using IndexedDB. ## Interface: BestiarySourceCache ### getCreature(creatureId: CreatureId): Creature | undefined Look up a full creature by its ID from the cache. - **Input**: `creatureId` — branded string in format `{source}:{slug}` - **Output**: Full `Creature` object if the creature's source is cached, `undefined` otherwise - **Side effects**: None (read-only) ### isSourceCached(sourceCode: string): boolean Check whether a source's data has been cached. - **Input**: `sourceCode` — source identifier (e.g., "XMM") - **Output**: `true` if the source has been fetched and cached, `false` otherwise ### cacheSource(sourceCode: string, displayName: string, creatures: Creature[]): Promise\ Store a full source's worth of normalized creature data. - **Input**: source code, display name, array of normalized creatures - **Output**: Resolves when data is persisted - **Behavior**: Overwrites any existing cache for this source ### getCachedSources(): CachedSourceInfo[] List all cached sources for the management UI. - **Output**: Array of `{ sourceCode: string, displayName: string, creatureCount: number, cachedAt: number }` ### clearSource(sourceCode: string): Promise\ Remove a single source's cached data. - **Input**: source code to clear - **Output**: Resolves when data is removed ### clearAll(): Promise\ Remove all cached source data. - **Output**: Resolves when all data is removed ## Index Adapter (no port) The index adapter (`bestiary-index-adapter.ts`) exposes plain exported functions consumed directly by the web adapter hooks. No application-layer port is needed because the index is a static build-time asset with no I/O variability. See `apps/web/src/adapters/bestiary-index-adapter.ts` for the implementation. Exported functions: `loadBestiaryIndex()`, `getSourceDisplayName(sourceCode)`, `getDefaultFetchUrl(sourceCode)`. ## Invariants 1. `getCreature` MUST return `undefined` for any creature whose source is not cached — never throw. 2. `cacheSource` MUST be idempotent — calling it twice with the same data produces the same result. 3. `clearSource` MUST NOT affect other cached sources. 4. `search` MUST return an empty array for queries shorter than 2 characters. 5. The index adapter MUST be available synchronously after app initialization — no async loading state for search.