slouch/scripts/system.js
Geoff Cox da7ca1123e 100% coverage (#4)
* doc(readme): clean up reasons

* doc(motto)

* test(db-and-doc): more coverage

* test(create-or-update-ignore-conflict)

* test(upsert)

* test(ignore-missing)

* test(post-and-ignore-conflict)

* test(get-merge-put)

* refactor(all): rename post and put

* test(get-merge-create-or-update)

* test(get-merge-update-ignore-conflict)

* test(get-merge-upsert)

* test(get-modify-upsert)

* refactor(doc): redundant code

* test(destroy-ignore-conflict)

* test(get-and-destroy)

* test(mark-as-destroyed)

* test(set-destroyed)

* refactor(attachment)

* test(doc): 100% coverage

* test(attachment): create with base 64

* test(attachment): clean up binary code

* test(attachment): get

* test(attachment): destroy

* test(system): is couchdb 1

* test(system): get

* test(system): reset

* test(updates)

* test(updates)

* test(all): unique DB names

* test(system): reactivate tests

* test(user): add role

* test(user): downsert role

* feat(stream-iterator): indefinite

* test(user): 100% coverage

* test(request-class)

* test(request-class): 100% coverage

* test(config)

* test(config): more coverage

* test(config): more coverage

* test(config): 100% coverage

* test(all): 100% coverage

* refactor(beautify)

* test(coverage): enforce 100%

* test(system): fix race condition

* test(user): shortcut for browser

* test(updates): test continuous stream in phantomjs

* test(updates): test continuous stream in phantomjs

* test(continuous): mock for phantomjs

* test(system): abort iterators

* test(system): fake abort
2017-07-18 07:45:32 -07:00

151 lines
4.3 KiB
JavaScript

'use strict';
var promisedRequest = require('./request'),
FilteredStreamIterator = require('quelle').FilteredStreamIterator,
PersistentStreamIterator = require('quelle').PersistentStreamIterator,
StreamIterator = require('quelle').StreamIterator,
sporks = require('sporks'),
Promise = require('sporks/scripts/promise'),
request = require('request');
var System = function (slouch) {
this._slouch = slouch;
this._couchDB1 = null;
this._request = request;
};
System.prototype._isCouchDB1 = function () {
return this.get().then(function (obj) {
return obj.version[0] === '1';
});
};
System.prototype.isCouchDB1 = function () {
var self = this;
return Promise.resolve().then(function () {
if (self._couchDB1 === null) {
return self._isCouchDB1().then(function (isCouchDB1) {
self._couchDB1 = isCouchDB1;
return self._couchDB1;
});
} else {
return self._couchDB1;
}
});
};
System.prototype.get = function () {
return promisedRequest.request({
uri: this._slouch._url + '/',
method: 'GET'
}, true);
};
System.prototype.reset = function (exceptDBNames) {
var self = this,
except = exceptDBNames ? sporks.flip(exceptDBNames) : {},
dbsToDestroyAndRecreate = [];
return self.isCouchDB1().then(function (isCouchDB1) {
if (isCouchDB1) {
dbsToDestroyAndRecreate = ['_replicator'];
// CouchDB 1 automatically recreates the _users database
} else {
// CouchDB 2 does not automatically recreate any databases so we have to do it ourselves
dbsToDestroyAndRecreate = ['_replicator', '_global_changes', '_users'];
}
return self._slouch.db.all().each(function (db) {
if (except[db]) {
// Do nothing
return Promise.resolve();
} else if (dbsToDestroyAndRecreate.indexOf(db) !== -1) {
return self._slouch.db.destroy(db).then(function () {
return self._slouch.db.create(db);
});
} else {
return self._slouch.db.destroy(db);
}
});
});
};
// Use a JSONStream so that we don't have to load a large JSON structure into memory
System.prototype.updates = function (params) {
var indefinite = false,
jsonStreamParseStr = null;
if (params && params.feed === 'continuous') {
indefinite = true;
jsonStreamParseStr = undefined;
} else {
jsonStreamParseStr = 'results.*';
}
return new PersistentStreamIterator({
url: this._slouch._url + '/_db_updates',
method: 'GET',
qs: params
}, jsonStreamParseStr, indefinite, this._request);
};
System.prototype._cloneParams = function (params) {
return params ? sporks.clone(params) : {};
};
System.prototype._itemToUpdate = function (item) {
if (item.id) {
// Repackage the item so that it is compatible with _db_updates.
var parts = item.id.split(':');
return {
db_name: parts[1],
type: parts[0]
};
} else {
// Ignore items that don't have ids
return undefined;
}
};
System.prototype.updatesViaGlobalChanges = function (params) {
var self = this,
iterator = new StreamIterator();
self._slouch.db.get('_global_changes').then(function (dbDoc) {
var clonedParams = self._cloneParams(params);
clonedParams.since = dbDoc.update_seq;
// We pipe to the returned iterator so that the function can return an iterator who's content is
// deferred.
self._slouch.db.changes('_global_changes', clonedParams).pipe(iterator);
});
return new FilteredStreamIterator(iterator, function (item) {
return self._itemToUpdate(item);
});
};
// The _db_updates feed in CouchDB does not include any history, i.e. any updates before when we
// start listening to the feed. CouchDB 2 on the other hand stores the complete history in the
// _global_changes database. We use the _changes feed on the _global_changes database to provide a
// backwards compatible API.
System.prototype.updatesNoHistory = function (params) {
var self = this,
iterator = new StreamIterator();
self._slouch.system.isCouchDB1().then(function (isCouchDB1) {
if (isCouchDB1) {
return self.updates(params);
} else {
return self.updatesViaGlobalChanges(params);
}
}).then(function (_iterator) {
// We pipe to the returned iterator so that the function can return an iterator who's content is
// deferred.
_iterator.pipe(iterator);
});
return iterator;
};
module.exports = System;