Skip to content

Commit

Permalink
Merge c929b12 into 30badb2
Browse files Browse the repository at this point in the history
  • Loading branch information
alecgibson committed Jan 13, 2021
2 parents 30badb2 + c929b12 commit a4737da
Show file tree
Hide file tree
Showing 6 changed files with 132 additions and 9 deletions.
3 changes: 3 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -412,6 +412,9 @@ Prevents own ops being submitted to the server. If subscribed, remote ops will s
`doc.resume()`
Resume sending own ops to the server if paused. Will flush the queued ops when called.

`doc.submitSource` (default: `false`)
If set to `true`, an op's `source` will be submitted to the server, and made available to access in middleware at `request.extra.source`. Note that a necessary side-effect of this is that ops that have different values for `source` will not be composed.

### Class: `ShareDB.Query`

`query.ready` _(Boolean)_
Expand Down
15 changes: 9 additions & 6 deletions lib/agent.js
Original file line number Diff line number Diff line change
Expand Up @@ -826,9 +826,9 @@ function createClientOp(request, clientId) {
// such as a resubmission after a reconnect, but it usually isn't needed
var src = request.src || clientId;
// c, d, and m arguments are intentionally undefined. These are set later
return ('op' in request) ? new EditOp(src, request.seq, request.v, request.op) :
(request.create) ? new CreateOp(src, request.seq, request.v, request.create) :
(request.del) ? new DeleteOp(src, request.seq, request.v, request.del) :
return ('op' in request) ? new EditOp(src, request.seq, request.v, request.op, request.x) :
(request.create) ? new CreateOp(src, request.seq, request.v, request.create, request.x) :
(request.del) ? new DeleteOp(src, request.seq, request.v, request.del, request.x) :
undefined;
}

Expand All @@ -840,30 +840,33 @@ function shallowCopy(object) {
return out;
}

function CreateOp(src, seq, v, create, c, d, m) {
function CreateOp(src, seq, v, create, x, c, d, m) {
this.src = src;
this.seq = seq;
this.v = v;
this.create = create;
this.c = c;
this.d = d;
this.m = m;
this.x = x;
}
function EditOp(src, seq, v, op, c, d, m) {
function EditOp(src, seq, v, op, x, c, d, m) {
this.src = src;
this.seq = seq;
this.v = v;
this.op = op;
this.c = c;
this.d = d;
this.m = m;
this.x = x;
}
function DeleteOp(src, seq, v, del, c, d, m) {
function DeleteOp(src, seq, v, del, x, c, d, m) {
this.src = src;
this.seq = seq;
this.v = v;
this.del = del;
this.c = c;
this.d = d;
this.m = m;
this.x = x;
}
4 changes: 3 additions & 1 deletion lib/client/connection.js
Original file line number Diff line number Diff line change
Expand Up @@ -454,11 +454,13 @@ Connection.prototype.sendOp = function(doc, op) {
d: doc.id,
v: doc.version,
src: op.src,
seq: op.seq
seq: op.seq,
x: {}
};
if ('op' in op) message.op = op.op;
if (op.create) message.create = op.create;
if (op.del) message.del = op.del;
if (doc.submitSource) message.x.source = op.source;
this.send(message);
};

Expand Down
15 changes: 13 additions & 2 deletions lib/client/doc.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ var logger = require('../logger');
var ShareDBError = require('../error');
var types = require('../types');
var util = require('../util');
var deepEqual = require('fast-deep-equal');

var ERROR_CODE = ShareDBError.CODES;

Expand Down Expand Up @@ -103,6 +104,11 @@ function Doc(connection, collection, id) {
// specifc op and toggled off afterward
this.preventCompose = false;

// If set to true, the source will be submitted over the connection. This
// will also have the side-effect of only composing ops whose sources are
// equal
this.submitSource = false;

// Prevent own ops being submitted to the server. If subscribed, remote
// ops are still received. Should be toggled through the pause() and
// resume() methods to correctly flush on resume.
Expand Down Expand Up @@ -738,7 +744,7 @@ Doc.prototype._submit = function(op, source, callback) {
}

try {
this._pushOp(op, callback);
this._pushOp(op, source, callback);
this._otApply(op, source);
} catch (error) {
return this._hardRollback(error);
Expand All @@ -752,7 +758,8 @@ Doc.prototype._submit = function(op, source, callback) {
});
};

Doc.prototype._pushOp = function(op, callback) {
Doc.prototype._pushOp = function(op, source, callback) {
op.source = source;
if (this.applyStack) {
// If we are in the process of incrementally applying an operation, don't
// compose the op and push it onto the applyStack so it can be transformed
Expand Down Expand Up @@ -809,6 +816,10 @@ Doc.prototype._tryCompose = function(op) {
var last = this.pendingOps[this.pendingOps.length - 1];
if (!last || last.sentAt) return;

// If we're submitting the op source, we can only combine ops that have
// a matching source
if (this.submitSource && !deepEqual(op.source, last.source)) return;

// Compose an op into a create by applying it. This effectively makes the op
// invisible, as if the document were created including the op originally
if (last.create && 'op' in op) {
Expand Down
3 changes: 3 additions & 0 deletions lib/submit-request.js
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,9 @@ function SubmitRequest(backend, agent, index, id, op, options) {
this.op = op;
this.options = options;

this.extra = op.x;
delete op.x;

this.start = Date.now();
this._addOpMeta();

Expand Down
101 changes: 101 additions & 0 deletions test/middleware.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ var Backend = require('../lib/backend');
var expect = require('chai').expect;
var util = require('./util');
var types = require('../lib/types');
var errorHandler = util.errorHandler;

describe('middleware', function() {
beforeEach(function() {
Expand Down Expand Up @@ -314,4 +315,104 @@ describe('middleware', function() {
});
});
});

describe('extra information (x)', function() {
var connection;
var db;
var doc;

beforeEach(function(done) {
connection = this.backend.connect();
db = this.backend.db;
doc = connection.get('dogs', 'fido');

doc.create({name: 'fido'}, done);
// Need to actively enable this feature
doc.submitSource = true;
});

it('has the source in commit middleware', function(done) {
this.backend.use('commit', function(request) {
expect(request.extra).to.eql({source: 'trainer'});
done();
});

doc.submitOp([{p: ['tricks'], oi: ['fetch']}], {source: 'trainer'}, errorHandler(done));
});

it('has the source in afterWrite middleware', function(done) {
this.backend.use('afterWrite', function(request) {
expect(request.extra).to.eql({source: 'trainer'});
done();
});

doc.submitOp([{p: ['tricks'], oi: ['fetch']}], {source: 'trainer'}, errorHandler(done));
});

it('does not commit extra information to the database', function(done) {
doc.submitOp([{p: ['tricks'], oi: ['fetch']}], {source: 'trainer'}, function(error) {
if (error) return done(error);
var ops = db.ops.dogs.fido;
ops.forEach(function(op) {
expect('x' in op).to.be.false;
});
done();
});
});

it('does not submit the source if it is disabled', function(done) {
doc.submitSource = false;

this.backend.use('commit', function(request) {
expect('source' in request.extra).to.be.false;
done();
});

doc.submitOp([{p: ['tricks'], oi: ['fetch']}], {source: 'trainer'}, errorHandler(done));
});

it('composes ops with the same source', function(done) {
doc.submitSource = true;

this.backend.use('commit', function(request) {
expect(request.op.op).to.have.length(3);
expect(request.extra).to.eql({source: {type: 'trainer'}});
done();
});

var source = {type: 'trainer'};
doc.submitOp([{p: ['tricks'], oi: []}], {source: source}, errorHandler(done));
doc.submitOp([{p: ['tricks', 0], li: 'fetch'}], {source: source}, errorHandler(done));
doc.submitOp([{p: ['tricks', 1], li: 'stay'}], {source: source}, errorHandler(done));
});

it('does not compose ops with the different sources', function(done) {
doc.submitSource = true;

this.backend.use('commit', function(request) {
expect(request.op.op).to.have.length(2);
expect(request.extra).to.eql({source: {type: 'trainer'}});
done();
});

var source1 = {type: 'trainer'};
var source2 = {type: 'owner'};
doc.submitOp([{p: ['tricks'], oi: []}], {source: source1}, errorHandler(done));
doc.submitOp([{p: ['tricks', 0], li: 'fetch'}], {source: source1}, errorHandler(done));
doc.submitOp([{p: ['tricks', 1], li: 'stay'}], {source: source2}, errorHandler(done));
});

it('composes ops with different sources when disabled', function(done) {
doc.submitSource = false;

this.backend.use('commit', function(request) {
expect(request.op.op).to.have.length(3);
done();
});

doc.submitOp([{p: ['tricks'], oi: []}], {source: 'a'}, errorHandler(done));
doc.submitOp([{p: ['tricks', 0], li: 'fetch'}], {source: 'b'}, errorHandler(done));
doc.submitOp([{p: ['tricks', 1], li: 'stay'}], {source: 'c'}, errorHandler(done));
});
});
});

0 comments on commit a4737da

Please sign in to comment.