feat(sync): add pagination, cancellation, and network restoration

- CloudKit pagination: fetchAllRecords() handles >400 record batches
  with cursor-based pagination (400 records per page)
- Cancellation support: SyncCancellationToken protocol enables graceful
  sync termination when background tasks expire
- Per-entity progress: SyncState now tracks timestamps per entity type
  so interrupted syncs resume where they left off
- NetworkMonitor: NWPathMonitor integration triggers sync on network
  restoration with 2.5s debounce to handle WiFi↔cellular flapping
- wasCancelled flag in SyncResult distinguishes partial from full syncs

This addresses critical data sync issues:
- CloudKit queries were limited to ~400 records but bundled data has ~5000 games
- Background tasks could be killed mid-sync without saving progress
- App had no awareness of network state changes

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
Trey t
2026-01-13 19:18:55 -06:00
parent 00b33202f5
commit 5686af262f
8 changed files with 1607 additions and 94 deletions

View File

@@ -0,0 +1,27 @@
//
// SyncCancellationToken.swift
// SportsTime
//
// Cancellation support for long-running sync operations.
//
import Foundation
import os
/// Protocol for cancellation tokens checked between sync pages
protocol SyncCancellationToken: Sendable {
var isCancelled: Bool { get }
}
/// Concrete cancellation token for background tasks
final class BackgroundTaskCancellationToken: SyncCancellationToken, @unchecked Sendable {
private let lock = OSAllocatedUnfairLock(initialState: false)
var isCancelled: Bool {
lock.withLock { $0 }
}
func cancel() {
lock.withLock { $0 = true }
}
}