feat: implement client-side sync with bulk API

Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
2025-11-12 07:50:28 +00:00
parent 82c537d659
commit 73171b5f18
2 changed files with 89 additions and 4 deletions

View File

@@ -76,4 +76,31 @@ describe('HighlightSyncManager', () => {
const syncing = await manager.getSyncingItems() const syncing = await manager.getSyncingItems()
expect(syncing.length).toBe(1) expect(syncing.length).toBe(1)
}) })
it('should perform sync and mark items as synced', async () => {
const highlight: BibleHighlight = {
id: 'h-1',
verseId: 'v-1',
color: 'yellow',
createdAt: Date.now(),
updatedAt: Date.now(),
syncStatus: 'pending'
}
await manager.queueHighlight(highlight)
await manager.init()
// Mock fetch
global.fetch = jest.fn(() =>
Promise.resolve({
ok: true,
json: () => Promise.resolve({ synced: 1, errors: [] })
})
) as jest.Mock
const result = await manager.performSync()
expect(result.synced).toBe(1)
expect(result.errors).toBe(0)
})
}) })

View File

@@ -108,11 +108,69 @@ export class HighlightSyncManager {
} }
} }
startAutoSync(intervalMs: number = 30000, onSyncNeeded?: () => void) { async performSync(): Promise<{ synced: number; errors: number }> {
this.syncInterval = setInterval(async () => { if (!this.db) await this.init()
try {
const pending = await this.getPendingSyncItems() const pending = await this.getPendingSyncItems()
if (pending.length > 0 && onSyncNeeded) { if (pending.length === 0) return { synced: 0, errors: 0 }
onSyncNeeded()
// Mark as syncing
await this.markSyncing(pending.map(h => h.id))
// POST to backend
const response = await fetch('/api/highlights/bulk', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ highlights: pending })
})
if (!response.ok) {
// Mark all as error
const errorIds = pending.map(h => h.id)
await this.markError(errorIds, `HTTP ${response.status}`)
return { synced: 0, errors: pending.length }
}
const result = await response.json()
// Mark successfully synced items
if (result.synced > 0) {
const syncedIds = pending
.filter(h => !result.errors.some((e: any) => e.verseId === h.verseId))
.map(h => h.id)
await this.markSynced(syncedIds)
}
// Mark errored items
if (result.errors && result.errors.length > 0) {
for (const error of result.errors) {
const h = pending.find(item => item.verseId === error.verseId)
if (h) {
await this.markError([h.id], error.error)
}
}
}
return { synced: result.synced, errors: result.errors?.length || 0 }
} catch (error) {
console.error('Sync failed:', error)
const pending = await this.getPendingSyncItems()
if (pending.length > 0) {
await this.markError(
pending.map(h => h.id),
'Network error'
)
}
return { synced: 0, errors: pending.length }
}
}
startAutoSync(intervalMs: number = 30000, onSyncNeeded?: (result: { synced: number; errors: number }) => void) {
this.syncInterval = setInterval(async () => {
const result = await this.performSync()
if (result.synced > 0 || result.errors > 0) {
onSyncNeeded?.(result)
} }
}, intervalMs) }, intervalMs)
} }