SyncManager
SyncManager es el broker responsable de sincronizar los datos entre la base de
datos local (PouchDB) y el servidor Tables. Coordina el envío de cambios
pendientes, procesa los cambios recibidos del servidor, resuelve conflictos y
expone el estado de sincronización al resto del SDK.
Para el panorama completo del modelo offline-first y la replicación PouchDB↔CouchDB, ver sincronización. Para los demás brokers del SDK, ver brokers.
Construcción
Sección titulada «Construcción»El constructor recibe sus dependencias por inyección y registra los listeners internos de base de datos:
new SyncManager( database: Database, apiUrl: string, deviceInfo: DeviceInfo, networkMonitor: NetworkMonitor)database: abstracción de PouchDB usada parafind,get,createyupdate.apiUrl: base del endpoint de sincronización (POST {apiUrl}/sync).deviceInfo: identidad del dispositivo;deviceInfo.idse envía comodeviceId.networkMonitor: fuente de verdad de conectividad (getStatus().online).
Métodos públicos
Sección titulada «Métodos públicos»initialize(token: string): Promise<void>
Sección titulada «initialize(token: string): Promise<void>»Guarda el token de autenticación y precarga el contador de cambios pendientes
mediante un conteo de documentos con metadata.syncStatus === 'pending'. Debe
llamarse antes de cualquier sincronización; sin token, synchronize() falla.
startAutoSync(interval?: number): void
Sección titulada «startAutoSync(interval?: number): void»Inicia la sincronización automática con un setInterval. El intervalo por
defecto es 300000 ms (5 minutos). En cada tick solo sincroniza si hay conexión
(networkMonitor.getStatus().online) y no hay una sincronización en curso
(isSyncing === false). Si ya existía un timer, lo limpia antes de crear el
nuevo.
stopAutoSync(): void
Sección titulada «stopAutoSync(): void»Detiene el timer de sincronización automática y lo deja en null. Es seguro
llamarlo aunque no haya timer activo.
synchronize(): Promise<SyncResult>
Sección titulada «synchronize(): Promise<SyncResult>»Ejecuta un ciclo de sincronización manual (o disparado por startAutoSync).
Siempre resuelve con un SyncResult, nunca lanza: los errores se devuelven en el
campo error.
-
Guards. Si ya hay una sincronización en curso devuelve
success: falseconerror: 'Sincronización ya en progreso'. Si no hay token, devuelveerror: 'No hay token de autenticación'. -
Recolectar pendientes. Busca documentos con
metadata.syncStatus === 'pending'. Si no hay ninguno, actualizalastSyncTimey retorna éxito consynced: 0. -
Enviar al servidor. Hace
POST {apiUrl}/synccon headerAuthorization: Bearer {token}y cuerpo{ deviceId, changes, lastSyncTime }. Un response no-OK lanza y se captura. -
Procesar respuesta. Aplica los cambios recibidos del servidor (
processServerChanges), marca los enviados comosynced, recalcula el contador de pendientes y refrescalastSyncTime. -
Retornar resultado. Devuelve un
SyncResultcondetails.sent,details.receivedydetails.conflicts.
getLastSyncTime(): string | null
Sección titulada «getLastSyncTime(): string | null»Devuelve el timestamp ISO de la última sincronización exitosa, o null si aún no
ocurrió ninguna.
getPendingSyncCount(): number
Sección titulada «getPendingSyncCount(): number»Devuelve el número de documentos pendientes de sincronizar conocido. Es un valor
cacheado que se recalcula en initialize() y al final de cada synchronize().
El tipo SyncResult
Sección titulada «El tipo SyncResult»synchronize() resuelve con esta forma:
interface SyncResult { success: boolean; synced: number; // documentos enviados con éxito failed: number; // documentos que fallaron timestamp: string; // ISO de cierre del ciclo error?: string; details?: { sent: number; // enviados al servidor received: number; // recibidos del servidor conflicts: number; // conflictos detectados };}Resolución de conflictos
Sección titulada «Resolución de conflictos»Cuando un documento recibido del servidor tiene un _rev distinto al local,
SyncManager lo trata como conflicto y delega en defaultConflictPipeline
(merge a nivel de campo). La estrategia preserva los cambios de ambos lados
cuando es posible y solo recurre al timestamp cuando ambos modificaron el mismo
campo.
El resultado del pipeline determina qué evento se emite por managerEventBus:
conflict:resolved— el pipeline pudo mergear. El payload (ConflictResolutionData) incluyestrategy: 'field-level-merge',localVersion,remoteVersion,mergedVersion, los arreglosfieldsFromServer/fieldsFromLocal/fieldsConflictedyconflictWinner.conflict:unresolved— ningún resolver pudo manejar el conflicto. El payload (ConflictUnresolvedData) incluyedocumentType,documentId,localVersion,remoteVersion,reasony campos auxiliares comoidPrefixyconflictingFields. En este caso se aplica un fallback que usa el documento del servidor.
Estado interno relevante
Sección titulada «Estado interno relevante»El broker mantiene estado privado que condiciona su comportamiento:
token— credencial; sin ellasynchronize()no opera.lastSyncTime— expuesto víagetLastSyncTime().syncTimer— handle delsetIntervalde auto-sync.isSyncing— flag de reentrada; evita ciclos solapados.pendingSyncCount— expuesto víagetPendingSyncCount().
Relación con otros brokers
Sección titulada «Relación con otros brokers»SyncManager no monitorea la red por sí mismo: consulta a
NetworkMonitor en cada tick de auto-sync. La emisión de
eventos de conflicto viaja por el bus de managers, de modo que cualquier manager
o app puede reaccionar a conflict:resolved / conflict:unresolved sin acoplarse
directamente al broker.