Comparing version 3.2.0 to 3.2.1
@@ -524,2 +524,3 @@ var Doc = require('./doc'); | ||
doc._wantsDestroy = false; | ||
return doc; | ||
@@ -535,3 +536,5 @@ }; | ||
Connection.prototype._destroyDoc = function(doc) { | ||
if (!doc._wantsDestroy) return; | ||
util.digAndRemove(this.collections, doc.collection, doc.id); | ||
doc.emit('destroy'); | ||
}; | ||
@@ -778,5 +781,7 @@ | ||
var connection = this; | ||
return util.digOrCreate(this._presences, channel, function() { | ||
var presence = util.digOrCreate(this._presences, channel, function() { | ||
return new Presence(connection, channel); | ||
}); | ||
presence._wantsDestroy = false; | ||
return presence; | ||
}; | ||
@@ -787,5 +792,7 @@ | ||
var connection = this; | ||
return util.digOrCreate(this._presences, channel, function() { | ||
var presence = util.digOrCreate(this._presences, channel, function() { | ||
return new DocPresence(connection, collection, id); | ||
}); | ||
presence._wantsDestroy = false; | ||
return presence; | ||
}; | ||
@@ -792,0 +799,0 @@ |
@@ -79,2 +79,4 @@ var emitter = require('../emitter'); | ||
this._wantsDestroy = false; | ||
// The op that is currently roundtripping to the server, or null. | ||
@@ -126,2 +128,3 @@ // | ||
Doc.prototype.destroy = function(callback) { | ||
this._wantsDestroy = true; | ||
var doc = this; | ||
@@ -136,3 +139,2 @@ doc.whenNothingPending(function() { | ||
doc.connection._destroyDoc(doc); | ||
doc.emit('destroy'); | ||
if (callback) callback(); | ||
@@ -142,3 +144,2 @@ }); | ||
doc.connection._destroyDoc(doc); | ||
doc.emit('destroy'); | ||
if (callback) callback(); | ||
@@ -145,0 +146,0 @@ } |
@@ -27,2 +27,3 @@ var emitter = require('../../emitter'); | ||
this._subscriptionCallbacksBySeq = {}; | ||
this._wantsDestroy = false; | ||
} | ||
@@ -40,2 +41,5 @@ emitter.mixin(Presence); | ||
Presence.prototype.create = function(id) { | ||
if (this._wantsDestroy) { | ||
throw new Error('Presence is being destroyed'); | ||
} | ||
id = id || hat(); | ||
@@ -48,6 +52,11 @@ var localPresence = this._createLocalPresence(id); | ||
Presence.prototype.destroy = function(callback) { | ||
this._wantsDestroy = true; | ||
var presence = this; | ||
// Store these at the time of destruction: any LocalPresence on this | ||
// instance at this time will be destroyed, but if the destroy is | ||
// cancelled, any future LocalPresence objects will be kept. | ||
// See: https://github.com/share/sharedb/pull/579 | ||
var localIds = Object.keys(presence.localPresences); | ||
this.unsubscribe(function(error) { | ||
if (error) return presence._callbackOrEmit(error, callback); | ||
var localIds = Object.keys(presence.localPresences); | ||
var remoteIds = Object.keys(presence._remotePresenceInstances); | ||
@@ -62,2 +71,7 @@ async.parallel( | ||
function(next) { | ||
// We don't bother stashing the RemotePresence instances because | ||
// they're not really bound to our local state: if we want to | ||
// destroy, we destroy them all, but if we cancel the destroy, | ||
// we'll want to keep them all | ||
if (!presence._wantsDestroy) return next(); | ||
async.each(remoteIds, function(presenceId, next) { | ||
@@ -69,3 +83,3 @@ presence._remotePresenceInstances[presenceId].destroy(next); | ||
function(error) { | ||
delete presence.connection._presences[presence.channel]; | ||
if (presence._wantsDestroy) delete presence.connection._presences[presence.channel]; | ||
presence._callbackOrEmit(error, callback); | ||
@@ -72,0 +86,0 @@ } |
{ | ||
"name": "sharedb", | ||
"version": "3.2.0", | ||
"version": "3.2.1", | ||
"description": "JSON OT database backend", | ||
@@ -5,0 +5,0 @@ "main": "lib/index.js", |
@@ -46,3 +46,2 @@ var Backend = require('../../lib/backend'); | ||
expect(doc).not.equal(doc2); | ||
expect(doc).eql(doc2); | ||
done(); | ||
@@ -52,2 +51,23 @@ }); | ||
it('destroying then getting synchronously does not destroy the new doc', function(done) { | ||
var connection = this.connection; | ||
var doc = connection.get('dogs', 'fido'); | ||
var doc2; | ||
doc.create({name: 'fido'}, function(error) { | ||
if (error) return done(error); | ||
doc.destroy(function(error) { | ||
if (error) return done(error); | ||
var doc3 = connection.get('dogs', 'fido'); | ||
async.parallel([ | ||
doc2.submitOp.bind(doc2, [{p: ['snacks'], oi: true}]), | ||
doc3.submitOp.bind(doc3, [{p: ['color'], oi: 'gray'}]) | ||
], done); | ||
}); | ||
doc2 = connection.get('dogs', 'fido'); | ||
}); | ||
}); | ||
it('doc.destroy() works without a callback', function() { | ||
@@ -54,0 +74,0 @@ var doc = this.connection.get('dogs', 'fido'); |
@@ -101,2 +101,114 @@ var Backend = require('../../../lib/backend'); | ||
it('gets presence during a destroy', function(done) { | ||
var localPresence1 = presence1.create('presence-1'); | ||
var presence2a; | ||
async.series([ | ||
presence2.subscribe.bind(presence2), | ||
function(next) { | ||
presence2.destroy(errorHandler(done)); | ||
next(); | ||
}, | ||
function(next) { | ||
presence2a = connection2.getPresence('test-channel'); | ||
presence2a.subscribe(function(error) { | ||
next(error); | ||
}); | ||
}, | ||
function(next) { | ||
localPresence1.submit({index: 5}, errorHandler(done)); | ||
presence2a.once('receive', function() { | ||
next(); | ||
}); | ||
} | ||
], done); | ||
}); | ||
it('destroys old local presence but keeps new local presence when getting during destroy', function(done) { | ||
presence2.create('presence-2'); | ||
var presence2a; | ||
async.series([ | ||
presence2.subscribe.bind(presence2), | ||
function(next) { | ||
presence2.destroy(function() { | ||
expect(presence2).to.equal(presence2a); | ||
expect(Object.keys(presence2.localPresences)).to.eql(['presence-2a']); | ||
done(); | ||
}); | ||
next(); | ||
}, | ||
function(next) { | ||
presence2a = connection2.getPresence('test-channel'); | ||
presence2a.create('presence-2a'); | ||
presence2a.subscribe(function(error) { | ||
next(error); | ||
}); | ||
} | ||
], errorHandler(done)); | ||
}); | ||
it('destroys old local presence but keeps new local presence when getting during destroy', function(done) { | ||
presence2.create('presence-2'); | ||
var presence2a; | ||
async.series([ | ||
presence2.subscribe.bind(presence2), | ||
function(next) { | ||
presence2.destroy(function() { | ||
expect(presence2).to.equal(presence2a); | ||
expect(Object.keys(presence2.localPresences)).to.eql(['presence-2a']); | ||
done(); | ||
}); | ||
next(); | ||
}, | ||
function(next) { | ||
presence2a = connection2.getPresence('test-channel'); | ||
presence2a.create('presence-2a'); | ||
presence2a.subscribe(function(error) { | ||
next(error); | ||
}); | ||
} | ||
], errorHandler(done)); | ||
}); | ||
it('throws if trying to create local presence when wanting destroy', function(done) { | ||
presence2.destroy(errorHandler(done)); | ||
expect(function() { | ||
presence2.create('presence-2'); | ||
}).to.throw('Presence is being destroyed'); | ||
done(); | ||
}); | ||
it('gets presence after destroy unsubscribe', function(done) { | ||
var localPresence2 = presence2.create('presence-2'); | ||
var presence2a; | ||
var flushLocalPresence2Destroy; | ||
sinon.stub(localPresence2, 'destroy').callsFake(function(callback) { | ||
flushLocalPresence2Destroy = callback; | ||
}); | ||
async.series([ | ||
presence2.subscribe.bind(presence2), | ||
function(next) { | ||
presence2.destroy(function() { | ||
expect(connection2.getPresence('test-channel')).to.equal(presence2a); | ||
done(); | ||
}); | ||
next(); | ||
}, | ||
// Wait for the destroy unsubscribe callback to start, where we check | ||
// _wantsDestroy for the first time | ||
presence2.unsubscribe.bind(presence2), | ||
function(next) { | ||
presence2a = connection2.getPresence('test-channel'); | ||
presence2a.subscribe(function(error) { | ||
next(error); | ||
}); | ||
flushLocalPresence2Destroy(); | ||
} | ||
], errorHandler(done)); | ||
}); | ||
it('requests existing presence from other subscribed clients when subscribing', function(done) { | ||
@@ -103,0 +215,0 @@ var localPresence1 = presence1.create('presence-1'); |
608107
16165
75