Skip to content

Commit

Permalink
(#1115) - Add event emitter for DB change events
Browse files Browse the repository at this point in the history
  • Loading branch information
calvinmetcalf authored and daleharvey committed Feb 28, 2014
1 parent e8d8c62 commit 1e2601f
Show file tree
Hide file tree
Showing 11 changed files with 247 additions and 9 deletions.
3 changes: 2 additions & 1 deletion docs/_layouts/learn.html
Expand Up @@ -31,7 +31,8 @@ <h3>API</h3>
<li><a href="api.html#database_information">Database Information</a></li>
<li><a href="api.html#compaction">Compaction</a></li>
<li><a href="api.html#revisions_diff">Revision diff</a></li>
<li><a href="api.html#list_databases">List databases</a></li>
<li><a href="api.html#events">Database Events</a></li>
<li><a href="api.html#plugins">Plugins</a></li>
</ul>

</div>
Expand Down
18 changes: 16 additions & 2 deletions docs/api.md
Expand Up @@ -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

Expand Down Expand Up @@ -743,6 +743,19 @@ db.revsDiff({
}
{% endhighlight %}

## Events<a id="events"></a>

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<a id="plugins"></a>

Writing a plugin is easy the api is
Expand All @@ -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.
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.

51 changes: 49 additions & 2 deletions lib/adapter.js
Expand Up @@ -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
*/
Expand Down Expand Up @@ -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;
Expand All @@ -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') {
Expand Down
10 changes: 9 additions & 1 deletion lib/adapters/http.js
Expand Up @@ -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);
}
});
});
}

Expand Down
14 changes: 13 additions & 1 deletion lib/adapters/leveldb.js
Expand Up @@ -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 = {};
Expand All @@ -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);
}
});
});
}
});
Expand Down
2 changes: 2 additions & 0 deletions lib/adapters/websql.js
Expand Up @@ -892,13 +892,15 @@ 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, []);
tx.executeSql('DROP TABLE IF EXISTS ' + BY_SEQ_STORE, []);
tx.executeSql('DROP TABLE IF EXISTS ' + ATTACH_STORE, []);
tx.executeSql('DROP TABLE IF EXISTS ' + META_STORE, []);
}, unknownError(callback), function () {
self.emit('destroyed');
callback();
});
});
Expand Down
9 changes: 9 additions & 0 deletions lib/constructor.js
Expand Up @@ -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);

Expand Down
29 changes: 28 additions & 1 deletion lib/setup.js
Expand Up @@ -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;
Expand Down Expand Up @@ -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');
Expand Down
1 change: 0 additions & 1 deletion package.json
Expand Up @@ -61,7 +61,6 @@
"request": false,
"levelup": false,
"leveldown": false,
"events": false,
"crypto": false,
"bluebird": "lie",
"level-sublevel": false
Expand Down
118 changes: 118 additions & 0 deletions 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
});
});
});
});

});
});
1 change: 1 addition & 0 deletions tests/test.html
Expand Up @@ -29,6 +29,7 @@
<script src='test.changes.js'></script>
<script src='test.bulk_docs.js'></script>
<script src='test.all_docs.js'></script>
<script src='test.events.js'></script>
<script src='test.conflicts.js'></script>
<script src='test.revs_diff.js'></script>
<script src='test.replication.js'></script>
Expand Down

0 comments on commit 1e2601f

Please sign in to comment.