Commits (5)
image: node:alpine
- couchdb
- npm install
- npm audit
- npm test
seem = require 'seem'
delay = 2000
`replicate(source,target,name,extensions)`: replicate database `name` from `source` to `target` (all strings) by creating a replication `pull` document on the target.
Before submission, the replication document is passed to the (optional) `extensions` callback.
Returns a Promise. Make sure you `catch()` any errors.
module.exports = replicate = seem (prefix_source,prefix_target,name,extensions_cb) ->
module.exports = replicate = (prefix_source,prefix_target,name,extensions_cb) ->
replicator_db = "#{prefix_target}/_replicator"
replicator = new PouchDB replicator_db, skip_setup: true
replicator = new CouchDB replicator_db
Here we have multiple solutions, so I'll test them:
- either delete any existing document with the same name (this should cancel the replication, based on the CouchDB docs), and recreate a new one;
......@@ -53,13 +51,13 @@ Remove authorization from the source and target, because...
...even with CouchDB ~~1.6.1~~ 2.1.1 we still have the issue with CouchDB not properly managing authorization headers when a username and password are provided in the original URI that contains "special" characters (like `@` or space). So let's handle it ourselves.
if source.auth?
auth = (new Buffer source.auth).toString 'base64'
auth = (Buffer.from source.auth).toString 'base64'
debug "Encoded `#{source.auth}` of `#{prefix_source}` as `#{auth}`."
model.source.headers =
Authorization: "Basic #{auth}"
if target.auth?
auth = (new Buffer target.auth).toString 'base64'
auth = (Buffer.from target.auth).toString 'base64'
debug "Encoded `#{target.auth}` of `#{prefix_target}` as `#{auth}`."
model.target.headers =
Authorization: "Basic #{auth}"
......@@ -68,7 +66,7 @@ Let the callback add any field they'd like.
Note: the callback might also prevent replication if it throws. This is intentional.
Note: the callback might return a Promise. Or not. We'll deal with both.
yield extensions_cb? model
await extensions_cb? model
Create a (somewhat) unique ID for the document.
......@@ -88,25 +86,39 @@ Let's get started.
Create the target database if it doesn't already exist.
target = new PouchDB "#{prefix_target}/#{name}", skip_setup: false
yield target.info()
target = new CouchDB "#{prefix_target}/#{name}"
await target.create()
.catch (error) ->
debug.error "create #{name}", error
Catch 412 errors as they indicate the database early exists.
if error.status? and error.status is 412
debug "Database already exists"
Report all other errors.
debug.error "Creating database #{name} failed.", error
Promise.reject error
target = null
When using the deletion method, first delete the existing replication document.
if use_delete
{_rev} = yield replicator
{_rev} = await replicator
.get model._id
.catch (error) -> {}
yield replicator.remove model._id, _rev if _rev?
await replicator.delete id:model._id, rev:_rev if _rev?
Give CouchDB some time to breath.
yield sleep delay
await sleep delay
Update the replication document.
{_rev} = yield replicator
{_rev} = await replicator
.get model._id
.catch (error) -> {}
......@@ -117,7 +129,7 @@ Update the replication document.
debug 'Creating replication', doc
yield replicator
await replicator
.put doc
.catch (error) ->
debug.error "put #{model._id}", error
......@@ -135,23 +147,22 @@ Report all other errors.
Give CouchDB some time to breath.
yield sleep delay
await sleep delay
Log the status of the replicator
doc = yield replicator
doc = await replicator
.get model._id
.catch (error) -> {}
debug 'Replication status', doc
replicator = null
PouchDB = require 'ccnq4-pouchdb'
.plugin require 'pouchdb-replication'
CouchDB = require 'most-couchdb'
sleep = (timeout) -> new Promise (resolve) -> setTimeout resolve, timeout
crypto = require 'crypto'
This diff is collapsed.
"name": "frantic-team",
"version": "1.3.0",
"version": "1.4.0",
"description": "Inject replication documents in CouchDB",
"main": "index.js",
"scripts": {
"prepublish": "npm install --only=dev && rm package-lock.json && coffee --no-header -c -M *.coffee.md",
"pretest": "npm install",
"test": "mocha"
"prepublishOnly": "npm run build",
"build": "npm install --only=dev && rm package-lock.json && coffee --no-header -c -M *.coffee.md",
"pretest": "npm run build && coffee -c test/*.coffee.md",
"test": "nyc mocha"
"repository": {
"type": "git",
"url": "github.com/shimaore/frantic-team"
"url": "gitlab.com/shimaore/frantic-team"
"keywords": [
......@@ -20,19 +21,17 @@
"author": "Stéphane Alnet <[email protected]> (https://stephane.shimaore.net/)",
"license": "Unlicense",
"bugs": {
"url": "https://github.com/shimaore/frantic-team/issues"
"url": "https://gitlab.com/shimaore/frantic-team/issues"
"homepage": "https://github.com/shimaore/frantic-team",
"homepage": "https://gitlab.com/shimaore/frantic-team",
"devDependencies": {
"chai": "^4.1.2",
"coffee-coverage": "^2.0.1",
"coffee-script": "^1.12.7",
"mocha": "^4.0.1"
"chai": "^4.2.0",
"coffeescript": "^2.3.2",
"mocha": "^5.2.0",
"nyc": "^13.1.0"
"dependencies": {
"ccnq4-pouchdb": "^1.0.0",
"pouchdb-replication": "^6.4.1",
"seem": "^2.0.0",
"tangible": "^1.9.0"
"most-couchdb": "^1.5.0",
"tangible": "^3.0.0"
--compilers coffee.md:coffee-script/register
--require coffee-coverage/register-istanbul
--reporter spec
describe 'A basic replication', ->
replicate = require '..'
prefix = 'http://admin:[email protected]:5984'
CouchDB = require 'most-couchdb'
it 'should run', ->
@timeout 8000
await (new CouchDB "#{prefix}/_replicator").create()
await (new CouchDB "#{prefix}/example").create()
await replicate prefix, prefix, 'example'