Yjs is a framework for p2p shared editing on structured data like (rich-)text, json, and XML. It is similar to ShareJs and OpenCoweb, but easy to use. For additional information, demos, and tutorials visit y-js.org.
Extensions
Yjs only knows how to resolve conflicts on shared data. You have to choose a ..
- Connector - a communication protocol that propagates changes to the clients
- Database - a database to store your changes
- one or more Types - that represent the shared data
Connectors, Databases, and Types are available as modules that extend Yjs. Here is a list of the modules we know of:
Connectors
Name | Description |
---|---|
webrtc | Propagate updates Browser2Browser via WebRTC |
websockets | Set up a central server, and connect to it via websockets |
xmpp | Propagate updates in a XMPP multi-user-chat room (XEP-0045) |
test | A Connector for testing purposes. It is designed to simulate delays that happen in worst case scenarios |
Database adapters
Name | Description |
---|---|
memory | In-memory storage. |
indexeddb | Offline storage for the browser |
leveldb | Persistent storage for node apps |
Types
Name | Description |
---|---|
map | A shared Map implementation. Maps from text to any stringify-able object |
array | A shared Array implementation |
xml | An implementation of the DOM. You can create a two way binding to Browser DOM objects |
text | Collaborate on text. Supports two way binding to the Ace Editor, textareas, input elements, and HTML elements (e.g. <h1>, or <p>) |
richtext | Collaborate on rich text. Supports two way binding to the Quill Rich Text Editor |
Use it!
Install Yjs, and its modules with bower, or npm.
Bower
bower install --save yjs y-array % add all y-* modules you want to use
You only need to include the y.js
file. Yjs is able to automatically require missing modules.
<script src="./bower_components/yjs/y.js"></script>
Npm
npm install --save yjs % add all y-* modules you want to use
If you don't include via script tag, you have to explicitly include all modules! (Same goes for other module systems)
var Y = require('yjs')
require('y-array')(Y) // add the y-array type to Yjs
require('y-websockets-client')(Y)
require('y-memory')(Y)
require('y-array')(Y)
require('y-map')(Y)
require('y-text')(Y)
// ..
// do the same for all modules you want to use
Text editing example
Install dependencies
bower i yjs y-memory y-webrtc y-array y-text
Here is a simple example of a shared textarea
<!DOCTYPE html>
<html>
<body>
<script src="./bower_components/yjs/y.js"></script>
<script>
Y({
db: {
name: 'memory' // use memory database adapter.
// name: 'indexeddb'
// name: 'leveldb'
},
connector: {
name: 'webrtc', // use webrtc connector
// name: 'websockets-client'
// name: 'xmpp'
room: 'my-room' // clients connecting to the same room share data
},
sourceDir: '/bower_components', // location of the y-* modules (browser only)
share: {
textarea: 'Text' // y.share.textarea is of type y-text
}
}).then(function (y) {
// The Yjs instance `y` is available
// y.share.* contains the shared types
// Bind the textarea to y.share.textarea
y.share.textarea.bind(document.querySelector('textarea'))
}
</script>
<textarea></textarea>
</body>
</html>
Get Help & Give Help
There are some friendly people on who are eager to help, and answer questions. Please join!
Report any issues to the Github issue page! I try to fix them very soon, if possible.
API
Y(options)
- options.db
- Will be forwarded to the database adapter. Specify the database adaper on
options.db.name
. - Have a look at the used database adapter repository to see all available options.
- Will be forwarded to the database adapter. Specify the database adaper on
- options.connector
- Will be forwarded to the connector adapter. Specify the connector adaper on
options.connector.name
. - All our connectors implement a
room
property. Clients that specify the same room share the same data. - All of our connectors specify an
url
property that defines the connection endpoint of the used connector.- All of our connectors also have a default connection endpoint that you can use for development.
- Have a look at the used connector repository to see all available options.
- Will be forwarded to the connector adapter. Specify the connector adaper on
- options.sourceDir (browser only)
- Path where all y-* modules are stored
- Defaults to
/bower_components
- Not required when running on
nodejs
/iojs
- When using nodejs you need to manually extend Yjs:
var Y = require('yjs')
// you have to require a db, connector, and *all* types you use!
require('y-memory')(Y)
require('y-webrtc')(Y)
require('y-map')(Y)
// ..
- options.share
- Specify on
options.share[arbitraryName]
types that are shared among all users. - E.g. Specify
options.share[arbitraryName] = 'Array'
to require y-array and create an y-array type ony.share[arbitraryName]
. - If userA doesn't specify
options.share[arbitraryName]
, it won't be available for userA. - If userB specifies
options.share[arbitraryName]
, it still won't be available for userA. But all the updates are send from userB to userA. - In contrast to y-map, types on
y.share.*
cannot be overwritten or deleted. Instead, they are merged among all users. This feature is only available ony.share.*
- Weird behavior: It is supported that two users specify different types with the same property name.
E.g. userA specifies
options.share.x = 'Array'
, and userB specifiesoptions.share.x = 'Text'
. But they only share data if they specified the same type with the same property name
- Specify on
- options.type (browser only)
- Array of modules that Yjs needs to require, before instantiating a shared type.
- By default Yjs requires the specified database adapter, the specified connector, and all modules that are used in
options.share.*
- Put all types here that you intend to use, but are not used in y.share.*
Instantiated Y object (y)
Y(options)
returns a promise that is fulfilled when..
- All modules are loaded
- The specified database adapter is loaded
- The specified connector is loaded
- All types are included
- The connector is initialized, and a unique user id is set (received from the server)
- Note: When using y-indexeddb, a retrieved user id is stored on
localStorage
- Note: When using y-indexeddb, a retrieved user id is stored on
The promise returns an instance of Y. We denote it with a lower case y
.
- y.share.*
- Instances of the types you specified on options.share.*
- y.share.* can only be defined once when you instantiate Y!
- y.connector is an instance of Y.AbstractConnector
- y.connector.onUserEvent(function (event) {..})
- Observe user events (event.action is either 'userLeft' or 'userJoined')
- y.connector.whenSynced(listener)
-
listener
is executed when y synced with at least one user. -
listener
is not called when no other user is in the same room. - y-websockets-client aways waits to sync with the server
-
- y.connector.disconnect()
- Force to disconnect this instance from the other instances
- y.connector.reconnect()
- Try to reconnect to the other instances (needs to be supported by the connector)
- Not supported by y-xmpp
- y.destroy()
- Destroy this object.
- Destroys all types (they will throw weird errors if you still use them)
- Disconnects from the other instances (via connector)
- Removes all data from the database
- y.db.stopGarbageCollector()
- Stop the garbage collector. Call y.db.garbageCollect() to continue garbage collection
- y.db.gcTimeout :: Number (defaults to 50000 ms)
- Time interval between two garbage collect cycles
- It is required that all instances exchanged all messages after two garbage collect cycles (after 100000 ms per default)
- y.db.userId :: String
- The used user id for this client. Never overwrite this
Changelog
12.0.0
-
Types are synchronous and never return a promise (except explicitly stated)
y.share.map.get('map type') // => returns a y-map instead of a promise
- The event property
oldValues
, andvalues
contain a list of values (without wrapper)
- Support for the y-leveldb database adapter
- y-richtext supports Quill@1.0.0-rc.2
11.0.0
-
All types return a single event instead of list of events
- Insert events contain a list of values
- Improved performance for large insertions & deletions
- Several bugfixes (offline editing related)
- Native support for node 4 (see #49)
10.0.0
- Support for more complex types (a type can be a composition of several types)
- Fixes several memory leaks
9.0.0
There were several rolling updates from 0.6 to 0.8. We consider Yjs stable since a long time, and intend to continue stable releases. From this release forward y-* modules will implement peer-dependencies for npm, and dependencies for bower. Furthermore, incompatible yjs instances throw errors now when syncing - this feature was influenced by #48. The versioning jump was influenced by react (see here)
0.6.0
This is a complete rewrite of the 0.5 version of Yjs. Since Yjs 0.6.0 it is possible to work asynchronously on a persistent database, which enables offline support.
- Switched to semver versioning
- Requires a promise implementation in environment (es6 promises suffice, included in all the major browsers). Otherwise you have to include a polyfill
- Y.Object has been renamed to Y.Map
- Y.Map exchanges
.val(name [, value])
in favor of.set(name, value)
and.get(name)
- Y.Map
.get(name)
returns a promise, if the value is a custom type - The Connector definition slightly changed (I'll update the wiki)
- The Type definitions completely changed, so you have to rewrite them (I'll rewrite the article in the wiki)
- Support for several packaging systems
- Flowtype
Contribution
I created this framework during my bachelor thesis at the chair of computer science 5 (i5), RWTH University. Since December 2014 I'm working on Yjs as a part of my student worker job at the i5.
License
Yjs is licensed under the MIT License.