diff --git a/docs/_layouts/learn.html b/docs/_layouts/learn.html index 4f49ddc4f1..7379b1c4ca 100644 --- a/docs/_layouts/learn.html +++ b/docs/_layouts/learn.html @@ -31,7 +31,8 @@

API

  • Database Information
  • Compaction
  • Revision diff
  • -
  • List databases
  • +
  • Database Events
  • +
  • Plugins
  • diff --git a/docs/api.md b/docs/api.md index 86a7e3e7eb..5bf0e337e8 100644 --- a/docs/api.md +++ b/docs/api.md @@ -19,7 +19,7 @@ Additionally, any method that only returns a single thing (e.g. `db.get`, but no new PouchDB([name], [options]) {% endhighlight %} -This method creates a database or opens an existing one. If you use a URL like `http://domain.com/dbname` then PouchDB will work as a client to an online CouchDB instance. Otherwise it will create a local database using whatever backend is present (i.e. IndexedDB, WebSQL, or LevelDB). +This method creates a database or opens an existing one. If you use a URL like `http://domain.com/dbname` then PouchDB will work as a client to an online CouchDB instance. Otherwise it will create a local database using whatever backend is present (i.e. IndexedDB, WebSQL, or LevelDB). ### Options @@ -743,6 +743,19 @@ db.revsDiff({ } {% endhighlight %} +## Events + +PouchDB is an [event emiter](http://nodejs.org/api/events.html#events_class_events_eventemitter) and will emit a 'created' event when a database is created. A 'destroy' event is emited when a database is destroyed. + +{% highlight js %} +PouchDB.on('created', function (dbName) { + // called whenver a db is created. +}); +PouchDB.on('destroyed', function (dbName) { + // called whenver a db is destroyed. +}); +{% endhighlight %} + ## Plugins Writing a plugin is easy the api is @@ -753,4 +766,5 @@ PouchDB.plugin({ }); {% endhighlight %} -This will add the function as a method of all databases with the given name, it will always be called in the so that `this` is db. \ No newline at end of file +This will add the function as a method of all databases with the given name, it will always be called in context so that `this` is db. + diff --git a/lib/adapter.js b/lib/adapter.js index f787ba4af4..523e662a78 100644 --- a/lib/adapter.js +++ b/lib/adapter.js @@ -3,7 +3,7 @@ var utils = require('./utils'); var merge = require('./merge'); var errors = require('./deps/errors'); - +var EventEmitter = require('events').EventEmitter; /* * A generic pouch adapter */ @@ -56,10 +56,11 @@ function computeHeight(revs) { }); return height; } - +utils.inherits(AbstractPouchDB, EventEmitter); module.exports = AbstractPouchDB; function AbstractPouchDB() { var self = this; + EventEmitter.call(this); self.autoCompact = function (callback) { if (!self.auto_compaction) { return callback; @@ -86,6 +87,52 @@ function AbstractPouchDB() { } }; }; + var listeners = 0, changes; + var eventNames = ['change', 'delete', 'create', 'update']; + this.on('newListener', function (eventName) { + if (~eventNames.indexOf(eventName)) { + if (listeners) { + listeners++; + return; + } else { + listeners++; + } + } else { + return; + } + var lastChange = 0; + changes = this.changes({ + conflicts: true, + include_docs: true, + continuous: true, + since: 'latest', + onChange: function (change) { + if (change.seq <= lastChange) { + return; + } + lastChange = change.seq; + self.emit('change', change); + if (change.doc._deleted) { + self.emit('delete', change); + } else if (change.doc._rev.split('-')[0] === '1') { + self.emit('create', change); + } else { + self.emit('update', change); + } + } + }); + }); + this.on('removeListener', function (eventName) { + if (~eventNames.indexOf(eventName)) { + listeners--; + if (listeners) { + return; + } + } else { + return; + } + changes.cancel(); + }); } AbstractPouchDB.prototype.post = utils.toPromise(function (doc, opts, callback) { if (typeof opts === 'function') { diff --git a/lib/adapters/http.js b/lib/adapters/http.js index f38edd278f..d55d21b633 100644 --- a/lib/adapters/http.js +++ b/lib/adapters/http.js @@ -1087,7 +1087,15 @@ function HttpPouch(opts, callback) { utils.ajax({ url: genDBUrl(host, ''), method: 'DELETE' - }, callback); + }, function (err, resp) { + if (err) { + api.emit('error', err); + callback(err); + } else { + api.emit('destroyed'); + callback(null, resp); + } + }); }); } diff --git a/lib/adapters/leveldb.js b/lib/adapters/leveldb.js index 40e5f918a9..79216b23ff 100644 --- a/lib/adapters/leveldb.js +++ b/lib/adapters/leveldb.js @@ -673,6 +673,10 @@ function LevelPouch(opts, callback) { }); }; api.destroy = utils.toPromise(function (opts, callback) { + if (!this.taskqueue.isReady) { + this.taskqueue.addTask('destroy', arguments); + return; + } if (typeof opts === 'function') { callback = opts; opts = {}; @@ -684,7 +688,15 @@ function LevelPouch(opts, callback) { if (err) { return callback(err); } - leveldown.destroy(name, callback); + leveldown.destroy(name, function (err, resp) { + if (err) { + api.emit('error', err); + callback(err); + } else { + api.emit('destroyed'); + callback(null, resp); + } + }); }); } }); diff --git a/lib/adapters/websql.js b/lib/adapters/websql.js index 69a1c56904..55c27ba61c 100644 --- a/lib/adapters/websql.js +++ b/lib/adapters/websql.js @@ -892,6 +892,7 @@ WebSqlPouch.valid = function () { }; WebSqlPouch.destroy = utils.toPromise(function (name, opts, callback) { + var self = this; var db = openDB(name, POUCH_VERSION, name, POUCH_SIZE); db.transaction(function (tx) { tx.executeSql('DROP TABLE IF EXISTS ' + DOC_STORE, []); @@ -899,6 +900,7 @@ WebSqlPouch.destroy = utils.toPromise(function (name, opts, callback) { tx.executeSql('DROP TABLE IF EXISTS ' + ATTACH_STORE, []); tx.executeSql('DROP TABLE IF EXISTS ' + META_STORE, []); }, unknownError(callback), function () { + self.emit('destroyed'); callback(); }); }); diff --git a/lib/constructor.js b/lib/constructor.js index 26f83854fa..174ab11d8a 100644 --- a/lib/constructor.js +++ b/lib/constructor.js @@ -133,6 +133,15 @@ function PouchDB(name, opts, callback) { } return; } + function destructionListner(event) { + if (event === 'destroyed') { + self.emit('destroyed'); + PouchDB.removeListener(opts.name, destructionListner); + } + } + PouchDB.on(opts.name, destructionListner); + self.emit('created', self); + PouchDB.emit('created', opts.originalName); self.taskqueue.ready(self); callback(null, self); diff --git a/lib/setup.js b/lib/setup.js index 39d84816bc..b4757bf404 100644 --- a/lib/setup.js +++ b/lib/setup.js @@ -2,10 +2,28 @@ var PouchDB = require("./constructor"); var utils = require('./utils'); +var EventEmitter = require('events').EventEmitter; PouchDB.adapters = {}; PouchDB.prefix = '_pouch_'; +var eventEmitter = new EventEmitter(); + +var eventEmitterMethods = [ + 'on', + 'addListener', + 'emit', + 'listeners', + 'once', + 'removeAllListeners', + 'removeListener', + 'setMaxListeners' +]; + +eventEmitterMethods.forEach(function (method) { + PouchDB[method] = eventEmitter[method].bind(eventEmitter); +}); +PouchDB.setMaxListeners(0); PouchDB.parseAdapter = function (name) { var match = name.match(/([a-z\-]*):\/\/(.*)/); var adapter; @@ -50,7 +68,16 @@ PouchDB.destroy = utils.toPromise(function (name, opts, callback) { var dbName = backend.name; // call destroy method of the particular adaptor - PouchDB.adapters[backend.adapter].destroy(dbName, opts, callback); + PouchDB.adapters[backend.adapter].destroy(dbName, opts, function (err, resp) { + if (err) { + callback(err); + } else { + PouchDB.emit('destroyed', dbName); + //so we don't have to sift through all dbnames + PouchDB.emit(dbName, 'destroyed'); + callback(null, resp); + } + }); }); PouchDB.allDbs = utils.toPromise(function (callback) { var err = new Error('allDbs method removed'); diff --git a/package.json b/package.json index 8b52e5a11e..82acae018d 100644 --- a/package.json +++ b/package.json @@ -61,7 +61,6 @@ "request": false, "levelup": false, "leveldown": false, - "events": false, "crypto": false, "bluebird": "lie", "level-sublevel": false diff --git a/tests/test.events.js b/tests/test.events.js new file mode 100644 index 0000000000..0e11dfe85e --- /dev/null +++ b/tests/test.events.js @@ -0,0 +1,118 @@ +'use strict'; + +var adapters = [ + 'http-1', + 'local-1' +]; +adapters.forEach(function (adapter) { + describe('test.events.js-' + adapter, function () { + //we can't use the same db becasue + var i = 0; + var dbs = {}; + beforeEach(function () { + dbs.name = testUtils.adapterUrl(adapter, 'events_tests' + i++); + }); + + afterEach(function (done) { + PouchDB.destroy(dbs.name, done); + }); + + it('PouchDB emits creation event', function (done) { + PouchDB.once('created', function (name) { + name.should.equal(dbs.name, 'should be same thing'); + done(); + }); + new PouchDB(dbs.name); + }); + it('PouchDB emits destruction event', function (done) { + new PouchDB(dbs.name + 1, function () { + PouchDB.destroy(dbs.name + 1); + }).once('destroyed', function () { + new PouchDB(dbs.name, function () { + done(); + }); + }); + }); + it('emit creation event', function (done) { + var db = new PouchDB(dbs.name).on('created', function (newDB) { + db.should.equal(newDB, 'should be same thing'); + done(); + }); + }); + + it('emit changes event', function (done) { + new PouchDB(dbs.name, function (err, db) { + var id = 'emiting'; + var obj = { + something: 'here', + somethingElse: 'overHere' + }; + db.on('change', function (change) { + change.seq.should.equal(1, 'changed'); + change.id.should.equal('emiting'); + done(err); + }); + db.put(obj, id); + }); + }); + + it('emit create event', function (done) { + new PouchDB(dbs.name, function (err, db) { + var id = 'creating'; + var obj = { + something: 'here', + somethingElse: 'overHere' + }; + db.on('create', function (change) { + change.id.should.equal('creating'); + change.seq.should.equal(1, 'created'); + done(err); + }); + db.put(obj, id); + }); + }); + + it('emit update event', function (done) { + new PouchDB(dbs.name, function (err, db) { + var id = 'updating'; + var obj = { + something: 'here', + somethingElse: 'overHere' + }; + db.on('update', function (change) { + change.id.should.equal('updating'); + change.seq.should.equal(2, 'seq 2, updated'); + done(err); + }); + + db.put(obj, id).then(function (doc) { + db.put({'something': 'else'}, id, doc.rev); + }); + + }); + }); + + it('emit delete event', function (done) { + new PouchDB(dbs.name, function (err, db) { + var id = 'emiting'; + var obj = { + something: 'here', + somethingElse: 'overHere' + }; + db.on('delete', function (change) { + change.seq.should.equal(2, 'deleted'); + change.id.should.equal('emiting'); + done(err); + }); + + db.put(obj, id).then(function (doc) { + db.remove({ + _id: id, + _rev: doc.rev + }); + }); + }); + }); + + }); +}); \ No newline at end of file diff --git a/tests/test.html b/tests/test.html index 25901f40c7..96856d3e41 100644 --- a/tests/test.html +++ b/tests/test.html @@ -29,6 +29,7 @@ +