Socket
Socket
Sign inDemoInstall

@sanity/mutator

Package Overview
Dependencies
Maintainers
7
Versions
1321
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

@sanity/mutator - npm Package Compare versions

Comparing version 0.1.6 to 0.1.7

.vscode/launch.json

54

lib/document/BufferedDocument.js

@@ -52,5 +52,5 @@ 'use strict';

var BufferedDocument = function () {
// Local mutations that are not scheduled to be committed yet
// Commits that are waiting to be delivered to the server
// The Document with local changes applied
// The Document we are wrapping
function BufferedDocument(doc) {

@@ -73,10 +73,20 @@ var _this = this;

// Add a change to the buffer
// Used to reset the state of the local document model. If the model has been inconsistent
// for too long, it has probably missed a notification, and should reload the document from the server
// Commits that are waiting to be delivered to the server
// Local mutations that are not scheduled to be committed yet
// The Document we are wrapping
// The Document with local changes applied
_createClass(BufferedDocument, [{
key: 'reset',
value: function reset(doc) {
this.document.reset(doc);
this.rebase();
}
// Add a change to the buffer
}, {
key: 'add',

@@ -162,3 +172,7 @@ value: function add(mutation) {

commit.tries += 1;
_this2.commits.unshift(commit);
if (_this2.LOCAL !== null) {
// Only schedule this commit for a retry of the document still exist to avoid looping
// indefinitely when the document was deleted from under our noses
_this2.commits.unshift(commit);
}
docResponder.failure();

@@ -184,2 +198,14 @@ // Retry

}, {
key: 'handleDocumentDeleted',
value: function handleDocumentDeleted() {
// If the document was just deleted, fire the onDelete event with the absolutely latest version of the document
// before someone deleted it so that the client may revive the document in the last state the user saw it, should
// she so desire.
if (this.LOCAL !== null && this.onDelete) {
this.onDelete(this.LOCAL);
}
this.commits = [];
this.mutations = [];
}
}, {
key: 'handleDocMutation',

@@ -194,3 +220,9 @@ value: function handleDocMutation(msg) {

}
// It wasn't. Need to rebase
// If there are local edits, and the document was deleted, we need to purge those local edits now
if (this.document.EDGE === null) {
this.handleDocumentDeleted();
}
// We had local changes, so need to signal rebase
this.rebase();

@@ -201,2 +233,6 @@ }

value: function rebase() {
if (this.document.EDGE === null) {
this.handleDocumentDeleted();
}
var oldLocal = this.LOCAL;

@@ -208,3 +244,5 @@ this.LOCAL = this.commits.reduce(function (doc, commit) {

// Copy over rev, since we don't care if it changed, we only care about the content
oldLocal._rev = this.LOCAL._rev;
if (oldLocal !== null && this.LOCAL !== null) {
oldLocal._rev = this.LOCAL._rev;
}
var changed = !(0, _isEqual3.default)(this.LOCAL, oldLocal);

@@ -211,0 +249,0 @@ if (changed && this.onRebase) {

@@ -49,11 +49,6 @@ 'use strict';

this.incoming = [];
this.submitted = [];
this.pending = [];
this.inconsistentAt = null;
this.HEAD = doc;
this.EDGE = doc;
this.reset(doc);
}
// Call when a mutation arrives from Sanity
// Reset the state of the Document, used to recover from unsavory states by reloading the document

@@ -77,2 +72,17 @@ // The last time we staged a patch of our own. If we have been inconsistent for a while, but it hasn't been long since

_createClass(Document, [{
key: 'reset',
value: function reset(doc) {
this.incoming = [];
this.submitted = [];
this.pending = [];
this.inconsistentAt = null;
this.HEAD = doc;
this.EDGE = doc;
this.considerIncoming();
this.updateConsistencyFlag();
}
// Call when a mutation arrives from Sanity
}, {
key: 'arrive',

@@ -147,7 +157,30 @@ value: function arrive(mutation) {

var nextMut = void 0;
// Filter mutations that are older than the document
if (this.HEAD) {
(function () {
var updatedAt = new Date(_this2.HEAD._updatedAt);
if (_this2.incoming.find(function (mut) {
return mut.timestamp && mut.timestamp < updatedAt;
})) {
_this2.incoming = _this2.incoming.filter(function (mut) {
return mut.timestamp < updatedAt;
});
}
})();
}
// Keep applying mutations as long as any apply
do {
// Find next mutation that can be applied to HEAD (if any)
nextMut = this.incoming.find(function (mut) {
return mut.previousRev == _this2.HEAD._rev;
});
if (this.HEAD) {
nextMut = this.incoming.find(function (mut) {
return mut.previousRev == _this2.HEAD._rev;
});
} else {
// When HEAD is null, that means the document is currently deleted. Only mutations that start with a create
// operation will be considered.
nextMut = this.incoming.find(function (mut) {
return mut.appliesToMissingDocument();
});
}
mustRebase = mustRebase || this.applyIncoming(nextMut);

@@ -191,2 +224,9 @@ } while (nextMut);

if (this.HEAD === null) {
// If the document was deleted, clear all upcoming pending edits that do no apply to missing documents
while (this.pending.length > 0 && !this.pending[0].appliesToMissingDocument()) {
this.pending.shift();
}
}
// Eliminate from incoming set

@@ -193,0 +233,0 @@ this.incoming = this.incoming.filter(function (m) {

@@ -37,2 +37,17 @@ 'use strict';

}
}, {
key: 'appliesToMissingDocument',
value: function appliesToMissingDocument() {
if (typeof this._appliesToMissingDocument != 'undefined') {
return this._appliesToMissingDocument;
}
// Only mutations starting with a create operation apply to documents that do not exist ...
var firstMut = this.mutations[0];
if (firstMut) {
this._appliesToMissingDocument = firstMut.create || firstMut.createIfNotExist || firstMut.createOrReplace;
} else {
this._appliesToMissingDocument = true;
}
return this._appliesToMissingDocument;
}
// Compiles all mutations into a handy function

@@ -46,4 +61,12 @@

if (mutation.create) {
operations.push(function (doc) {
return doc === null ? mutation.create : doc;
});
} else if (mutation.createIfNotExist) {
operations.push(function (doc) {
return doc === null ? mutation.createIfNotExist : doc;
});
} else if (mutation.createOrReplace) {
operations.push(function () {
return mutation.create;
return mutation.createOrReplace;
});

@@ -118,2 +141,10 @@ } else if (mutation.delete) {

}
}, {
key: 'timestamp',
get: function get() {
if (typeof this.params.timestamp == 'string') {
return new Date(this.params.timestamp);
}
return undefined;
}
}], [{

@@ -120,0 +151,0 @@ key: 'applyAll',

2

package.json
{
"name": "@sanity/mutator",
"version": "0.1.6",
"version": "0.1.7",
"description": "A set of models to make it easier to utilize the powerful real time collaborative features of Sanity",

@@ -5,0 +5,0 @@ "main": "lib/index.js",

@@ -37,2 +37,3 @@ // A wrapper for Document that allows the client to gather mutations on the client side and commit them

onRebase : Function
onDelete : Function
commitHandler : Function

@@ -48,2 +49,9 @@ constructor(doc) {

// Used to reset the state of the local document model. If the model has been inconsistent
// for too long, it has probably missed a notification, and should reload the document from the server
reset(doc) {
this.document.reset(doc)
this.rebase()
}
// Add a change to the buffer

@@ -115,3 +123,7 @@ add(mutation : Mutation) {

commit.tries += 1
this.commits.unshift(commit)
if (this.LOCAL !== null) {
// Only schedule this commit for a retry of the document still exist to avoid looping
// indefinitely when the document was deleted from under our noses
this.commits.unshift(commit)
}
docResponder.failure()

@@ -134,2 +146,13 @@ // Retry

handleDocumentDeleted() {
// If the document was just deleted, fire the onDelete event with the absolutely latest version of the document
// before someone deleted it so that the client may revive the document in the last state the user saw it, should
// she so desire.
if (this.LOCAL !== null && this.onDelete) {
this.onDelete(this.LOCAL)
}
this.commits = []
this.mutations = []
}
handleDocMutation(msg) {

@@ -143,3 +166,9 @@ // If we have no local changes, we can just pass this on to the client

}
// It wasn't. Need to rebase
// If there are local edits, and the document was deleted, we need to purge those local edits now
if (this.document.EDGE === null) {
this.handleDocumentDeleted()
}
// We had local changes, so need to signal rebase
this.rebase()

@@ -149,2 +178,6 @@ }

rebase() {
if (this.document.EDGE === null) {
this.handleDocumentDeleted()
}
const oldLocal = this.LOCAL

@@ -154,3 +187,5 @@ this.LOCAL = this.commits.reduce((doc, commit) => commit.apply(doc), this.document.EDGE)

// Copy over rev, since we don't care if it changed, we only care about the content
oldLocal._rev = this.LOCAL._rev
if (oldLocal !== null && this.LOCAL !== null) {
oldLocal._rev = this.LOCAL._rev
}
const changed = !isEqual(this.LOCAL, oldLocal)

@@ -157,0 +192,0 @@ if (changed && this.onRebase) {

@@ -55,2 +55,7 @@ // @flow

constructor(doc : Object) {
this.reset(doc)
}
// Reset the state of the Document, used to recover from unsavory states by reloading the document
reset(doc : Object) {
this.incoming = []

@@ -62,2 +67,4 @@ this.submitted = []

this.EDGE = doc
this.considerIncoming()
this.updateConsistencyFlag()
}

@@ -121,5 +128,20 @@

let nextMut : Mutation
// Filter mutations that are older than the document
if (this.HEAD) {
const updatedAt = new Date(this.HEAD._updatedAt)
if (this.incoming.find(mut => mut.timestamp && mut.timestamp < updatedAt)) {
this.incoming = this.incoming.filter(mut => mut.timestamp < updatedAt)
}
}
// Keep applying mutations as long as any apply
do {
// Find next mutation that can be applied to HEAD (if any)
nextMut = this.incoming.find(mut => mut.previousRev == this.HEAD._rev)
if (this.HEAD) {
nextMut = this.incoming.find(mut => mut.previousRev == this.HEAD._rev)
} else {
// When HEAD is null, that means the document is currently deleted. Only mutations that start with a create
// operation will be considered.
nextMut = this.incoming.find(mut => mut.appliesToMissingDocument())
}
mustRebase = mustRebase || this.applyIncoming(nextMut)

@@ -157,2 +179,9 @@ } while (nextMut)

if (this.HEAD === null) {
// If the document was deleted, clear all upcoming pending edits that do no apply to missing documents
while (this.pending.length > 0 && !this.pending[0].appliesToMissingDocument()) {
this.pending.shift()
}
}
// Eliminate from incoming set

@@ -159,0 +188,0 @@ this.incoming = this.incoming.filter(m => m.transactionId != mut.transactionId)

@@ -17,3 +17,4 @@ // @flow

resultRev : string,
mutations : Array<Object>
mutations : Array<Object>,
timestamp: String,
}

@@ -43,5 +44,24 @@ compiled : Function

}
get timestamp() : Date {
if (typeof this.params.timestamp == 'string') {
return new Date(this.params.timestamp)
}
return undefined
}
assignRandomTransactionId() {
this.params.resultRev = this.params.transactionId = luid()
}
appliesToMissingDocument() {
if (typeof this._appliesToMissingDocument != 'undefined') {
return this._appliesToMissingDocument
}
// Only mutations starting with a create operation apply to documents that do not exist ...
const firstMut = this.mutations[0]
if (firstMut) {
this._appliesToMissingDocument = (firstMut.create || firstMut.createIfNotExist || firstMut.createOrReplace)
} else {
this._appliesToMissingDocument = true
}
return this._appliesToMissingDocument
}
// Compiles all mutations into a handy function

@@ -52,3 +72,7 @@ compile() {

if (mutation.create) {
operations.push(() => mutation.create)
operations.push(doc => (doc === null ? mutation.create : doc))
} else if (mutation.createIfNotExist) {
operations.push(doc => (doc === null ? mutation.createIfNotExist : doc))
} else if (mutation.createOrReplace) {
operations.push(() => mutation.createOrReplace)
} else if (mutation.delete) {

@@ -55,0 +79,0 @@ operations.push(() => null)

@@ -98,1 +98,49 @@ // @flow

})
test('document being deleted by remote', tap => {
(new BufferedDocumentTester(tap, {
_id: 'a',
_rev: '1',
text: 'hello'
}))
.hasNoLocalEdits()
.stage('when applying first local edit')
.localPatch({
set: {
text: 'goodbye'
}
})
.onMutationFired()
.hasLocalEdits()
.assertLOCAL('text', 'goodbye')
.stage('when remote patch appear')
.remoteMutation('1', '2', {
delete: {id: '1'}
})
.didRebase()
.onDeleteDidFire()
.hasNoLocalEdits()
.assertALLDeleted()
.stage('when local user creates document')
.localMutation(null, '3', {
create: {_id: 'a', text: 'good morning'}
})
.assertLOCAL('text', 'good morning')
.assertHEADDeleted()
.assertEDGEDeleted()
.stage('when committing local create')
.commit()
.assertHEADDeleted()
.assertEDGE('text', 'good morning')
.stage('when commit succeeds')
.commitSucceeds()
.assertALL('text', 'good morning')
.end()
})

@@ -20,2 +20,5 @@ // @flow

}
this.doc.onDelete = (local) => {
this.onDeleteCalled = true
}
this.doc.commitHandler = opts => {

@@ -28,6 +31,11 @@ this.pendingCommit = opts

}
reset() {
resetState() {
this.onMutationCalled = false
this.onRebaseCalled = false
this.onDeleteCalled = false
}
resetDocument(doc) {
this.resetState()
this.doc.reset(doc)
}
stage(title) {

@@ -38,3 +46,3 @@ this.context = title

remotePatch(fromRev, toRev, patch) {
this.reset()
this.resetState()
const mut = new Mutation({

@@ -49,4 +57,15 @@ transactionId: toRev,

}
remoteMutation(fromRev, toRev, operation) {
this.resetState()
const mut = new Mutation({
transactionId: toRev,
resultRev: toRev,
previousRev: fromRev,
mutations: [operation]
})
this.doc.arrive(mut)
return this
}
localPatch(patch) {
this.reset()
this.resetState()
const mut = new Mutation({

@@ -58,4 +77,15 @@ mutations: [{patch}]

}
localMutation(fromRev, toRev, operation) {
this.resetState()
const mut = new Mutation({
transactionId: toRev,
resultRev: toRev,
previousRev: fromRev,
mutations: [operation]
})
this.doc.add(mut)
return this
}
commit() {
this.reset()
this.resetState()
this.doc.commit()

@@ -65,6 +95,8 @@ return this

commitSucceeds() {
this.reset()
this.resetState()
this.pendingCommit.success()
// Magically this commit is based on the current HEAD revision
this.pendingCommit.mutation.params.previousRev = this.doc.document.HEAD._rev
if (this.doc.document.HEAD) {
this.pendingCommit.mutation.params.previousRev = this.doc.document.HEAD._rev
}
this.doc.arrive(this.pendingCommit.mutation)

@@ -75,3 +107,3 @@ this.pendingCommit = null

commitFails() {
this.reset()
this.resetState()
this.pendingCommit.failure()

@@ -99,2 +131,20 @@ this.pendingCommit = null

}
assertLOCALDeleted() {
this.tap.ok(this.doc.LOCAL === null, `LOCAL should be deleted ${this.context}`)
return this
}
assertEDGEDeleted() {
this.tap.ok(this.doc.document.EDGE === null, `EDGE should be deleted ${this.context}`)
return this
}
assertHEADDeleted() {
this.tap.ok(this.doc.document.HEAD === null, `HEAD should be deleted ${this.context}`)
return this
}
assertALLDeleted() {
this.assertLOCALDeleted()
this.assertEDGEDeleted()
this.assertHEADDeleted()
return this
}
didRebase() {

@@ -116,2 +166,10 @@ this.tap.ok(this.onRebaseCalled, `should rebase ${this.context}`)

}
onDeleteDidFire() {
this.tap.ok(this.onDeleteCalled, `should fire onDelete event ${this.context}`)
return this
}
onDeleteDidNotFire() {
this.tap.notOk(this.onDeleteCalled, `should not fire onDelete event ${this.context}`)
return this
}
isConsistent() {

@@ -118,0 +176,0 @@ this.tap.ok(this.doc.document.isConsistent(), `should be consistent ${this.context}`)

SocketSocket SOC 2 Logo

Product

  • Package Alerts
  • Integrations
  • Docs
  • Pricing
  • FAQ
  • Roadmap
  • Changelog

Packages

npm

Stay in touch

Get open source security insights delivered straight into your inbox.


  • Terms
  • Privacy
  • Security

Made with ⚡️ by Socket Inc