Implemented TDD approach for highlight persistence: - Created IndexedDB store with 'highlights' object store - Added indexes for syncStatus and verseId for efficient queries - Implemented CRUD operations: add, update, get, getAll, delete - Added query methods: getHighlightsByVerse, getPendingHighlights - Full test coverage with fake-indexeddb mock - Added structuredClone polyfill for test environment Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
134 lines
4.8 KiB
TypeScript
134 lines
4.8 KiB
TypeScript
import { BibleHighlight } from '@/types'
|
|
|
|
const DB_NAME = 'BiblicalGuide'
|
|
const DB_VERSION = 2 // Increment version if schema changes
|
|
const HIGHLIGHTS_STORE = 'highlights'
|
|
|
|
let db: IDBDatabase | null = null
|
|
|
|
export async function initHighlightsDatabase(): Promise<IDBDatabase> {
|
|
if (db) return db
|
|
|
|
return new Promise((resolve, reject) => {
|
|
const request = indexedDB.open(DB_NAME, DB_VERSION)
|
|
|
|
request.onerror = () => reject(new Error('Failed to open IndexedDB'))
|
|
|
|
request.onsuccess = () => {
|
|
db = request.result
|
|
resolve(db)
|
|
}
|
|
|
|
request.onupgradeneeded = (event) => {
|
|
const database = (event.target as IDBOpenDBRequest).result
|
|
|
|
// Create highlights store if it doesn't exist
|
|
if (!database.objectStoreNames.contains(HIGHLIGHTS_STORE)) {
|
|
const store = database.createObjectStore(HIGHLIGHTS_STORE, { keyPath: 'id' })
|
|
// Index for finding highlights by syncStatus for batch operations
|
|
store.createIndex('syncStatus', 'syncStatus', { unique: false })
|
|
// Index for finding highlights by verse
|
|
store.createIndex('verseId', 'verseId', { unique: false })
|
|
}
|
|
}
|
|
})
|
|
}
|
|
|
|
export async function addHighlight(highlight: BibleHighlight): Promise<string> {
|
|
const db = await initHighlightsDatabase()
|
|
return new Promise((resolve, reject) => {
|
|
const tx = db.transaction(HIGHLIGHTS_STORE, 'readwrite')
|
|
const store = tx.objectStore(HIGHLIGHTS_STORE)
|
|
const request = store.add(highlight)
|
|
|
|
request.onsuccess = () => resolve(request.result as string)
|
|
request.onerror = () => reject(new Error('Failed to add highlight'))
|
|
})
|
|
}
|
|
|
|
export async function updateHighlight(highlight: BibleHighlight): Promise<void> {
|
|
const db = await initHighlightsDatabase()
|
|
return new Promise((resolve, reject) => {
|
|
const tx = db.transaction(HIGHLIGHTS_STORE, 'readwrite')
|
|
const store = tx.objectStore(HIGHLIGHTS_STORE)
|
|
const request = store.put(highlight)
|
|
|
|
request.onsuccess = () => resolve()
|
|
request.onerror = () => reject(new Error('Failed to update highlight'))
|
|
})
|
|
}
|
|
|
|
export async function getHighlight(id: string): Promise<BibleHighlight | null> {
|
|
const db = await initHighlightsDatabase()
|
|
return new Promise((resolve, reject) => {
|
|
const tx = db.transaction(HIGHLIGHTS_STORE, 'readonly')
|
|
const store = tx.objectStore(HIGHLIGHTS_STORE)
|
|
const request = store.get(id)
|
|
|
|
request.onsuccess = () => resolve(request.result || null)
|
|
request.onerror = () => reject(new Error('Failed to get highlight'))
|
|
})
|
|
}
|
|
|
|
export async function getHighlightsByVerse(verseId: string): Promise<BibleHighlight[]> {
|
|
const db = await initHighlightsDatabase()
|
|
return new Promise((resolve, reject) => {
|
|
const tx = db.transaction(HIGHLIGHTS_STORE, 'readonly')
|
|
const store = tx.objectStore(HIGHLIGHTS_STORE)
|
|
const index = store.index('verseId')
|
|
const request = index.getAll(verseId)
|
|
|
|
request.onsuccess = () => resolve(request.result || [])
|
|
request.onerror = () => reject(new Error('Failed to get highlights by verse'))
|
|
})
|
|
}
|
|
|
|
export async function getAllHighlights(): Promise<BibleHighlight[]> {
|
|
const db = await initHighlightsDatabase()
|
|
return new Promise((resolve, reject) => {
|
|
const tx = db.transaction(HIGHLIGHTS_STORE, 'readonly')
|
|
const store = tx.objectStore(HIGHLIGHTS_STORE)
|
|
const request = store.getAll()
|
|
|
|
request.onsuccess = () => resolve(request.result || [])
|
|
request.onerror = () => reject(new Error('Failed to get all highlights'))
|
|
})
|
|
}
|
|
|
|
export async function getPendingHighlights(): Promise<BibleHighlight[]> {
|
|
const db = await initHighlightsDatabase()
|
|
return new Promise((resolve, reject) => {
|
|
const tx = db.transaction(HIGHLIGHTS_STORE, 'readonly')
|
|
const store = tx.objectStore(HIGHLIGHTS_STORE)
|
|
const index = store.index('syncStatus')
|
|
const request = index.getAll(IDBKeyRange.only('pending'))
|
|
|
|
request.onsuccess = () => resolve(request.result || [])
|
|
request.onerror = () => reject(new Error('Failed to get pending highlights'))
|
|
})
|
|
}
|
|
|
|
export async function deleteHighlight(id: string): Promise<void> {
|
|
const db = await initHighlightsDatabase()
|
|
return new Promise((resolve, reject) => {
|
|
const tx = db.transaction(HIGHLIGHTS_STORE, 'readwrite')
|
|
const store = tx.objectStore(HIGHLIGHTS_STORE)
|
|
const request = store.delete(id)
|
|
|
|
request.onsuccess = () => resolve()
|
|
request.onerror = () => reject(new Error('Failed to delete highlight'))
|
|
})
|
|
}
|
|
|
|
export async function clearAllHighlights(): Promise<void> {
|
|
const db = await initHighlightsDatabase()
|
|
return new Promise((resolve, reject) => {
|
|
const tx = db.transaction(HIGHLIGHTS_STORE, 'readwrite')
|
|
const store = tx.objectStore(HIGHLIGHTS_STORE)
|
|
const request = store.clear()
|
|
|
|
request.onsuccess = () => resolve()
|
|
request.onerror = () => reject(new Error('Failed to clear highlights'))
|
|
})
|
|
}
|