import type { AppTickerRepositoryPort } from "../core/app-service-ports"; import { hydrateTickerMetadata } from "../tickers/metadata"; import type { TickerMetadata, TickerRecord } from "../types/ticker"; import { BROWSER_STORAGE_KEYS, SafeJsonStorage, type StorageLike } from "./json-storage"; type StoredTickers = Record; function isRecord(value: unknown): value is Record { return !!value && typeof value === "object" && !Array.isArray(value); } function normalizeSymbol(symbol: string): string { return symbol.trim().toUpperCase(); } function hydrate(metadata: TickerMetadata): TickerRecord | null { try { return { metadata: hydrateTickerMetadata(metadata as unknown as Record) }; } catch { return null; } } export class JsonTickerRepository implements AppTickerRepositoryPort { private readonly data: SafeJsonStorage; constructor(storage?: StorageLike, tickers: readonly TickerRecord[] = []) { this.data = new SafeJsonStorage(storage, BROWSER_STORAGE_KEYS.tickers, Object.fromEntries(tickers.map((ticker) => [normalizeSymbol(ticker.metadata.ticker), ticker.metadata])), (value): value is StoredTickers => isRecord(value)); } async loadAllTickers(): Promise { return Object.values(this.data.get()).flatMap((metadata) => { const ticker = hydrate(metadata); return ticker ? [ticker] : []; }); } async loadTicker(symbol: string): Promise { const metadata = this.data.get()[normalizeSymbol(symbol)]; return metadata ? hydrate(metadata) : null; } async saveTicker(ticker: TickerRecord): Promise { const symbol = normalizeSymbol(ticker.metadata.ticker); this.data.set({ ...this.data.get(), [symbol]: ticker.metadata }); } async createTicker(metadata: TickerMetadata): Promise { const ticker = { metadata }; await this.saveTicker(ticker); return ticker; } async deleteTicker(symbol: string): Promise { const tickers = { ...this.data.get() }; delete tickers[normalizeSymbol(symbol)]; this.data.set(tickers); } }