diff --git a/helpers/eventify.js b/helpers/eventify.js new file mode 100644 index 0000000..939e4c5 --- /dev/null +++ b/helpers/eventify.js @@ -0,0 +1,31 @@ +module.exports = eventify + +/** + * runs a method from the API and triggers events on each object. + * + * Note that we didn't implement this pased on PouchDB's .changes() + * API on purpose, because of the timeing the events would get triggered. + * See https://github.com/hoodiehq/pouchdb-hoodie-api/issues/54 + **/ +function eventify (db, state, method, eventName) { + return function () { + return method.apply(db, arguments).then(function (result) { + if (Array.isArray(result)) { + result.forEach(triggerEvent.bind(null, state, eventName)) + } else { + triggerEvent(state, eventName, result) + } + + return result + }) + } +} + +function triggerEvent (state, eventName, object) { + if (!eventName) { + eventName = parseInt(object._rev, 10) > 1 ? 'update' : 'add' + } + + state.emitter.emit(eventName, object) + state.emitter.emit('change', eventName, object) +} diff --git a/helpers/start-listen-to-changes.js b/helpers/start-listen-to-changes.js deleted file mode 100644 index c4179d0..0000000 --- a/helpers/start-listen-to-changes.js +++ /dev/null @@ -1,26 +0,0 @@ -module.exports = startListenToChanges - -var toObject = require('../utils/to-object') - -function startListenToChanges (state) { - this.changes({ - since: 'now', - live: true, - include_docs: true - }) - .on('create', function (change) { - var doc = change.doc - state.emitter.emit('add', toObject(doc)) - state.emitter.emit('change', 'add', toObject(doc)) - }) - .on('update', function (change) { - var doc = change.doc - state.emitter.emit('update', toObject(doc)) - state.emitter.emit('change', 'update', toObject(doc)) - }) - .on('delete', function (change) { - var doc = change.doc - state.emitter.emit('remove', toObject(doc)) - state.emitter.emit('change', 'remove', toObject(doc)) - }) -} diff --git a/index.js b/index.js index 24a628c..b0270cd 100644 --- a/index.js +++ b/index.js @@ -3,26 +3,25 @@ var exports = module.exports = { hoodieApi: hoodieApi } var EventEmitter = require('events').EventEmitter -var startListenToChanges = require('./helpers/start-listen-to-changes') + +var eventify = require('./helpers/eventify') function hoodieApi (options) { var state = { emitter: options && options.emitter || new EventEmitter() } - state.emitter.once('newListener', startListenToChanges.bind(this, state)) - return { db: this, - add: require('./add').bind(this), + add: eventify(this, state, require('./add')), find: require('./find').bind(this), findAll: require('./find-all').bind(this), - findOrAdd: require('./find-or-add').bind(this), - update: require('./update').bind(this), - updateOrAdd: require('./update-or-add').bind(this), - updateAll: require('./update-all').bind(this), - remove: require('./remove').bind(this), - removeAll: require('./remove-all').bind(this), + findOrAdd: eventify(this, state, require('./find-or-add')), + update: eventify(this, state, require('./update')), + updateOrAdd: eventify(this, state, require('./update-or-add')), + updateAll: eventify(this, state, require('./update-all')), + remove: eventify(this, state, require('./remove'), 'remove'), + removeAll: eventify(this, state, require('./remove-all'), 'remove'), on: require('./lib/on').bind(this, state), one: require('./lib/one').bind(this, state), off: require('./lib/off').bind(this, state), diff --git a/remove-all.js b/remove-all.js index 6685eac..9fa1613 100644 --- a/remove-all.js +++ b/remove-all.js @@ -14,9 +14,8 @@ module.exports = removeAll */ function removeAll (filter) { var objects - var db = this - return db.allDocs({ + return this.allDocs({ include_docs: true }) @@ -36,7 +35,7 @@ function removeAll (filter) { }) }) - .then(db.bulkDocs.bind(db)) + .then(this.bulkDocs.bind(this)) .then(function (results) { return results.map(function (result, i) { diff --git a/tests/specs/events.js b/tests/specs/events.js index 8fad2cf..f58622d 100644 --- a/tests/specs/events.js +++ b/tests/specs/events.js @@ -2,7 +2,6 @@ var test = require('tape') var dbFactory = require('../utils/db') -var waitFor = require('../utils/wait-for') test('has "on" method', function (t) { t.plan(1) @@ -44,10 +43,6 @@ test('store.on("add") with adding one', function (t) { foo: 'bar' }) - .then(waitFor(function () { - return addEvents.length - }, 1)) - .then(function () { t.is(addEvents.length, 1, 'triggers 1 add event') t.is(addEvents[0].object.foo, 'bar', 'event passes object') @@ -68,10 +63,6 @@ test('store.on("add") with adding two', function (t) { {foo: 'baz'} ]) - .then(waitFor(function () { - return addEvents.length - }, 2)) - .then(function () { var orderedObjAttrs = [ addEvents[0].object.foo, @@ -103,10 +94,6 @@ test('store.on("add") with one element added before registering event and one af }) }) - .then(waitFor(function () { - return addEvents.length - }, 1)) - .then(function () { t.is(addEvents.length, 1, 'triggers only 1 add event') t.is(addEvents[0].object.foo, 'baz', 'event passes object') @@ -131,10 +118,6 @@ test('store.on("add") with add & update', function (t) { return store.updateOrAdd('test', {nr: 2}) }) - .then(waitFor(function () { - return addEvents.length - }, 1)) - .then(function () { t.is(addEvents.length, 1, 'triggers 1 add event') t.is(addEvents[0].object.nr, 1, 'event passes object') @@ -161,10 +144,6 @@ test('store.on("update") with updating one', function (t) { }) }) - .then(waitFor(function () { - return updateEvents.length - }, 1)) - .then(function () { t.is(updateEvents.length, 1, 'triggers 1 update event') t.is(updateEvents[0].object.foo, 'bar', 'event passes object') @@ -192,10 +171,6 @@ test('store.on("update") with updating two', function (t) { ]) }) - .then(waitFor(function () { - return updateEvents.length - }, 2)) - .then(function () { var orderedObjAttrs = [ updateEvents[0].object.foo, @@ -226,10 +201,6 @@ test('store.on("update") with add & update', function (t) { return store.updateOrAdd('test', {nr: 2}) }) - .then(waitFor(function () { - return updateEvents.length - }, 1)) - .then(function () { t.is(updateEvents.length, 1, 'triggers 1 update event') t.is(updateEvents[0].object.nr, 2, 'event passes object') @@ -256,10 +227,6 @@ test('store.on("update") with update all', function (t) { }) }) - .then(waitFor(function () { - return updateEvents.length - }, 2)) - .then(function () { var orderedObjAttrs = [ updateEvents[0].object.foo, @@ -290,10 +257,6 @@ test('store.on("remove") with removing one', function (t) { return store.remove('one') }) - .then(waitFor(function () { - return removeEvents.length - }, 1)) - .then(function () { t.is(removeEvents.length, 1, 'triggers 1 remove event') t.is(removeEvents[0].object.foo, 'bar', 'event passes object') @@ -318,10 +281,6 @@ test('store.on("remove") with removing two', function (t) { return store.remove(['one', 'two']) }) - .then(waitFor(function () { - return removeEvents.length - }, 2)) - .then(function () { var orderedObjAttrs = [ removeEvents[0].object.id, @@ -352,10 +311,6 @@ test('store.on("remove") with remove all', function (t) { return store.removeAll() }) - .then(waitFor(function () { - return removeEvents.length - }, 2)) - .then(function () { var orderedObjAttrs = [ removeEvents[0].object.id, @@ -381,10 +336,6 @@ test('store.on("change") with adding one', function (t) { foo: 'bar' }) - .then(waitFor(function () { - return changeEvents.length - }, 1)) - .then(function () { t.is(changeEvents.length, 1, 'triggers 1 change event') t.is(changeEvents[0].eventName, 'add', 'passes the event name') @@ -412,10 +363,6 @@ test('store.on("change") with updating one', function (t) { }) }) - .then(waitFor(function () { - return changeEvents.length - }, 1)) - .then(function () { t.is(changeEvents.length, 1, 'triggers 1 change event') t.is(changeEvents[0].eventName, 'update', 'passes the event name') @@ -441,10 +388,6 @@ test('store.on("change") with removing one', function (t) { return store.remove('test') }) - .then(waitFor(function () { - return changeEvents.length - }, 1)) - .then(function () { t.is(changeEvents.length, 1, 'triggers 1 change event') t.is(changeEvents[0].eventName, 'remove', 'passes the event name') @@ -473,10 +416,6 @@ test('store.on("change") with adding one and updating it afterwards', function ( }) }) - .then(waitFor(function () { - return changeEvents.length - }, 2)) - .then(function () { t.is(changeEvents.length, 2, 'triggers 2 change events') t.is(changeEvents[0].object.foo, 'bar', '1st event passes object') @@ -506,10 +445,6 @@ test('store.off("add") with one add handler', function (t) { foo: 'bar' }) - .then(waitFor(function () { - return changeEvents.length - }, 1)) - .then(function () { t.is(addEvents.length, 0, 'triggers no add event') }) @@ -535,10 +470,6 @@ test('store.off("add") with removing one of two add handlers', function (t) { foo: 'bar' }) - .then(waitFor(function () { - return secondAddHandlerEvents.length - }, 1)) - .then(function () { t.is(firstAddHandlerEvents.length, 0, 'triggers no add event on removed handler') }) @@ -571,10 +502,6 @@ test('store.off("update") with one update handler', function (t) { }) }) - .then(waitFor(function () { - return changeEvents.length - }, 1)) - .then(function () { t.is(updateEvents.length, 0, 'triggers no update event') }) @@ -604,10 +531,6 @@ test('store.off("remove") with one remove handler', function (t) { return store.remove('one') }) - .then(waitFor(function () { - return changeEvents.length - }, 1)) - .then(function () { t.is(removeEvents.length, 0, 'triggers no remove event') }) @@ -626,10 +549,6 @@ test('store.one("add") with adding one', function (t) { foo: 'bar' }) - .then(waitFor(function () { - return addEvents.length - }, 1)) - .then(function () { t.is(addEvents.length, 1, 'triggers 1 add event') t.is(addEvents[0].object.foo, 'bar', 'event passes object') @@ -652,10 +571,6 @@ test('store.one("add") with adding two', function (t) { {foo: 'baz'} ]) - .then(waitFor(function () { - return addEvents.length - }, 2)) - .then(function () { t.is(oneAddEvent.length, 1, 'triggers only add event') t.is(oneAddEvent[0].object.foo, 'bar', 'event passes object') @@ -677,10 +592,6 @@ test('store.one("add") with add & update', function (t) { return store.updateOrAdd('test', {nr: 2}) }) - .then(waitFor(function () { - return addEvents.length - }, 1)) - .then(function () { t.is(addEvents.length, 1, 'triggers 1 add event') t.is(addEvents[0].object.nr, 1, 'event passes object') @@ -706,10 +617,6 @@ test('store.one("add") with one element added before registering event and one a }) }) - .then(waitFor(function () { - return addEvents.length - }, 1)) - .then(function () { t.is(addEvents.length, 1, 'triggers only 1 add event') t.is(addEvents[0].object.foo, 'baz', 'event passes object') @@ -736,10 +643,6 @@ test('store.one("update") with updating one', function (t) { }) }) - .then(waitFor(function () { - return updateEvents.length - }, 1)) - .then(function () { t.is(updateEvents.length, 1, 'triggers 1 update event') t.is(updateEvents[0].object.foo, 'bar', 'event passes object') @@ -770,10 +673,6 @@ test('store.one("update") with updating two', function (t) { ]) }) - .then(waitFor(function () { - return updateEvents.length - }, 2)) - .then(function () { t.is(oneUpdateEvent.length, 1, 'triggers 1 update event') t.is(oneUpdateEvent[0].object.foo, 'bar', 'event passes object') @@ -801,10 +700,6 @@ test('store.one("update") with add & update', function (t) { }) }) - .then(waitFor(function () { - return updateEvents.length - }, 1)) - .then(function () { t.is(updateEvents.length, 1, 'triggers 1 update event') t.is(updateEvents[0].object.foo, 'bar', 'event passes object') @@ -833,10 +728,6 @@ test('store.one("update") with update all', function (t) { }) }) - .then(waitFor(function () { - return updateEvents.length - }, 2)) - .then(function () { t.is(oneUpdateEvent.length, 1, 'triggers 1 update event') t.is(oneUpdateEvent[0].object.id, 'first', 'event passes object') @@ -861,10 +752,6 @@ test('store.one("remove") with removing one', function (t) { return store.remove('one') }) - .then(waitFor(function () { - return removeEvents.length - }, 1)) - .then(function () { t.is(removeEvents.length, 1, 'triggers 1 remove event') t.is(removeEvents[0].object.id, 'one', 'event passes object') @@ -891,10 +778,6 @@ test('store.one("remove") with removing two', function (t) { return store.remove(['one', 'two']) }) - .then(waitFor(function () { - return removeEvents.length - }, 2)) - .then(function () { t.is(oneRemoveEvent.length, 1, 'triggers 1 remove event') t.is(oneRemoveEvent[0].object.id, 'one', 'event passes object') @@ -921,10 +804,6 @@ test('store.one("remove") with remove all', function (t) { return store.removeAll() }) - .then(waitFor(function () { - return removeEvents.length - }, 2)) - .then(function () { t.is(oneRemoveEvent.length, 1, 'triggers 1 remove event') t.is(oneRemoveEvent[0].object.id, 'one', 'event passes object') @@ -944,10 +823,6 @@ test('store.one("change") with adding one', function (t) { foo: 'bar' }) - .then(waitFor(function () { - return changeEvents.length - }, 1)) - .then(function () { t.is(changeEvents.length, 1, 'triggers 1 change event') t.is(changeEvents[0].eventName, 'add', 'passes the event name') @@ -971,10 +846,6 @@ test('store.one("change") with adding two', function (t) { {foo: 'baz'} ]) - .then(waitFor(function () { - return changeEvents.length - }, 2)) - .then(function () { t.is(oneChangeEvent.length, 1, 'triggers 1 change event') t.is(oneChangeEvent[0].eventName, 'add', 'passes the event name') diff --git a/tests/specs/find-or-add.js b/tests/specs/find-or-add.js index 7137734..41108ea 100644 --- a/tests/specs/find-or-add.js +++ b/tests/specs/find-or-add.js @@ -122,3 +122,25 @@ test('store.findOrAdd([object1, object2])', function (t) { }) }) }) + +test.skip('#58 store.findOrAdd(id, object) triggers no events when finds existing', function (t) { + t.plan(1) + + var db = dbFactory() + var store = db.hoodieApi() + var triggeredEvents = false + + store.on('add', function () { + triggeredEvents = true + }) + + store.add({id: 'exists', foo: 'bar'}) + + .then(function () { + return store.findOrAdd('exists', {foo: 'baz'}) + }) + + .then(function () { + t.is(triggeredEvents, false, 'triggers no events') + }) +}) diff --git a/tests/specs/remove-all.js b/tests/specs/remove-all.js index 9566209..0643cf8 100644 --- a/tests/specs/remove-all.js +++ b/tests/specs/remove-all.js @@ -14,18 +14,17 @@ test('store.removeAll exists', function (t) { }) test('store.removeAll()', function (t) { - t.plan(4) + t.plan(6) var db = dbFactory() var store = db.hoodieApi() return store.add([{ - foo: 'foo', - bar: 'foo' + foo: 'foo' }, { - foo: 'bar' + foo: 'foo' }, { - foo: 'baz' + foo: 'foo' }]) .then(function () { @@ -33,6 +32,9 @@ test('store.removeAll()', function (t) { }) .then(function (objects) { + t.is(objects.length, 3, 'resolves all') + t.is(objects[0].foo, 'foo', 'resolves with properties') + objects.forEach(function (object) { t.is(parseInt(object._rev, 10), 2, 'new revision') }) diff --git a/tests/specs/update-all.js b/tests/specs/update-all.js index d77a0f8..54fdf7e 100644 --- a/tests/specs/update-all.js +++ b/tests/specs/update-all.js @@ -14,7 +14,7 @@ test('has "updateAll" method', function (t) { }) test('store.updateAll(changedProperties)', function (t) { - t.plan(10) + t.plan(12) var db = dbFactory() var store = db.hoodieApi() @@ -36,9 +36,11 @@ test('store.updateAll(changedProperties)', function (t) { .then(function (results) { t.is(results.length, 3, 'resolves all') + t.ok(results[0].id, 'resolves with id') + t.is(results[0].bar, 'bar', 'resolves with properties') results.forEach(function (result) { - t.ok(/^2-/.test(result.rev), 'new revision') + t.ok(/^2-/.test(result._rev), 'new revision') }) return null @@ -80,7 +82,7 @@ test('store.updateAll(updateFunction)', function (t) { t.is(results.length, 3, 'resolves all') results.forEach(function (result) { - t.ok(/^2-/.test(result.rev), 'new revision') + t.ok(/^2-/.test(result._rev), 'new revision') }) return null diff --git a/tests/utils/wait-for.js b/tests/utils/wait-for.js deleted file mode 100644 index ee68220..0000000 --- a/tests/utils/wait-for.js +++ /dev/null @@ -1,36 +0,0 @@ -module.exports = waitFor - -var Promise = require('pouchdb/extras/promise') - -function waitFor (check, expected, timeout, interval) { - return function () { - if (!timeout) { - timeout = 1000 - } - if (!interval) { - interval = 10 - } - - if (check() === expected) { - return Promise.resolve() - } - - return new Promise(function (resolve, reject) { - var i = setInterval(test, interval) - - function test () { - timeout -= interval - if (timeout <= 0) { - reject(new Error('timeout')) - clearInterval(i) - return - } - if (check() !== expected) { - return - } - resolve() - clearInterval(i) - } - }) - } -} diff --git a/update-all.js b/update-all.js index d2bb6ff..ae404f4 100644 --- a/update-all.js +++ b/update-all.js @@ -2,6 +2,9 @@ var extend = require('pouchdb-extend') +var toObject = require('./utils/to-object') +var toDoc = require('./utils/to-doc') + module.exports = updateAll /** @@ -15,6 +18,7 @@ function updateAll (changedProperties) { var Promise = this.constructor.utils.Promise var type = typeof changedProperties + var objects if (type !== 'object' && type !== 'function') { return Promise.reject(new Error('Must provide object or function')) @@ -25,22 +29,30 @@ function updateAll (changedProperties) { }) .then(function (res) { - var docs = res.rows.map(function (row) { - return row.doc + objects = res.rows.map(function (row) { + return toObject(row.doc) }) if (type === 'function') { - return docs.map(changedProperties) + objects.forEach(changedProperties) + return objects.map(toDoc) } - return docs.map(function (doc) { - return extend(doc, changedProperties) + return objects.map(function (object) { + extend(object, changedProperties) + return toDoc(object) }) }) + .then(function (result) { + return result + }) .then(this.bulkDocs.bind(this)) .then(function (results) { - return results + return results.map(function (result, i) { + objects[i]._rev = result.rev + return objects[i] + }) }) }