🎯 empfohlene Sammlungen
Balanced sample collections from various categories for you to explore
CouchDB Beispiele
Apache CouchDB NoSQL-Datenbank-Beispiele einschließlich Dokumentoperationen, Ansichten und Replikation
💻 CouchDB Grundoperationen javascript
🟢 simple
⭐⭐
Grundlegende CRUD-Operationen mit CouchDB-Dokumenten und -Datenbanken
⏱️ 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
};
💻 CouchDB Map-Reduce Ansichten javascript
🟡 intermediate
⭐⭐⭐⭐
Erstellung und Verwendung von Map-Reduce-Ansichten für Datenaggregation und -analyse
⏱️ 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;
💻 CouchDB Replikation und Synchronisation javascript
🔴 complex
⭐⭐⭐⭐
Einrichtung der Replikation, Konfliktlösung und Datensynchronisation
⏱️ 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;