Huge News!Announcing our $40M Series B led by Abstract Ventures.Learn More
Socket
Sign inDemoInstall
Socket

couch-continuum

Package Overview
Dependencies
Maintainers
1
Versions
18
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

couch-continuum - npm Package Compare versions

Comparing version 2.0.1-alpha to 2.1.0-alpha

lib/fs.js

191

bin.js
#!/usr/bin/env node
'use strict'
const CouchContinuum = require('.')
const readline = require('readline')
const CouchContinuum = require('.')
const log = require('./lib/log')
/*

@@ -11,16 +13,29 @@ HELPERS

const prefix = '[couch-continuum]'
function log () {
arguments[0] = [prefix, arguments[0]].join(' ')
console.log.apply(console, arguments)
}
function getContinuum (argv) {
const { copyName, couchUrl, dbName, filterTombstones, interval, n, placement, q, verbose } = argv
function getContinuum ({
couchUrl,
filterTombstones,
interval,
n,
placement,
q,
replicateSecurity,
source,
target,
verbose
}) {
if (verbose) process.env.LOG = true
const options = { copyName, couchUrl, dbName, filterTombstones, interval, n, placement, q }
return new CouchContinuum(options)
return new CouchContinuum({
couchUrl,
filterTombstones,
interval,
n,
placement,
q,
replicateSecurity,
source,
target
})
}
function getConsent (question) {
async function getConsent (question) {
question = question || 'Ready to replace the primary with the replica. Continue? [y/N] '

@@ -41,11 +56,11 @@ const rl = readline.createInterface({

function catchError (error) {
log('ERROR')
console.log('ERROR')
if (error.error === 'not_found') {
log('Primary database does not exist. There is nothing to migrate.')
console.log('Primary database does not exist. There is nothing to migrate.')
} else if (error.error === 'unauthorized') {
log('Could not authenticate with CouchDB. Are the credentials correct?')
console.log('Could not authenticate with CouchDB. Are the credentials correct?')
} else if (error.code === 'EACCES') {
log('Could not access the checkpoint document. Are you running as a different user?')
console.log('Could not access the checkpoint document. Are you running as a different user?')
} else {
log('Unexpected error: %j', error)
console.log('Unexpected error: %j', error)
}

@@ -64,28 +79,12 @@ process.exit(1)

description: 'Migrate a database to new settings.',
builder: function (yargs) {
yargs.options({
dbName: {
alias: 'N',
description: 'The name of the database to modify.',
required: true,
type: 'string'
},
copyName: {
alias: 'c',
description: 'The name of the database to use as a replica. Defaults to {dbName}_temp_copy',
type: 'string'
}
})
},
handler: function (argv) {
handler: async function (argv) {
const continuum = getContinuum(argv)
log(`Migrating database '${argv.dbName}'...`)
continuum.createReplica().then(function () {
return getConsent()
}).then((consent) => {
log(`Migrating database: ${continuum.source.host}${continuum.source.path}`)
try {
await continuum.createReplica()
const consent = await getConsent()
if (!consent) return log('Could not acquire consent. Exiting...')
return continuum.replacePrimary().then(() => {
log('... success!')
})
}).catch(catchError)
await continuum.replacePrimary()
console.log(`Migrated database: ${continuum.source.host}${continuum.source.path}`)
} catch (error) { catchError(error) }
}

@@ -97,23 +96,9 @@ })

description: 'Create a replica of the given primary.',
builder: function (yargs) {
yargs.options({
dbName: {
alias: 'n',
description: 'The name of the database to modify.',
required: true,
type: 'string'
},
copyName: {
alias: 'c',
description: 'The name of the database to use as a replica. Defaults to {dbName}_temp_copy',
type: 'string'
}
})
},
handler: function (argv) {
handler: async function (argv) {
const continuum = getContinuum(argv)
log(`Creating replica of ${continuum.db1} at ${continuum.db2}`)
continuum.createReplica().then(() => {
log('... success!')
}).catch(catchError)
log(`Creating replica of ${continuum.source.host}${continuum.source.path} at ${continuum.target.host}${continuum.target.path}`)
try {
await continuum.createReplica()
console.log(`Created replica of ${continuum.source.host}${continuum.source.path}`)
} catch (error) { catchError(error) }
}

@@ -125,11 +110,11 @@ })

description: 'Replace the given primary with the indicated replica.',
handler: function (argv) {
handler: async function (argv) {
const continuum = getContinuum(argv)
log(`Replacing primary ${continuum.db1} with ${continuum.db2}...`)
getConsent().then((consent) => {
log(`Replacing primary ${continuum.source.host}${continuum.source.path} with ${continuum.target.host}${continuum.target.path}`)
try {
const consent = await getConsent()
if (!consent) return log('Could not acquire consent. Exiting...')
return continuum.replacePrimary().then(() => {
log('... success!')
})
}).catch(catchError)
await continuum.replacePrimary()
console.log(`Successfully replaced ${continuum.source.host}${continuum.source.path}`)
} catch (error) { catchError(error) }
}

@@ -141,38 +126,39 @@ })

description: 'Migrate all non-special databases to new settings.',
handler: function (argv) {
const { couchUrl, filterTombstones, interval, placement, q, verbose } = argv
handler: async function (argv) {
const { couchUrl, verbose } = argv
if (verbose) { process.env.LOG = true }
CouchContinuum
.getCheckpoint(couchUrl)
.then((dbNames) => {
return dbNames.map((dbName) => {
const options = { couchUrl, dbName, filterTombstones, interval, placement, q }
return new CouchContinuum(options)
})
try {
const dbNames = await CouchContinuum.getRemaining(couchUrl)
const continuums = dbNames.map((dbName) => {
return new CouchContinuum({ dbName, ...argv })
})
.then((continuums) => {
log('Creating replicas...')
return CouchContinuum
.createReplicas(continuums)
.then(() => {
return getConsent('Ready to replace primaries with replicas. Continue? [y/N] ')
})
.then((consent) => {
if (!consent) return log('Could not acquire consent. Exiting...')
log('Replacing primaries...')
return CouchContinuum
.replacePrimaries(continuums)
})
})
.then(() => {
return CouchContinuum
.removeCheckpoint()
})
.then(() => {
log('...success!')
})
.catch(catchError)
log('Creating replicas...')
await CouchContinuum.createReplicas(continuums)
const consent = await getConsent('Ready to replace primaries with replicas. Continue? [y/N] ')
if (!consent) return console.log('Could not acquire consent. Exiting...')
log('Replacing primaries...')
await CouchContinuum.replacePrimaries(continuums)
await CouchContinuum.removeCheckpoint()
console.log(`Successfully migrated databases: ${dbNames.join(', ')}`)
} catch (error) { catchError(error) }
}
})
// backwards compat with old flag names
.alias('source', 'dbNames')
.alias('source', 'N')
.alias('target', 'copyName')
.alias('target', 'c')
// actual options
.options({
source: {
alias: 's',
description: 'The name or URL of a database to use as a primary.',
required: true,
type: 'string'
},
target: {
alias: 't',
description: 'The name or URL of a database to use as a replica. Defaults to {source}_temp_copy',
type: 'string'
},
couchUrl: {

@@ -208,4 +194,9 @@ alias: 'u',

alias: 'f',
description: 'Filter tombstones during replica creation.',
description: 'Filter tombstones during replica creation. Does not work with CouchDB 1.x',
default: false
},
replicateSecurity: {
alias: 'r',
description: 'Replicate a database\'s /_security object in addition to its documents.',
default: true
}

@@ -212,0 +203,0 @@ })

@@ -1,45 +0,21 @@

'use strict'
const assert = require('assert')
const fs = require('fs')
const path = require('path')
const ProgressBar = require('progress')
const request = require('request')
const urlParse = require('url').parse
const log = require('./lib/log')
const request = require('./lib/request')
const { name } = require('./package.json')
const { readFile, unlink, writeFile } = require('./lib/fs')
const checkpoint = path.join(__dirname, '.checkpoint')
const prefix = '[couch-continuum]'
function log () {
if (process.env.DEBUG || process.env.LOG) {
arguments[0] = [prefix, arguments[0]].join(' ')
console.log.apply(console, arguments)
}
}
function makeCallBack (resolve, reject) {
return (err, res, body) => {
if (typeof body === 'string') body = JSON.parse(body)
if (err || body.error) return reject(err || body)
else return resolve(body)
}
}
function makeRequest (options) {
return new Promise((resolve, reject) => {
const done = makeCallBack(resolve, reject)
request(options, done)
})
}
module.exports =
class CouchContinuum {
static allDbs (url) {
return makeRequest({
url: [url, '_all_dbs'].join('/'),
json: true
}).then((body) => {
return body.filter((dbName) => {
const isSpecial = (dbName[0] === '_') // ignore special dbs
const isReplica = dbName.indexOf('_temp_copy') > -1
return !isSpecial && !isReplica
})
static async allDbs (url) {
const allDbs = await request({ url: `${url}/_all_dbs`, json: true })
return allDbs.filter((dbName) => {
const isSpecial = (dbName[0] === '_') // ignore special dbs
const isReplica = dbName.indexOf('_temp_copy') > -1
return !isSpecial && !isReplica
})

@@ -51,74 +27,99 @@ }

* against a particular CouchDB cluster.
* @param {String} couchUrl Location of the CouchDB cluster
* @return {Promise<Array>} List of databases still to be migrated.
* @return {Array<String>} List of databases still to be migrated.
*/
static getCheckpoint (couchUrl) {
// skip already done
return CouchContinuum.allDbs(couchUrl).then((dbNames) => {
return new Promise((resolve, reject) => {
fs.readFile(checkpoint, 'utf-8', (err, lastDb) => {
if (err) {
if (err.code === 'ENOENT') return resolve('\u0000')
return reject(err)
}
return resolve(lastDb)
})
}).then((lastDb) => {
// ignore any databases that sort lower than
// the name in the checkpoint doc,
// or the default: the lowest unicode value
return dbNames.filter((dbName) => {
return dbName > lastDb
})
})
static async getCheckpoint () {
return readFile(checkpoint, 'utf-8').catch((error) => {
if (error.code === 'ENOENT') {
return '\u0000'
} else {
throw error
}
})
}
static makeCheckpoint (dbName) {
return new Promise((resolve, reject) => {
fs.writeFile(checkpoint, dbName, 'utf-8', (err) => {
if (err) return reject(err)
return resolve()
})
})
static async makeCheckpoint (dbName) {
await writeFile(checkpoint, dbName, 'utf-8')
}
static removeCheckpoint () {
return new Promise((resolve, reject) => {
fs.unlink(checkpoint, (err) => {
if (err) return reject(err)
return resolve()
})
static async removeCheckpoint () {
await unlink(checkpoint)
}
static async getRemaining (couchUrl) {
const dbNames = await CouchContinuum.allDbs(couchUrl)
const lastDb = await CouchContinuum.getCheckpoint()
// ignore any databases that sort lower than
// the name in the checkpoint doc.
return dbNames.filter((dbName) => { return dbName > lastDb })
}
static async createReplicas (continuums) {
for (let continuum of continuums) {
await continuum.createReplica()
}
}
static async replacePrimaries (continuums) {
for (let continuum of continuums) {
await continuum.replacePrimary()
await CouchContinuum.makeCheckpoint(continuum.source.path.slice(1))
}
await CouchContinuum.removeCheckpoint()
}
static async _isAvailable (dbUrl) {
const { down } = await request({
url: `${dbUrl}/_local/in-maintenance`,
json: true
})
return !down
}
static createReplicas (continuums) {
return continuums.map((continuum) => {
return () => {
return continuum.createReplica()
}
}).reduce((a, b) => {
return a.then(b)
}, Promise.resolve())
static async _setUnavailable (dbUrl) {
await request({
url: `${dbUrl}/_local/in-maintenance`,
method: 'PUT',
json: { down: true }
})
}
static replacePrimaries (continuums) {
return continuums.map((continuum) => {
return () => {
log('Replacing primary "%s" with replica "%s"', continuum.db1, continuum.db2)
return continuum.replacePrimary().then(() => {
return CouchContinuum.makeCheckpoint(continuum.db1)
})
}
}).reduce((a, b) => {
return a.then(b)
}, Promise.resolve())
static async _setAvailable (dbUrl) {
const url = `${dbUrl}/_local/in-maintenance`
const { _rev: rev } = await request({ url, json: true })
return request({ url, qs: { rev }, method: 'DELETE' })
}
constructor ({ couchUrl, dbName, copyName, filterTombstones, placement, interval, q, n }) {
constructor ({
couchUrl,
filterTombstones,
interval,
n,
placement,
q,
replicateSecurity,
source,
target
}) {
assert(couchUrl, 'The Continuum requires a URL for accessing CouchDB.')
assert(dbName, 'The Continuum requires a target database.')
this.url = couchUrl
this.db1 = encodeURIComponent(dbName)
this.db2 = (copyName && encodeURIComponent(copyName)) || (this.db1 + '_temp_copy')
assert(source, 'The Continuum requires a source database.')
this.url = urlParse(couchUrl)
// get source url
const parsedSource = urlParse(source)
if (parsedSource.host) {
this.source = parsedSource
} else {
this.source = urlParse(`${this.url.href}${encodeURIComponent(source)}`)
}
// get target url
if (target) {
const parsedTarget = urlParse(target)
if (parsedTarget.host) {
this.target = parsedTarget
} else {
this.target = urlParse(`${this.url.href}${encodeURIComponent(target)}`)
}
} else {
this.target = urlParse(`${this.source.href}_temp_copy`)
}
// save other variables
this.interval = interval || 1000

@@ -129,21 +130,28 @@ this.q = q

this.filterTombstones = filterTombstones
log('Created new continuum: %j', {
db1: this.db1,
db2: this.db2,
this.replicateSecurity = replicateSecurity
// what's great for a snack and fits on your back
// it's log it's log it's log
// everyone wants a log
log(`Created new continuum: ${JSON.stringify({
filterTombstones: this.filterTombstones,
interval: this.interval,
n: this.n,
placement: this.placement,
q: this.q,
n: this.n,
placement: this.placement
})
replicateSecurity: this.replicateSecurity,
source: `${this.source.host}${this.source.path}`,
target: `${this.target.host}${this.target.path}`,
url: this.url.host
}, undefined, 2)}`)
}
_createDb (dbName) {
var qs = {}
if (this.q) qs.q = this.q
if (this.n) qs.n = this.n
if (this.placement) qs.placement = this.placement
return makeRequest({
url: [this.url, dbName].join('/'),
async _createDb (dbUrl) {
const qs = {}
if (this.q) { qs.q = this.q }
if (this.n) { qs.n = this.n }
if (this.placement) { qs.placement = this.placement }
return request({
url: dbUrl,
method: 'PUT',
qs: qs,
qs,
json: true

@@ -153,5 +161,5 @@ })

_destroyDb (dbName) {
return makeRequest({
url: [this.url, dbName].join('/'),
async _destroyDb (dbUrl) {
return request({
url: dbUrl,
method: 'DELETE',

@@ -162,80 +170,56 @@ json: true

_replicate (source, target, selector) {
return makeRequest({
url: [this.url, source].join('/'),
json: true
}).then((body) => {
const total = body.doc_count
if (total === 0) return Promise.resolve()
console.log('[couch-continuum] Replicating %s to %s', source, target)
const text = '[couch-continuum] (:bar) :percent :etas'
const bar = new ProgressBar(text, {
incomplete: ' ',
width: 20,
total
async _replicate (source, target, selector) {
const { doc_count: total } = await request({ url: source, json: true })
if (total === 0) return null
console.log(`[${name}] Replicating ${target.host}${source.path} to ${target.host}${target.path}`)
const text = `[${name}] (:bar) :percent :etas`
const bar = new ProgressBar(text, {
incomplete: ' ',
width: 20,
total
})
var current = 0
const timer = setInterval(async () => {
const { doc_count: latest } = await request({
url: target.href,
json: true
})
var current = 0
const timer = setInterval(() => {
makeRequest({
url: [this.url, target].join('/'),
json: true
}).then((body) => {
const latest = body.doc_count
const delta = latest - current
bar.tick(delta)
current = latest
if (bar.complete) clearInterval(timer)
}).catch((error) => {
if (error) console.error(error)
})
}, this.interval)
return makeRequest({
url: [this.url, '_replicate'].join('/'),
method: 'POST',
json: { source, target, selector }
}).then(() => {
bar.tick(total)
clearInterval(timer)
})
const delta = latest - current
bar.tick(delta)
current = latest
if (bar.complete) clearInterval(timer)
// TODO catch errors produced by this loop
}, this.interval)
await request({
url: `${this.url.href}_replicate`,
method: 'POST',
json: { source: source.href, target: target.href, selector }
})
}
_verifyReplica () {
const getDocCount = (dbName) => {
return makeRequest({
url: [this.url, dbName].join('/'),
// copy security object over
if (this.replicateSecurity) {
log(`Replicating ${source}/_security to ${target}...`)
const security = await request({
url: `${this.source.href}/_security`,
json: true
}).then((body) => {
return body.doc_count
})
await request({
url: `${this.target.href}/_security`,
method: 'PUT',
json: security
})
}
return Promise.all([
getDocCount(this.db1),
getDocCount(this.db2)
]).then(([docCount1, docCount2]) => {
assert.equal(docCount1, docCount2, 'Primary and replica do not have the same number of documents.')
})
bar.tick(total)
clearInterval(timer)
}
_setUnavailable () {
return makeRequest({
url: [this.url, this.db1, '_local', 'in-maintenance'].join('/'),
method: 'PUT',
json: { down: true }
}).catch((error) => {
if (error.error === 'file_exists') return null
else throw error
async _verifyReplica () {
const { doc_count: docCount1 } = await request({
url: this.source.href,
json: true
})
}
_setAvailable () {
const url = [this.url, this.db1, '_local', 'in-maintenance'].join('/')
return makeRequest({ url, json: true }).catch((error) => {
if (error.error === 'not_found') return {}
else throw error
}).then(({ _rev }) => {
const qs = _rev ? { rev: _rev } : {}
return makeRequest({ url, qs, method: 'DELETE' })
const { doc_count: docCount2 } = await request({
url: this.target.href,
json: true
})
assert.strictEqual(docCount1, docCount2, 'Primary and replica do not have the same number of documents.')
}

@@ -246,11 +230,7 @@

* @param {String} dbName Name of the database to check.
* @return {Promise} Resolves with the database's update sequence.
* @return {String} The database's update sequence.
*/
_getUpdateSeq (dbName) {
return makeRequest({
url: [this.url, dbName].join('/'),
json: true
}).then((body) => {
return body.update_seq
})
async _getUpdateSeq (dbUrl) {
const { update_seq: updateSeq } = await request({ url: dbUrl, json: true })
return updateSeq
}

@@ -262,29 +242,19 @@

* @param {String} dbName Name of the database to check.
* @return {Promise<Boolean>} Whether the database is in use.
* @return {Boolean} Whether the database is in use.
*/
_isInUse (dbName) {
return Promise.all([
makeRequest({
url: [this.url, '_active_tasks'].join('/'),
json: true
}),
makeRequest({
url: [this.url, '_scheduler', 'jobs'].join('/'),
json: true
}).catch((error) => {
// catch 1.x
if (error.error === 'illegal_database_name') {
return { jobs: [] }
} else {
throw error
}
})
]).then(([activeTasks, jobsResponse]) => {
const { jobs } = jobsResponse
// verify that the given dbName is not involved
// in any active jobs or tasks
jobs.concat(activeTasks).forEach(({ database }) => {
assert.notEqual(database, dbName, `${dbName} is still in use.`)
})
async _isInUse (dbName) {
// TODO check all known hosts
const activeTasks = await request({
url: `${this.url.href}_active_tasks`,
json: true
})
const { jobs } = await request({
url: `${this.url.href}_scheduler/jobs`,
json: true
}).then(({ jobs }) => {
return { jobs: jobs || [] }
})
for (let { database } of [...jobs, ...activeTasks]) {
assert.notEqual(database, dbName, `${dbName} is still in use.`)
}
}

@@ -297,83 +267,60 @@

*/
createReplica () {
let lastSeq1, lastSeq2
log(`Creating replica ${this.db2}...`)
async createReplica () {
log(`Creating replica ${this.target.host}${this.target.path}...`)
log('[0/5] Checking if primary is in use...')
return this._isInUse(this.db1).then(() => {
return this._getUpdateSeq(this.db1).then((seq) => {
lastSeq1 = seq
})
}).then(() => {
log('[1/5] Creating replica db:', this.db2)
return this._createDb(this.db2).catch((err) => {
const exists = (err.error && err.error === 'file_exists')
if (exists) return true
else throw err
})
}).then(() => {
log('[2/5] Beginning replication of primary to replica...')
var selector
if (this.filterTombstones) selector = { _deleted: { '$exists': false } }
return this._replicate(this.db1, this.db2, selector)
}).then(() => {
log('[3/5] Verifying primary did not change during replication...')
return this._getUpdateSeq(this.db1).then((seq) => {
lastSeq2 = seq
assert(lastSeq1 <= lastSeq2, `${this.db1} is still receiving updates. Exiting...`)
})
}).then(() => {
log('[4/5] Verifying primary and replica match...')
return this._verifyReplica()
}).then(() => {
log('[5/5] Primary copied to replica.')
})
await this._isInUse(this.source.path.slice(1))
const lastSeq1 = await this._getUpdateSeq(this.source.href)
log(`[1/5] Creating replica db: ${this.target.host}${this.target.path}`)
await this._createDb(this.target.href)
log('[2/5] Beginning replication of primary to replica...')
const selector = this.filterTombstones ? {
_deleted: { $exists: false }
} : undefined
await this._replicate(this.source, this.target, selector)
log('[3/5] Verifying primary did not change during replication...')
const lastSeq2 = await this._getUpdateSeq(this.source.href)
assert(lastSeq1 <= lastSeq2, `${this.source.host}${this.source.path} is still receiving updates. Exiting...`)
log('[4/5] Verifying primary and replica match...')
await this._verifyReplica()
log('[5/5] Primary copied to replica.')
}
replacePrimary () {
log(`Replacing primary ${this.db1}...`)
async replacePrimary () {
log(`Replacing primary ${this.source.host}${this.source.path} using ${this.target.host}${this.target.path}...`)
log('[0/8] Checking if primary is in use...')
return this._isInUse(this.db1).then(() => {
log('[1/8] Verifying primary and replica match...')
return this._verifyReplica()
}).then(() => {
log('[2/8] Destroying primary...')
return this._destroyDb(this.db1)
}).then(() => {
log('[3/8] Recreating primary with new settings...')
return this._createDb(this.db1).then(() => {
return new Promise((resolve) => {
// sleep, giving the cluster a chance to sort
// out the rapid recreation.
console.log('[couch-continuum] Recreating primary %s', this.db1)
const text = '[couch-continuum] (:bar) :percent :etas'
const bar = new ProgressBar(text, {
incomplete: ' ',
width: 20,
total: 150
})
const timer = setInterval(() => {
bar.tick()
if (bar.complete) {
clearInterval(timer)
return resolve()
}
}, 100)
})
await this._isInUse(this.source.path.slice(1))
log('[1/8] Verifying primary and replica match...')
await this._verifyReplica()
log('[2/8] Destroying primary...')
await this._destroyDb(this.source.href)
log('[3/8] Recreating primary with new settings...')
await this._createDb(this.source.href)
await new Promise((resolve) => {
// sleep, giving the cluster a chance to sort
// out the rapid recreation.
console.log(`[${name}] Recreating primary ${this.source.host}${this.source.path}`)
const text = `[${name}] (:bar) :percent :etas`
const bar = new ProgressBar(text, {
incomplete: ' ',
width: 20,
total: 150
})
}).then(() => {
log('[4/8] Setting primary to unavailable.')
return this._setUnavailable()
}).then(() => {
log('[5/8] Beginning replication of replica to primary...')
return this._replicate(this.db2, this.db1)
}).then(() => {
log('[6/8] Replicated. Destroying replica...')
return this._destroyDb(this.db2)
}).then(() => {
log('[7/8] Setting primary to available.')
return this._setAvailable()
}).then(() => {
log('[8/8] Primary migrated to new settings.')
const timer = setInterval(() => {
bar.tick()
if (bar.complete) {
clearInterval(timer)
return resolve()
}
}, 100)
})
log('[4/8] Setting primary to unavailable.')
await CouchContinuum._setUnavailable(this.source.href)
log('[5/8] Beginning replication of replica to primary...')
await this._replicate(this.target, this.source)
log('[6/8] Replicated. Destroying replica...')
await this._destroyDb(this.target.href)
log('[7/8] Setting primary to available.')
await CouchContinuum._setAvailable(this.source.href)
log('[8/8] Primary migrated to new settings.')
}
}
{
"name": "couch-continuum",
"version": "2.0.1-alpha",
"version": "2.1.0-alpha",
"description": "Tool for migrating CouchDB databases to new configuration values.",

@@ -5,0 +5,0 @@ "main": "index.js",

@@ -87,18 +87,24 @@ # couch-continuum

Options:
--version Show version number [boolean]
--couchUrl, -u The URL of the CouchDB cluster to act upon.
--version Show version number [boolean]
--source, --dbNames, -N, -s The name or URL of a database to use as a
primary. [string] [required]
--target, --copyName, -c, -t The name or URL of a database to use as a
replica. Defaults to {source}_temp_copy [string]
--couchUrl, -u The URL of the CouchDB cluster to act upon.
[default: "http://admin:password@localhost:5984"]
--interval, -i How often (in milliseconds) to check replication tasks
for progress. [default: 1000]
-q The desired "q" value for the new database. [number]
-n The desired "n" value for the new database. [number]
--verbose, -v Enable verbose logging. [boolean]
--placement, -p Placement rule for the affected database(s). [string]
--filterTombstones, -f Filter tombstones during replica creation.
[default: false]
--config Path to JSON config file
--dbName, -N The name of the database to modify.[string] [required]
--copyName, -c The name of the database to use as a replica. Defaults
to {dbName}_temp_copy [string]
-h, --help Show help [boolean]
--interval, -i How often (in milliseconds) to check replication
tasks for progress. [default: 1000]
-q The desired "q" value for the new database.
[number]
-n The desired "n" value for the new database.
[number]
--verbose, -v Enable verbose logging. [boolean]
--placement, -p Placement rule for the affected database(s).
[string]
--filterTombstones, -f Filter tombstones during replica creation. Does
not work with CouchDB 1.x [default: false]
--replicateSecurity, -r Replicate a database's /_security object in
addition to its documents. [default: false]
--config Path to JSON config file
-h, --help Show help [boolean]
```

@@ -109,8 +115,14 @@

```
$ couch-continuum -N hello-world -q 4 -u http://... -v
[couch-continuum] Created new continuum: {"db1":"hello-world","db2":"hello-world_temp_copy","interval":1000,"q":4,"n":1}
[couch-continuum] Migrating database 'hello-world'...
[couch-continuum] Creating replica hello-world_temp_copy...
$ couch-continuum -s hello-world -q 4 -u https://... -v
[couch-continuum] Created new continuum: {
"url": "localhost:5984",
"source": "localhost:5984/hello-world",
"target": "localhost:5984/hello-world_temp_copy",
"interval": 1000,
"q": 4
}
[couch-continuum] Migrating database: localhost:5984/hello-world
[couch-continuum] Creating replica localhost:5984/hello-world_temp_copy...
[couch-continuum] [0/5] Checking if primary is in use...
[couch-continuum] [1/5] Creating replica db: hello-world_temp_copy
[couch-continuum] [1/5] Creating replica db: localhost:5984/hello-world_temp_copy
[couch-continuum] [2/5] Beginning replication of primary to replica...

@@ -121,3 +133,3 @@ [couch-continuum] [3/5] Verifying primary did not change during replication...

Ready to replace the primary with the replica. Continue? [y/N] y
[couch-continuum] Replacing primary hello-world...
[couch-continuum] Replacing primary localhost:5984/hello-world using localhost:5984/hello-world_temp_copy...
[couch-continuum] [0/8] Checking if primary is in use...

@@ -127,3 +139,3 @@ [couch-continuum] [1/8] Verifying primary and replica match...

[couch-continuum] [3/8] Recreating primary with new settings...
[couch-continuum] Recreating primary hello-world
[couch-continuum] Recreating primary localhost:5984/hello-world
[couch-continuum] (====================) 100% 0.0s

@@ -135,3 +147,3 @@ [couch-continuum] [4/8] Setting primary to unavailable.

[couch-continuum] [8/8] Primary migrated to new settings.
[couch-continuum] ... success!
Migrated database: localhost:5984/hello-world
```

@@ -138,0 +150,0 @@

Sorry, the diff of this file is not supported yet

SocketSocket SOC 2 Logo

Product

  • Package Alerts
  • Integrations
  • Docs
  • Pricing
  • FAQ
  • Roadmap
  • Changelog

Packages

npm

Stay in touch

Get open source security insights delivered straight into your inbox.


  • Terms
  • Privacy
  • Security

Made with ⚡️ by Socket Inc