UNPKG

chord-component

Version:

Lit-based web components for displaying musical chord diagrams and chord lists

276 lines (240 loc) 8.47 kB
/** * Chord Data Service * * This service provides a unified interface for accessing chord data. * It implements a caching strategy using IndexedDB: * 1. First checks IndexedDB for cached data * 2. Falls back to local default chord data if not found * 3. Caches the data in IndexedDB for future use * 4. Can be easily extended to fetch from a remote API */ import { indexedDBService } from './indexed-db-service.js'; import { systemDefaultChords, type InstrumentDefault } from './default-chords.js'; export interface ChordDataSource { type: 'indexeddb' | 'local' | 'api'; timestamp?: number; } export interface ChordDataResult { data: Record<string, InstrumentDefault>; source: ChordDataSource; } class ChordDataService { private useRemoteAPI: boolean = false; private apiEndpoint: string = ''; /** * Configure the service to use a remote API endpoint */ configureAPI(endpoint: string) { this.apiEndpoint = endpoint; this.useRemoteAPI = true; } /** * Disable remote API and use local data */ disableAPI() { this.useRemoteAPI = false; } /** * Get chord data for a specific instrument * This method implements the fallback chain: IndexedDB -> API -> Local */ async getChordData(instrument: string): Promise<ChordDataResult> { // Step 1: Try to get from IndexedDB cache try { const cachedData = await indexedDBService.getChordData(instrument); if (cachedData && cachedData.chords) { console.log(`[ChordDataService] Loaded ${instrument} from IndexedDB cache`); return { data: cachedData.chords, source: { type: 'indexeddb', timestamp: cachedData.timestamp } }; } } catch (error) { console.warn('[ChordDataService] IndexedDB error, falling back:', error); } // Step 2: Try to fetch from remote API if configured if (this.useRemoteAPI && this.apiEndpoint) { try { const apiData = await this.fetchFromAPI(instrument); if (apiData) { console.log(`[ChordDataService] Loaded ${instrument} from remote API`); // Cache the API data in IndexedDB for future use await this.cacheChordData(instrument, apiData); return { data: apiData, source: { type: 'api', timestamp: Date.now() } }; } } catch (error) { console.warn('[ChordDataService] API fetch error, falling back to local:', error); } } // Step 3: Fall back to local default data console.log(`[ChordDataService] Loaded ${instrument} from local defaults`); const localData = systemDefaultChords[instrument] || {}; // Cache the local data in IndexedDB for future use await this.cacheChordData(instrument, localData); return { data: localData, source: { type: 'local', timestamp: Date.now() } }; } /** * Fetch chord data from remote API * This is a placeholder that can be implemented when API is ready */ private async fetchFromAPI(instrument: string): Promise<Record<string, InstrumentDefault> | null> { if (!this.apiEndpoint) { return null; } try { const url = `${this.apiEndpoint}?instrument=${encodeURIComponent(instrument)}`; const response = await fetch(url); if (!response.ok) { throw new Error(`API request failed: ${response.status} ${response.statusText}`); } const data = await response.json(); return data.chords || data; // Flexible response format } catch (error) { console.error('[ChordDataService] API fetch error:', error); return null; } } /** * Cache chord data in IndexedDB */ private async cacheChordData(instrument: string, chords: Record<string, InstrumentDefault>): Promise<void> { try { await indexedDBService.setChordData(instrument, chords); console.log(`[ChordDataService] Cached ${instrument} in IndexedDB`); } catch (error) { console.warn('[ChordDataService] Failed to cache in IndexedDB:', error); } } /** * Get all available instruments from local defaults * In the future, this could also query the API */ getAvailableInstruments(): string[] { return Object.keys(systemDefaultChords); } /** * Clear all cached data from IndexedDB */ async clearCache(): Promise<void> { await indexedDBService.clearAll(); console.log('[ChordDataService] Cache cleared'); } /** * Force refresh data from source (API or local) and update cache */ async refreshData(instrument: string): Promise<ChordDataResult> { // If using API, fetch fresh data if (this.useRemoteAPI && this.apiEndpoint) { try { const apiData = await this.fetchFromAPI(instrument); if (apiData) { await this.cacheChordData(instrument, apiData); return { data: apiData, source: { type: 'api', timestamp: Date.now() } }; } } catch (error) { console.warn('[ChordDataService] Refresh from API failed:', error); } } // Fall back to local data const localData = systemDefaultChords[instrument] || {}; await this.cacheChordData(instrument, localData); return { data: localData, source: { type: 'local', timestamp: Date.now() } }; } /** * Check if a specific chord exists for an instrument */ async hasChord(instrument: string, chordName: string): Promise<boolean> { const result = await this.getChordData(instrument); return chordName in result.data; } /** * Get data for a specific chord * @param instrument - The instrument name * @param chordName - The chord name * @param preferUser - If true, returns user override if it exists; if false, returns system default only */ async getChord(instrument: string, chordName: string, preferUser: boolean = true): Promise<InstrumentDefault | null> { // Check for user override first if preferUser is true if (preferUser) { try { const userChord = await indexedDBService.getUserChord(instrument, chordName); if (userChord) { return { fingers: userChord.fingers, barres: userChord.barres, position: userChord.position }; } } catch (error) { console.warn('[ChordDataService] Failed to get user chord:', error); } } // Fall back to system default const result = await this.getChordData(instrument); return result.data[chordName] || null; } /** * Save a user-defined chord override */ async saveUserChord(instrument: string, chordName: string, chordData: InstrumentDefault): Promise<void> { await indexedDBService.saveUserChord(instrument, chordName, chordData); console.log(`[ChordDataService] Saved user chord: ${instrument} - ${chordName}`); } /** * Delete a user-defined chord override (revert to system default) */ async deleteUserChord(instrument: string, chordName: string): Promise<void> { await indexedDBService.deleteUserChord(instrument, chordName); console.log(`[ChordDataService] Deleted user chord: ${instrument} - ${chordName}`); } /** * Get all user-defined chords for an instrument */ async getUserChordsByInstrument(instrument: string): Promise<Array<{ chordName: string, data: InstrumentDefault }>> { const userChords = await indexedDBService.getUserChordsByInstrument(instrument); return userChords.map(chord => ({ chordName: chord.chordName, data: { fingers: chord.fingers, barres: chord.barres, position: chord.position } })); } /** * Get all user-defined chords across all instruments */ async getAllUserChords(): Promise<Array<{ instrument: string, chordName: string, data: InstrumentDefault }>> { const userChords = await indexedDBService.getAllUserChords(); return userChords.map(chord => ({ instrument: chord.instrument, chordName: chord.chordName, data: { fingers: chord.fingers, barres: chord.barres, position: chord.position } })); } /** * Clear all user-defined chord overrides */ async clearUserChords(): Promise<void> { await indexedDBService.clearUserChords(); console.log('[ChordDataService] Cleared all user chords'); } } // Export a singleton instance export const chordDataService = new ChordDataService();