🎯 Exemples recommandés
Balanced sample collections from various categories for you to explore
Exemples CouchDB
Exemples de base de données NoSQL Apache CouchDB incluant les opérations de documents, les vues et la réplication
💻 Opérations de Base CouchDB javascript
🟢 simple
⭐⭐
Opérations CRUD fondamentales avec les documents et bases de données CouchDB
⏱️ 20 min
🏷️ couchdb, nosql, document database, nodejs
Prerequisites:
Basic JavaScript/Node.js, NoSQL concepts
// CouchDB Basic Operations
// JavaScript (Node.js) - Using nano library
const nano = require('nano')('http://localhost:5984');
// 1. Database Operations
class CouchDBManager {
constructor(dbName) {
this.dbName = dbName;
this.db = nano.db.use(dbName);
}
// Create database
async createDatabase() {
try {
await nano.db.create(this.dbName);
console.log(`✅ Database '${this.dbName}' created successfully`);
return true;
} catch (error) {
if (error.statusCode === 412) {
console.log(`ℹ️ Database '${this.dbName}' already exists`);
} else {
console.error('❌ Error creating database:', error.message);
}
return false;
}
}
// Delete database
async deleteDatabase() {
try {
await nano.db.destroy(this.dbName);
console.log(`✅ Database '${this.dbName}' deleted successfully`);
return true;
} catch (error) {
console.error('❌ Error deleting database:', error.message);
return false;
}
}
// List all databases
async listDatabases() {
try {
const databases = await nano.db.list();
console.log('📊 Available databases:', databases);
return databases;
} catch (error) {
console.error('❌ Error listing databases:', error.message);
return [];
}
}
// 2. Document Operations (CRUD)
// Create document
async createDocument(doc) {
try {
const response = await this.db.insert(doc);
console.log(`✅ Document created with ID: ${response.id}, Rev: ${response.rev}`);
return response;
} catch (error) {
console.error('❌ Error creating document:', error.message);
throw error;
}
}
// Read document by ID
async getDocument(docId) {
try {
const doc = await this.db.get(docId);
console.log(`📄 Retrieved document: ${JSON.stringify(doc, null, 2)}`);
return doc;
} catch (error) {
if (error.statusCode === 404) {
console.log(`⚠️ Document with ID '${docId}' not found`);
} else {
console.error('❌ Error getting document:', error.message);
}
return null;
}
}
// Update document
async updateDocument(docId, updatedDoc) {
try {
// First get the current document to get the _rev
const currentDoc = await this.getDocument(docId);
if (!currentDoc) {
throw new Error('Document not found');
}
// Update the document with current _rev
const docToUpdate = {
...updatedDoc,
_id: docId,
_rev: currentDoc._rev
};
const response = await this.db.insert(docToUpdate);
console.log(`✅ Document updated with new Rev: ${response.rev}`);
return response;
} catch (error) {
console.error('❌ Error updating document:', error.message);
throw error;
}
}
// Delete document
async deleteDocument(docId) {
try {
const doc = await this.getDocument(docId);
if (!doc) {
throw new Error('Document not found');
}
const response = await this.db.destroy(docId, doc._rev);
console.log(`✅ Document '${docId}' deleted successfully`);
return response;
} catch (error) {
console.error('❌ Error deleting document:', error.message);
throw error;
}
}
// 3. Bulk Operations
// Bulk create documents
async bulkCreateDocuments(docs) {
try {
const response = await this.db.bulk({ docs });
console.log(`✅ Bulk operation completed. ${response.filter(r => !r.error).length} documents created`);
return response;
} catch (error) {
console.error('❌ Error in bulk create:', error.message);
throw error;
}
}
// 4. Query Operations
// Get all documents
async getAllDocuments() {
try {
const response = await this.db.list({ include_docs: true });
console.log(`📋 Found ${response.total_rows} documents`);
return response.rows.map(row => row.doc);
} catch (error) {
console.error('❌ Error getting all documents:', error.message);
return [];
}
}
// Query documents with Mango query
async queryDocuments(selector, options = {}) {
try {
const response = await this.db.find({
selector,
...options
});
console.log(`🔍 Query returned ${response.docs.length} documents`);
return response.docs;
} catch (error) {
console.error('❌ Error querying documents:', error.message);
return [];
}
}
// 5. Document Design and Views
async createDesignDocument(dDoc) {
try {
const response = await this.db.insert(dDoc);
console.log(`✅ Design document created: ${response.id}`);
return response;
} catch (error) {
console.error('❌ Error creating design document:', error.message);
throw error;
}
}
// Query view
async queryView(dDocName, viewName, options = {}) {
try {
const response = await this.db.view(dDocName, viewName, options);
console.log(`📊 View '${dDocName}/${viewName}' returned ${response.rows.length} results`);
return response;
} catch (error) {
console.error('❌ Error querying view:', error.message);
return null;
}
}
}
// Usage Examples
async function demonstrateCouchDB() {
const dbManager = new CouchDBManager('example_db');
// Create database
await dbManager.createDatabase();
// Create sample documents
const userDoc = {
type: 'user',
name: 'John Doe',
email: '[email protected]',
age: 30,
created_at: new Date().toISOString()
};
const productDoc = {
type: 'product',
name: 'Laptop',
price: 999.99,
category: 'Electronics',
in_stock: true,
created_at: new Date().toISOString()
};
// Create documents
const userResult = await dbManager.createDocument(userDoc);
const productResult = await dbManager.createDocument(productDoc);
// Read documents
await dbManager.getDocument(userResult.id);
// Update document
await dbManager.updateDocument(userResult.id, {
...userDoc,
age: 31,
last_updated: new Date().toISOString()
});
// Query with Mango
const electronics = await dbManager.queryDocuments({
type: 'product',
category: 'Electronics'
});
console.log('Electronics products:', electronics);
// Create design document with views
const designDoc = {
_id: '_design/products',
views: {
by_category: {
map: function(doc) {
if (doc.type === 'product' && doc.category) {
emit(doc.category, doc.price);
}
}.toString()
},
in_stock: {
map: function(doc) {
if (doc.type === 'product' && doc.in_stock === true) {
emit(doc.name, null);
}
}.toString()
}
}
};
await dbManager.createDesignDocument(designDoc);
// Query views
const categoryView = await dbManager.queryView('products', 'by_category', {
group: true,
reduce: true
});
console.log('Products by category:', categoryView);
}
// 6. Attachment Management
class CouchAttachmentManager extends CouchDBManager {
// Add attachment to document
async addAttachment(docId, attachmentName, attachmentData, contentType) {
try {
const doc = await this.getDocument(docId);
const response = await this.db.attachment.insert(
docId,
attachmentName,
attachmentData,
contentType,
doc._rev
);
console.log(`✅ Attachment '${attachmentName}' added to document '${docId}'`);
return response;
} catch (error) {
console.error('❌ Error adding attachment:', error.message);
throw error;
}
}
// Get attachment
async getAttachment(docId, attachmentName) {
try {
const response = await this.db.attachment.get(docId, attachmentName);
console.log(`📎 Retrieved attachment '${attachmentName}' from document '${docId}'`);
return response;
} catch (error) {
console.error('❌ Error getting attachment:', error.message);
return null;
}
}
// Delete attachment
async deleteAttachment(docId, attachmentName) {
try {
const doc = await this.getDocument(docId);
const response = await this.db.attachment.destroy(docId, attachmentName, doc._rev);
console.log(`🗑️ Attachment '${attachmentName}' deleted from document '${docId}'`);
return response;
} catch (error) {
console.error('❌ Error deleting attachment:', error.message);
throw error;
}
}
}
// 7. Change Management
class CouchChangeTracker extends CouchDBManager {
// Track changes since last update
async trackChanges(since = 'now', options = {}) {
try {
const feed = this.db.follow({
since,
include_docs: true,
...options
});
feed.on('change', (change) => {
console.log('📝 Change detected:', {
id: change.id,
seq: change.seq,
deleted: change.deleted,
changes: change.changes
});
if (change.doc) {
console.log('📄 Changed document:', change.doc);
}
});
feed.on('error', (error) => {
console.error('❌ Change tracking error:', error);
});
feed.follow();
console.log('👂 Started tracking changes...');
return feed;
} catch (error) {
console.error('❌ Error setting up change tracking:', error.message);
return null;
}
}
// Get changes once
async getChanges(since = 0, options = {}) {
try {
const changes = await this.db.changes({
since,
include_docs: true,
...options
});
console.log(`📋 Found ${changes.results.length} changes`);
return changes;
} catch (error) {
console.error('❌ Error getting changes:', error.message);
return null;
}
}
}
// 8. Replication Management
class CouchReplicationManager {
constructor() {
this.nano = nano('http://localhost:5984');
}
// Setup replication
async setupReplication(source, target, options = {}) {
try {
const replicationDoc = {
_id: `replication_${Date.now()}`,
source,
target,
create_target: options.createTarget || false,
continuous: options.continuous || false,
filter: options.filter,
query_params: options.queryParams,
doc_ids: options.docIds,
...options
};
const response = await this.nano.request({
db: '_replicator',
method: 'post',
body: replicationDoc
});
console.log(`🔄 Replication setup initiated: ${response.id}`);
return response;
} catch (error) {
console.error('❌ Error setting up replication:', error.message);
throw error;
}
}
// Monitor replication
async getReplicationStatus(replicationId) {
try {
const replication = await this.nano.request({
db: '_replicator',
doc: replicationId
});
console.log(`📊 Replication status: ${replication.state}`, {
source: replication.source,
target: replication.target,
state: replication.state,
docs_read: replication.docs_read,
docs_written: replication.docs_written,
errors: replication.errors
});
return replication;
} catch (error) {
console.error('❌ Error getting replication status:', error.message);
return null;
}
}
}
// Run the demonstration
if (require.main === module) {
demonstrateCouchDB().catch(console.error);
}
module.exports = {
CouchDBManager,
CouchAttachmentManager,
CouchChangeTracker,
CouchReplicationManager
};
💻 Vues Map-Reduce CouchDB javascript
🟡 intermediate
⭐⭐⭐⭐
Création et utilisation de vues Map-Reduce pour l'agrégation et l'analyse de données
⏱️ 30 min
🏷️ couchdb, map-reduce, views, analytics, aggregation
Prerequisites:
Basic CouchDB operations, JavaScript functions, Map-Reduce concepts
// CouchDB Map-Reduce Views
// JavaScript - Creating design documents with views and queries
const nano = require('nano')('http://localhost:5984');
class CouchViewManager {
constructor(dbName) {
this.db = nano.use(dbName);
}
// 1. Simple Map View
async createSimpleMapViews() {
const designDoc = {
_id: '_design/simple',
language: 'javascript',
views: {
// View to get all documents by type
by_type: {
map: function(doc) {
if (doc.type) {
emit(doc.type, null);
}
}.toString()
},
// View to get all users by age
users_by_age: {
map: function(doc) {
if (doc.type === 'user' && doc.age) {
emit(doc.age, doc.name);
}
}.toString()
},
// View to get products by price
products_by_price: {
map: function(doc) {
if (doc.type === 'product' && doc.price) {
emit([doc.category, doc.price], doc);
}
}.toString()
}
}
};
const response = await this.db.insert(designDoc);
console.log('✅ Simple views created:', response.id);
return response;
}
// 2. Map-Reduce Views with Aggregation
async createReduceViews() {
const designDoc = {
_id: '_design/analytics',
language: 'javascript',
views: {
// Count documents by type
count_by_type: {
map: function(doc) {
if (doc.type) {
emit(doc.type, 1);
}
}.toString(),
reduce: '_count'
},
// Sum prices by category
total_price_by_category: {
map: function(doc) {
if (doc.type === 'product' && doc.category && doc.price) {
emit(doc.category, doc.price);
}
}.toString(),
reduce: '_sum'
},
// Average age by city
avg_age_by_city: {
map: function(doc) {
if (doc.type === 'user' && doc.city && doc.age) {
emit(doc.city, doc.age);
}
}.toString(),
reduce: function(keys, values, rereduce) {
if (rereduce) {
// Re-reduce step
const totalSum = values.reduce((sum, item) => sum + item.sum, 0);
const totalCount = values.reduce((sum, item) => sum + item.count, 0);
return {
sum: totalSum,
count: totalCount,
avg: totalSum / totalCount
};
} else {
// First reduce step
const sum = values.reduce((a, b) => a + b, 0);
return {
sum: sum,
count: values.length,
avg: sum / values.length
};
}
}.toString()
},
// Statistics view with multiple reduce functions
price_statistics: {
map: function(doc) {
if (doc.type === 'product' && doc.price) {
emit(doc.category, doc.price);
}
}.toString(),
reduce: function(keys, values, rereduce) {
if (rereduce) {
// Combine partial results
const allPrices = [];
values.forEach(item => {
if (item.prices) {
allPrices.push(...item.prices);
}
});
return this.calculateStats(allPrices);
} else {
return this.calculateStats(values);
}
}.toString()
}
}
};
// Helper function for statistics
designDoc.views.price_statistics.reduce = designDoc.views.price_statistics.replace(
'this.calculateStats',
`function(prices) {
prices.sort((a, b) => a - b);
const count = prices.length;
const sum = prices.reduce((a, b) => a + b, 0);
const mean = sum / count;
const median = count % 2 === 0
? (prices[count/2 - 1] + prices[count/2]) / 2
: prices[Math.floor(count/2)];
return {
count: count,
sum: sum,
mean: mean,
median: median,
min: prices[0],
max: prices[count - 1],
prices: prices // Include for rereduce
};
}`
);
const response = await this.db.insert(designDoc);
console.log('✅ Reduce views created:', response.id);
return response;
}
// 3. Complex Map Views
async createComplexMapViews() {
const designDoc = {
_id: '_design/complex',
language: 'javascript',
views: {
// View with compound keys
users_by_city_and_age: {
map: function (doc) {
if (doc.type === 'user' && doc.city && doc.age) {
emit([doc.city, Math.floor(doc.age / 10) * 10], {
name: doc.name,
age: doc.age,
email: doc.email
});
}
}.toString()
},
// Time-series view
activity_by_day: {
map: function (doc) {
if (doc.created_at) {
var date = new Date(doc.created_at);
var dayKey = [date.getFullYear(), date.getMonth() + 1, date.getDate()];
emit(dayKey, {
type: doc.type,
hour: date.getHours()
});
}
}.toString(),
reduce: function (keys, values, rereduce) {
if (rereduce) {
return values.reduce((acc, curr) => {
Object.keys(curr).forEach(key => {
acc[key] = (acc[key] || 0) + curr[key];
});
return acc;
}, {});
} else {
const stats = {};
values.forEach(val => {
const type = val.type;
const hour = val.hour;
if (!stats[type]) stats[type] = { total: 0, hours: {} };
stats[type].total++;
stats[type].hours[hour] = (stats[type].hours[hour] || 0) + 1;
});
return stats;
}
}.toString()
},
// View for full-text search (simple implementation)
text_search: {
map: function (doc) {
if (doc.type && doc.name) {
// Extract words from name and description
var text = doc.name + ' ' + (doc.description || '');
var words = text.toLowerCase().split(/\W+/);
words.forEach(function (word) {
if (word.length > 2) {
emit([word, doc.type], {
id: doc._id,
name: doc.name,
type: doc.type
});
}
});
}
}.toString()
}
}
};
const response = await this.db.insert(designDoc);
console.log('✅ Complex views created:', response.id);
return response;
}
// 4. View Query Methods
async queryView(dDocName, viewName, options = {}) {
try {
const response = await this.db.view(dDocName, viewName, options);
return response;
} catch (error) {
console.error('❌ Error querying view:', error.message);
throw error;
}
}
// Query examples
async demonstrateQueries() {
// Query users by specific age
const users25to30 = await this.queryView('simple', 'users_by_age', {
startkey: 25,
endkey: 30,
include_docs: true
});
console.log('Users aged 25-30:', users25to30.rows.length);
// Query products by category range
const electronics = await this.queryView('simple', 'products_by_price', {
startkey: ['Electronics'],
endkey: ['Electronics', {}],
include_docs: true
});
console.log('Electronics products:', electronics.rows.length);
// Get document counts by type
const typeCounts = await this.queryView('analytics', 'count_by_type', {
group: true
});
console.log('Document counts by type:');
typeCounts.rows.forEach(row => {
console.log(` ${row.key}: ${row.value}`);
});
// Get total price by category
const priceTotals = await this.queryView('analytics', 'total_price_by_category', {
group: true
});
console.log('Total price by category:');
priceTotals.rows.forEach(row => {
console.log(` ${row.key}: $${row.value.toFixed(2)}`);
});
// Get users by city and age groups
const usersByCityAge = await this.queryView('complex', 'users_by_city_and_age', {
group_level: 1,
include_docs: true
});
console.log('Users by city:');
usersByCityAge.rows.forEach(row => {
console.log(` ${row.key[0]}: ${row.value.length} users`);
});
}
// 5. Advanced View Features
async createAdvancedViews() {
const designDoc = {
_id: '_design/advanced',
language: 'javascript',
views: {
// View with list function (using _list in URL)
recent_activities: {
map: function(doc) {
if (doc.created_at && doc.type) {
emit(doc.created_at, {
_id: doc._id,
type: doc.type,
name: doc.name || doc.title || 'Unknown'
});
}
}.toString()
},
// View for filtering with built-in filters
active_users: {
map: function(doc) {
if (doc.type === 'user' && doc.is_active !== false) {
emit(doc.email, {
name: doc.name,
last_login: doc.last_login,
created_at: doc.created_at
});
}
}.toString()
},
// View for relationship analysis
user_connections: {
map: function(doc) {
if (doc.type === 'user' && doc.friends) {
doc.friends.forEach(function(friendId) {
emit([doc._id, friendId], {
since: doc.friendship_since,
strength: doc.friendship_strength || 1
});
});
}
}.toString(),
reduce: function(keys, values, rereduce) {
if (rereduce) {
return {
total_strength: values.reduce((sum, v) => sum + v.total_strength, 0),
count: values.reduce((sum, v) => sum + v.count, 0),
avg_strength: 0
};
} else {
const totalStrength = values.reduce((sum, v) => sum + v.strength, 0);
return {
total_strength: totalStrength,
count: values.length,
avg_strength: totalStrength / values.length
};
}
}.toString()
}
},
// List functions for formatting output
lists: {
recent_activities_json: function(head, req) {
provides('json', function() {
var results = [];
while (row = getRow()) {
results.push({
id: row.value._id,
type: row.value.type,
name: row.value.name,
timestamp: row.key
});
}
send(JSON.stringify({
total_rows: head.total_rows,
offset: head.offset,
activities: results
}));
});
}.toString()
},
// Show functions for document formatting
shows: {
user_profile: function(doc, req) {
if (doc.type === 'user') {
return {
html: '<h1>' + doc.name + '</h1>' +
'<p>Email: ' + doc.email + '</p>' +
'<p>Age: ' + doc.age + '</p>' +
'<p>City: ' + doc.city + '</p>'
};
}
return null;
}.toString()
}
};
const response = await this.db.insert(designDoc);
console.log('✅ Advanced views created:', response.id);
return response;
}
// 6. View Performance Optimization
async optimizeViews() {
const designDoc = {
_id: '_design/optimized',
language: 'javascript',
views: {
// Efficient view with proper key design
orders_by_date: {
map: function(doc) {
if (doc.type === 'order' && doc.date) {
// Emit date in ISO format for proper sorting
emit([doc.customer_id, doc.date], {
order_id: doc._id,
total: doc.total,
items: doc.items.length
});
}
}.toString()
},
// View with selective indexing
indexed_products: {
map: function(doc) {
// Only index documents we actually need to query
if (doc.type === 'product' &&
doc.status === 'active' &&
doc.price > 0) {
emit(doc.category, {
id: doc._id,
name: doc.name,
price: doc.price,
rating: doc.rating || 0
});
}
}.toString()
}
},
options: {
// Automatic view indexing
// Changes will automatically update views
}
};
const response = await this.db.insert(designDoc);
console.log('✅ Optimized views created:', response.id);
return response;
}
}
// Usage Example
async function demonstrateViews() {
const viewManager = new CouchViewManager('analytics_db');
// Create different types of views
await viewManager.createSimpleMapViews();
await viewManager.createReduceViews();
await viewManager.createComplexMapViews();
await viewManager.createAdvancedViews();
await viewManager.optimizeViews();
// Demonstrate queries
await viewManager.demonstrateQueries();
}
if (require.main === module) {
demonstrateViews().catch(console.error);
}
module.exports = CouchViewManager;
💻 Réplication et Synchronisation CouchDB javascript
🔴 complex
⭐⭐⭐⭐
Configuration de la réplication, résolution de conflits et synchronisation des données
⏱️ 35 min
🏷️ couchdb, replication, sync, distributed systems, conflict resolution
Prerequisites:
Advanced CouchDB operations, Network concepts, Conflict resolution strategies
// CouchDB Replication and Synchronization
// JavaScript - Advanced replication patterns and conflict resolution
const nano = require('nano')('http://localhost:5984');
class CouchReplicationManager {
constructor() {
this.nano = nano;
this.replications = new Map();
}
// 1. Basic Replication Setup
async setupOneTimeReplication(source, target, options = {}) {
const replicationDoc = {
_id: `replication_${Date.now()}`,
source: source,
target: target,
create_target: options.createTarget || false,
continuous: false,
// Filter documents if needed
filter: options.filter,
query_params: options.queryParams,
doc_ids: options.docIds
};
try {
const response = await this.nano.request({
db: '_replicator',
method: 'post',
body: replicationDoc
});
console.log(`✅ One-time replication started: ${response.id}`);
this.replications.set(response.id, replicationDoc);
return response;
} catch (error) {
console.error('❌ Error setting up one-time replication:', error.message);
throw error;
}
}
// 2. Continuous Replication
async setupContinuousReplication(source, target, options = {}) {
const replicationDoc = {
_id: `continuous_${Date.now()}`,
source: source,
target: target,
create_target: options.createTarget || false,
continuous: true,
// Replication options
filter: options.filter,
query_params: options.queryParams,
doc_ids: options.docIds,
// Heartbeat configuration
heartbeat: options.heartbeat || 30000,
// Worker processes
worker_processes: options.workerProcesses || 1,
// Batch size
worker_batch_size: options.batchSize || 500,
// Connection timeout
timeout: options.timeout || 30000
};
try {
const response = await this.nano.request({
db: '_replicator',
method: 'post',
body: replicationDoc
});
console.log(`🔄 Continuous replication started: ${response.id}`);
this.replications.set(response.id, replicationDoc);
// Start monitoring
this.monitorReplication(response.id);
return response;
} catch (error) {
console.error('❌ Error setting up continuous replication:', error.message);
throw error;
}
}
// 3. Bi-directional Sync
async setupBiDirectionalSync(db1, db2, options = {}) {
const syncOptions = {
...options,
continuous: true,
create_target: false
};
// Replicate db1 -> db2
const rep1 = await this.setupContinuousReplication(db1, db2, {
...syncOptions,
_id: `sync_${db1}_to_${db2}_${Date.now()}`
});
// Replicate db2 -> db1
const rep2 = await this.setupContinuousReplication(db2, db1, {
...syncOptions,
_id: `sync_${db2}_to_${db1}_${Date.now()}`
});
console.log(`🔄 Bi-directional sync established between ${db1} and ${db2}`);
return {
forward: rep1,
backward: rep2
};
}
// 4. Filtered Replication
async setupFilteredReplication(source, target, filterConfig) {
// Create filter function in design document
const filterDesignDoc = {
_id: '_design/filters',
filters: {
by_type: function(doc, req) {
// Only replicate certain document types
const allowedTypes = req.query.types ?
req.query.types.split(',') :
['user', 'product'];
return allowedTypes.includes(doc.type);
}.toString(),
by_date: function(doc, req) {
// Only replicate documents after certain date
if (!doc.created_at) return false;
const sinceDate = req.query.since || '1970-01-01';
const docDate = new Date(doc.created_at);
const filterDate = new Date(sinceDate);
return docDate >= filterDate;
}.toString(),
by_user: function(doc, req) {
// Only replicate user-specific data
if (doc.type === 'user') {
return doc._id === req.query.userId;
}
if (doc.owner) {
return doc.owner === req.query.userId;
}
if (doc.shared_with && doc.shared_with.includes(req.query.userId)) {
return true;
}
return false;
}.toString()
}
};
try {
// Save filter design document
const sourceDb = this.nano.use(source);
await sourceDb.insert(filterDesignDoc);
// Setup replication with filter
return await this.setupContinuousReplication(source, target, {
filter: 'filters/by_' + filterConfig.type,
query_params: filterConfig.params
});
} catch (error) {
console.error('❌ Error setting up filtered replication:', error.message);
throw error;
}
}
// 5. Replication Monitoring
async monitorReplication(replicationId) {
const checkInterval = 5000; // Check every 5 seconds
const monitor = setInterval(async () => {
try {
const status = await this.getReplicationStatus(replicationId);
if (status) {
console.log(`📊 Replication ${replicationId} status: ${status.state}`, {
docs_read: status.docs_read || 0,
docs_written: status.docs_written || 0,
errors: status.errors || 0,
progress: status.progress || 0
});
// Stop monitoring if replication completes
if (status.state === 'completed' || status.state === 'error') {
clearInterval(monitor);
console.log(`🏁 Replication ${replicationId} finished with status: ${status.state}`);
}
}
} catch (error) {
console.error('❌ Error monitoring replication:', error.message);
clearInterval(monitor);
}
}, checkInterval);
}
// Get replication status
async getReplicationStatus(replicationId) {
try {
const replication = await this.nano.request({
db: '_replicator',
doc: replicationId
});
return replication;
} catch (error) {
if (error.statusCode === 404) {
console.log(`⚠️ Replication ${replicationId} not found`);
} else {
console.error('❌ Error getting replication status:', error.message);
}
return null;
}
}
// 6. Conflict Resolution
async resolveConflicts(dbName, conflictResolver) {
const db = this.nano.use(dbName);
// Get all documents with conflicts
const conflicts = await db.view('_docs_with_conflicts', 'conflicts', {
include_docs: true
});
console.log(`⚠️ Found ${conflicts.rows.length} documents with conflicts`);
for (const row of conflicts.rows) {
const doc = row.doc;
const conflictingRevs = doc._conflicts;
if (conflictingRevs && conflictingRevs.length > 0) {
console.log(`🔄 Resolving conflicts for document: ${doc._id}`);
// Get all conflicting revisions
const revisions = [];
for (const rev of conflictingRevs) {
try {
const conflictDoc = await db.get(doc._id, { rev: rev });
revisions.push(conflictDoc);
} catch (error) {
console.error(`❌ Error getting revision ${rev}:`, error.message);
}
}
// Use custom conflict resolver
const resolvedDoc = await conflictResolver(doc, revisions);
if (resolvedDoc) {
// Save resolved document
try {
const response = await db.insert(resolvedDoc);
console.log(`✅ Conflict resolved for ${doc._id}: ${response.rev}`);
} catch (error) {
console.error(`❌ Error saving resolved document ${doc._id}:`, error.message);
}
}
}
}
}
// Default conflict resolution strategies
static conflictResolvers = {
// Use most recent document based on timestamp
latestWins: async (currentDoc, conflicts) => {
let latestDoc = currentDoc;
let latestTime = new Date(currentDoc.updated_at || currentDoc.created_at || 0);
for (const conflict of conflicts) {
const conflictTime = new Date(conflict.updated_at || conflict.created_at || 0);
if (conflictTime > latestTime) {
latestTime = conflictTime;
latestDoc = conflict;
}
}
// Clean up conflicts
delete latestDoc._conflicts;
return latestDoc;
},
// Merge documents (for specific types)
merge: async (currentDoc, conflicts) => {
// Simple merge strategy
const merged = { ...currentDoc };
for (const conflict of conflicts) {
// Merge properties from conflicts
Object.keys(conflict).forEach(key => {
if (key !== '_id' && key !== '_rev' && key !== '_conflicts') {
if (!merged[key] ||
(conflict.updated_at && (!merged.updated_at || new Date(conflict.updated_at) > new Date(merged.updated_at)))) {
merged[key] = conflict[key];
}
}
});
}
delete merged._conflicts;
return merged;
},
// Keep document with highest version number
versionBased: async (currentDoc, conflicts) => {
let versionDoc = currentDoc;
let highestVersion = parseInt(currentDoc.version || '1');
for (const conflict of conflicts) {
const conflictVersion = parseInt(conflict.version || '1');
if (conflictVersion > highestVersion) {
highestVersion = conflictVersion;
versionDoc = conflict;
}
}
delete versionDoc._conflicts;
versionDoc.version = (highestVersion + 1).toString();
return versionDoc;
}
};
// 7. Sync Status Management
async getSyncStatus(sourceDb, targetDb) {
try {
// Get info from both databases
const sourceInfo = await this.nano.db.info(sourceDb);
const targetInfo = await this.nano.db.info(targetDb);
// Get replication tasks
const activeTasks = await this.nano.request({
path: '_active_tasks',
method: 'get'
});
const replications = activeTasks.filter(task =>
task.type === 'replication' &&
(task.source === sourceDb || task.target === targetDb)
);
return {
source: {
name: sourceDb,
doc_count: sourceInfo.doc_count,
update_seq: sourceInfo.update_seq
},
target: {
name: targetDb,
doc_count: targetInfo.doc_count,
update_seq: targetInfo.update_seq
},
active_replications: replications.map(rep => ({
task_id: rep.task_id,
source: rep.source,
target: rep.target,
state: rep.state,
progress: rep.progress,
docs_read: rep.docs_read,
docs_written: rep.docs_written
}))
};
} catch (error) {
console.error('❌ Error getting sync status:', error.message);
return null;
}
}
// 8. Backup and Restore with Replication
async setupBackupReplication(sourceDb, backupDb, options = {}) {
const backupOptions = {
...options,
continuous: options.continuousBackup || false,
create_target: true,
// Backup filter
filter: function(doc) {
// Don't replicate temporary or design documents unless specified
if (doc._id && doc._id.startsWith('_temp/')) return false;
if (doc._id && doc._id.startsWith('_design/') && !options.includeDesignDocs) return false;
return true;
}.toString(),
query_params: {
include_design_docs: options.includeDesignDocs || false
}
};
return await this.setupContinuousReplication(sourceDb, backupDb, backupOptions);
}
// 9. Cleanup and Maintenance
async cleanupReplication(replicationId) {
try {
// Stop the replication
await this.nano.request({
db: '_replicator',
doc: replicationId,
method: 'delete'
});
this.replications.delete(replicationId);
console.log(`🗑️ Replication ${replicationId} stopped and cleaned up`);
return true;
} catch (error) {
console.error('❌ Error cleaning up replication:', error.message);
return false;
}
}
// Cleanup old replications
async cleanupOldReplications(maxAge = 24 * 60 * 60 * 1000) { // Default 24 hours
try {
const replications = await this.nano.request({
db: '_replicator',
method: 'get'
});
const now = Date.now();
const oldReplications = [];
for (const rep of replications.rows) {
const repDoc = rep.doc;
const timestamp = parseInt(repDoc._id.split('_').pop());
if (now - timestamp > maxAge) {
oldReplications.push(repDoc._id);
}
}
// Delete old replications
for (const repId of oldReplications) {
await this.cleanupReplication(repId);
}
console.log(`🗑️ Cleaned up ${oldReplications.length} old replications`);
return oldReplications.length;
} catch (error) {
console.error('❌ Error cleaning up old replications:', error.message);
return 0;
}
}
}
// Usage Example
async function demonstrateReplication() {
const repManager = new CouchReplicationManager();
// Setup one-time replication
await repManager.setupOneTimeReplication('source_db', 'backup_db', {
createTarget: true
});
// Setup continuous replication
await repManager.setupContinuousReplication('source_db', 'replica_db', {
createTarget: true
});
// Setup bi-directional sync
await repManager.setupBiDirectionalSync('db1', 'db2');
// Setup filtered replication
await repManager.setupFilteredReplication('source_db', 'filtered_replica', {
type: 'by_user',
params: { userId: 'user123' }
});
// Resolve conflicts
await repManager.resolveConflicts('sync_db', CouchReplicationManager.conflictResolvers.latestWins);
// Get sync status
const syncStatus = await repManager.getSyncStatus('source_db', 'replica_db');
console.log('Sync status:', JSON.stringify(syncStatus, null, 2));
// Setup backup replication
await repManager.setupBackupReplication('production_db', 'backup_db', {
continuousBackup: true,
includeDesignDocs: true
});
}
if (require.main === module) {
demonstrateReplication().catch(console.error);
}
module.exports = CouchReplicationManager;