PouchDB is a pocket-sized database inspired by Apache CouchDB that runs in the browser and Node.js.
—
Quality
Pending
Does it follow best practices?
Impact
Pending
No eval scenarios have been run
Bidirectional synchronization with remote databases, including continuous replication and conflict resolution. PouchDB's replication system enables offline-first applications with seamless data synchronization.
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'
});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();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'
}
});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'
}
});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;
}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();
}
}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
}
});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);
}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();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:
Install with Tessl CLI
npx tessl i tessl/npm-pouchdb