CtrlK
BlogDocsLog inGet started
Tessl Logo

tessl/npm-pouchdb

PouchDB is a pocket-sized database inspired by Apache CouchDB that runs in the browser and Node.js.

Pending

Quality

Pending

Does it follow best practices?

Impact

Pending

No eval scenarios have been run

Overview
Eval results
Files

replication-sync.mddocs/

Replication and Synchronization

Bidirectional synchronization with remote databases, including continuous replication and conflict resolution. PouchDB's replication system enables offline-first applications with seamless data synchronization.

Capabilities

One-Way Replication

Replicate data from a source database to a target database in one direction.

/**
 * Replicate from another database to this database
 * @param source - Source database (URL string or PouchDB instance)
 * @param options - Replication options
 * @returns Replication object with event emitter interface
 */
db.replicate.from(source: string | PouchDB, options?: ReplicationOptions): Replication;

/**
 * Replicate from this database to another database
 * @param target - Target database (URL string or PouchDB instance)
 * @param options - Replication options
 * @returns Replication object with event emitter interface
 */
db.replicate.to(target: string | PouchDB, options?: ReplicationOptions): Replication;

/**
 * Static method for one-way replication between any two databases
 * @param source - Source database
 * @param target - Target database
 * @param options - Replication options
 * @returns Replication object
 */
PouchDB.replicate(source: string | PouchDB, target: string | PouchDB, options?: ReplicationOptions): Replication;

Usage Examples:

// Replicate from remote to local
const replication = db.replicate.from('http://localhost:5984/remote-db', {
  continuous: true,
  retry: true
}).on('change', function(info) {
  console.log('Received changes:', info.docs.length);
}).on('complete', function(info) {
  console.log('Replication completed');
}).on('error', function(err) {
  console.error('Replication error:', err);
});

// Replicate from local to remote
db.replicate.to('http://localhost:5984/remote-db')
  .on('complete', function(info) {
    console.log('Upload completed:', info.docs_written);
  });

// Static replication method
PouchDB.replicate(localDb, remoteDb, {
  filter: 'myapp/active_users'
});

Bidirectional Synchronization

Synchronize data in both directions between databases with automatic conflict detection.

/**
 * Bidirectional synchronization with another database
 * @param remoteDB - Remote database to sync with
 * @param options - Synchronization options
 * @returns Sync object with event emitter interface
 */
db.sync(remoteDB: string | PouchDB, options?: SyncOptions): Sync;

/**
 * Static method for bidirectional sync between any two databases
 * @param source - First database
 * @param target - Second database
 * @param options - Synchronization options
 * @returns Sync object
 */
PouchDB.sync(source: string | PouchDB, target: string | PouchDB, options?: SyncOptions): Sync;

interface SyncOptions extends ReplicationOptions {
  // Inherits all replication options
}

interface Sync extends EventEmitter {
  cancel(): void;
  on(event: 'change', listener: (info: SyncChangeInfo) => void): Sync;
  on(event: 'complete', listener: (info: SyncComplete) => void): Sync;
  on(event: 'error', listener: (error: Error) => void): Sync;
  on(event: 'active', listener: () => void): Sync;
  on(event: 'paused', listener: () => void): Sync;
}

Usage Examples:

// Basic bidirectional sync
const sync = db.sync('http://localhost:5984/remote-db', {
  live: true,
  retry: true
}).on('change', function(info) {
  console.log('Sync change:', info.direction);
  if (info.direction === 'pull') {
    console.log('Downloaded:', info.change.docs.length);
  } else {
    console.log('Uploaded:', info.change.docs_written);
  }
}).on('active', function() {
  console.log('Sync active');
}).on('paused', function() {
  console.log('Sync paused');
});

// Stop sync
sync.cancel();

Replication Options

Comprehensive configuration options for customizing replication behavior.

interface ReplicationOptions {
  continuous?: boolean;          // Continuous replication
  retry?: boolean;               // Retry on failure
  filter?: string | Function;    // Replication filter
  query_params?: object;         // Filter parameters
  doc_ids?: string[];            // Replicate specific documents only
  checkpoint?: string | boolean; // Checkpoint handling
  batch_size?: number;           // Number of docs per batch (default: 100)
  batches_limit?: number;        // Number of concurrent batches (default: 10)
  since?: number | string;       // Starting sequence number
  timeout?: number;              // Request timeout
  heartbeat?: number;            // Heartbeat interval
  selector?: MangoSelector;      // Mango query selector (if supported)
}

interface Replication extends EventEmitter {
  cancel(): void;
  on(event: 'change', listener: (info: ReplicationChangeInfo) => void): Replication;
  on(event: 'complete', listener: (info: ReplicationComplete) => void): Replication;
  on(event: 'error', listener: (error: Error) => void): Replication;
  on(event: 'active', listener: () => void): Replication;
  on(event: 'paused', listener: () => void): Replication;
}

interface ReplicationChangeInfo {
  docs: Document[];              // Documents that changed
  docs_read: number;             // Number of docs read
  docs_written: number;          // Number of docs written
  doc_write_failures: number;    // Number of write failures
  errors: Error[];               // Array of errors
  last_seq: number | string;     // Last sequence processed
  start_time: string;            // Replication start time
}

Usage Examples:

// Filtered replication
const filteredReplication = db.replicate.from(remoteDb, {
  filter: function(doc) {
    return doc.type === 'user' && doc.active === true;
  },
  continuous: true,
  retry: true,
  batch_size: 50,
  batches_limit: 5
});

// Replicate specific documents
const specificReplication = db.replicate.from(remoteDb, {
  doc_ids: ['user1', 'user2', 'config'],
  continuous: true
});

// Replicate with design document filter
const designDocReplication = db.replicate.from(remoteDb, {
  filter: 'myapp/active_users',
  query_params: {
    department: 'engineering'
  }
});

Authentication and Security

Handle authentication for remote database replication.

// Basic authentication
const authReplication = db.replicate.from('http://username:password@localhost:5984/remote-db');

// URL with credentials
const remoteUrl = 'http://localhost:5984/remote-db';
const replication = db.replicate.from(remoteUrl, {
  auth: {
    username: 'myuser',
    password: 'mypassword'
  }
});

// Custom headers
const headerReplication = db.replicate.from(remoteUrl, {
  headers: {
    'Authorization': 'Bearer ' + token,
    'X-Custom-Header': 'value'
  }
});

Conflict Resolution

Handle and resolve conflicts that occur during replication.

// Monitor for conflicts during sync
const conflictSync = db.sync(remoteDb, {
  live: true,
  retry: true
}).on('change', function(info) {
  // Check for conflicts in replicated documents
  if (info.direction === 'pull') {
    info.change.docs.forEach(async (doc) => {
      if (doc._conflicts) {
        await handleConflict(doc);
      }
    });
  }
});

async function handleConflict(doc) {
  console.log(`Conflict in document ${doc._id}`);
  
  // Get all conflicting revisions
  const conflicts = await Promise.all(
    doc._conflicts.map(rev => db.get(doc._id, { rev }))
  );
  
  // Resolve conflict (example: merge strategy)
  const resolved = mergeDocuments(doc, conflicts);
  
  // Save resolved version
  await db.put(resolved);
  
  // Delete conflicting revisions
  for (const conflict of conflicts) {
    await db.remove(conflict._id, conflict._rev);
  }
}

function mergeDocuments(main, conflicts) {
  // Implement your merge strategy
  // This example combines all unique fields
  const merged = { ...main };
  
  conflicts.forEach(conflict => {
    Object.keys(conflict).forEach(key => {
      if (!key.startsWith('_') && !merged[key]) {
        merged[key] = conflict[key];
      }
    });
  });
  
  return merged;
}

Continuous Sync with Error Handling

Implement robust continuous synchronization with proper error handling and recovery.

function startContinuousSync(localDb, remoteUrl) {
  let syncHandler;
  
  function createSync() {
    syncHandler = localDb.sync(remoteUrl, {
      live: true,
      retry: true,
      heartbeat: 10000,
      timeout: 30000
    });
    
    syncHandler.on('change', function(info) {
      console.log(`Sync ${info.direction}:`, info.change.docs.length, 'docs');
      updateSyncStatus('active', info);
    });
    
    syncHandler.on('active', function() {
      console.log('Sync resumed');
      updateSyncStatus('active');
    });
    
    syncHandler.on('paused', function(err) {
      if (err) {
        console.log('Sync paused due to error:', err);
        updateSyncStatus('error', err);
      } else {
        console.log('Sync paused');
        updateSyncStatus('paused');
      }
    });
    
    syncHandler.on('error', function(err) {
      console.error('Sync error:', err);
      updateSyncStatus('error', err);
      
      // Restart sync after delay
      setTimeout(() => {
        console.log('Restarting sync...');
        createSync();
      }, 5000);
    });
    
    syncHandler.on('complete', function(info) {
      console.log('Sync completed');
      updateSyncStatus('complete', info);
    });
    
    return syncHandler;
  }
  
  function updateSyncStatus(status, data) {
    // Update UI or application state
    document.dispatchEvent(new CustomEvent('syncStatusChange', {
      detail: { status, data }
    }));
  }
  
  // Start initial sync
  return createSync();
}

// Usage
const syncHandler = startContinuousSync(db, 'http://localhost:5984/remote-db');

// Stop sync when needed
function stopSync() {
  if (syncHandler) {
    syncHandler.cancel();
  }
}

Selective Replication

Replicate only specific types of documents or data subsets.

// Replicate only user documents
const userReplication = db.replicate.from(remoteDb, {
  filter: function(doc) {
    return doc._id.startsWith('user_');
  },
  continuous: true
});

// Replicate documents modified after specific date
const recentReplication = db.replicate.from(remoteDb, {
  filter: function(doc) {
    const modifiedDate = new Date(doc.modified);
    const cutoffDate = new Date('2023-01-01');
    return modifiedDate > cutoffDate;
  }
});

// Use query parameters with design document filter
const parameterizedFilter = db.replicate.from(remoteDb, {
  filter: 'myapp/by_department',
  query_params: {
    department: 'sales',
    active: true
  }
});

Event Handling Patterns

Promise-based Replication

Convert replication to Promise for one-time operations:

function replicateOnce(source, target, options = {}) {
  return new Promise((resolve, reject) => {
    PouchDB.replicate(source, target, {
      ...options,
      continuous: false
    }).on('complete', resolve).on('error', reject);
  });
}

// Usage
try {
  const result = await replicateOnce(localDb, remoteDb);
  console.log('Replication completed:', result.docs_written);
} catch (error) {
  console.error('Replication failed:', error);
}

Sync State Management

Track synchronization state across application lifecycle:

class SyncManager {
  constructor(localDb, remoteUrl) {
    this.localDb = localDb;
    this.remoteUrl = remoteUrl;
    this.syncHandler = null;
    this.status = 'stopped';
    this.listeners = new Set();
  }
  
  start() {
    if (this.syncHandler) {
      this.stop();
    }
    
    this.syncHandler = this.localDb.sync(this.remoteUrl, {
      live: true,
      retry: true
    });
    
    this.syncHandler.on('change', (info) => {
      this.emit('change', info);
    });
    
    this.syncHandler.on('active', () => {
      this.status = 'active';
      this.emit('statusChange', 'active');
    });
    
    this.syncHandler.on('paused', () => {
      this.status = 'paused';
      this.emit('statusChange', 'paused');
    });
    
    this.syncHandler.on('error', (err) => {
      this.status = 'error';
      this.emit('error', err);
    });
  }
  
  stop() {
    if (this.syncHandler) {
      this.syncHandler.cancel();
      this.syncHandler = null;
    }
    this.status = 'stopped';
    this.emit('statusChange', 'stopped');
  }
  
  on(event, callback) {
    this.listeners.add({ event, callback });
  }
  
  emit(event, data) {
    this.listeners.forEach(listener => {
      if (listener.event === event) {
        listener.callback(data);
      }
    });
  }
}

// Usage
const syncManager = new SyncManager(db, 'http://localhost:5984/remote-db');
syncManager.on('statusChange', (status) => {
  console.log('Sync status:', status);
});
syncManager.start();

Error Handling

Handle various error scenarios in replication:

const replication = db.replicate.from(remoteDb)
  .on('error', function(err) {
    if (err.status === 401) {
      console.log('Authentication failed');
      // Handle auth error
    } else if (err.status === 404) {
      console.log('Remote database not found');
      // Handle missing database
    } else if (err.name === 'timeout') {
      console.log('Replication timeout');
      // Retry or handle timeout
    } else if (err.error === 'forbidden') {
      console.log('Insufficient permissions');
      // Handle permissions error
    } else {
      console.error('Replication error:', err);
    }
  });

Common error scenarios:

  • Network connectivity issues
  • Authentication and authorization failures
  • Database not found or deleted
  • Insufficient disk space
  • Replication conflicts
  • Invalid document structures

Install with Tessl CLI

npx tessl i tessl/npm-pouchdb

docs

changes-monitoring.md

database-management.md

document-operations.md

index.md

plugins-adapters.md

replication-sync.md

tile.json