Cloud Sync Guide

Ecosistema completo para organización y gestión de contenido con interfaces espaciales

☁️ Complete Cloud Sync Guide

Comprehensive guide to all cloud synchronization options: MongoDB, Firebase/Firestore, and REST APIs.


Overview

Our storage system provides optional cloud synchronization while maintaining blazing-fast local-first performance.

Session 166.3 Update: For fire-and-forget backend sync with persistent retry queue, use the new BackendSyncEngine. It provides SyncDescriptor<T> (5-line descriptor per feature) + syncToBackend() (one-liner for autoFlashState signals) with automatic offline detection, retry, and dedup. The patterns below remain valid for advanced/custom sync scenarios.

Key Principles

  1. Offline-First: Local storage always fast (0.3ms cache hits)
  2. Optional: Cloud sync is completely optional
  3. Strategic: Configure per-collection/key strategies
  4. Flexible: MongoDB, Firebase, or REST API backends
  5. Resilient (Session 166.3): Persistent retry queue survives page refresh

📡 MongoDB Sync (Recommended)

Why MongoDB?

Quick Setup

// app.config.ts
import { provideMongoDBSync } from '@storage-system';

export const appConfig: ApplicationConfig = {
  providers: [
    provideGgStorage({ namespace: 'my-app' }),
    provideMongoDBSync({
      connectionString: environment.mongoDbUri,
      database: 'my-app',
      defaultStrategy: {
        mode: 'offline-first',  // Local first, sync in background
        priority: 'normal',     // Sync every 30 minutes
        conflictResolution: 'last-write-wins'
      },
      enableRealtime: true,     // Enable Change Streams
      autoInit: true            // Initialize on app start
    })
  ]
};

Basic Usage

import { autoFlashState } from '@storage-system';

// Automatic MongoDB sync with offline-first strategy
const userPreferences = autoFlashState({
  key: 'preferences',
  defaultValue: { theme: 'dark', language: 'en' },
  database: 'app-data',
  collection: 'user-preferences',
  priority: 'high'  // Sync every 5 minutes
});

// Instant local access (0.3ms) + background sync to MongoDB
userPreferences.set({ theme: 'light', language: 'es' });

Advanced Configuration

provideMongoDBSync({
  connectionString: 'mongodb+srv://...',
  database: 'my-app',
  defaultStrategy: {
    mode: 'offline-first',
    priority: 'normal',
    conflictResolution: 'merge'  // Field-level merge
  },
  enableRealtime: true,
  autoInit: true,
  // Per-collection overrides
  collectionConfig: {
    'chat-messages': {
      mode: 'realtime',        // Real-time for chat
      priority: 'critical'     // Immediate sync
    },
    'user-analytics': {
      mode: 'offline-first',   // Offline for analytics
      priority: 'low'          // Manual sync only
    }
  }
})

Sync Strategies

Offline-First (Default)

{
  mode: 'offline-first',
  priority: 'normal'  // Sync every 30 minutes
}

Online-First

{
  mode: 'online-first',
  priority: 'high'  // Sync every 5 minutes
}

Realtime

{
  mode: 'realtime',
  priority: 'critical'  // Immediate sync
}

Priority Levels

Conflict Resolution

// Last-Write-Wins (Default)
conflictResolution: 'last-write-wins'

// Field-Level Merge
conflictResolution: 'merge'

// Custom Logic
conflictResolution: (local, remote) => {
  // Your custom merge logic
  return { ...remote, ...local };
}

Complete Example

// services/data.service.ts
@Injectable({ providedIn: 'root' })
export class DataService {
  // Critical data - immediate sync
  authToken = autoFlashState({
    key: 'auth-token',
    defaultValue: null,
    database: 'auth',
    collection: 'tokens',
    priority: 'critical'
  });

  // User preferences - sync every 5 min
  preferences = autoFlashState({
    key: 'user-prefs',
    defaultValue: { theme: 'light', language: 'en' },
    database: 'user',
    collection: 'preferences',
    priority: 'high'
  });

  // Analytics - manual sync only
  analytics = autoFlashState({
    key: 'analytics',
    defaultValue: [],
    database: 'analytics',
    collection: 'events',
    priority: 'low'
  });
}

🔥 Firebase/Firestore Sync

Why Firebase?

Quick Setup

import { FirestoreSyncAdapter } from '@storage-system';

@Component({})
export class AppComponent {
  private storage = inject(StorageService);

  async ngOnInit() {
    const adapter = new FirestoreSyncAdapter({
      databaseId: 'g-space-db-standard',
      collection: 'app-data'
    });

    await adapter.waitForReady();
    this.storage.setSyncAdapter(adapter);
    
    console.log('✅ Firebase sync enabled');
  }
}

Configuration

interface FirestoreSyncConfig {
  databaseId?: string;      // Firebase database ID
  collection?: string;      // Firestore collection name
  // Firebase credentials auto-detected from environment
}

Core Operations

Push Changes

const changes = [{
  key: 'user/profile',
  op: 'put',
  value: { name: 'John Doe', email: '[email protected]' },
  updatedAt: Date.now()
}];

const result = await adapter.push(changes);
console.log('Synced:', result.acked);

Pull Changes

// Pull all changes
const pullResult = await adapter.pull(null, 100);
console.log('Retrieved:', pullResult.changes.length);

// Apply changes locally
for (const change of pullResult.changes) {
  if (change.op === 'put') {
    await storage.put(change.key, change.value);
  }
}

Real-Time Sync

const unsubscribe = await adapter.setupRealtimeSync((changes) => {
  console.log('Real-time changes:', changes);
  
  for (const change of changes) {
    if (change.op === 'put') {
      await storage.put(change.key, change.value);
    }
  }
});

// Cleanup on destroy
ngOnDestroy() {
  unsubscribe();
}

Database Management

// Switch between databases
await adapter.switchDatabase('g-space-db-ent');
console.log('Switched to enterprise database');

// Create new collection
await adapter.createCollection('user-preferences');

// Switch collection
await adapter.setCollection('project-data');

// Get current database info
const info = adapter.getDatabaseInfo();
console.log('Database:', info);

Complete Example

@Injectable({ providedIn: 'root' })
export class FirebaseSyncService {
  private storage = inject(StorageService);
  private adapter: FirestoreSyncAdapter;

  async initialize() {
    this.adapter = new FirestoreSyncAdapter({
      databaseId: 'g-space-db-standard',
      collection: 'app-data'
    });

    await this.adapter.waitForReady();
    this.storage.setSyncAdapter(this.adapter);

    // Setup real-time sync
    await this.setupRealtimeSync();
  }

  private async setupRealtimeSync() {
    await this.adapter.setupRealtimeSync(async (changes) => {
      for (const change of changes) {
        if (change.op === 'put') {
          await this.storage.put(change.key, change.value);
        } else if (change.op === 'remove') {
          await this.storage.remove(change.key);
        }
      }
    });
  }

  async switchToEnterprise() {
    await this.adapter.switchDatabase('g-space-db-ent');
  }
}

🌐 REST API Sync

Why REST API?

Quick Setup

import { ApiSyncAdapter } from '@storage-system';

const adapter = new ApiSyncAdapter({
  baseUrl: 'https://api.myapp.com',
  endpoint: '/storage/sync',
  apiKey: environment.apiKey,
  headers: {
    'Authorization': `Bearer ${token}`
  }
});

storage.setSyncAdapter(adapter);

Mock API Integration

For development and testing:

// mock-api/storage.mock.ts
import { MockApiHandler } from '@mock-api/core';

export const storageMockApi: MockApiHandler[] = [
  // Pull changes
  {
    method: 'GET',
    path: '/api/storage/sync',
    handler: (req) => {
      const cursor = req.query.cursor || '0';
      return {
        cursor: Date.now().toString(),
        changes: [
          {
            key: 'user/profile',
            op: 'put',
            value: { name: 'John', updated: true },
            updatedAt: Date.now()
          }
        ]
      };
    }
  },

  // Push changes
  {
    method: 'POST',
    path: '/api/storage/sync',
    handler: (req) => {
      const changes = req.body.changes;
      
      // Store changes
      mockDatabase.set(changes);
      
      return {
        success: true,
        processed: changes.length
      };
    }
  }
];

Configuration

interface ApiSyncConfig {
  baseUrl: string;                    // API base URL
  endpoint: string;                   // Sync endpoint
  apiKey?: string;                    // API key
  headers?: Record<string, string>;   // Custom headers
  conflictResolution?: 'last-write-wins' | ((local, remote) => any);
}

Complete Example

@Injectable({ providedIn: 'root' })
export class ApiSyncService {
  private storage = inject(StorageService);

  async initialize() {
    const adapter = new ApiSyncAdapter({
      baseUrl: environment.apiUrl,
      endpoint: '/storage/sync',
      headers: {
        'Authorization': `Bearer ${this.getToken()}`,
        'Content-Type': 'application/json'
      },
      conflictResolution: (local, remote) => {
        // Custom conflict resolution
        if (remote.updatedAt > local.updatedAt) {
          return remote;
        }
        return local;
      }
    });

    this.storage.setSyncAdapter(adapter);
    
    // Start periodic sync
    this.startPeriodicSync();
  }

  private startPeriodicSync() {
    setInterval(async () => {
      await this.performSync();
    }, 300000); // Every 5 minutes
  }

  private async performSync() {
    const adapter = this.storage.getSyncAdapter();
    if (!adapter) return;

    try {
      // Pull remote changes
      const status = await adapter.getStatus();
      const pullResult = await adapter.pull(status.cursor);
      
      // Apply changes locally
      for (const change of pullResult.changes) {
        if (change.op === 'put') {
          await this.storage.put(change.key, change.value);
        }
      }

      console.log('✅ Sync complete');
    } catch (error) {
      console.error('❌ Sync failed:', error);
    }
  }
}

📊 Comparison Matrix

Feature MongoDB Firebase REST API
Setup Complexity Medium Low Low
Real-Time Updates Change Streams Native Custom
Offline-First ✅ Built-in Requires setup Requires setup
Conflict Resolution ✅ Advanced Basic Custom
Priority-Based Sync ✅ Built-in Custom Custom
Scalability Excellent Excellent Depends
Cost Pay-as-you-go Pay-as-you-go Your backend
Best For Production apps Real-time apps Existing APIs

🎯 Choosing the Right Sync

Use MongoDB If:

Use Firebase If:

Use REST API If:


🚀 Best Practices

1. Start Offline-First

Always keep local performance optimal:

// ✅ Good: Local always fast
const settings = autoFlashState({
  key: 'settings',
  defaultValue: {},
  database: 'app',
  collection: 'settings',
  priority: 'normal'  // Background sync
});

// ❌ Avoid: Blocking on network
await fetch('/api/settings'); // Slow!

2. Use Appropriate Priorities

Match sync priority to data importance:

// Critical: Auth tokens
priority: 'critical'  // Immediate sync

// High: User preferences
priority: 'high'  // Every 5 minutes

// Normal: App data
priority: 'normal'  // Every 30 minutes

// Low: Analytics
priority: 'low'  // Manual sync only

3. Handle Offline Gracefully

// Detect online/offline
window.addEventListener('online', () => {
  console.log('✅ Back online - syncing...');
  syncService.performSync();
});

window.addEventListener('offline', () => {
  console.log('📴 Offline - changes queued');
});

4. Monitor Sync Status

const syncStatus = inject(SyncStatusService);

// Watch sync status
effect(() => {
  const status = syncStatus.status();
  console.log('Sync status:', status);
  console.log('Pending operations:', status.pendingOperations);
});

📚 Related Documentation


Choose the sync strategy that fits your needs - all maintain local-first performance! ⚡