Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Adds two new doc events: 'before op batch' and 'after op batch' #247

Open
wants to merge 3 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -321,12 +321,18 @@ same time as callbacks to `fetch` and `subscribe`.
`doc.on('create', function(source) {...})`
The document was created. Technically, this means it has a type. `source` will be `false` for ops received from the server and defaults to `true` for ops generated locally.

`doc.on('before op batch'), function() {...})`
An operation batch is about to be applied to the data. For each partial operation a pair of `before op` and `op` events will be emitted after this event.

`doc.on('before op'), function(op, source) {...})`
An operation is about to be applied to the data. `source` will be `false` for ops received from the server and defaults to `true` for ops generated locally.

`doc.on('op', function(op, source) {...})`
An operation was applied to the data. `source` will be `false` for ops received from the server and defaults to `true` for ops generated locally.

`doc.on('after op batch'), function() {...})`
An operation batch was applied to the data.

`doc.on('del', function(data, source) {...})`
The document was deleted. Document contents before deletion are passed in as an argument. `source` will be `false` for ops received from the server and defaults to `true` for ops generated locally.

Expand Down
17 changes: 12 additions & 5 deletions lib/client/doc.js
Original file line number Diff line number Diff line change
Expand Up @@ -33,10 +33,13 @@ var types = require('../types');
* ------
*
* You can use doc.on(eventName, callback) to subscribe to the following events:
* * - `before op batch ()` Fired before an operation batch is applied to the data.
* It may be used to read the old data just before applying an operation
* - `before op (op, source)` Fired before a partial operation is applied to the data.
* It may be used to read the old data just before applying an operation
* - `op (op, source)` Fired after every partial operation with this operation as the
* first argument
* - `after op batch ()` Fired after an operation batch was applied to the data.
* - `create (source)` The document was created. That means its type was
* set and it has some initial data.
* - `del (data, source)` Fired after the document is deleted, that is
Expand Down Expand Up @@ -503,10 +506,10 @@ function transformX(client, server) {
/**
* Applies the operation to the snapshot
*
* If the operation is create or delete it emits `create` or `del`. Then the
* operation is applied to the snapshot and `op` and `after op` are emitted.
* If the type supports incremental updates and `this.incremental` is true we
* fire `op` after every small operation.
* If the operation is create or delete it emits `create` or `del`.
* For each partial operation `before op` is emitted, then the operation is applied
* to the snapshot and `op` is emitted. For each operation batch `before op batch` and
* `after op batch` are emitted before and after applying all partial operations.
*
* This is the only function to fire the above mentioned events.
*
Expand Down Expand Up @@ -537,6 +540,7 @@ Doc.prototype._otApply = function(op, source) {
if (!source && this.type === types.defaultType && op.op.length > 1) {
if (!this.applyStack) this.applyStack = [];
var stackLength = this.applyStack.length;
this.emit('before op batch');
for (var i = 0; i < op.op.length; i++) {
var component = op.op[i];
var componentOp = {op: [component]};
Expand All @@ -552,11 +556,13 @@ Doc.prototype._otApply = function(op, source) {
this.data = this.type.apply(this.data, componentOp.op);
this.emit('op', componentOp.op, source);
}
this.emit('after op batch');
// Pop whatever was submitted since we started applying this op
this._popApplyStack(stackLength);
return;
}

this.emit('before op batch');
// The 'before op' event enables clients to pull any necessary data out of
// the snapshot before it gets changed
this.emit('before op', op.op, source);
Expand All @@ -568,6 +574,7 @@ Doc.prototype._otApply = function(op, source) {
// For ops from other clients, this will be after the op has been
// committed to the database and published
this.emit('op', op.op, source);
this.emit('after op batch');
return;
}

Expand Down Expand Up @@ -753,7 +760,7 @@ Doc.prototype._tryCompose = function(op) {
// @param options {source: ...}
// @param [callback] called after operation submitted
//
// @fires before op, op, after op
// @fires before op batch, before op, op, after op batch
Doc.prototype.submitOp = function(component, options, callback) {
if (typeof options === 'function') {
callback = options;
Expand Down
37 changes: 37 additions & 0 deletions test/client/doc.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
var Backend = require('../../lib/backend');
var expect = require('expect.js');
var sinon = require('sinon');
var util = require('../util')

describe('client query subscribe', function() {
Expand Down Expand Up @@ -154,6 +155,42 @@ describe('client query subscribe', function() {
});
});

it('remote multi component ops are emit only one of `before op batch` and `after op batch` events', function(done) {
var doc = this.doc;
var doc2 = this.doc2;
var doc3 = this.doc3;

var receivedOps = [];

var beforeOpBatchHandler = sinon.fake();
var afterOpBatchHandler = sinon.fake();

doc.on('before op', beforeOpBatchHandler);
doc.on('after op', afterOpBatchHandler);
doc.on('op', function(op, source) {
receivedOps.push(op);
});

var remoteOp = [
{p: ['tricks'], oi: ['fetching']},
{p: ['tricks', 0], li: 'stand'},
{p: ['tricks', 1], li: 'shake'},
{p: ['tricks', 2, 5], sd: 'ing'},
{p: ['tricks', 0], lm: 2}
];

doc2.submitOp(remoteOp, function(err) {
if (err) return done(err);
doc.fetch();
verifyConsistency(doc, doc2, doc3, [], done);
});

expect(beforeOpBatchHandler.calledOnce);
expect(afterOpBatchHandler.calledOnce);
expect(receivedOps.length == remoteOp.length);

});

it('remote multi component ops are transformed by ops submitted in `op` event handlers', function(done) {
var doc = this.doc;
var doc2 = this.doc2;
Expand Down