pouchdb-replication-stream
Version:
PouchDB/CouchDB replication as a stream
162 lines (146 loc) • 4.09 kB
JavaScript
;
var utils = require('./pouch-utils');
var Promise = require('pouchdb-promise');
var ERROR_REV_CONFLICT = {
status: 409,
name: 'conflict',
message: 'Document update conflict'
};
var ndj = require('ndjson');
var ERROR_MISSING_DOC = {
status: 404,
name: 'not_found',
message: 'missing'
};
function WritableStreamPouch(opts, callback) {
var api = this;
api.instanceId = Math.random().toString();
api.ndj = ndj.serialize();
api.localStore = {};
api.originalName = opts.name;
// TODO: I would pass this in as a constructor opt, but
// PouchDB changed how it clones in 5.0.0 so this broke
api.setupStream = function (stream) {
api.ndj.pipe(stream);
};
/* istanbul ignore next */
api.type = function () {
return 'writableStream';
};
api._id = utils.toPromise(function (callback) {
callback(null, api.instanceId);
});
api._bulkDocs = function (req, opts, callback) {
var docs = req.docs;
var self = this;
/* istanbul ignore else */
if (opts.new_edits === false) {
// assume we're only getting this with new_edits=false,
// since this adapter is just a replication target
this.ndj.write({docs: docs}, function () {
callback(null, docs.map(function (doc) {
return {
ok: true,
id: doc._id,
rev: doc._rev
};
}));
});
} else {
// writing local docs for replication
Promise.all(docs.map(function (doc) {
self.localStore[doc._id] = doc;
})).then(function (res) {
callback(null, res);
}).catch(function (err) {
callback(err);
});
}
};
api._getRevisionTree = function (docId, callback) {
process.nextTick(function () {
callback(ERROR_MISSING_DOC);
});
};
api._close = function (callback) {
this.ndj.end(callback);
};
api._getLocal = function (id, callback) {
var self = this;
process.nextTick(function () {
var existingDoc = self.localStore[id];
/* istanbul ignore else */
if (existingDoc) {
callback(null, existingDoc);
} else {
callback(ERROR_MISSING_DOC);
}
});
};
api._putLocal = function (doc, opts, callback) {
var self = this;
/* istanbul ignore else */
if (typeof opts === 'function') {
callback = opts;
opts = {};
}
delete doc._revisions; // ignore this, trust the rev
var oldRev = doc._rev;
var id = doc._id;
var newRev;
if (!oldRev) {
newRev = doc._rev = '0-1';
} else {
newRev = doc._rev = '0-' + (parseInt(oldRev.split('-')[1], 10) + 1);
}
process.nextTick(function () {
var existingDoc = self.localStore[id];
/* istanbul ignore if */
if (existingDoc && oldRev !== existingDoc._rev) {
callback(ERROR_REV_CONFLICT);
} else {
self.localStore[id] = doc;
var done = function () {
callback(null, {ok: true, id: id, rev: newRev});
};
/* istanbul ignore else */
if ('last_seq' in doc) {
self.ndj.write({seq: doc.last_seq}, done);
} else {
done();
}
}
});
};
/* istanbul ignore next */
api._removeLocal = function (doc, callback) {
var self = this;
process.nextTick(function () {
var existingDoc = self.localStore[doc._id];
if (existingDoc && doc._rev !== existingDoc._rev) {
callback(ERROR_REV_CONFLICT);
} else {
delete self.localStore[doc._id];
callback(null, {ok: true, id: doc._id, rev: '0-0'});
}
});
};
/* istanbul ignore next */
api._destroy = function (opts, callback) {
if (typeof opts === 'function') {
callback = opts;
opts = {};
}
WritableStreamPouch.Changes.removeAllListeners(api.originalName);
process.nextTick(function () {
callback(null, {'ok': true});
});
};
process.nextTick(function () {
callback(null, api);
});
}
WritableStreamPouch.valid = function () {
return true;
};
module.exports = WritableStreamPouch;