Ir al contenido

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.

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 para find, get, create y update.
  • apiUrl: base del endpoint de sincronización (POST {apiUrl}/sync).
  • deviceInfo: identidad del dispositivo; deviceInfo.id se envía como deviceId.
  • networkMonitor: fuente de verdad de conectividad (getStatus().online).

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.

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.

Detiene el timer de sincronización automática y lo deja en null. Es seguro llamarlo aunque no haya timer activo.

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.

  1. Guards. Si ya hay una sincronización en curso devuelve success: false con error: 'Sincronización ya en progreso'. Si no hay token, devuelve error: 'No hay token de autenticación'.

  2. Recolectar pendientes. Busca documentos con metadata.syncStatus === 'pending'. Si no hay ninguno, actualiza lastSyncTime y retorna éxito con synced: 0.

  3. Enviar al servidor. Hace POST {apiUrl}/sync con header Authorization: Bearer {token} y cuerpo { deviceId, changes, lastSyncTime }. Un response no-OK lanza y se captura.

  4. Procesar respuesta. Aplica los cambios recibidos del servidor (processServerChanges), marca los enviados como synced, recalcula el contador de pendientes y refresca lastSyncTime.

  5. Retornar resultado. Devuelve un SyncResult con details.sent, details.received y details.conflicts.

Devuelve el timestamp ISO de la última sincronización exitosa, o null si aún no ocurrió ninguna.

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().

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
};
}

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) incluye strategy: 'field-level-merge', localVersion, remoteVersion, mergedVersion, los arreglos fieldsFromServer / fieldsFromLocal / fieldsConflicted y conflictWinner.
  • conflict:unresolved — ningún resolver pudo manejar el conflicto. El payload (ConflictUnresolvedData) incluye documentType, documentId, localVersion, remoteVersion, reason y campos auxiliares como idPrefix y conflictingFields. En este caso se aplica un fallback que usa el documento del servidor.

El broker mantiene estado privado que condiciona su comportamiento:

  • token — credencial; sin ella synchronize() no opera.
  • lastSyncTime — expuesto vía getLastSyncTime().
  • syncTimer — handle del setInterval de auto-sync.
  • isSyncing — flag de reentrada; evita ciclos solapados.
  • pendingSyncCount — expuesto vía getPendingSyncCount().

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.