Huge News!Announcing our $40M Series B led by Abstract Ventures.Learn More
Socket
Sign inDemoInstall
Socket

gulf

Package Overview
Dependencies
Maintainers
1
Versions
43
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

gulf - npm Package Compare versions

Comparing version 0.3.0 to 0.4.0

lib/MemoryAdapter.js

1

index.js

@@ -7,2 +7,3 @@ module.exports = {

, History: require('./lib/History')
, MemoryAdapter: require('./lib/MemoryAdapter')
}

107

lib/Document.js

@@ -5,6 +5,7 @@ var Link = require('./Link')

function Document(ottype) {
function Document(adapter, ottype) {
this.adapter = adapter
this.ottype = ottype
this.content = null
this.history = new History
this.history = new History(this)
this.slaves = []

@@ -15,2 +16,3 @@ this.links = []

if(!this.ottype) throw new Error('Document: No ottype specified')
if(!this.adapter) throw new Error('Document: No adapter specified')
}

@@ -23,7 +25,8 @@

*/
Document.create = function(ottype, content) {
var doc = new Document(ottype)
Document.create = function(adapter, ottype, content, cb) {
var doc = new Document(adapter, ottype)
doc.content = content
doc.history.pushEdit(Edit.newInitial(ottype))
return doc
doc.history.pushEdit(Edit.newInitial(ottype), function(er) {
cb(er, doc)
})
}

@@ -86,3 +89,6 @@

}
link.send('init', {content: this.content, initialEdit: this.history.latest().pack()})
this.history.latest(function(er, latest) {
if(er) return link.emit('error', er)
link.send('init', {content: this.content, initialEdit: latest.pack()})
}.bind(this))
}.bind(this))

@@ -124,7 +130,9 @@

this.history.reset()
this.history.pushEdit(initialEdit)
// I got an init, so my slaves get one, too
this.slaves.forEach(function(slave) {
slave.send('init', {content: this.content, initialEdit: this.history.latest().pack()})
this.history.pushEdit(initialEdit, function() {
// I got an init, so my slaves get one, too
this.slaves.forEach(function(slave) {
this.history.latest(function(er, latest) {
slave.send('init', {content: this.content, initialEdit: latest.pack()})
})
}.bind(this))
}.bind(this))

@@ -159,27 +167,31 @@ }

Document.prototype.dispatchEdit = function(edit, fromLink) {
if (this.history.remembers(edit.id)) {
// We've got this edit already.
if(fromLink) fromLink.send('ack', edit.id)
return
}
this.history.remembers(edit.id, function(er, remembers) {
if(er) return fromLink.emit('error',er)
try {
if (remembers) {
// We've got this edit already.
if(fromLink) fromLink.send('ack', edit.id)
return
}
// Check integrity of this edit
if (!this.history.remembers(edit.parent)) {
throw new Error('Edit "'+edit.id+'" has unknown parent "'+edit.parent+'"')
}
this.history.remembers(edit.parent, function(er, remembersParent) {
if (!remembersParent) {
return fromLink.emit('error', new Error('Edit "'+edit.id+'" has unknown parent "'+edit.parent+'"'))
}
this.applyEdit(this.sanitizeEdit(edit, fromLink))
}catch(er) {
if(!fromLink) throw er // XXX: In case of an error we can't just terminate the link,
// what if it's using us as a master link and just passing on an
// edit for us to verify?
fromLink.emit('error', er) // ^^
}
if(fromLink) fromLink.send('ack', edit.id)
this.distributeEdit(edit, fromLink)
this.sanitizeEdit(edit, fromLink, function(er, edit) {
if(er) return fromLink.emit('error', er)
try {
this.applyEdit(edit)
}catch(er) {
fromLink.emit('error', er)
}
if(fromLink) fromLink.send('ack', edit.id)
this.distributeEdit(edit, fromLink)
}.bind(this))
}.bind(this))
}.bind(this))
}

@@ -190,3 +202,3 @@

*/
Document.prototype.sanitizeEdit = function(edit, fromLink) {
Document.prototype.sanitizeEdit = function(edit, fromLink, cb) {

@@ -197,6 +209,7 @@ if(this.master === fromLink) {

// add to history + set id
this.history.pushEdit(edit)
return edit
// add to history
this.history.pushEdit(edit, function(er) {
if(er) return cb(er)
cb(null, edit)
})
}else {

@@ -206,12 +219,14 @@ // We are master!

// Transform against missed edits from history that have happened in the meantime
var missed = this.history.getAllAfter(edit.parent)
missed.forEach(function(oldEdit) {
edit.follow(oldEdit)
})
this.history.getAllAfter(edit.parent, function(er, missed) {
if(er) return cb(er)
// add to history + set id
this.history.pushEdit(edit)
missed.forEach(function(oldEdit) {
edit.follow(oldEdit)
})
return edit
// add to history
this.history.pushEdit(edit)
cb(null, edit)
}.bind(this))
}

@@ -218,0 +233,0 @@ }

@@ -35,22 +35,27 @@ var Document = require('./Document')

var edit = Edit.newFromChangeset(cs, this.ottype)
edit.parent = this.history.latest().id
this.history.latest(function(er, latestEdit) {
if(er) throw er
// Merge into the queue for increased collab speed
if(this.master.queue.length == 1) {
var parent = this.master.queue[0].parent
, callback =this.master.queue[0].callback
this.master.queue[0] = this.master.queue[0].merge(edit)
this.master.queue[0].callback = callback
this.master.queue[0].parent = parent
return
}
edit.parent = latestEdit.id
// Merge into the queue for increased collab speed
if(this.master.queue.length == 1) {
var parent = this.master.queue[0].parent
, callback =this.master.queue[0].callback
this.master.queue[0] = this.master.queue[0].merge(edit)
this.master.queue[0].callback = callback
this.master.queue[0].parent = parent
return
}
this.master.sendEdit(edit, function onack(err, edit) {
// Update queue
this.master.queue.forEach(function(queuedEdit) {
queuedEdit.parent = edit.id
})
this.applyEdit(edit, true)
//this.distributeEdit(edit) // Unnecessary round trip
this.history.pushEdit(edit)
this.master.sendEdit(edit, function onack(err, edit) {
// Update queue
this.master.queue.forEach(function(queuedEdit) {
queuedEdit.parent = edit.id
})
this.applyEdit(edit, true)
//this.distributeEdit(edit) // Unnecessary round trip
this.history.pushEdit(edit)
}.bind(this))
}.bind(this))

@@ -60,3 +65,3 @@ }

// overrides Document#sanitizeEdit
EditableDocument.prototype.sanitizeEdit = function(incoming, fromLink) {
EditableDocument.prototype.sanitizeEdit = function(incoming, fromLink, cb) {
// Collect undetected local changes, before applying the new edit

@@ -96,5 +101,7 @@ this._collectChanges()

// add edit to history
this.history.pushEdit(incoming)
return incoming
this.history.pushEdit(incoming, function(er) {
if(er) return cb(er)
cb(null, incoming)
})
}

@@ -101,0 +108,0 @@

// Stores revisions that are synced with the server
function History() {
this.reset()
function History(document) {
this.document = document
}
module.exports = History
History.prototype.earliest = function() {
if(!this.history.length) return
return this.edits[this.history[0]]
History.prototype.reset = function() {
}
History.prototype.latest = function() {
if(!this.history.length) return
return this.edits[this.history[this.history.length-1]]
History.prototype.earliest = function(cb) {
this.document.adapter.getEarliestEdit(cb)
}
History.prototype.pushEdit = function(edit) {
History.prototype.latest = function(cb) {
this.document.adapter.getLatestEdit(cb)
}
History.prototype.pushEdit = function(edit, cb) {
// Only Master Document may set ids
if(!edit.id) edit.id = ++this.idCounter
if(this.latest() && this.latest().id != edit.parent) throw new Error('This edit\'s parent is not the latest edit in history: '+JSON.stringify(edit), console.log(this.history))
this.history.push(edit.id)
this.edits[edit.id] = edit
this.latest(function(er, latest) {
if(er) cb && cb(er)
if(latest && latest.id != edit.parent) cb && cb(new Error('This edit\'s parent is not the latest edit in history: '+JSON.stringify(edit)))
this.document.adapter.storeEdit(edit, function(er) {
cb && cb(er)
})
}.bind(this))
}
History.prototype.reset = function() {
this.edits = {}
this.history = []
this.idCounter = 0
History.prototype.remembers = function(editId, cb) {
this.document.adapter.existsEdit(editId, function(er, remembers) {
cb && cb(er, remembers)
})
}
History.prototype.remembers = function(editId) {
return (this.edits[editId] && true)
History.prototype.getAllAfter = function(editId, cb) {
this.document.adapter.getEditsAfter(editId, function(er, edits) {
cb && cb(er, edits)
})
}
History.prototype.getAllAfter = function(editId) {
var arr = []
for(var i = this.history.indexOf(editId)+1; i < this.history.length; i++) {
arr.push(this.edits[this.history[i]])
}
return arr
}
/**
* Prunes an edit from a history of edits (supplied as a list of edit ids)
*
* @return a chronological list of edit ojects
*/
History.prototype.pruneFrom = function(editId, history) {
if(!this.edits[editId]) throw new Error('Can\'t prune unknown edit')
if(!~history.indexOf(editId)) return history // Nothing to prune
var pruningEdit = this.edits[editId].clone().invert()
var prunedHistory = history.slice(history.indexOf(editId)+1)
.map(function(otherEdit) {
if(!this.edits[otherEdit]) throw new Error('Can\'t reconstruct edit '+otherEdit)
otherEdit = this.edits[otherEdit].clone()
otherEdit.transformAgainst(pruningEdit)
pruningEdit.transformAgainst(otherEdit)
return otherEdit
})
return prunedHistory
}
{
"name": "gulf",
"version": "0.3.0",
"version": "0.4.0",
"description": "Sync anything!",

@@ -5,0 +5,0 @@ "repository": {

@@ -19,3 +19,3 @@ # Gulf [![Build Status](https://travis-ci.org/marcelklehr/gulf.png)](https://travis-ci.org/marcelklehr/gulf)

// Create a new master document
var doc = gulf.Document.create(textOT, 'abc')
var doc = gulf.Document.create(new gulf.MemoryAdapter, textOT, 'abc')

@@ -42,3 +42,3 @@ // Set up a server

// Create a new slave document (empty by default)
var doc = new gulf.Document(textOT)
var doc = new gulf.Document(new gulf.MemoryAdapter, textOT)

@@ -85,3 +85,3 @@ // Connect to alice's server

```js
masterDoc = new gulf.Document(ot)
masterDoc = new gulf.Document(adapter, ot)

@@ -92,3 +92,3 @@ socket.pipe(masterDoc.slaveLink()).pipe(socket)

```js
slaveDoc = new gulf.Document(ot)
slaveDoc = new gulf.Document(adapter, ot)

@@ -103,3 +103,3 @@ socket.pipe(slaveDoc.masterLink()).pipe(socket)

```js
var document = new gulf.EditableDocument(ottype)
var document = new gulf.EditableDocument(adapter, ottype)

@@ -128,3 +128,3 @@ document._change = function(newcontent, cs) {

You can use [shareJS's built in ottypes](https://github.com/share/ottypes) or [other](https://github.com/marcelklehr/changesets) [libraries](https://github.com/marcelklehr/dom-ot).
You can use [shareJS's built in ottypes](https://github.com/share/ottypes) or [some other](https://github.com/marcelklehr/changesets) [libraries](https://github.com/marcelklehr/dom-ot).

@@ -136,5 +136,11 @@ For example, you could use shareJS's OT engine for plain text.

var document = new gulf.Document(textOT)
var document = new gulf.Document(new gulf.MemoryAdapter, textOT)
```
## Storage adapters
Gulf allows you to store your data anywhere you like, if you can provide it with a storage adapter. It comes with an in-memory adapter, ready for you to test your app quickly, but when the time comes to get ready for production you will want to change to a persistent storage backend like mongoDB or redis.
Currently implemented adapters are:
* [In-memory adapter](https://github.com/marcelklehr/gulf/blob/master/lib/MemoryAdapter.js)
## FAQ

@@ -154,3 +160,3 @@

## Todo
* per-document edit queue
* Check whether objects might get ripped apart in raw streams

@@ -157,0 +163,0 @@ * Catch misusage (i.e. attaching the same link twice to the same or different docs, employing a pipeline as master on both ends, piping a link twice -- is that even possible?)

@@ -17,8 +17,15 @@ /* global xdescribe, describe, it, xit */

describe('Linking to new documents', function() {
var docA = gulf.Document.create(ottype, 'abc')
, docB = new gulf.Document(ottype)
var docA, docB
var linkA, linkB
before(function(cb) {
gulf.Document.create(new gulf.MemoryAdapter, ottype, 'abc', function(er, doc) {
docA = doc
docB = new gulf.Document(new gulf.MemoryAdapter, ottype)
linkA = docA.slaveLink()
linkB = docB.masterLink()
cb()
})
})
var linkA = docA.slaveLink()
, linkB = docB.masterLink()
it('should adopt the current document state correctly', function(done) {

@@ -36,14 +43,24 @@ linkA.pipe(linkB).pipe(linkA)

var initialContent = 'abc'
var docA = gulf.Document.create(ottype, initialContent)
, docB = new gulf.EditableDocument(ottype)
var docA, docB
var linkA, linkB
var content
var content = ''
docB._change = function(newcontent, cs) {
content = newcontent
console.log('_change: ', newcontent)
}
before(function(cb) {
gulf.Document.create(new gulf.MemoryAdapter, ottype, initialContent, function(er, doc) {
docA = doc
docB = new gulf.EditableDocument(new gulf.MemoryAdapter, ottype)
var linkA = docA.slaveLink()
, linkB = docB.masterLink()
content = ''
docB._change = function(newcontent, cs) {
content = newcontent
console.log('_change: ', newcontent)
}
linkA = docA.slaveLink()
linkB = docB.masterLink()
cb()
})
})
/*linkA.on('link:edit', console.log.bind(console, 'edit in linkA'))

@@ -76,3 +93,3 @@ linkA.on('link:ack', console.log.bind(console, 'ack in linkA'))

done()
}, 100)
}, 0)
})

@@ -91,5 +108,5 @@

done()
}, 100)
}, 20)
})
})
})

Sorry, the diff of this file is not supported yet

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