From d07c87fae972f18bb92699c40e2df0aaa6dc8007 Mon Sep 17 00:00:00 2001 From: Andreas Siegel Date: Wed, 3 Apr 2019 15:50:43 +0200 Subject: [PATCH] Add support for filters in change feed (#111) * Add find to doc functions * Replace const by var * Merge upstream * Add support for filtered changes --- API.md | 49 ++++++++++++++++++++++++++-- package-lock.json | 4 +++ scripts/db.js | 24 +++++++++++--- test/spec/db.js | 82 +++++++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 153 insertions(+), 6 deletions(-) diff --git a/API.md b/API.md index 28fcee6..3605756 100644 --- a/API.md +++ b/API.md @@ -16,8 +16,8 @@ * unsetIgnoreMissing(path) * db * all() - * changes(dbName, params) - * changesArray(dbName, params) + * [changes(dbName, params, filter)](https://github.com/redgeoff/slouch/blob/master/API.md#changesdbname-params-filter) + * [changesArray(dbName, params, filter)](https://github.com/redgeoff/slouch/blob/master/API.md#changesarraydbname-params-filter) * copy(fromDBName, toDBName) * create(dbName) * destroy(dbName) @@ -97,6 +97,51 @@ * toUsername(userId) * upsertRole(username, role) +### DB + +#### changes(dbName, params, filter) + +Returns a list of changes in the database. See https://docs.couchdb.org/en/stable/api/database/changes.html for more details. + +The function returns an iterator that indefinitely returns changes from the database. + +You can use an optional third argument to pass a selector for filtering the change feed. + +Example: + +```js +slouch.db.changes('myDB', { + include_docs: true, + feed: 'continuous', + heartbeat: true +}, { + selector: { + thing: 'findme' + } +}); +``` + +#### changesArray(dbName, params, filter) + +Returns a list of changes in the database. See https://docs.couchdb.org/en/stable/api/database/changes.html for more details. + +The function returns an array of changes from the database. + +You can use an optional third argument to pass a selector for filtering the change feed. + +Example: + +```js +slouch.db.changesArray('myDB', { + include_docs: true, + feed: 'continuous', + heartbeat: true +}, { + selector: { + thing: 'findme' + } +}); +``` ### Doc diff --git a/package-lock.json b/package-lock.json index 2b1ffa6..d0b4154 100644 --- a/package-lock.json +++ b/package-lock.json @@ -4128,7 +4128,11 @@ "dependencies": { "events": { "version": "1.1.1", +<<<<<<< Updated upstream "resolved": "https://registry.npmjs.org/events/-/events-1.1.1.tgz", +======= + "resolved": "http://registry.npmjs.org/events/-/events-1.1.1.tgz", +>>>>>>> Stashed changes "integrity": "sha1-nr23Y1rQmccNzEwqH1AEKI6L2SQ=" }, "sporks": { diff --git a/scripts/db.js b/scripts/db.js index 138f836..3c778bc 100644 --- a/scripts/db.js +++ b/scripts/db.js @@ -63,7 +63,7 @@ DB.prototype._setSince = function (opts, lastSeq) { }; // Use a JSONStream so that we don't have to load a large JSON structure into memory -DB.prototype.changes = function (dbName, params) { +DB.prototype.changes = function (dbName, params, filter) { var self = this, indefinite = false, @@ -94,10 +94,18 @@ DB.prototype.changes = function (dbName, params) { request = self._slouch._request; } + // When a filter object is provided use the key to set the filter query param + // Supported filters include doc_ids or selector + if (filter && Object.keys(filter).length === 1) { + params.filter = '_' + Object.keys(filter)[0]; + } + var iterator = new CouchPersistentStreamIterator({ url: self._slouch._url + '/' + dbName + '/_changes', - method: 'GET', - qs: params + method: filter ? 'POST' : 'GET', // don't send a POST request if there is nothing for the body + qs: params, + json: filter, + parseBody: true }, jsonStreamParseStr, indefinite, request, forceReconnectAfterMilliseconds); return new FilteredStreamIterator(iterator, function (item) { @@ -108,10 +116,18 @@ DB.prototype.changes = function (dbName, params) { }; -DB.prototype.changesArray = function (dbName, params) { +DB.prototype.changesArray = function (dbName, params, filter) { + // When a filter object is provided use the key to set the filter query param + // Supported filters include doc_ids or selector + if (filter && Object.keys(filter).length === 1) { + params.filter = '_' + Object.keys(filter)[0]; + } + return this._slouch._req({ url: this._slouch._url + '/' + encodeURIComponent(dbName) + '/_changes', + method: filter ? 'POST' : 'GET', // don't send a POST request if there is nothing for the body qs: params, + json: filter, parseBody: true }); }; diff --git a/test/spec/db.js b/test/spec/db.js index 2091993..c255b6e 100644 --- a/test/spec/db.js +++ b/test/spec/db.js @@ -181,6 +181,45 @@ describe('db', function () { }); }); + it('should get changes with a selector', function () { + var changes = {}; + return createDocs().then(function () { + return db.changes(utils.createdDB, { + include_docs: true + }, { + selector: { + thing: 'jam' + } + }).each(function (change) { + // Use associative array as order is not guaranteed + changes[change.doc.thing] = true; + }); + }).then(function () { + changes.should.eql({ + jam: true + }); + }); + }); + + it('should get no changes with an unknown selector', function () { + var changes = {}; + return createDocs().then(function () { + return db.changes(utils.createdDB, { + include_docs: true + }, { + selector: { + thing: 'does-not-exist' + } + }).each(function (change) { + // Use associative array as order is not guaranteed + changes[change.doc.thing] = true; + }); + }).then(function () { + changes.should.eql({}); + }); + }); + + it('should get changes array', function () { var indexedChanges = {}; return createDocs().then(function () { @@ -201,6 +240,49 @@ describe('db', function () { }); }); + it('should get changes array with a selector', function () { + var indexedChanges = {}; + return createDocs().then(function () { + return db.changesArray(utils.createdDB, { + include_docs: true + }, { + selector: { + thing: 'jam' + } + }); + }).then(function (changes) { + // Order of changes not guaranteed so we will index the values for easy comparison + changes.results.forEach(function (change) { + indexedChanges[change.doc.thing] = true; + }); + + indexedChanges.should.eql({ + jam: true + }); + }); + }); + + it('should get no changes array with a unknown selector', function () { + var indexedChanges = {}; + return createDocs().then(function () { + return db.changesArray(utils.createdDB, { + include_docs: true + }, { + selector: { + thing: 'does-not-exist' + } + }); + }).then(function (changes) { + // Order of changes not guaranteed so we will index the values for easy comparison + changes.results.forEach(function (change) { + indexedChanges[change.doc.thing] = true; + }); + + indexedChanges.should.eql({}); + }); + }); + + var waitForChange = function (thing) { return sporks.waitFor(function () { return changes[thing];