Compare commits
1 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
9374c363c3 |
12
.babelrc
12
.babelrc
@@ -1,12 +0,0 @@
|
|||||||
{
|
|
||||||
"presets": [
|
|
||||||
["latest", {
|
|
||||||
"es2015": {
|
|
||||||
"modules": false
|
|
||||||
}
|
|
||||||
}]
|
|
||||||
],
|
|
||||||
"plugins": [
|
|
||||||
"external-helpers"
|
|
||||||
]
|
|
||||||
}
|
|
||||||
14
.gitignore
vendored
14
.gitignore
vendored
@@ -1,3 +1,15 @@
|
|||||||
node_modules
|
node_modules
|
||||||
bower_components
|
bower_components
|
||||||
/y.*
|
build
|
||||||
|
build_test
|
||||||
|
.directory
|
||||||
|
.codio
|
||||||
|
.settings
|
||||||
|
.jshintignore
|
||||||
|
.jshintrc
|
||||||
|
.validate.json
|
||||||
|
/y.js
|
||||||
|
/y.js.map
|
||||||
|
/y-*
|
||||||
|
.vscode
|
||||||
|
jsconfig.json
|
||||||
|
|||||||
4
.gitmodules
vendored
Normal file
4
.gitmodules
vendored
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
[submodule "dist"]
|
||||||
|
path = dist
|
||||||
|
url = https://github.com/y-js/yjs.git
|
||||||
|
branch = dist
|
||||||
332
README.md
332
README.md
@@ -1,215 +1,133 @@
|
|||||||
|
|
||||||
# 
|
# 
|
||||||
|
|
||||||
Yjs is a framework for offline-first p2p shared editing on structured data like
|
Yjs is a framework for optimistic concurrency control and automatic conflict resolution on shared data.
|
||||||
text, richtext, json, or XML. It is fairly easy to get started, as Yjs hides
|
The framework provides similar functionality as [ShareJs] and [OpenCoweb], but supports peer-to-peer
|
||||||
most of the complexity of concurrent editing. For additional information, demos,
|
communication protocols by default. Yjs was designed to handle concurrent actions on arbitrary data
|
||||||
and tutorials visit [y-js.org](http://y-js.org/).
|
like Text, Json, and XML. We also provide support for storing and manipulating your shared data offline.
|
||||||
|
For more information and demo applications visit our [homepage](http://y-js.org/).
|
||||||
|
|
||||||
### Extensions
|
You can create you own shared types easily.
|
||||||
Yjs only knows how to resolve conflicts on shared data. You have to choose a ..
|
Therefore, you can design the structure of your custom type,
|
||||||
* *Connector* - a communication protocol that propagates changes to the clients
|
and ensure data validity, while Yjs ensures data consistency (everyone will eventually end up with the same data).
|
||||||
* *Database* - a database to store your changes
|
We already provide abstract data types for
|
||||||
* 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](https://github.com/y-js/y-webrtc) | Propagate updates Browser2Browser via WebRTC|
|
|
||||||
|[websockets](https://github.com/y-js/y-websockets-client) | Set up [a central server](https://github.com/y-js/y-websockets-client), and connect to it via websockets |
|
|
||||||
|[xmpp](https://github.com/y-js/y-xmpp) | Propagate updates in a XMPP multi-user-chat room ([XEP-0045](http://xmpp.org/extensions/xep-0045.html))|
|
|
||||||
|[test](https://github.com/y-js/y-test) | A Connector for testing purposes. It is designed to simulate delays that happen in worst case scenarios|
|
|
||||||
|
|
||||||
##### Database adapters
|
|
||||||
|
|
||||||
|Name | Description |
|
|
||||||
|----------------|-----------------------------------|
|
|
||||||
|[memory](https://github.com/y-js/y-memory) | In-memory storage. |
|
|
||||||
|[indexeddb](https://github.com/y-js/y-indexeddb) | Offline storage for the browser |
|
|
||||||
|[leveldb](https://github.com/y-js/y-leveldb) | Persistent storage for node apps |
|
|
||||||
|
|
||||||
|
|
||||||
##### Types
|
|
||||||
|
|
||||||
| Name | Description |
|
| Name | Description |
|
||||||
|----------|-------------------|
|
|----------|-------------------|
|
||||||
|[map](https://github.com/y-js/y-map) | A shared Map implementation. Maps from text to any stringify-able object |
|
|[map](https://github.com/y-js/y-map) | A shared Map implementation. Maps from text to any stringify-able object |
|
||||||
|[array](https://github.com/y-js/y-array) | A shared Array implementation |
|
|[array](https://github.com/y-js/y-array) | A shared Array implementation |
|
||||||
|[xml](https://github.com/y-js/y-xml) | An implementation of the DOM. You can create a two way binding to Browser DOM objects |
|
|[xml](https://github.com/y-js/y-xml) | An implementation of the DOM. You can create a two way binding to Browser DOM objects |
|
||||||
|[text](https://github.com/y-js/y-text) | Collaborate on text. Supports two way binding to the [Ace Editor](https://ace.c9.io), [CodeMirror](https://codemirror.net/), [Monaco](https://github.com/Microsoft/monaco-editor), textareas, input elements, and HTML elements (e.g. <*h1*>, or <*p*>) |
|
|[text](https://github.com/y-js/y-text) | Collaborate on text. Supports two way binding to textareas, input elements, or HTML elements (e.g. <*h1*>, or <*p*>). Also supports the [Ace Editor](https://ace.c9.io) |
|
||||||
|[richtext](https://github.com/y-js/y-richtext) | Collaborate on rich text. Supports two way binding to the [Quill Rich Text Editor](http://quilljs.com/)|
|
|[richtext](https://github.com/y-js/y-richtext) | Collaborate on rich text. Supports two way binding to the [Quill Rich Text Editor](http://quilljs.com/)|
|
||||||
|
|
||||||
##### Other
|
Yjs supports P2P message propagation, and is not bound to a specific communication protocol. Therefore, Yjs is extremely scalable and can be used in a wide range of application scenarios.
|
||||||
|
|
||||||
| Name | Description |
|
We support several communication protocols as so called *Connectors*.
|
||||||
|-----------|-------------------|
|
You can create your own connector too - read [this wiki page](https://github.com/y-js/yjs/wiki/Custom-Connectors).
|
||||||
|[y-element](http://y-js.org/y-element/) | Yjs Polymer Element |
|
Currently, we support the following communication protocols:
|
||||||
|
|
||||||
|
|Name | Description |
|
||||||
|
|----------------|-----------------------------------|
|
||||||
|
|[xmpp](https://github.com/y-js/y-xmpp) | Propagate updates in a XMPP multi-user-chat room ([XEP-0045](http://xmpp.org/extensions/xep-0045.html))|
|
||||||
|
|[webrtc](https://github.com/y-js/y-webrtc) | Propagate updates Browser2Browser via WebRTC|
|
||||||
|
|[websockets](https://github.com/y-js/y-websockets-client) | Exchange updates efficiently in the classical client-server model |
|
||||||
|
|[test](https://github.com/y-js/y-test) | A Connector for testing purposes. It is designed to simulate delays that happen in worst case scenarios|
|
||||||
|
|
||||||
|
You are not limited to use a specific database to store the shared data. We provide the following database adapters:
|
||||||
|
|
||||||
|
|Name | Description |
|
||||||
|
|----------------|-----------------------------------|
|
||||||
|
|[memory](https://github.com/y-js/y-memory) | In-memory storage. |
|
||||||
|
|[indexeddb](https://github.com/y-js/y-indexeddb) | Offline storage for the browser |
|
||||||
|
|
||||||
|
The advantages over similar frameworks are support for
|
||||||
|
* .. P2P message propagation and arbitrary communication protocols
|
||||||
|
* .. share any type of data. The types provide a convenient interface
|
||||||
|
* .. offline support: Changes are stored persistently and only relevant changes are propagated on rejoin
|
||||||
|
* .. Intention Preservation: When working on Text, the intention of your changes are preserved. This is particularily important when working offline. Every type has a notion on how we define Intention Preservation on it.
|
||||||
|
|
||||||
## Use it!
|
## Use it!
|
||||||
Install Yjs, and its modules with [bower](http://bower.io/), or
|
Install yjs and its modules with [bower](http://bower.io/), or with [npm](https://www.npmjs.org/package/yjs).
|
||||||
[npm](https://www.npmjs.org/package/yjs).
|
|
||||||
|
|
||||||
### Bower
|
### Bower
|
||||||
```
|
```
|
||||||
bower install --save yjs y-array % add all y-* modules you want to use
|
bower install yjs --save
|
||||||
```
|
```
|
||||||
You only need to include the `y.js` file. Yjs is able to automatically require
|
Then you include the libraries directly from the installation folder.
|
||||||
missing modules.
|
|
||||||
```
|
```
|
||||||
<script src="./bower_components/yjs/y.js"></script>
|
<script src="./bower_components/yjs/y.js"></script>
|
||||||
```
|
```
|
||||||
|
|
||||||
### Npm
|
### Npm
|
||||||
```
|
```
|
||||||
npm install --save yjs % add all y-* modules you want to use
|
npm install yjs --save
|
||||||
```
|
```
|
||||||
|
|
||||||
If you don't include via script tag, you have to explicitly include all modules!
|
And use it like this with *npm*:
|
||||||
(Same goes for other module systems)
|
|
||||||
```
|
```
|
||||||
var Y = require('yjs')
|
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
|
|
||||||
```
|
|
||||||
|
|
||||||
### ES6 Syntax
|
|
||||||
```
|
|
||||||
import Y from 'yjs'
|
|
||||||
import yArray from 'y-array'
|
|
||||||
import yWebsocketsClient from 'y-webrtc'
|
|
||||||
import yMemory from 'y-memory'
|
|
||||||
import yArray from 'y-array'
|
|
||||||
import yMap from 'y-map'
|
|
||||||
import yText from 'y-text'
|
|
||||||
// ..
|
|
||||||
Y.extend(yArray, yWebsocketsClient, yMemory, yArray, yMap, yText /*, .. */)
|
|
||||||
```
|
```
|
||||||
|
|
||||||
# Text editing example
|
# Text editing example
|
||||||
Install dependencies
|
|
||||||
```
|
```
|
||||||
bower i yjs y-memory y-webrtc y-array y-text
|
Y({
|
||||||
|
db: {
|
||||||
|
name: 'memory' // store in memory.
|
||||||
|
// name: 'indexeddb'
|
||||||
|
},
|
||||||
|
connector: {
|
||||||
|
name: 'websockets-client', // choose the websockets connector
|
||||||
|
// name: 'webrtc'
|
||||||
|
// name: 'xmpp'
|
||||||
|
room: 'Textarea-example-dev'
|
||||||
|
},
|
||||||
|
sourceDir: '/bower_components', // location of the y-* modules
|
||||||
|
share: {
|
||||||
|
textarea: 'Text' // y.share.textarea is of type Y.Text
|
||||||
|
}
|
||||||
|
// types: ['Richtext', 'Array'] // optional list of types you want to import
|
||||||
|
}).then(function (y) {
|
||||||
|
// bind the textarea to a shared text element
|
||||||
|
y.share.textarea.bind(document.getElementById('textfield'))
|
||||||
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
Here is a simple example of a shared textarea
|
# Api
|
||||||
```HTML
|
|
||||||
<!DOCTYPE html>
|
|
||||||
<html>
|
|
||||||
<body>
|
|
||||||
<script src="./bower_components/yjs/y.js"></script>
|
|
||||||
<!-- Yjs automatically includes all missing dependencies (browser only) -->
|
|
||||||
<script>
|
|
||||||
Y({
|
|
||||||
db: {
|
|
||||||
name: 'memory' // use memory database adapter.
|
|
||||||
// name: 'indexeddb' // use indexeddb database adapter instead for offline apps
|
|
||||||
},
|
|
||||||
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 `y.share.textarea` to `<textarea/>`
|
|
||||||
y.share.textarea.bind(document.querySelector('textarea'))
|
|
||||||
})
|
|
||||||
</script>
|
|
||||||
<textarea></textarea>
|
|
||||||
</body>
|
|
||||||
</html>
|
|
||||||
```
|
|
||||||
|
|
||||||
## Get Help & Give Help
|
|
||||||
There are some friendly people on [](https://gitter.im/y-js/yjs?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge) who are eager to help, and answer questions. Please join!
|
|
||||||
|
|
||||||
Report _any_ issues to the
|
|
||||||
[Github issue page](https://github.com/y-js/yjs/issues)! I try to fix them very
|
|
||||||
soon, if possible.
|
|
||||||
|
|
||||||
# API
|
|
||||||
|
|
||||||
### Y(options)
|
### Y(options)
|
||||||
* Y.extend(module1, module2, ..)
|
|
||||||
* Add extensions to Y
|
|
||||||
* `Y.extend(require('y-webrtc'))` has the same semantics as
|
|
||||||
`require('y-webrtc')(Y)`
|
|
||||||
* options.db
|
* options.db
|
||||||
* Will be forwarded to the database adapter. Specify the database adaper on
|
* Will be forwarded to the database adapter. Specify the database adaper on `options.db.name`.
|
||||||
`options.db.name`.
|
* Have a look at the used database adapter repository to see all available options.
|
||||||
* Have a look at the used database adapter repository to see all available
|
|
||||||
options.
|
|
||||||
* options.connector
|
* options.connector
|
||||||
* Will be forwarded to the connector adapter. Specify the connector adaper on
|
* Will be forwarded to the connector adapter. Specify the connector adaper on `options.connector.name`.
|
||||||
`options.connector.name`.
|
* All our connectors implement a `room` property. Clients that specify the same room share the same data.
|
||||||
* All our connectors implement a `room` property. Clients that specify the
|
* All of our connectors specify an `url` property that defines the connection endpoint of the used connector.
|
||||||
same room share the same data.
|
* All of our connectors also have a default connection endpoint that you can use for development.
|
||||||
* 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.
|
|
||||||
* Set `options.connector.generateUserId = true` in order to genenerate a
|
|
||||||
userid, instead of receiving one from the server. This way the `Y(..)` is
|
|
||||||
immediately going to be resolved, without waiting for any confirmation from
|
|
||||||
the server. Use with caution.
|
|
||||||
* Have a look at the used connector repository to see all available options.
|
* Have a look at the used connector repository to see all available options.
|
||||||
* *Only if you know what you are doing:* Set
|
* options.sourceDir
|
||||||
`options.connector.preferUntransformed = true` in order receive the shared
|
* Path where all y-* modules are stored.
|
||||||
data untransformed. This is very efficient as the database content is simply
|
|
||||||
copied to this client. This does only work if this client receives content
|
|
||||||
from only one client.
|
|
||||||
* options.sourceDir (browser only)
|
|
||||||
* Path where all y-* modules are stored
|
|
||||||
* Defaults to `/bower_components`
|
* Defaults to `/bower_components`
|
||||||
* Not required when running on `nodejs` / `iojs`
|
* Not required when running on `nodejs` / `iojs`
|
||||||
* When using nodejs you need to manually extend Yjs:
|
* When using browserify you can specify all used modules like this:
|
||||||
```
|
```
|
||||||
var Y = require('yjs')
|
var Y = require('yjs')
|
||||||
// you have to require a db, connector, and *all* types you use!
|
// you need to require the db, connector, and *all* types you use!
|
||||||
require('y-memory')(Y)
|
require('y-memory')(Y)
|
||||||
require('y-webrtc')(Y)
|
require('y-webrtc')(Y)
|
||||||
require('y-map')(Y)
|
require('y-map')(Y)
|
||||||
// ..
|
// ..
|
||||||
```
|
```
|
||||||
* options.share
|
* options.share
|
||||||
* Specify on `options.share[arbitraryName]` types that are shared among all
|
* Specify on `options.share[arbitraryName]` types that are shared among all users.
|
||||||
users.
|
* E.g. Specify `options.share[arbitraryName] = 'Array'` to require y-array and create an Y.Array type on `y.share[arbitraryName]`.
|
||||||
* E.g. Specify `options.share[arbitraryName] = 'Array'` to require y-array and
|
* If userA doesn't specify `options.share[arbitraryName]`, it won't be available for userA.
|
||||||
create an y-array type on `y.share[arbitraryName]`.
|
* If userB specifies `options.share[arbitraryName]`, it still won't be available for userA. But all the updates are send from userB to userA.
|
||||||
* If userA doesn't specify `options.share[arbitraryName]`, it won't be
|
* 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 on `y.share.*`
|
||||||
available for userA.
|
* Weird behavior: It is supported that two users specify different types with the same property name.
|
||||||
* If userB specifies `options.share[arbitraryName]`, it still won't be
|
E.g. userA specifies `options.share.x = 'Array'`, and userB specifies `options.share.x = 'Text'`. But they'll only share data if they specified the same type with the same property name
|
||||||
available for userA. But all the updates are send from userB to userA.
|
* options.type
|
||||||
* In contrast to y-map, types on `y.share.*` cannot be overwritten or deleted.
|
* Array of modules that Yjs needs to require, before instantiating a shared type.
|
||||||
Instead, they are merged among all users. This feature is only available on
|
* By default Yjs requires the specified database adapter, the specified connector, and all modules that are used in `options.share.*`
|
||||||
`y.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 specifies
|
|
||||||
`options.share.x = 'Text'`. But they only share data if they specified the
|
|
||||||
same type with the same property name
|
|
||||||
* 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.*
|
* Put all types here that you intend to use, but are not used in y.share.*
|
||||||
|
|
||||||
### Instantiated Y object (y)
|
### Instantiated Y object (y)
|
||||||
@@ -219,8 +137,7 @@ require('y-map')(Y)
|
|||||||
* The specified database adapter is loaded
|
* The specified database adapter is loaded
|
||||||
* The specified connector is loaded
|
* The specified connector is loaded
|
||||||
* All types are included
|
* All types are included
|
||||||
* The connector is initialized, and a unique user id is set (received from the
|
* The connector is initialized, and a unique user id is set (received from the server)
|
||||||
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 `localStorage`
|
||||||
|
|
||||||
The promise returns an instance of Y. We denote it with a lower case `y`.
|
The promise returns an instance of Y. We denote it with a lower case `y`.
|
||||||
@@ -238,57 +155,70 @@ The promise returns an instance of Y. We denote it with a lower case `y`.
|
|||||||
* y.connector.disconnect()
|
* y.connector.disconnect()
|
||||||
* Force to disconnect this instance from the other instances
|
* Force to disconnect this instance from the other instances
|
||||||
* y.connector.reconnect()
|
* y.connector.reconnect()
|
||||||
* Try to reconnect to the other instances (needs to be supported by the
|
* Try to reconnect to the other instances (needs to be supported by the connector)
|
||||||
connector)
|
* Not supported by y-xmpp
|
||||||
* Not supported by y-xmpp
|
* y.destroy()
|
||||||
* y.close()
|
|
||||||
* Destroy this object.
|
* Destroy this object.
|
||||||
* Destroys all types (they will throw weird errors if you still use them)
|
* Destroys all types (they will throw weird errors if you still use them)
|
||||||
* Disconnects from the other instances (via connector)
|
* Disconnects from the other instances (via connector)
|
||||||
* Returns a promise
|
|
||||||
* y.destroy()
|
|
||||||
* calls y.close()
|
|
||||||
* Removes all data from the database
|
* Removes all data from the database
|
||||||
* Returns a promise
|
|
||||||
* y.db.stopGarbageCollector()
|
* y.db.stopGarbageCollector()
|
||||||
* Stop the garbage collector. Call y.db.garbageCollect() to continue garbage
|
* Stop the garbage collector. Call y.db.garbageCollect() to continue garbage collection
|
||||||
collection
|
|
||||||
* y.db.gc :: Boolean
|
|
||||||
* Whether gc is turned on
|
|
||||||
* y.db.gcTimeout :: Number (defaults to 50000 ms)
|
* y.db.gcTimeout :: Number (defaults to 50000 ms)
|
||||||
* Time interval between two garbage collect cycles
|
* Time interval between two garbage collect cycles
|
||||||
* It is required that all instances exchanged all messages after two garbage
|
* It is required that all instances exchanged all messages after two garbage collect cycles (after 100000 ms per default)
|
||||||
collect cycles (after 100000 ms per default)
|
|
||||||
* y.db.userId :: String
|
* y.db.userId :: String
|
||||||
* The used user id for this client. **Never overwrite this**
|
* The used user id for this client. **Never overwrite this**
|
||||||
|
|
||||||
### Logging
|
## Get help
|
||||||
Yjs uses [debug](https://github.com/visionmedia/debug) for logging. The flag
|
There are some friendly people on [](https://gitter.im/y-js/yjs?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge) who may help you with your problem, and answer your questions.
|
||||||
`y*` enables logging for all y-* components. You can selectively remove
|
|
||||||
components you are not interested in: E.g. The flag `y*,-y:connector-message`
|
|
||||||
will not log the long `y:connector-message` messages.
|
|
||||||
|
|
||||||
##### Enable logging in Node.js
|
Please report _any_ issues to the [Github issue page](https://github.com/y-js/yjs/issues)! I try to fix them very soon, if possible.
|
||||||
```sh
|
If you want to see an issue fixed, please subscribe to the thread (or remind me via gitter).
|
||||||
DEBUG=y* node app.js
|
|
||||||
```
|
|
||||||
|
|
||||||
Remove the colors in order to log to a file:
|
|
||||||
```sh
|
|
||||||
DEBUG_COLORS=0 DEBUG=y* node app.js > log
|
|
||||||
```
|
|
||||||
|
|
||||||
##### Enable logging in the browser
|
## Changelog
|
||||||
```js
|
|
||||||
localStorage.debug = 'y*'
|
### 11.0.0
|
||||||
```
|
|
||||||
|
* **All types now 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 will now throw errors when syncing - this feature was influenced by #48. The versioning jump was influenced by react (see [here](https://facebook.github.io/react/blog/2016/02/19/new-versioning-scheme.html))
|
||||||
|
|
||||||
|
|
||||||
|
### 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
|
## Contribution
|
||||||
I created this framework during my bachelor thesis at the chair of computer
|
I created this framework during my bachelor thesis at the chair of computer science 5 [(i5)](http://dbis.rwth-aachen.de/cms), RWTH University. Since December 2014 I'm working on Yjs as a part of my student worker job at the i5.
|
||||||
science 5 [(i5)](http://dbis.rwth-aachen.de/cms), RWTH University. Since
|
|
||||||
December 2014 I'm working on Yjs as a part of my student worker job at the i5.
|
|
||||||
|
|
||||||
## License
|
## License
|
||||||
Yjs is licensed under the [MIT License](./LICENSE).
|
Yjs is licensed under the [MIT License](./LICENSE.txt).
|
||||||
|
|
||||||
<yjs@dbis.rwth-aachen.de>
|
<yjs@dbis.rwth-aachen.de>
|
||||||
|
|
||||||
|
[ShareJs]: https://github.com/share/ShareJS
|
||||||
|
[OpenCoweb]: https://github.com/opencoweb/coweb/wiki
|
||||||
|
|
||||||
|
|||||||
72
declarations/Structs.js
Normal file
72
declarations/Structs.js
Normal file
@@ -0,0 +1,72 @@
|
|||||||
|
/* @flow */
|
||||||
|
|
||||||
|
type UserId = string
|
||||||
|
type Id = [UserId, number|string]
|
||||||
|
|
||||||
|
/*
|
||||||
|
type Struct = {
|
||||||
|
id: Id,
|
||||||
|
left?: Id,
|
||||||
|
right?: Id,
|
||||||
|
target?: Id,
|
||||||
|
struct: 'Insert' | 'Delete'
|
||||||
|
}*/
|
||||||
|
|
||||||
|
type Struct = Insertion | Deletion
|
||||||
|
type Operation = Struct
|
||||||
|
|
||||||
|
type Insertion = {
|
||||||
|
id: Id,
|
||||||
|
left: ?Id,
|
||||||
|
origin: ?Id,
|
||||||
|
right: ?Id,
|
||||||
|
parent: Id,
|
||||||
|
parentSub: ?Id,
|
||||||
|
opContent: ?Id,
|
||||||
|
content: ?any,
|
||||||
|
struct: 'Insert'
|
||||||
|
}
|
||||||
|
|
||||||
|
type Deletion = {
|
||||||
|
target: Id,
|
||||||
|
struct: 'Delete'
|
||||||
|
}
|
||||||
|
|
||||||
|
type MapStruct = {
|
||||||
|
id: Id,
|
||||||
|
type: TypeNames,
|
||||||
|
map: any
|
||||||
|
}
|
||||||
|
|
||||||
|
type ListStruct = {
|
||||||
|
id: Id,
|
||||||
|
type: TypeNames,
|
||||||
|
start: Id,
|
||||||
|
end: Id
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
type MessageSyncStep1 = {
|
||||||
|
type: 'sync step 1',
|
||||||
|
deleteSet: any,
|
||||||
|
stateSet: any
|
||||||
|
}
|
||||||
|
|
||||||
|
type MessageSyncStep2 = {
|
||||||
|
type: 'sync step 2',
|
||||||
|
os: Array<Operation>,
|
||||||
|
deleteSet: any,
|
||||||
|
stateSet: any
|
||||||
|
}
|
||||||
|
|
||||||
|
type MessageUpdate = {
|
||||||
|
type: 'update',
|
||||||
|
ops: Array<Operation>
|
||||||
|
}
|
||||||
|
|
||||||
|
type MessageSyncDone = {
|
||||||
|
type: 'sync done'
|
||||||
|
}
|
||||||
|
|
||||||
|
type Message = MessageSyncStep1 | MessageSyncStep2 | MessageUpdate | MessageSyncDone
|
||||||
|
|
||||||
0
declarations/Type.js
Normal file
0
declarations/Type.js
Normal file
34
declarations/Y.js
Normal file
34
declarations/Y.js
Normal file
@@ -0,0 +1,34 @@
|
|||||||
|
/* @flow */
|
||||||
|
|
||||||
|
type YGlobal = {
|
||||||
|
utils: Object,
|
||||||
|
Struct: any,
|
||||||
|
AbstractDatabase: any,
|
||||||
|
AbstractConnector: any,
|
||||||
|
Transaction: any
|
||||||
|
}
|
||||||
|
|
||||||
|
type YConfig = {
|
||||||
|
db: Object,
|
||||||
|
connector: Object,
|
||||||
|
root: Object
|
||||||
|
}
|
||||||
|
|
||||||
|
type TypeName = 'array' | 'map' | 'text'
|
||||||
|
|
||||||
|
declare var YConcurrency_TestingMode : boolean
|
||||||
|
|
||||||
|
type Transaction<A> = Generator<any, A, any>
|
||||||
|
|
||||||
|
type SyncRole = 'master' | 'slave'
|
||||||
|
|
||||||
|
declare class Store {
|
||||||
|
find: (id:Id) => Transaction<any>;
|
||||||
|
put: (n:any) => Transaction<void>;
|
||||||
|
delete: (id:Id) => Transaction<void>;
|
||||||
|
findWithLowerBound: (start:Id) => Transaction<any>;
|
||||||
|
findWithUpperBound: (end:Id) => Transaction<any>;
|
||||||
|
findNext: (id:Id) => Transaction<any>;
|
||||||
|
findPrev: (id:Id) => Transaction<any>;
|
||||||
|
iterate: (t:any,start:?Id,end:?Id,gen:any) => Transaction<any>;
|
||||||
|
}
|
||||||
1
dist
Submodule
1
dist
Submodule
Submodule dist added at 47bcec8bc7
@@ -1,32 +0,0 @@
|
|||||||
<!DOCTYPE html>
|
|
||||||
<html>
|
|
||||||
<head>
|
|
||||||
<style type="text/css" media="screen">
|
|
||||||
#aceContainer {
|
|
||||||
position: absolute;
|
|
||||||
top: 0;
|
|
||||||
right: 0;
|
|
||||||
bottom: 0;
|
|
||||||
left: 0;
|
|
||||||
}
|
|
||||||
.inserted {
|
|
||||||
position:absolute;
|
|
||||||
z-index:20;
|
|
||||||
background-color: #FFC107;
|
|
||||||
}
|
|
||||||
.deleted {
|
|
||||||
position:absolute;
|
|
||||||
z-index:20;
|
|
||||||
background-color: #FFC107;
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
</head>
|
|
||||||
<body>
|
|
||||||
|
|
||||||
<div id="aceContainer"></div>
|
|
||||||
<script src="../bower_components/yjs/y.js"></script>
|
|
||||||
<script src="../bower_components/ace-builds/src/ace.js"></script>
|
|
||||||
|
|
||||||
<script src="./index.js"></script>
|
|
||||||
</body>
|
|
||||||
</html>
|
|
||||||
@@ -1,24 +0,0 @@
|
|||||||
/* global Y, ace */
|
|
||||||
|
|
||||||
Y({
|
|
||||||
db: {
|
|
||||||
name: 'memory'
|
|
||||||
},
|
|
||||||
connector: {
|
|
||||||
name: 'websockets-client',
|
|
||||||
room: 'ace-example'
|
|
||||||
},
|
|
||||||
sourceDir: '/bower_components',
|
|
||||||
share: {
|
|
||||||
ace: 'Text' // y.share.textarea is of type Y.Text
|
|
||||||
}
|
|
||||||
}).then(function (y) {
|
|
||||||
window.yAce = y
|
|
||||||
|
|
||||||
// bind the textarea to a shared text element
|
|
||||||
var editor = ace.edit('aceContainer')
|
|
||||||
editor.setTheme('ace/theme/chrome')
|
|
||||||
editor.getSession().setMode('ace/mode/javascript')
|
|
||||||
|
|
||||||
y.share.ace.bindAce(editor)
|
|
||||||
})
|
|
||||||
@@ -1,29 +0,0 @@
|
|||||||
{
|
|
||||||
"name": "yjs-examples",
|
|
||||||
"version": "0.0.0",
|
|
||||||
"homepage": "y-js.org",
|
|
||||||
"authors": [
|
|
||||||
"Kevin Jahns <kevin.jahns@rwth-aachen.de>"
|
|
||||||
],
|
|
||||||
"description": "Examples for Yjs",
|
|
||||||
"license": "MIT",
|
|
||||||
"ignore": [],
|
|
||||||
"dependencies": {
|
|
||||||
"yjs": "latest",
|
|
||||||
"y-array": "latest",
|
|
||||||
"y-map": "latest",
|
|
||||||
"y-memory": "latest",
|
|
||||||
"y-richtext": "latest",
|
|
||||||
"y-webrtc": "latest",
|
|
||||||
"y-websockets-client": "latest",
|
|
||||||
"y-text": "latest",
|
|
||||||
"y-indexeddb": "latest",
|
|
||||||
"y-xml": "latest",
|
|
||||||
"quill": "^1.0.0-rc.2",
|
|
||||||
"ace": "~1.2.3",
|
|
||||||
"ace-builds": "~1.2.3",
|
|
||||||
"jquery": "~2.2.2",
|
|
||||||
"d3": "^3.5.16",
|
|
||||||
"codemirror": "^5.25.0"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,18 +0,0 @@
|
|||||||
<!DOCTYPE html>
|
|
||||||
<html>
|
|
||||||
<body>
|
|
||||||
<style>
|
|
||||||
#chat p span {
|
|
||||||
color: blue;
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
<div id="chat"></div>
|
|
||||||
<form id="chatform">
|
|
||||||
<input name="username" type="text" style="width:15%;">
|
|
||||||
<input name="message" type="text" style="width:60%;">
|
|
||||||
<input type="submit" value="Send">
|
|
||||||
</form>
|
|
||||||
<script src="../bower_components/yjs/y.js"></script>
|
|
||||||
<script src="./index.js"></script>
|
|
||||||
</body>
|
|
||||||
</html>
|
|
||||||
@@ -1,73 +0,0 @@
|
|||||||
/* global Y, chat */
|
|
||||||
|
|
||||||
// initialize a shared object. This function call returns a promise!
|
|
||||||
Y({
|
|
||||||
db: {
|
|
||||||
name: 'memory'
|
|
||||||
},
|
|
||||||
connector: {
|
|
||||||
name: 'websockets-client',
|
|
||||||
room: 'chat-example'
|
|
||||||
},
|
|
||||||
sourceDir: '/bower_components',
|
|
||||||
share: {
|
|
||||||
chat: 'Array'
|
|
||||||
}
|
|
||||||
}).then(function (y) {
|
|
||||||
window.yChat = y
|
|
||||||
// This functions inserts a message at the specified position in the DOM
|
|
||||||
function appendMessage (message, position) {
|
|
||||||
var p = document.createElement('p')
|
|
||||||
var uname = document.createElement('span')
|
|
||||||
uname.appendChild(document.createTextNode(message.username + ': '))
|
|
||||||
p.appendChild(uname)
|
|
||||||
p.appendChild(document.createTextNode(message.message))
|
|
||||||
document.querySelector('#chat').insertBefore(p, chat.children[position] || null)
|
|
||||||
}
|
|
||||||
// This function makes sure that only 7 messages exist in the chat history.
|
|
||||||
// The rest is deleted
|
|
||||||
function cleanupChat () {
|
|
||||||
if (y.share.chat.length > 7) {
|
|
||||||
y.share.chat.delete(0, y.chat.length - 7)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// Insert the initial content
|
|
||||||
y.share.chat.toArray().forEach(appendMessage)
|
|
||||||
cleanupChat()
|
|
||||||
|
|
||||||
// whenever content changes, make sure to reflect the changes in the DOM
|
|
||||||
y.share.chat.observe(function (event) {
|
|
||||||
if (event.type === 'insert') {
|
|
||||||
for (let i = 0; i < event.length; i++) {
|
|
||||||
appendMessage(event.values[i], event.index + i)
|
|
||||||
}
|
|
||||||
} else if (event.type === 'delete') {
|
|
||||||
for (let i = 0; i < event.length; i++) {
|
|
||||||
chat.children[event.index].remove()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// concurrent insertions may result in a history > 7, so cleanup here
|
|
||||||
cleanupChat()
|
|
||||||
})
|
|
||||||
document.querySelector('#chatform').onsubmit = function (event) {
|
|
||||||
// the form is submitted
|
|
||||||
var message = {
|
|
||||||
username: this.querySelector('[name=username]').value,
|
|
||||||
message: this.querySelector('[name=message]').value
|
|
||||||
}
|
|
||||||
if (message.username.length > 0 && message.message.length > 0) {
|
|
||||||
if (y.share.chat.length > 6) {
|
|
||||||
// If we are goint to insert the 8th element, make sure to delete first.
|
|
||||||
y.share.chat.delete(0)
|
|
||||||
}
|
|
||||||
// Here we insert a message in the shared chat type.
|
|
||||||
// This will call the observe function (see line 40)
|
|
||||||
// and reflect the change in the DOM
|
|
||||||
y.share.chat.push([message])
|
|
||||||
this.querySelector('[name=message]').value = ''
|
|
||||||
}
|
|
||||||
// Do not send this form!
|
|
||||||
event.preventDefault()
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
})
|
|
||||||
@@ -1,23 +0,0 @@
|
|||||||
<!DOCTYPE html>
|
|
||||||
<html>
|
|
||||||
<head>
|
|
||||||
</head>
|
|
||||||
<body>
|
|
||||||
<div id="codeMirrorContainer"></div>
|
|
||||||
|
|
||||||
<script src="../bower_components/yjs/y.js"></script>
|
|
||||||
<script src="../bower_components/codemirror/lib/codemirror.js"></script>
|
|
||||||
<script src="../bower_components/codemirror/mode/javascript/javascript.js"></script>
|
|
||||||
<link rel="stylesheet" href="../bower_components/codemirror/lib/codemirror.css">
|
|
||||||
<style>
|
|
||||||
.CodeMirror {
|
|
||||||
position: fixed;
|
|
||||||
top: 0;
|
|
||||||
left: 0;
|
|
||||||
height: 100%;
|
|
||||||
width: 100%;
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
<script src="./index.js"></script>
|
|
||||||
</body>
|
|
||||||
</html>
|
|
||||||
@@ -1,24 +0,0 @@
|
|||||||
/* global Y, CodeMirror */
|
|
||||||
|
|
||||||
// initialize a shared object. This function call returns a promise!
|
|
||||||
Y({
|
|
||||||
db: {
|
|
||||||
name: 'memory'
|
|
||||||
},
|
|
||||||
connector: {
|
|
||||||
name: 'websockets-client',
|
|
||||||
room: 'codemirror-example'
|
|
||||||
},
|
|
||||||
sourceDir: '/bower_components',
|
|
||||||
share: {
|
|
||||||
codemirror: 'Text' // y.share.codemirror is of type Y.Text
|
|
||||||
}
|
|
||||||
}).then(function (y) {
|
|
||||||
window.yCodeMirror = y
|
|
||||||
|
|
||||||
var editor = CodeMirror(document.querySelector('#codeMirrorContainer'), {
|
|
||||||
mode: 'javascript',
|
|
||||||
lineNumbers: true
|
|
||||||
})
|
|
||||||
y.share.codemirror.bindCodeMirror(editor)
|
|
||||||
})
|
|
||||||
@@ -1,19 +0,0 @@
|
|||||||
<!DOCTYPE html>
|
|
||||||
<html>
|
|
||||||
<body>
|
|
||||||
<style>
|
|
||||||
path {
|
|
||||||
fill: none;
|
|
||||||
stroke: blue;
|
|
||||||
stroke-width: 1px;
|
|
||||||
stroke-linejoin: round;
|
|
||||||
stroke-linecap: round;
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
<button type="button" id="clearDrawingCanvas">Clear Drawing</button>
|
|
||||||
<svg id="drawingCanvas" viewbox="0 0 100 100" width="100%"></svg>
|
|
||||||
<script src="../bower_components/yjs/y.js"></script>
|
|
||||||
<script src="../bower_components/d3/d3.js"></script>
|
|
||||||
<script src="./index.js"></script>
|
|
||||||
</body>
|
|
||||||
</html>
|
|
||||||
@@ -1,84 +0,0 @@
|
|||||||
/* globals Y, d3 */
|
|
||||||
'strict mode'
|
|
||||||
|
|
||||||
Y({
|
|
||||||
db: {
|
|
||||||
name: 'memory'
|
|
||||||
},
|
|
||||||
connector: {
|
|
||||||
name: 'websockets-client',
|
|
||||||
room: 'drawing-example'
|
|
||||||
// url: 'localhost:1234'
|
|
||||||
},
|
|
||||||
sourceDir: '/bower_components',
|
|
||||||
share: {
|
|
||||||
drawing: 'Array'
|
|
||||||
}
|
|
||||||
}).then(function (y) {
|
|
||||||
window.yDrawing = y
|
|
||||||
var drawing = y.share.drawing
|
|
||||||
var renderPath = d3.svg.line()
|
|
||||||
.x(function (d) { return d[0] })
|
|
||||||
.y(function (d) { return d[1] })
|
|
||||||
.interpolate('basis')
|
|
||||||
|
|
||||||
var svg = d3.select('#drawingCanvas')
|
|
||||||
.call(d3.behavior.drag()
|
|
||||||
.on('dragstart', dragstart)
|
|
||||||
.on('drag', drag)
|
|
||||||
.on('dragend', dragend))
|
|
||||||
|
|
||||||
// create line from a shared array object and update the line when the array changes
|
|
||||||
function drawLine (yarray) {
|
|
||||||
var line = svg.append('path').datum(yarray.toArray())
|
|
||||||
line.attr('d', renderPath)
|
|
||||||
yarray.observe(function (event) {
|
|
||||||
// we only implement insert events that are appended to the end of the array
|
|
||||||
event.values.forEach(function (value) {
|
|
||||||
line.datum().push(value)
|
|
||||||
})
|
|
||||||
line.attr('d', renderPath)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
// call drawLine every time an array is appended
|
|
||||||
y.share.drawing.observe(function (event) {
|
|
||||||
if (event.type === 'insert') {
|
|
||||||
event.values.forEach(drawLine)
|
|
||||||
} else {
|
|
||||||
// just remove all elements (thats what we do anyway)
|
|
||||||
svg.selectAll('path').remove()
|
|
||||||
}
|
|
||||||
})
|
|
||||||
// draw all existing content
|
|
||||||
for (var i = 0; i < drawing.length; i++) {
|
|
||||||
drawLine(drawing.get(i))
|
|
||||||
}
|
|
||||||
|
|
||||||
// clear canvas on request
|
|
||||||
document.querySelector('#clearDrawingCanvas').onclick = function () {
|
|
||||||
drawing.delete(0, drawing.length)
|
|
||||||
}
|
|
||||||
|
|
||||||
var sharedLine = null
|
|
||||||
function dragstart () {
|
|
||||||
drawing.insert(drawing.length, [Y.Array])
|
|
||||||
sharedLine = drawing.get(drawing.length - 1)
|
|
||||||
}
|
|
||||||
|
|
||||||
// After one dragged event is recognized, we ignore them for 33ms.
|
|
||||||
var ignoreDrag = null
|
|
||||||
function drag () {
|
|
||||||
if (sharedLine != null && ignoreDrag == null) {
|
|
||||||
ignoreDrag = window.setTimeout(function () {
|
|
||||||
ignoreDrag = null
|
|
||||||
}, 33)
|
|
||||||
sharedLine.push([d3.mouse(this)])
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function dragend () {
|
|
||||||
sharedLine = null
|
|
||||||
window.clearTimeout(ignoreDrag)
|
|
||||||
ignoreDrag = null
|
|
||||||
}
|
|
||||||
})
|
|
||||||
@@ -1,23 +0,0 @@
|
|||||||
<!DOCTYPE html>
|
|
||||||
<html>
|
|
||||||
<head>
|
|
||||||
<style type="text/css">
|
|
||||||
.draggable {
|
|
||||||
cursor: move;
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
</head>
|
|
||||||
<body>
|
|
||||||
<svg id="puzzle-example" width="100%" viewBox="0 0 800 800">
|
|
||||||
<g>
|
|
||||||
<path d="M 311.76636,154.23389 C 312.14136,171.85693 318.14087,184.97998 336.13843,184.23047 C 354.13647,183.48047 351.88647,180.48096 354.88599,178.98096 C 357.8855,177.48096 368.38452,170.35693 380.00806,169.98193 C 424.61841,168.54297 419.78296,223.6001 382.25757,223.6001 C 377.75806,223.6001 363.51001,219.10107 356.38599,211.97656 C 349.26196,204.85254 310.64185,207.10254 314.76636,236.34863 C 316.34888,247.5708 324.08374,267.90723 324.84595,286.23486 C 325.29321,296.99414 323.17603,307.00635 321.58911,315.6377 C 360.11353,305.4585 367.73462,304.30518 404.00513,312.83936 C 410.37915,314.33887 436.62573,310.21436 421.25269,290.3418 C 405.87964,270.46924 406.25464,248.34717 417.12817,240.84814 C 428.00171,233.34912 446.74976,228.84961 457.99829,234.09912 C 469.24683,239.34814 484.61987,255.84619 475.24585,271.59424 C 465.87231,287.34229 452.74878,290.7168 456.49829,303.84033 C 460.2478,316.96387 479.74536,320.33838 500.74292,321.83789 C 509.70142,322.47803 527.97192,323.28467 542.10864,320.12939 C 549.91821,318.38672 556.92212,315.89502 562.46753,313.56396 C 561.40796,277.80664 560.84888,245.71729 560.3606,241.97314 C 558.85278,230.41455 542.49536,217.28564 525.86499,223.2251 C 520.61548,225.1001 519.86548,231.84912 505.24243,232.59912 C 444.92798,235.69238 462.06958,143.26709 525.86499,180.48096 C 539.52759,188.45068 575.19409,190.7583 570.10913,156.85889 C 567.85962,141.86035 553.98608,102.86523 553.98608,102.86523 C 553.98608,102.86523 477.23755,111.82227 451.99878,91.991699 C 441.50024,83.74292 444.87476,69.494629 449.37427,61.245605 C 453.87378,52.996582 465.12231,46.622559 464.74731,36.123779 C 463.02563,-12.086426 392.96704,-10.902832 396.5061,36.873535 C 397.25562,46.997314 406.62964,52.621582 410.75415,60.495605 C 420.00757,78.161377 405.50024,96.073486 384.50757,99.490723 C 377.36206,100.65381 349.17505,102.65332 320.39429,102.23486 C 319.677,102.22461 318.95923,102.21143 318.24194,102.19775 C 315.08423,120.9751 311.55688,144.39697 311.76636,154.23389 z " style="fill:#f2c569;stroke:#000000" id="path2502"/>
|
|
||||||
<path d="M 500.74292,321.83789 C 479.74536,320.33838 460.2478,316.96387 456.49829,303.84033 C 452.74878,290.7168 465.87231,287.34229 475.24585,271.59424 C 484.61987,255.84619 469.24683,239.34814 457.99829,234.09912 C 446.74976,228.84961 428.00171,233.34912 417.12817,240.84814 C 406.25464,248.34717 405.87964,270.46924 421.25269,290.3418 C 436.62573,310.21436 410.37915,314.33887 404.00513,312.83936 C 367.73462,304.30518 360.11353,305.4585 321.58911,315.6377 C 320.56372,321.21484 319.75854,326.2207 320.01538,330.46191 C 320.76538,342.83545 329.3894,385.95508 327.8894,392.7041 C 326.3894,399.45312 313.64136,418.20117 297.89331,407.32715 C 282.14526,396.45361 276.52075,393.4541 265.27222,394.5791 C 254.02368,395.70361 239.77563,402.07812 239.77563,419.32568 C 239.77563,436.57373 250.27417,449.69727 268.64673,447.82227 C 287.36353,445.9126 317.92163,423.11035 325.63989,452.69678 C 330.1394,469.94434 330.51392,487.19238 330.1394,498.44092 C 329.95825,503.87646 326.09985,518.06592 322.16089,531.28125 C 353.2854,532.73682 386.47095,531.26611 394.2561,529.93701 C 430.30933,523.78174 429.31909,496.09766 412.62866,477.44385 C 406.25464,470.31934 401.75513,455.32129 405.87964,444.82275 C 414.07056,423.97314 458.8064,422.17773 473.37134,438.82324 C 483.86987,450.82178 475.99585,477.44385 468.49683,482.69287 C 453.52222,493.17529 457.22485,516.83008 473.37134,528.06201 C 504.79126,549.91943 572.35913,535.56152 572.35913,535.56152 C 572.35913,535.56152 567.85962,498.06592 567.48462,471.81934 C 567.10962,445.57275 589.60669,450.07227 593.3562,450.07227 C 597.10571,450.07227 604.22974,455.32129 609.47925,459.4458 C 614.72876,463.57031 618.85327,469.94434 630.85181,470.69434 C 677.43726,473.60596 674.58813,420.7373 631.97632,413.32666 C 623.35229,411.82666 614.72876,416.32617 603.10522,424.57519 C 591.48169,432.82422 577.23315,425.32519 570.10913,417.45117 C 566.07788,412.99561 563.8479,360.16406 562.46753,313.56396 C 556.92212,315.89502 549.91821,318.38672 542.10864,320.12939 C 527.97192,323.28467 509.70142,322.47803 500.74292,321.83789 z " style="fill:#f3f3d6;stroke:#000000" id="path2504"/>
|
|
||||||
<path d="M 240.52563,141.86035 C 257.60327,159.6499 243.94507,188.68799 214.65356,190.22949 C 185.09448,191.78516 164.66675,157.17822 190.28589,136.61621 C 200.49585,128.42139 198.05786,114.12158 179.78296,106.98975 C 154.4187,97.091553 90.54419,107.73975 90.54419,107.73975 C 90.54419,107.73975 100.88794,135.11328 101.41772,168.48242 C 101.79272,192.104 68.796875,189.47949 63.172607,186.85498 C 57.54834,184.23047 45.924805,173.73145 37.675781,173.73145 C -14.411865,173.73145 -10.013184,245.84375 39.925537,232.22412 C 48.174316,229.97461 56.42334,220.97559 68.796875,222.47559 C 81.17041,223.9751 87.544434,232.59912 87.544434,246.09766 C 87.544434,252.51709 87.0354,281.24268 86.340576,312.87012 C 119.15894,313.67676 160.60962,314.46582 170.03442,313.58887 C 186.15698,312.08936 195.90601,301.59033 188.40698,293.3418 C 180.90796,285.09277 156.16089,256.59619 179.03296,239.34814 C 201.90503,222.10059 235.65112,231.84912 239.77563,247.22217 C 243.90015,262.59521 240.52563,273.46924 234.90112,279.09326 C 229.27661,284.71777 210.52905,298.96582 221.40259,308.71484 C 232.27661,318.46338 263.77222,330.83691 302.39282,320.71338 C 309.58862,318.82715 315.92114,317.13525 321.58911,315.6377 C 323.17603,307.00635 325.29321,296.99414 324.84595,286.23486 C 324.08374,267.90723 316.34888,247.5708 314.76636,236.34863 C 310.64185,207.10254 349.26196,204.85254 356.38599,211.97656 C 363.51001,219.10107 377.75806,223.6001 382.25757,223.6001 C 419.78296,223.6001 424.61841,168.54297 380.00806,169.98193 C 368.38452,170.35693 357.8855,177.48096 354.88599,178.98096 C 351.88647,180.48096 354.13647,183.48047 336.13843,184.23047 C 318.14087,184.97998 312.14136,171.85693 311.76636,154.23389 C 311.55688,144.39697 315.08423,120.9751 318.24194,102.19775 C 290.37524,101.67725 262.46069,98.968262 254.39868,97.991211 C 233.38013,95.443359 217.17456,117.53662 240.52563,141.86035 z " style="fill:#bebcdb;stroke:#000000" id="path2506"/>
|
|
||||||
<path d="M 325.63989,452.69678 C 317.92163,423.11035 287.36353,445.9126 268.64673,447.82227 C 250.27417,449.69727 239.77563,436.57373 239.77563,419.32568 C 239.77563,402.07812 254.02368,395.70361 265.27222,394.5791 C 276.52075,393.4541 282.14526,396.45361 297.89331,407.32715 C 313.64136,418.20117 326.3894,399.45313 327.8894,392.7041 C 329.3894,385.95508 320.76538,342.83545 320.01538,330.46191 C 319.75855,326.2207 320.56372,321.21484 321.58911,315.6377 C 315.92114,317.13525 309.58862,318.82715 302.39282,320.71338 C 263.77222,330.83691 232.27661,318.46338 221.40259,308.71484 C 210.52905,298.96582 229.27661,284.71777 234.90112,279.09326 C 240.52563,273.46924 243.90015,262.59521 239.77563,247.22217 C 235.65112,231.84912 201.90503,222.10059 179.03296,239.34814 C 156.16089,256.59619 180.90796,285.09277 188.40698,293.3418 C 195.90601,301.59033 186.15698,312.08936 170.03442,313.58887 C 160.60962,314.46582 119.15894,313.67676 86.340576,312.87012 C 85.573975,347.74561 84.581299,386.15088 83.794922,402.07812 C 82.295166,432.44922 109.29175,422.32568 115.66577,420.82568 C 122.04028,419.32568 126.16479,409.57715 143.03735,408.45215 C 185.9231,405.59326 186.09985,466.69629 144.16235,467.69482 C 128.41431,468.06982 113.79126,451.19678 108.16675,447.44727 C 102.54272,443.69775 87.919433,442.94775 83.794922,457.9458 C 82.01709,464.41113 78.118652,481.65137 78.098144,496.18994 C 78.071045,515.38037 82.295166,531.81201 82.295166,531.81201 C 82.295166,531.81201 105.54224,526.5625 149.41187,526.5625 C 193.28149,526.5625 199.65552,547.93506 194.78101,558.80859 C 189.90649,569.68213 181.28296,568.93213 179.40796,583.18066 C 172.7063,634.11133 253.34106,631.08203 249.14917,584.68018 C 247.96948,571.62354 237.16528,571.66699 232.27661,557.68359 C 222.17944,528.80273 244.64966,523.56299 257.39819,524.68799 C 263.59351,525.23437 290.95679,529.73389 320.75757,531.21582 C 321.22437,531.23877 321.69312,531.25928 322.16089,531.28125 C 326.09985,518.06592 329.95825,503.87646 330.1394,498.44092 C 330.51392,487.19238 330.1394,469.94434 325.63989,452.69678 z " style="fill:#d3ea9d;stroke:#000000" id="path2508"/>
|
|
||||||
</g>
|
|
||||||
</svg>
|
|
||||||
<script src="../bower_components/yjs/y.js"></script>
|
|
||||||
<script src="../bower_components/d3/d3.js"></script>
|
|
||||||
<script src="./index.js"></script>
|
|
||||||
</body>
|
|
||||||
</html>
|
|
||||||
@@ -1,74 +0,0 @@
|
|||||||
/* @flow */
|
|
||||||
/* global Y, d3 */
|
|
||||||
|
|
||||||
// initialize a shared object. This function call returns a promise!
|
|
||||||
Y({
|
|
||||||
db: {
|
|
||||||
name: 'memory'
|
|
||||||
},
|
|
||||||
connector: {
|
|
||||||
name: 'websockets-client',
|
|
||||||
room: 'Puzzle-example'
|
|
||||||
},
|
|
||||||
sourceDir: '/bower_components',
|
|
||||||
share: {
|
|
||||||
piece1: 'Map',
|
|
||||||
piece2: 'Map',
|
|
||||||
piece3: 'Map',
|
|
||||||
piece4: 'Map'
|
|
||||||
}
|
|
||||||
}).then(function (y) {
|
|
||||||
window.yJigsaw = y
|
|
||||||
var origin // mouse start position - translation of piece
|
|
||||||
var drag = d3.behavior.drag()
|
|
||||||
.on('dragstart', function (params) {
|
|
||||||
// get the translation of the element
|
|
||||||
var translation = d3
|
|
||||||
.select(this)
|
|
||||||
.attr('transform')
|
|
||||||
.slice(10, -1)
|
|
||||||
.split(',')
|
|
||||||
.map(Number)
|
|
||||||
// mouse coordinates
|
|
||||||
var mouse = d3.mouse(this.parentNode)
|
|
||||||
origin = {
|
|
||||||
x: mouse[0] - translation[0],
|
|
||||||
y: mouse[1] - translation[1]
|
|
||||||
}
|
|
||||||
})
|
|
||||||
.on('drag', function () {
|
|
||||||
var mouse = d3.mouse(this.parentNode)
|
|
||||||
var x = mouse[0] - origin.x // =^= mouse - mouse at dragstart + translation at dragstart
|
|
||||||
var y = mouse[1] - origin.y
|
|
||||||
d3.select(this).attr('transform', 'translate(' + x + ',' + y + ')')
|
|
||||||
})
|
|
||||||
.on('dragend', function (piece, i) {
|
|
||||||
// save the current translation of the puzzle piece
|
|
||||||
var mouse = d3.mouse(this.parentNode)
|
|
||||||
var x = mouse[0] - origin.x
|
|
||||||
var y = mouse[1] - origin.y
|
|
||||||
piece.set('translation', {x: x, y: y})
|
|
||||||
})
|
|
||||||
|
|
||||||
var data = [y.share.piece1, y.share.piece2, y.share.piece3, y.share.piece4]
|
|
||||||
var pieces = d3.select(document.querySelector('#puzzle-example')).selectAll('path').data(data)
|
|
||||||
|
|
||||||
pieces
|
|
||||||
.classed('draggable', true)
|
|
||||||
.attr('transform', function (piece) {
|
|
||||||
var translation = piece.get('translation') || {x: 0, y: 0}
|
|
||||||
return 'translate(' + translation.x + ',' + translation.y + ')'
|
|
||||||
}).call(drag)
|
|
||||||
|
|
||||||
data.forEach(function (piece) {
|
|
||||||
piece.observe(function () {
|
|
||||||
// whenever a property of a piece changes, update the translation of the pieces
|
|
||||||
pieces
|
|
||||||
.transition()
|
|
||||||
.attr('transform', function (piece) {
|
|
||||||
var translation = piece.get('translation') || {x: 0, y: 0}
|
|
||||||
return 'translate(' + translation.x + ',' + translation.y + ')'
|
|
||||||
})
|
|
||||||
})
|
|
||||||
})
|
|
||||||
})
|
|
||||||
@@ -1,24 +0,0 @@
|
|||||||
<!DOCTYPE html>
|
|
||||||
<html>
|
|
||||||
<head>
|
|
||||||
</head>
|
|
||||||
<body>
|
|
||||||
<div id="monacoContainer"></div>
|
|
||||||
<style>
|
|
||||||
#monacoContainer {
|
|
||||||
position: fixed;
|
|
||||||
top: 0;
|
|
||||||
left: 0;
|
|
||||||
height: 100%;
|
|
||||||
width: 100%;
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
<script src="../bower_components/yjs/y.js"></script>
|
|
||||||
<script src="../bower_components/y-array/y-array.js"></script>
|
|
||||||
<script src="../bower_components/y-text/y-text.js"></script>
|
|
||||||
<script src="../bower_components/y-websockets-client/y-websockets-client.js"></script>
|
|
||||||
<script src="../bower_components/y-memory/y-memory.js"></script>
|
|
||||||
<script src="../node_modules/monaco-editor/min/vs/loader.js"></script>
|
|
||||||
<script src="./index.js"></script>
|
|
||||||
</body>
|
|
||||||
</html>
|
|
||||||
@@ -1,30 +0,0 @@
|
|||||||
/* global Y, monaco */
|
|
||||||
|
|
||||||
require.config({ paths: { 'vs': '../node_modules/monaco-editor/min/vs' } })
|
|
||||||
|
|
||||||
require(['vs/editor/editor.main'], function () {
|
|
||||||
// Initialize a shared object. This function call returns a promise!
|
|
||||||
Y({
|
|
||||||
db: {
|
|
||||||
name: 'memory'
|
|
||||||
},
|
|
||||||
connector: {
|
|
||||||
name: 'websockets-client',
|
|
||||||
room: 'monaco-example'
|
|
||||||
},
|
|
||||||
sourceDir: '/bower_components',
|
|
||||||
share: {
|
|
||||||
monaco: 'Text' // y.share.monaco is of type Y.Text
|
|
||||||
}
|
|
||||||
}).then(function (y) {
|
|
||||||
window.yMonaco = y
|
|
||||||
|
|
||||||
// Create Monaco editor
|
|
||||||
var editor = monaco.editor.create(document.getElementById('monacoContainer'), {
|
|
||||||
language: 'javascript'
|
|
||||||
})
|
|
||||||
|
|
||||||
// Bind to y.share.monaco
|
|
||||||
y.share.monaco.bindMonaco(editor)
|
|
||||||
})
|
|
||||||
})
|
|
||||||
1173
examples/package-lock.json
generated
1173
examples/package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@@ -1,16 +0,0 @@
|
|||||||
{
|
|
||||||
"name": "examples",
|
|
||||||
"version": "0.0.0",
|
|
||||||
"description": "",
|
|
||||||
"author": "Kevin Jahns",
|
|
||||||
"license": "MIT",
|
|
||||||
"dependencies": {
|
|
||||||
"monaco-editor": "^0.8.3"
|
|
||||||
},
|
|
||||||
"devDependencies": {
|
|
||||||
"standard": "^10.0.2"
|
|
||||||
},
|
|
||||||
"standard": {
|
|
||||||
"ignore": ["bower_components"]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,31 +0,0 @@
|
|||||||
<!DOCTYPE html>
|
|
||||||
<html>
|
|
||||||
<head>
|
|
||||||
<!-- quill does not include dist files! We are using the hosted version instead -->
|
|
||||||
<!--link rel="stylesheet" href="../bower_components/quill/dist/quill.snow.css" /-->
|
|
||||||
<link href="https://cdn.quilljs.com/1.0.4/quill.snow.css" rel="stylesheet">
|
|
||||||
<link href="//cdnjs.cloudflare.com/ajax/libs/KaTeX/0.5.1/katex.min.css" rel="stylesheet">
|
|
||||||
<link href="//cdnjs.cloudflare.com/ajax/libs/highlight.js/9.2.0/styles/monokai-sublime.min.css" rel="stylesheet">
|
|
||||||
<style>
|
|
||||||
#quill-container {
|
|
||||||
border: 1px solid gray;
|
|
||||||
box-shadow: 0px 0px 10px gray;
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
</head>
|
|
||||||
<body>
|
|
||||||
<div id="quill-container">
|
|
||||||
<div id="quill">
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<script src="//cdnjs.cloudflare.com/ajax/libs/KaTeX/0.5.1/katex.min.js" type="text/javascript"></script>
|
|
||||||
<script src="//cdnjs.cloudflare.com/ajax/libs/highlight.js/9.2.0/highlight.min.js" type="text/javascript"></script>
|
|
||||||
<script src="https://cdn.quilljs.com/1.0.4/quill.js"></script>
|
|
||||||
<!-- quill does not include dist files! We are using the hosted version instead (see above)
|
|
||||||
<script src="../bower_components/quill/dist/quill.js"></script>
|
|
||||||
-->
|
|
||||||
<script src="../bower_components/yjs/y.js"></script>
|
|
||||||
<script src="./index.js"></script>
|
|
||||||
</body>
|
|
||||||
</html>
|
|
||||||
@@ -1,39 +0,0 @@
|
|||||||
/* global Y, Quill */
|
|
||||||
|
|
||||||
// initialize a shared object. This function call returns a promise!
|
|
||||||
|
|
||||||
Y({
|
|
||||||
db: {
|
|
||||||
name: 'memory'
|
|
||||||
},
|
|
||||||
connector: {
|
|
||||||
name: 'websockets-client',
|
|
||||||
room: 'richtext-example-quill-1.0-test'
|
|
||||||
},
|
|
||||||
sourceDir: '/bower_components',
|
|
||||||
share: {
|
|
||||||
richtext: 'Richtext' // y.share.richtext is of type Y.Richtext
|
|
||||||
}
|
|
||||||
}).then(function (y) {
|
|
||||||
window.yQuill = y
|
|
||||||
|
|
||||||
// create quill element
|
|
||||||
window.quill = new Quill('#quill', {
|
|
||||||
modules: {
|
|
||||||
formula: true,
|
|
||||||
syntax: true,
|
|
||||||
toolbar: [
|
|
||||||
[{ size: ['small', false, 'large', 'huge'] }],
|
|
||||||
['bold', 'italic', 'underline'],
|
|
||||||
[{ color: [] }, { background: [] }], // Snow theme fills in values
|
|
||||||
[{ script: 'sub' }, { script: 'super' }],
|
|
||||||
['link', 'image'],
|
|
||||||
['link', 'code-block'],
|
|
||||||
[{ list: 'ordered' }]
|
|
||||||
]
|
|
||||||
},
|
|
||||||
theme: 'snow'
|
|
||||||
})
|
|
||||||
// bind quill to richtext type
|
|
||||||
y.share.richtext.bind(window.quill)
|
|
||||||
})
|
|
||||||
@@ -1,31 +0,0 @@
|
|||||||
<!DOCTYPE html>
|
|
||||||
<html>
|
|
||||||
<head>
|
|
||||||
<!-- quill does not include dist files! We are using the hosted version instead -->
|
|
||||||
<!--link rel="stylesheet" href="../bower_components/quill/dist/quill.snow.css" /-->
|
|
||||||
<link href="https://cdn.quilljs.com/1.0.4/quill.snow.css" rel="stylesheet">
|
|
||||||
<link href="//cdnjs.cloudflare.com/ajax/libs/KaTeX/0.5.1/katex.min.css" rel="stylesheet">
|
|
||||||
<link href="//cdnjs.cloudflare.com/ajax/libs/highlight.js/9.2.0/styles/monokai-sublime.min.css" rel="stylesheet">
|
|
||||||
<style>
|
|
||||||
#quill-container {
|
|
||||||
border: 1px solid gray;
|
|
||||||
box-shadow: 0px 0px 10px gray;
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
</head>
|
|
||||||
<body>
|
|
||||||
<div id="quill-container">
|
|
||||||
<div id="quill">
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<script src="//cdnjs.cloudflare.com/ajax/libs/KaTeX/0.5.1/katex.min.js" type="text/javascript"></script>
|
|
||||||
<script src="//cdnjs.cloudflare.com/ajax/libs/highlight.js/9.2.0/highlight.min.js" type="text/javascript"></script>
|
|
||||||
<script src="https://cdn.quilljs.com/1.0.4/quill.js"></script>
|
|
||||||
<!-- quill does not include dist files! We are using the hosted version instead (see above)
|
|
||||||
<script src="../bower_components/quill/dist/quill.js"></script>
|
|
||||||
-->
|
|
||||||
<script src="../bower_components/yjs/y.js"></script>
|
|
||||||
<script src="./index.js"></script>
|
|
||||||
</body>
|
|
||||||
</html>
|
|
||||||
@@ -1,49 +0,0 @@
|
|||||||
/* global Y, Quill */
|
|
||||||
|
|
||||||
// register yjs service worker
|
|
||||||
if ('serviceWorker' in navigator) {
|
|
||||||
// Register service worker
|
|
||||||
// it is important to copy yjs-sw-template to the root directory!
|
|
||||||
navigator.serviceWorker.register('./yjs-sw-template.js').then(function (reg) {
|
|
||||||
console.log('Yjs service worker registration succeeded. Scope is ' + reg.scope)
|
|
||||||
}).catch(function (err) {
|
|
||||||
console.error('Yjs service worker registration failed with error ' + err)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
// initialize a shared object. This function call returns a promise!
|
|
||||||
Y({
|
|
||||||
db: {
|
|
||||||
name: 'memory'
|
|
||||||
},
|
|
||||||
connector: {
|
|
||||||
name: 'serviceworker',
|
|
||||||
room: 'ServiceWorkerExample2'
|
|
||||||
},
|
|
||||||
sourceDir: '/bower_components',
|
|
||||||
share: {
|
|
||||||
richtext: 'Richtext' // y.share.richtext is of type Y.Richtext
|
|
||||||
}
|
|
||||||
}).then(function (y) {
|
|
||||||
window.yServiceWorker = y
|
|
||||||
|
|
||||||
// create quill element
|
|
||||||
window.quill = new Quill('#quill', {
|
|
||||||
modules: {
|
|
||||||
formula: true,
|
|
||||||
syntax: true,
|
|
||||||
toolbar: [
|
|
||||||
[{ size: ['small', false, 'large', 'huge'] }],
|
|
||||||
['bold', 'italic', 'underline'],
|
|
||||||
[{ color: [] }, { background: [] }], // Snow theme fills in values
|
|
||||||
[{ script: 'sub' }, { script: 'super' }],
|
|
||||||
['link', 'image'],
|
|
||||||
['link', 'code-block'],
|
|
||||||
[{ list: 'ordered' }]
|
|
||||||
]
|
|
||||||
},
|
|
||||||
theme: 'snow'
|
|
||||||
})
|
|
||||||
// bind quill to richtext type
|
|
||||||
y.share.richtext.bind(window.quill)
|
|
||||||
})
|
|
||||||
@@ -1,22 +0,0 @@
|
|||||||
/* eslint-env worker */
|
|
||||||
|
|
||||||
// copy and modify this file
|
|
||||||
|
|
||||||
self.DBConfig = {
|
|
||||||
name: 'indexeddb'
|
|
||||||
}
|
|
||||||
self.ConnectorConfig = {
|
|
||||||
name: 'websockets-client',
|
|
||||||
// url: '..',
|
|
||||||
options: {
|
|
||||||
jsonp: false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
importScripts(
|
|
||||||
'/bower_components/yjs/y.js',
|
|
||||||
'/bower_components/y-memory/y-memory.js',
|
|
||||||
'/bower_components/y-indexeddb/y-indexeddb.js',
|
|
||||||
'/bower_components/y-websockets-client/y-websockets-client.js',
|
|
||||||
'/bower_components/y-serviceworker/yjs-sw-include.js'
|
|
||||||
)
|
|
||||||
@@ -1,12 +0,0 @@
|
|||||||
<!DOCTYPE html>
|
|
||||||
<html>
|
|
||||||
<body>
|
|
||||||
<textarea style="width:80%;" rows=40 id="textfield" autocomplete="off" autocorrect="off" autocapitalize="off" spellcheck="false"></textarea>
|
|
||||||
<script src="../../y.js"></script>
|
|
||||||
<script src="../../../y-array/y-array.js"></script>
|
|
||||||
<script src="../../../y-text/dist/y-text.js"></script>
|
|
||||||
<script src="../../../y-memory/y-memory.js"></script>
|
|
||||||
<script src="../../../y-websockets-client/dist/y-websockets-client.js"></script>
|
|
||||||
<script src="./index.js"></script>
|
|
||||||
</body>
|
|
||||||
</html>
|
|
||||||
@@ -1,23 +0,0 @@
|
|||||||
/* global Y */
|
|
||||||
|
|
||||||
// initialize a shared object. This function call returns a promise!
|
|
||||||
Y({
|
|
||||||
db: {
|
|
||||||
name: 'memory'
|
|
||||||
},
|
|
||||||
connector: {
|
|
||||||
name: 'websockets-client',
|
|
||||||
room: 'Textarea-example'
|
|
||||||
// url: '127.0.0.1:1234'
|
|
||||||
},
|
|
||||||
sourceDir: '/bower_components',
|
|
||||||
share: {
|
|
||||||
textarea: 'Text' // y.share.textarea is of type Y.Text
|
|
||||||
}
|
|
||||||
}).then(function (y) {
|
|
||||||
window.yTextarea = y
|
|
||||||
|
|
||||||
// bind the textarea to a shared text element
|
|
||||||
y.share.textarea.bind(document.getElementById('textfield'))
|
|
||||||
// thats it..
|
|
||||||
})
|
|
||||||
@@ -1,39 +0,0 @@
|
|||||||
<!DOCTYPE html>
|
|
||||||
<html>
|
|
||||||
</head>
|
|
||||||
<script src="../bower_components/yjs/y.js"></script>
|
|
||||||
<script src="../bower_components/jquery/dist/jquery.min.js"></script>
|
|
||||||
<script src="./index.js"></script>
|
|
||||||
</head>
|
|
||||||
<body>
|
|
||||||
<h1> Shared DOM Example </h1>
|
|
||||||
<p> Use native DOM function or jQuery to manipulate the shared DOM (window.sharedDom). </p>
|
|
||||||
<div class="command">
|
|
||||||
<button type="button">Execute</button>
|
|
||||||
<input type="text" value='$(sharedDom).append("<h3>Appended headline</h3>")' size="40"/>
|
|
||||||
</div>
|
|
||||||
<div class="command">
|
|
||||||
<button type="button">Execute</button>
|
|
||||||
<input type="text" value='$(sharedDom).attr("align","right")' size="40"/>
|
|
||||||
</div>
|
|
||||||
<div class="command">
|
|
||||||
<button type="button">Execute</button>
|
|
||||||
<input type="text" value='$(sharedDom).attr("style","color:blue;")' size="40"/>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<script>
|
|
||||||
var commands = document.querySelectorAll(".command");
|
|
||||||
Array.prototype.forEach.call(document.querySelectorAll('.command'), function (command) {
|
|
||||||
var execute = function(){
|
|
||||||
eval(command.querySelector("input").value);
|
|
||||||
}
|
|
||||||
command.querySelector("button").onclick = execute
|
|
||||||
$(command.querySelector("input")).keyup(function (e) {
|
|
||||||
if (e.keyCode == 13) {
|
|
||||||
execute()
|
|
||||||
}
|
|
||||||
})
|
|
||||||
})
|
|
||||||
</script>
|
|
||||||
</body>
|
|
||||||
</html>
|
|
||||||
@@ -1,21 +0,0 @@
|
|||||||
/* global Y */
|
|
||||||
|
|
||||||
// initialize a shared object. This function call returns a promise!
|
|
||||||
Y({
|
|
||||||
db: {
|
|
||||||
name: 'memory'
|
|
||||||
},
|
|
||||||
connector: {
|
|
||||||
name: 'websockets-client',
|
|
||||||
room: 'Xml-example'
|
|
||||||
},
|
|
||||||
sourceDir: '/bower_components',
|
|
||||||
share: {
|
|
||||||
xml: 'Xml("p")' // y.share.xml is of type Y.Xml with tagname "p"
|
|
||||||
}
|
|
||||||
}).then(function (y) {
|
|
||||||
window.yXml = y
|
|
||||||
// bind xml type to a dom, and put it in body
|
|
||||||
window.sharedDom = y.share.xml.getDom()
|
|
||||||
document.body.appendChild(window.sharedDom)
|
|
||||||
})
|
|
||||||
202
gulpfile.helper.js
Normal file
202
gulpfile.helper.js
Normal file
@@ -0,0 +1,202 @@
|
|||||||
|
|
||||||
|
var $ = require('gulp-load-plugins')()
|
||||||
|
var minimist = require('minimist')
|
||||||
|
var browserify = require('browserify')
|
||||||
|
var source = require('vinyl-source-stream')
|
||||||
|
var buffer = require('vinyl-buffer')
|
||||||
|
|
||||||
|
module.exports = function (gulp, helperOptions) {
|
||||||
|
var runSequence = require('run-sequence').use(gulp)
|
||||||
|
var options = minimist(process.argv.slice(2), {
|
||||||
|
string: ['modulename', 'export', 'name', 'port', 'testfiles', 'es6'],
|
||||||
|
default: {
|
||||||
|
modulename: helperOptions.moduleName,
|
||||||
|
targetName: helperOptions.targetName,
|
||||||
|
export: 'ignore',
|
||||||
|
port: '8888',
|
||||||
|
testfiles: '**/*.spec.js',
|
||||||
|
es6: false,
|
||||||
|
browserify: helperOptions.browserify != null ? helperOptions.browserify : false,
|
||||||
|
includeRuntime: helperOptions.includeRuntime || false,
|
||||||
|
debug: false
|
||||||
|
}
|
||||||
|
})
|
||||||
|
if (options.es6 !== false) {
|
||||||
|
options.es6 = true
|
||||||
|
}
|
||||||
|
var files = {
|
||||||
|
dist: helperOptions.entry,
|
||||||
|
specs: helperOptions.specs,
|
||||||
|
src: './src/**/*.js'
|
||||||
|
}
|
||||||
|
|
||||||
|
if (options.includeRuntime) {
|
||||||
|
files.distEs5 = ['node_modules/regenerator/runtime.js', files.dist]
|
||||||
|
} else {
|
||||||
|
files.distEs5 = [files.dist]
|
||||||
|
}
|
||||||
|
|
||||||
|
gulp.task('dist:es5', function () {
|
||||||
|
var babelOptions = {
|
||||||
|
presets: ['es2015']
|
||||||
|
}
|
||||||
|
return (browserify({
|
||||||
|
entries: files.distEs5,
|
||||||
|
debug: true
|
||||||
|
}).transform('babelify', babelOptions)
|
||||||
|
.bundle()
|
||||||
|
.pipe(source(options.targetName))
|
||||||
|
.pipe(buffer())
|
||||||
|
.pipe($.sourcemaps.init({loadMaps: true}))
|
||||||
|
.pipe($.if(!options.debug, $.uglify().on('error', function (e) {
|
||||||
|
console.log('\x07', e.message, JSON.stringify(e)); return this.end()
|
||||||
|
})))
|
||||||
|
.pipe($.sourcemaps.write('.'))
|
||||||
|
.pipe(gulp.dest('./dist/')))
|
||||||
|
})
|
||||||
|
|
||||||
|
gulp.task('dist:es6', function () {
|
||||||
|
return (browserify({
|
||||||
|
entries: files.dist,
|
||||||
|
debug: true
|
||||||
|
}).bundle()
|
||||||
|
.pipe(source(options.targetName))
|
||||||
|
.pipe(buffer())
|
||||||
|
.pipe($.sourcemaps.init({loadMaps: true}))
|
||||||
|
// .pipe($.uglify()) -- generators not yet supported see #448
|
||||||
|
.pipe($.rename({
|
||||||
|
extname: '.es6'
|
||||||
|
}))
|
||||||
|
.pipe($.sourcemaps.write('.'))
|
||||||
|
|
||||||
|
.pipe(gulp.dest('./dist/')))
|
||||||
|
})
|
||||||
|
|
||||||
|
gulp.task('dist', ['dist:es6', 'dist:es5'])
|
||||||
|
|
||||||
|
gulp.task('watch:dist', function (cb) {
|
||||||
|
options.debug = true
|
||||||
|
gulp.src(['./README.md'])
|
||||||
|
.pipe($.watch('./README.md'))
|
||||||
|
.pipe(gulp.dest('./dist/'))
|
||||||
|
runSequence('dist', function () {
|
||||||
|
gulp.watch(files.src.concat('./README.md'), ['dist'])
|
||||||
|
cb()
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
gulp.task('dev:node', ['test'], function () {
|
||||||
|
gulp.watch(files.src, ['test'])
|
||||||
|
})
|
||||||
|
|
||||||
|
gulp.task('spec-build', function () {
|
||||||
|
var browserify = require('browserify')
|
||||||
|
var source = require('vinyl-source-stream')
|
||||||
|
var buffer = require('vinyl-buffer')
|
||||||
|
|
||||||
|
return browserify({
|
||||||
|
entries: files.specs, // .concat(files.distEs5),
|
||||||
|
debug: true
|
||||||
|
})// .transform('babelify', { presets: ['es2015'] })
|
||||||
|
.bundle()
|
||||||
|
.pipe(source('specs.js'))
|
||||||
|
.pipe(buffer())
|
||||||
|
// .pipe($.sourcemaps.init({loadMaps: true}))
|
||||||
|
// .pipe($.sourcemaps.write('.'))
|
||||||
|
.pipe(gulp.dest('./build/'))
|
||||||
|
})
|
||||||
|
|
||||||
|
gulp.task('dev:browser', ['spec-build'], function () {
|
||||||
|
gulp.watch(files.src, ['spec-build'])
|
||||||
|
return gulp.src('./build/specs.js')
|
||||||
|
.pipe($.jasmineBrowser.specRunner())
|
||||||
|
.pipe($.jasmineBrowser.server({port: options.port}))
|
||||||
|
})
|
||||||
|
|
||||||
|
gulp.task('test', function () {
|
||||||
|
return gulp.src(files.specs)
|
||||||
|
.pipe($.jasmine({
|
||||||
|
verbose: true,
|
||||||
|
includeStuckTrace: true
|
||||||
|
}))
|
||||||
|
})
|
||||||
|
|
||||||
|
gulp.task('updateSubmodule', function () {
|
||||||
|
return gulp.src('./package.json', {read: false})
|
||||||
|
.pipe($.shell([
|
||||||
|
'git submodule update --init',
|
||||||
|
'cd dist && git pull origin dist'
|
||||||
|
]))
|
||||||
|
})
|
||||||
|
|
||||||
|
gulp.task('bump', function (cb) {
|
||||||
|
gulp.src(['./package.json', './bower.json', './dist/bower.json'], {base: '.'})
|
||||||
|
.pipe($.prompt.prompt({
|
||||||
|
type: 'checkbox',
|
||||||
|
name: 'bump',
|
||||||
|
message: 'What type of bump would you like to do?',
|
||||||
|
choices: ['patch', 'minor', 'major']
|
||||||
|
}, function (res) {
|
||||||
|
if (res.bump.length === 0) {
|
||||||
|
console.info('You have to select a bump type. Now I\'m going to use "patch" as bump type..')
|
||||||
|
}
|
||||||
|
var bumptype = res.bump[0]
|
||||||
|
if (bumptype === 'major') {
|
||||||
|
runSequence('bump_major', cb)
|
||||||
|
} else if (bumptype === 'minor') {
|
||||||
|
runSequence('bump_minor', cb)
|
||||||
|
} else {
|
||||||
|
runSequence('bump_patch', cb)
|
||||||
|
}
|
||||||
|
}))
|
||||||
|
})
|
||||||
|
gulp.task('bump_patch', function () {
|
||||||
|
return gulp.src(['./package.json', './bower.json', './dist/bower.json'], {base: '.'})
|
||||||
|
.pipe($.bump({type: 'patch'}))
|
||||||
|
.pipe(gulp.dest('./'))
|
||||||
|
})
|
||||||
|
gulp.task('bump_minor', function () {
|
||||||
|
return gulp.src(['./package.json', './bower.json', './dist/bower.json'], {base: '.'})
|
||||||
|
.pipe($.bump({type: 'minor'}))
|
||||||
|
.pipe(gulp.dest('./'))
|
||||||
|
})
|
||||||
|
gulp.task('bump_major', function () {
|
||||||
|
return gulp.src(['./package.json', './bower.json', './dist/bower.json'], {base: '.'})
|
||||||
|
.pipe($.bump({type: 'major'}))
|
||||||
|
.pipe(gulp.dest('./'))
|
||||||
|
})
|
||||||
|
|
||||||
|
gulp.task('publish_commits', function () {
|
||||||
|
return gulp.src('./package.json')
|
||||||
|
.pipe($.prompt.confirm({
|
||||||
|
message: 'Are you sure you want to publish this release?',
|
||||||
|
default: false
|
||||||
|
}))
|
||||||
|
.pipe($.shell([
|
||||||
|
'cp README.md dist',
|
||||||
|
'standard',
|
||||||
|
'echo "Deploying version <%= getVersion(file.path) %>"',
|
||||||
|
'git pull',
|
||||||
|
'cd ./dist/ && git add -A',
|
||||||
|
'cd ./dist/ && git commit -am "Deploy <%= getVersion(file.path) %>" -n',
|
||||||
|
'cd ./dist/ && git push origin HEAD:dist',
|
||||||
|
'cd ./dist/ && git tag -a v<%= getVersion(file.path) %> -m "Release <%= getVersion(file.path) %>"',
|
||||||
|
'cd ./dist/ && git push origin --tags',
|
||||||
|
'git commit -am "Release <%= getVersion(file.path) %>" -n',
|
||||||
|
'git push',
|
||||||
|
'npm publish',
|
||||||
|
'echo Finished'
|
||||||
|
], {
|
||||||
|
templateData: {
|
||||||
|
getVersion: function () {
|
||||||
|
return JSON.parse(String.fromCharCode.apply(null, this.file._contents)).version
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}))
|
||||||
|
})
|
||||||
|
|
||||||
|
gulp.task('publish', function (cb) {
|
||||||
|
/* TODO: include 'test',*/
|
||||||
|
runSequence('updateSubmodule', 'bump', 'dist', 'publish_commits', cb)
|
||||||
|
})
|
||||||
|
}
|
||||||
104
gulpfile.js
Normal file
104
gulpfile.js
Normal file
@@ -0,0 +1,104 @@
|
|||||||
|
/* eslint-env node */
|
||||||
|
|
||||||
|
/** Gulp Commands
|
||||||
|
|
||||||
|
gulp command*
|
||||||
|
[--export ModuleType]
|
||||||
|
[--name ModuleName]
|
||||||
|
[--testport TestPort]
|
||||||
|
[--testfiles TestFiles]
|
||||||
|
|
||||||
|
Module name (ModuleName):
|
||||||
|
Compile this to "y.js" (default)
|
||||||
|
|
||||||
|
Supported module types (ModuleType):
|
||||||
|
- amd
|
||||||
|
- amdStrict
|
||||||
|
- common
|
||||||
|
- commonStrict
|
||||||
|
- ignore (default)
|
||||||
|
- system
|
||||||
|
- umd
|
||||||
|
- umdStrict
|
||||||
|
|
||||||
|
Test port (TestPort):
|
||||||
|
Serve the specs on port 8888 (default)
|
||||||
|
|
||||||
|
Test files (TestFiles):
|
||||||
|
Specify which specs to use!
|
||||||
|
|
||||||
|
Commands:
|
||||||
|
- build:deploy
|
||||||
|
Build this library for deployment (es6->es5, minified)
|
||||||
|
- dev:browser
|
||||||
|
Watch the ./src directory.
|
||||||
|
Builds the library on changes.
|
||||||
|
Starts an http-server and serves the test suite on http://127.0.0.1:8888.
|
||||||
|
- dev:node
|
||||||
|
Watch the ./src directory.
|
||||||
|
Builds and specs the library on changes.
|
||||||
|
Usefull to run with node-inspector.
|
||||||
|
`node-debug $(which gulp) dev:node
|
||||||
|
- test:
|
||||||
|
Test this library
|
||||||
|
*/
|
||||||
|
|
||||||
|
var gulp = require('gulp')
|
||||||
|
var $ = require('gulp-load-plugins')()
|
||||||
|
var runSequence = require('run-sequence').use(gulp)
|
||||||
|
|
||||||
|
require('./gulpfile.helper.js')(gulp, {
|
||||||
|
polyfills: [],
|
||||||
|
entry: './src/y.js',
|
||||||
|
targetName: 'y.js',
|
||||||
|
moduleName: 'yjs',
|
||||||
|
includeRuntime: true,
|
||||||
|
specs: [
|
||||||
|
'./src/Database.spec.js',
|
||||||
|
'../y-array/src/Array.spec.js',
|
||||||
|
'../y-map/src/Map.spec.js'
|
||||||
|
]
|
||||||
|
})
|
||||||
|
|
||||||
|
gulp.task('dev:examples', ['watch:dist'], function () {
|
||||||
|
// watch all distfiles and copy them to bower_components
|
||||||
|
var distfiles = ['./dist/*.{js,es6}', './dist/*.{js,es6}.map', '../y-*/dist/*.{js,es6}', '../y-*/dist/*.{js,es6}.map']
|
||||||
|
gulp.src(distfiles)
|
||||||
|
.pipe($.watch(distfiles))
|
||||||
|
.pipe($.rename(function (path) {
|
||||||
|
var dir = path.dirname.split(/[\\\/]/)[0]
|
||||||
|
console.log(JSON.stringify(path))
|
||||||
|
path.dirname = dir === '.' ? 'yjs' : dir
|
||||||
|
}))
|
||||||
|
.pipe(gulp.dest('./dist/Examples/bower_components/'))
|
||||||
|
|
||||||
|
return $.serve('dist/Examples/')()
|
||||||
|
})
|
||||||
|
|
||||||
|
gulp.task('default', ['updateSubmodule'], function (cb) {
|
||||||
|
gulp.src('package.json')
|
||||||
|
.pipe($.prompt.prompt({
|
||||||
|
type: 'checkbox',
|
||||||
|
name: 'tasks',
|
||||||
|
message: 'Which tasks would you like to run?',
|
||||||
|
choices: [
|
||||||
|
'test Test this project',
|
||||||
|
'dev:examples Serve the examples directory in ./dist/',
|
||||||
|
'dev:browser Watch files & serve the testsuite for the browser',
|
||||||
|
'dev:nodejs Watch filse & test this project with nodejs',
|
||||||
|
'bump Bump the current state of the project',
|
||||||
|
'publish Publish this project. Creates a github tag',
|
||||||
|
'dist Build the distribution files'
|
||||||
|
]
|
||||||
|
}, function (res) {
|
||||||
|
var tasks = res.tasks.map(function (task) {
|
||||||
|
return task.split(' ')[0]
|
||||||
|
})
|
||||||
|
if (tasks.length > 0) {
|
||||||
|
console.info('gulp ' + tasks.join(' '))
|
||||||
|
runSequence(tasks, cb)
|
||||||
|
} else {
|
||||||
|
console.info('Ok, .. goodbye')
|
||||||
|
}
|
||||||
|
}))
|
||||||
|
})
|
||||||
3085
package-lock.json
generated
3085
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
81
package.json
81
package.json
@@ -1,24 +1,24 @@
|
|||||||
{
|
{
|
||||||
"name": "yjs",
|
"name": "yjs",
|
||||||
"version": "13.0.0-4",
|
"version": "11.2.6",
|
||||||
"description": "A framework for real-time p2p shared editing on any data",
|
"description": "A framework for real-time p2p shared editing on arbitrary complex data types",
|
||||||
"main": "./y.node.js",
|
"main": "./src/y.js",
|
||||||
"browser": "./y.js",
|
|
||||||
"module": "./src/y.js",
|
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"test": "npm run lint",
|
"test": "node --harmony ./node_modules/.bin/gulp test",
|
||||||
"lint": "standard",
|
"lint": "./node_modules/.bin/standard"
|
||||||
"dist": "rollup -c rollup.browser.js; rollup -c rollup.node.js",
|
|
||||||
"postversion": "npm run dist",
|
|
||||||
"postpublish": "tag-dist-files --overwrite-existing-tag"
|
|
||||||
},
|
},
|
||||||
"files": [
|
"pre-commit": [
|
||||||
"y.*"
|
"lint",
|
||||||
|
"test"
|
||||||
],
|
],
|
||||||
"standard": {
|
"standard": {
|
||||||
|
"parser": "babel-eslint",
|
||||||
"ignore": [
|
"ignore": [
|
||||||
"/y.js",
|
"build/**",
|
||||||
"/y.js.map"
|
"dist/**",
|
||||||
|
"declarations/**",
|
||||||
|
"./y.js",
|
||||||
|
"./y.js.map"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"repository": {
|
"repository": {
|
||||||
@@ -42,25 +42,38 @@
|
|||||||
},
|
},
|
||||||
"homepage": "http://y-js.org",
|
"homepage": "http://y-js.org",
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"babel-cli": "^6.24.1",
|
"babel-eslint": "^5.0.0-beta6",
|
||||||
"babel-plugin-external-helpers": "^6.22.0",
|
"babel-plugin-transform-runtime": "^6.1.18",
|
||||||
"babel-plugin-transform-regenerator": "^6.24.1",
|
"babel-preset-es2015": "^6.1.18",
|
||||||
"babel-plugin-transform-runtime": "^6.23.0",
|
"babelify": "^7.2.0",
|
||||||
"babel-preset-latest": "^6.24.1",
|
"browserify": "^12.0.1",
|
||||||
"chance": "^1.0.9",
|
"eslint": "^1.10.2",
|
||||||
"concurrently": "^3.4.0",
|
"gulp": "^3.9.0",
|
||||||
"rollup-plugin-babel": "^2.7.1",
|
"gulp-bump": "^1.0.0",
|
||||||
"rollup-plugin-commonjs": "^8.0.2",
|
"gulp-concat": "^2.6.0",
|
||||||
"rollup-plugin-inject": "^2.0.0",
|
"gulp-filter": "^3.0.1",
|
||||||
"rollup-plugin-multi-entry": "^2.0.1",
|
"gulp-git": "^1.6.0",
|
||||||
"rollup-plugin-node-resolve": "^3.0.0",
|
"gulp-if": "^2.0.0",
|
||||||
"rollup-plugin-uglify": "^1.0.2",
|
"gulp-jasmine": "^2.0.1",
|
||||||
"rollup-regenerator-runtime": "^6.23.1",
|
"gulp-jasmine-browser": "^0.2.3",
|
||||||
"rollup-watch": "^3.2.2",
|
"gulp-load-plugins": "^1.0.0",
|
||||||
"standard": "^10.0.2",
|
"gulp-prompt": "^0.1.2",
|
||||||
"tag-dist-files": "^0.1.6"
|
"gulp-rename": "^1.2.2",
|
||||||
|
"gulp-serve": "^1.2.0",
|
||||||
|
"gulp-shell": "^0.5.1",
|
||||||
|
"gulp-sourcemaps": "^1.5.2",
|
||||||
|
"gulp-tag-version": "^1.3.0",
|
||||||
|
"gulp-uglify": "1.4.*",
|
||||||
|
"gulp-util": "^3.0.6",
|
||||||
|
"gulp-watch": "^4.3.5",
|
||||||
|
"minimist": "^1.2.0",
|
||||||
|
"pre-commit": "^1.1.1",
|
||||||
|
"regenerator": "^0.8.42",
|
||||||
|
"run-sequence": "^1.1.4",
|
||||||
|
"seedrandom": "^2.4.2",
|
||||||
|
"standard": "^5.2.2",
|
||||||
|
"vinyl-buffer": "^1.0.0",
|
||||||
|
"vinyl-source-stream": "^1.1.0"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {}
|
||||||
"debug": "^2.6.8"
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,47 +0,0 @@
|
|||||||
import inject from 'rollup-plugin-inject'
|
|
||||||
import babel from 'rollup-plugin-babel'
|
|
||||||
import uglify from 'rollup-plugin-uglify'
|
|
||||||
import nodeResolve from 'rollup-plugin-node-resolve'
|
|
||||||
import commonjs from 'rollup-plugin-commonjs'
|
|
||||||
var pkg = require('./package.json')
|
|
||||||
|
|
||||||
export default {
|
|
||||||
entry: 'src/y.js',
|
|
||||||
moduleName: 'Y',
|
|
||||||
format: 'umd',
|
|
||||||
plugins: [
|
|
||||||
nodeResolve({
|
|
||||||
main: true,
|
|
||||||
module: true,
|
|
||||||
browser: true
|
|
||||||
}),
|
|
||||||
commonjs(),
|
|
||||||
babel({
|
|
||||||
runtimeHelpers: true
|
|
||||||
}),
|
|
||||||
inject({
|
|
||||||
regeneratorRuntime: 'regenerator-runtime'
|
|
||||||
}),
|
|
||||||
uglify({
|
|
||||||
output: {
|
|
||||||
comments: function (node, comment) {
|
|
||||||
var text = comment.value
|
|
||||||
var type = comment.type
|
|
||||||
if (type === 'comment2') {
|
|
||||||
// multiline comment
|
|
||||||
return /@license/i.test(text)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
})
|
|
||||||
],
|
|
||||||
dest: 'y.js',
|
|
||||||
sourceMap: true,
|
|
||||||
banner: `
|
|
||||||
/**
|
|
||||||
* ${pkg.name} - ${pkg.description}
|
|
||||||
* @version v${pkg.version}
|
|
||||||
* @license ${pkg.license}
|
|
||||||
*/
|
|
||||||
`
|
|
||||||
}
|
|
||||||
@@ -1,26 +0,0 @@
|
|||||||
import nodeResolve from 'rollup-plugin-node-resolve'
|
|
||||||
import commonjs from 'rollup-plugin-commonjs'
|
|
||||||
var pkg = require('./package.json')
|
|
||||||
|
|
||||||
export default {
|
|
||||||
entry: 'src/y.js',
|
|
||||||
moduleName: 'Y',
|
|
||||||
format: 'umd',
|
|
||||||
plugins: [
|
|
||||||
nodeResolve({
|
|
||||||
main: true,
|
|
||||||
module: true,
|
|
||||||
browser: true
|
|
||||||
}),
|
|
||||||
commonjs()
|
|
||||||
],
|
|
||||||
dest: 'y.node.js',
|
|
||||||
sourceMap: true,
|
|
||||||
banner: `
|
|
||||||
/**
|
|
||||||
* ${pkg.name} - ${pkg.description}
|
|
||||||
* @version v${pkg.version}
|
|
||||||
* @license ${pkg.license}
|
|
||||||
*/
|
|
||||||
`
|
|
||||||
}
|
|
||||||
@@ -1,20 +0,0 @@
|
|||||||
import nodeResolve from 'rollup-plugin-node-resolve'
|
|
||||||
import commonjs from 'rollup-plugin-commonjs'
|
|
||||||
import multiEntry from 'rollup-plugin-multi-entry'
|
|
||||||
|
|
||||||
export default {
|
|
||||||
entry: 'tests/*.js',
|
|
||||||
moduleName: 'y-array-tests',
|
|
||||||
format: 'umd',
|
|
||||||
plugins: [
|
|
||||||
nodeResolve({
|
|
||||||
main: true,
|
|
||||||
module: true,
|
|
||||||
browser: true
|
|
||||||
}),
|
|
||||||
commonjs(),
|
|
||||||
multiEntry()
|
|
||||||
],
|
|
||||||
dest: 'y-array.test.js',
|
|
||||||
sourceMap: true
|
|
||||||
}
|
|
||||||
313
src/Connector.js
313
src/Connector.js
@@ -1,10 +1,7 @@
|
|||||||
/* @flow */
|
/* @flow */
|
||||||
'use strict'
|
'use strict'
|
||||||
|
|
||||||
function canRead (auth) { return auth === 'read' || auth === 'write' }
|
module.exports = function (Y/* :any */) {
|
||||||
function canWrite (auth) { return auth === 'write' }
|
|
||||||
|
|
||||||
export default function extendConnector (Y/* :any */) {
|
|
||||||
class AbstractConnector {
|
class AbstractConnector {
|
||||||
/* ::
|
/* ::
|
||||||
y: YConfig;
|
y: YConfig;
|
||||||
@@ -17,6 +14,7 @@ export default function extendConnector (Y/* :any */) {
|
|||||||
syncingClients: Array<UserId>;
|
syncingClients: Array<UserId>;
|
||||||
forwardToSyncingClients: boolean;
|
forwardToSyncingClients: boolean;
|
||||||
debug: boolean;
|
debug: boolean;
|
||||||
|
broadcastedHB: boolean;
|
||||||
syncStep2: Promise;
|
syncStep2: Promise;
|
||||||
userId: UserId;
|
userId: UserId;
|
||||||
send: Function;
|
send: Function;
|
||||||
@@ -35,11 +33,6 @@ export default function extendConnector (Y/* :any */) {
|
|||||||
if (opts == null) {
|
if (opts == null) {
|
||||||
opts = {}
|
opts = {}
|
||||||
}
|
}
|
||||||
// Prefer to receive untransformed operations. This does only work if
|
|
||||||
// this client receives operations from only one other client.
|
|
||||||
// In particular, this does not work with y-webrtc.
|
|
||||||
// It will work with y-websockets-client
|
|
||||||
this.preferUntransformed = opts.preferUntransformed || false
|
|
||||||
if (opts.role == null || opts.role === 'master') {
|
if (opts.role == null || opts.role === 'master') {
|
||||||
this.role = 'master'
|
this.role = 'master'
|
||||||
} else if (opts.role === 'slave') {
|
} else if (opts.role === 'slave') {
|
||||||
@@ -47,8 +40,6 @@ export default function extendConnector (Y/* :any */) {
|
|||||||
} else {
|
} else {
|
||||||
throw new Error("Role must be either 'master' or 'slave'!")
|
throw new Error("Role must be either 'master' or 'slave'!")
|
||||||
}
|
}
|
||||||
this.log = Y.debug('y:connector')
|
|
||||||
this.logMessage = Y.debug('y:connector-message')
|
|
||||||
this.y.db.forwardAppliedOperations = opts.forwardAppliedOperations || false
|
this.y.db.forwardAppliedOperations = opts.forwardAppliedOperations || false
|
||||||
this.role = opts.role
|
this.role = opts.role
|
||||||
this.connections = {}
|
this.connections = {}
|
||||||
@@ -59,49 +50,34 @@ export default function extendConnector (Y/* :any */) {
|
|||||||
this.syncingClients = []
|
this.syncingClients = []
|
||||||
this.forwardToSyncingClients = opts.forwardToSyncingClients !== false
|
this.forwardToSyncingClients = opts.forwardToSyncingClients !== false
|
||||||
this.debug = opts.debug === true
|
this.debug = opts.debug === true
|
||||||
|
this.broadcastedHB = false
|
||||||
|
this.syncStep2 = Promise.resolve()
|
||||||
this.broadcastOpBuffer = []
|
this.broadcastOpBuffer = []
|
||||||
this.protocolVersion = 11
|
this.protocolVersion = 11
|
||||||
this.authInfo = opts.auth || null
|
|
||||||
this.checkAuth = opts.checkAuth || function () { return Promise.resolve('write') } // default is everyone has write access
|
|
||||||
if (opts.generateUserId !== false) {
|
|
||||||
this.setUserId(Y.utils.generateGuid())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
resetAuth (auth) {
|
|
||||||
if (this.authInfo !== auth) {
|
|
||||||
this.authInfo = auth
|
|
||||||
this.broadcast({
|
|
||||||
type: 'auth',
|
|
||||||
auth: this.authInfo
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
reconnect () {
|
reconnect () {
|
||||||
this.log('reconnecting..')
|
|
||||||
return this.y.db.startGarbageCollector()
|
|
||||||
}
|
}
|
||||||
disconnect () {
|
disconnect () {
|
||||||
this.log('discronnecting..')
|
|
||||||
this.connections = {}
|
this.connections = {}
|
||||||
this.isSynced = false
|
this.isSynced = false
|
||||||
this.currentSyncTarget = null
|
this.currentSyncTarget = null
|
||||||
|
this.broadcastedHB = false
|
||||||
this.syncingClients = []
|
this.syncingClients = []
|
||||||
this.whenSyncedListeners = []
|
this.whenSyncedListeners = []
|
||||||
this.y.db.stopGarbageCollector()
|
return this.y.db.stopGarbageCollector()
|
||||||
return this.y.db.whenTransactionsFinished()
|
|
||||||
}
|
}
|
||||||
repair () {
|
repair () {
|
||||||
this.log('Repairing the state of Yjs. This can happen if messages get lost, and Yjs detects that something is wrong. If this happens often, please report an issue here: https://github.com/y-js/yjs/issues')
|
console.info('Repairing the state of Yjs. This can happen if messages get lost, and Yjs detects that something is wrong. If this happens often, please report an issue here: https://github.com/y-js/yjs/issues')
|
||||||
for (var name in this.connections) {
|
for (var name in this.connections) {
|
||||||
this.connections[name].isSynced = false
|
this.connections[name].isSynced = false
|
||||||
}
|
}
|
||||||
this.isSynced = false
|
this.isSynced = false
|
||||||
this.currentSyncTarget = null
|
this.currentSyncTarget = null
|
||||||
|
this.broadcastedHB = false
|
||||||
this.findNextSyncTarget()
|
this.findNextSyncTarget()
|
||||||
}
|
}
|
||||||
setUserId (userId) {
|
setUserId (userId) {
|
||||||
if (this.userId == null) {
|
if (this.userId == null) {
|
||||||
this.log('Set userId to "%s"', userId)
|
|
||||||
this.userId = userId
|
this.userId = userId
|
||||||
return this.y.db.setUserId(userId)
|
return this.y.db.setUserId(userId)
|
||||||
} else {
|
} else {
|
||||||
@@ -111,12 +87,8 @@ export default function extendConnector (Y/* :any */) {
|
|||||||
onUserEvent (f) {
|
onUserEvent (f) {
|
||||||
this.userEventListeners.push(f)
|
this.userEventListeners.push(f)
|
||||||
}
|
}
|
||||||
removeUserEventListener (f) {
|
|
||||||
this.userEventListeners = this.userEventListeners.filter(g => f !== g)
|
|
||||||
}
|
|
||||||
userLeft (user) {
|
userLeft (user) {
|
||||||
if (this.connections[user] != null) {
|
if (this.connections[user] != null) {
|
||||||
this.log('User left: %s', user)
|
|
||||||
delete this.connections[user]
|
delete this.connections[user]
|
||||||
if (user === this.currentSyncTarget) {
|
if (user === this.currentSyncTarget) {
|
||||||
this.currentSyncTarget = null
|
this.currentSyncTarget = null
|
||||||
@@ -140,14 +112,10 @@ export default function extendConnector (Y/* :any */) {
|
|||||||
if (this.connections[user] != null) {
|
if (this.connections[user] != null) {
|
||||||
throw new Error('This user already joined!')
|
throw new Error('This user already joined!')
|
||||||
}
|
}
|
||||||
this.log('User joined: %s', user)
|
|
||||||
this.connections[user] = {
|
this.connections[user] = {
|
||||||
isSynced: false,
|
isSynced: false,
|
||||||
role: role
|
role: role
|
||||||
}
|
}
|
||||||
let defer = {}
|
|
||||||
defer.promise = new Promise(function (resolve) { defer.resolve = resolve })
|
|
||||||
this.connections[user].syncStep2 = defer
|
|
||||||
for (var f of this.userEventListeners) {
|
for (var f of this.userEventListeners) {
|
||||||
f({
|
f({
|
||||||
action: 'userJoined',
|
action: 'userJoined',
|
||||||
@@ -168,8 +136,13 @@ export default function extendConnector (Y/* :any */) {
|
|||||||
this.whenSyncedListeners.push(f)
|
this.whenSyncedListeners.push(f)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
/*
|
||||||
|
|
||||||
|
returns false, if there is no sync target
|
||||||
|
true otherwise
|
||||||
|
*/
|
||||||
findNextSyncTarget () {
|
findNextSyncTarget () {
|
||||||
if (this.currentSyncTarget != null) {
|
if (this.currentSyncTarget != null || this.isSynced) {
|
||||||
return // "The current sync has not finished!"
|
return // "The current sync has not finished!"
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -183,46 +156,33 @@ export default function extendConnector (Y/* :any */) {
|
|||||||
var conn = this
|
var conn = this
|
||||||
if (syncUser != null) {
|
if (syncUser != null) {
|
||||||
this.currentSyncTarget = syncUser
|
this.currentSyncTarget = syncUser
|
||||||
this.y.db.requestTransaction(function * () {
|
this.y.db.requestTransaction(function *() {
|
||||||
var stateSet = yield * this.getStateSet()
|
var stateSet = yield* this.getStateSet()
|
||||||
// var deleteSet = yield * this.getDeleteSet()
|
var deleteSet = yield* this.getDeleteSet()
|
||||||
var answer = {
|
conn.send(syncUser, {
|
||||||
type: 'sync step 1',
|
type: 'sync step 1',
|
||||||
stateSet: stateSet,
|
stateSet: stateSet,
|
||||||
// deleteSet: deleteSet,
|
deleteSet: deleteSet,
|
||||||
protocolVersion: conn.protocolVersion,
|
protocolVersion: conn.protocolVersion
|
||||||
auth: conn.authInfo
|
})
|
||||||
}
|
|
||||||
if (conn.preferUntransformed && Object.keys(stateSet).length === 0) {
|
|
||||||
answer.preferUntransformed = true
|
|
||||||
}
|
|
||||||
conn.send(syncUser, answer)
|
|
||||||
})
|
})
|
||||||
} else {
|
} else {
|
||||||
if (!conn.isSynced) {
|
this.y.db.requestTransaction(function *() {
|
||||||
this.y.db.requestTransaction(function * () {
|
// it is crucial that isSynced is set at the time garbageCollectAfterSync is called
|
||||||
if (!conn.isSynced) {
|
conn.isSynced = true
|
||||||
// it is crucial that isSynced is set at the time garbageCollectAfterSync is called
|
yield* this.garbageCollectAfterSync()
|
||||||
conn.isSynced = true
|
// call whensynced listeners
|
||||||
// It is safer to remove this!
|
for (var f of conn.whenSyncedListeners) {
|
||||||
// TODO: remove: yield * this.garbageCollectAfterSync()
|
f()
|
||||||
// call whensynced listeners
|
}
|
||||||
for (var f of conn.whenSyncedListeners) {
|
conn.whenSyncedListeners = []
|
||||||
f()
|
})
|
||||||
}
|
|
||||||
conn.whenSyncedListeners = []
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
send (uid, message) {
|
send (uid, message) {
|
||||||
this.log('Send \'%s\' to %s', message.type, uid)
|
if (this.debug) {
|
||||||
this.logMessage('Message: %j', message)
|
console.log(`send ${this.userId} -> ${uid}: ${message.type}`, message) // eslint-disable-line
|
||||||
}
|
}
|
||||||
broadcast (message) {
|
|
||||||
this.log('Broadcast \'%s\'', message.type)
|
|
||||||
this.logMessage('Message: %j', message)
|
|
||||||
}
|
}
|
||||||
/*
|
/*
|
||||||
Buffer operations, and broadcast them when ready.
|
Buffer operations, and broadcast them when ready.
|
||||||
@@ -243,7 +203,11 @@ export default function extendConnector (Y/* :any */) {
|
|||||||
}
|
}
|
||||||
if (this.broadcastOpBuffer.length === 0) {
|
if (this.broadcastOpBuffer.length === 0) {
|
||||||
this.broadcastOpBuffer = ops
|
this.broadcastOpBuffer = ops
|
||||||
this.y.db.whenTransactionsFinished().then(broadcastOperations)
|
if (this.y.db.transactionInProgress) {
|
||||||
|
this.y.db.whenTransactionsFinished().then(broadcastOperations)
|
||||||
|
} else {
|
||||||
|
setTimeout(broadcastOperations, 0)
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
this.broadcastOpBuffer = this.broadcastOpBuffer.concat(ops)
|
this.broadcastOpBuffer = this.broadcastOpBuffer.concat(ops)
|
||||||
}
|
}
|
||||||
@@ -253,12 +217,13 @@ export default function extendConnector (Y/* :any */) {
|
|||||||
*/
|
*/
|
||||||
receiveMessage (sender/* :UserId */, message/* :Message */) {
|
receiveMessage (sender/* :UserId */, message/* :Message */) {
|
||||||
if (sender === this.userId) {
|
if (sender === this.userId) {
|
||||||
return Promise.resolve()
|
return
|
||||||
|
}
|
||||||
|
if (this.debug) {
|
||||||
|
console.log(`receive ${sender} -> ${this.userId}: ${message.type}`, JSON.parse(JSON.stringify(message))) // eslint-disable-line
|
||||||
}
|
}
|
||||||
this.log('Receive \'%s\' from %s', message.type, sender)
|
|
||||||
this.logMessage('Message: %j', message)
|
|
||||||
if (message.protocolVersion != null && message.protocolVersion !== this.protocolVersion) {
|
if (message.protocolVersion != null && message.protocolVersion !== this.protocolVersion) {
|
||||||
this.log(
|
console.error(
|
||||||
`You tried to sync with a yjs instance that has a different protocol version
|
`You tried to sync with a yjs instance that has a different protocol version
|
||||||
(You: ${this.protocolVersion}, Client: ${message.protocolVersion}).
|
(You: ${this.protocolVersion}, Client: ${message.protocolVersion}).
|
||||||
The sync was stopped. You need to upgrade your dependencies (especially Yjs & the Connector)!
|
The sync was stopped. You need to upgrade your dependencies (especially Yjs & the Connector)!
|
||||||
@@ -267,125 +232,91 @@ export default function extendConnector (Y/* :any */) {
|
|||||||
type: 'sync stop',
|
type: 'sync stop',
|
||||||
protocolVersion: this.protocolVersion
|
protocolVersion: this.protocolVersion
|
||||||
})
|
})
|
||||||
return Promise.reject(new Error('Incompatible protocol version'))
|
return
|
||||||
}
|
}
|
||||||
if (message.auth != null && this.connections[sender] != null) {
|
if (message.type === 'sync step 1') {
|
||||||
// authenticate using auth in message
|
let conn = this
|
||||||
var auth = this.checkAuth(message.auth, this.y)
|
let m = message
|
||||||
this.connections[sender].auth = auth
|
this.y.db.requestTransaction(function *() {
|
||||||
auth.then(auth => {
|
var currentStateSet = yield* this.getStateSet()
|
||||||
for (var f of this.userEventListeners) {
|
yield* this.applyDeleteSet(m.deleteSet)
|
||||||
f({
|
|
||||||
action: 'userAuthenticated',
|
|
||||||
user: sender,
|
|
||||||
auth: auth
|
|
||||||
})
|
|
||||||
}
|
|
||||||
})
|
|
||||||
} else if (this.connections[sender] != null && this.connections[sender].auth == null) {
|
|
||||||
// authenticate without otherwise
|
|
||||||
this.connections[sender].auth = this.checkAuth(null, this.y)
|
|
||||||
}
|
|
||||||
if (this.connections[sender] != null && this.connections[sender].auth != null) {
|
|
||||||
return this.connections[sender].auth.then((auth) => {
|
|
||||||
if (message.type === 'sync step 1' && canRead(auth)) {
|
|
||||||
let conn = this
|
|
||||||
let m = message
|
|
||||||
let wait // wait for sync step 2 to complete
|
|
||||||
if (this.role === 'slave') {
|
|
||||||
wait = Promise.all(Object.keys(this.connections)
|
|
||||||
.map(uid => this.connections[uid])
|
|
||||||
.filter(conn => conn.role === 'master')
|
|
||||||
.map(conn => conn.syncStep2.promise)
|
|
||||||
)
|
|
||||||
} else {
|
|
||||||
wait = Promise.resolve()
|
|
||||||
}
|
|
||||||
wait.then(() => {
|
|
||||||
this.y.db.requestTransaction(function * () {
|
|
||||||
var currentStateSet = yield * this.getStateSet()
|
|
||||||
// TODO: remove
|
|
||||||
// if (canWrite(auth)) {
|
|
||||||
// yield * this.applyDeleteSet(m.deleteSet)
|
|
||||||
// }
|
|
||||||
|
|
||||||
var ds = yield * this.getDeleteSet()
|
var ds = yield* this.getDeleteSet()
|
||||||
var answer = {
|
var ops = yield* this.getOperations(m.stateSet)
|
||||||
type: 'sync step 2',
|
conn.send(sender, {
|
||||||
stateSet: currentStateSet,
|
type: 'sync step 2',
|
||||||
deleteSet: ds,
|
os: ops,
|
||||||
protocolVersion: this.protocolVersion,
|
stateSet: currentStateSet,
|
||||||
auth: this.authInfo
|
deleteSet: ds,
|
||||||
}
|
protocolVersion: this.protocolVersion
|
||||||
if (message.preferUntransformed === true && Object.keys(m.stateSet).length === 0) {
|
})
|
||||||
answer.osUntransformed = yield * this.getOperationsUntransformed()
|
if (this.forwardToSyncingClients) {
|
||||||
} else {
|
conn.syncingClients.push(sender)
|
||||||
answer.os = yield * this.getOperations(m.stateSet)
|
setTimeout(function () {
|
||||||
}
|
conn.syncingClients = conn.syncingClients.filter(function (cli) {
|
||||||
conn.send(sender, answer)
|
return cli !== sender
|
||||||
if (this.forwardToSyncingClients) {
|
|
||||||
conn.syncingClients.push(sender)
|
|
||||||
setTimeout(function () {
|
|
||||||
conn.syncingClients = conn.syncingClients.filter(function (cli) {
|
|
||||||
return cli !== sender
|
|
||||||
})
|
|
||||||
conn.send(sender, {
|
|
||||||
type: 'sync done'
|
|
||||||
})
|
|
||||||
}, 5000) // TODO: conn.syncingClientDuration)
|
|
||||||
} else {
|
|
||||||
conn.send(sender, {
|
|
||||||
type: 'sync done'
|
|
||||||
})
|
|
||||||
}
|
|
||||||
})
|
})
|
||||||
})
|
conn.send(sender, {
|
||||||
} else if (message.type === 'sync step 2' && canWrite(auth)) {
|
type: 'sync done'
|
||||||
var db = this.y.db
|
|
||||||
let defer = this.connections[sender].syncStep2
|
|
||||||
let m = message
|
|
||||||
// apply operations first
|
|
||||||
db.requestTransaction(function * () {
|
|
||||||
// yield * this.applyDeleteSet(m.deleteSet)
|
|
||||||
if (m.osUntransformed != null) {
|
|
||||||
yield * this.applyOperationsUntransformed(m.osUntransformed, m.stateSet)
|
|
||||||
} else {
|
|
||||||
this.store.apply(m.os)
|
|
||||||
}
|
|
||||||
// defer.resolve()
|
|
||||||
})
|
|
||||||
// then apply ds
|
|
||||||
db.whenTransactionsFinished().then(() => {
|
|
||||||
db.requestTransaction(function * () {
|
|
||||||
yield * this.applyDeleteSet(m.deleteSet)
|
|
||||||
})
|
})
|
||||||
defer.resolve()
|
}, 5000) // TODO: conn.syncingClientDuration)
|
||||||
|
} else {
|
||||||
|
conn.send(sender, {
|
||||||
|
type: 'sync done'
|
||||||
})
|
})
|
||||||
return defer.promise
|
|
||||||
} else if (message.type === 'sync done') {
|
|
||||||
var self = this
|
|
||||||
this.connections[sender].syncStep2.promise.then(function () {
|
|
||||||
self._setSyncedWith(sender)
|
|
||||||
})
|
|
||||||
} else if (message.type === 'update' && canWrite(auth)) {
|
|
||||||
if (this.forwardToSyncingClients) {
|
|
||||||
for (var client of this.syncingClients) {
|
|
||||||
this.send(client, message)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (this.y.db.forwardAppliedOperations) {
|
|
||||||
var delops = message.ops.filter(function (o) {
|
|
||||||
return o.struct === 'Delete'
|
|
||||||
})
|
|
||||||
if (delops.length > 0) {
|
|
||||||
this.broadcastOps(delops)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
this.y.db.apply(message.ops)
|
|
||||||
}
|
}
|
||||||
|
conn._setSyncedWith(sender)
|
||||||
})
|
})
|
||||||
} else {
|
} else if (message.type === 'sync step 2') {
|
||||||
return Promise.reject(new Error('Unable to deliver message'))
|
let conn = this
|
||||||
|
var broadcastHB = !this.broadcastedHB
|
||||||
|
this.broadcastedHB = true
|
||||||
|
var db = this.y.db
|
||||||
|
var defer = {}
|
||||||
|
defer.promise = new Promise(function (resolve) {
|
||||||
|
defer.resolve = resolve
|
||||||
|
})
|
||||||
|
this.syncStep2 = defer.promise
|
||||||
|
let m /* :MessageSyncStep2 */ = message
|
||||||
|
db.requestTransaction(function * () {
|
||||||
|
yield* this.applyDeleteSet(m.deleteSet)
|
||||||
|
this.store.apply(m.os)
|
||||||
|
db.requestTransaction(function * () {
|
||||||
|
var ops = yield* this.getOperations(m.stateSet)
|
||||||
|
if (ops.length > 0) {
|
||||||
|
if (!broadcastHB) { // TODO: consider to broadcast here..
|
||||||
|
conn.send(sender, {
|
||||||
|
type: 'update',
|
||||||
|
ops: ops
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
// broadcast only once!
|
||||||
|
conn.broadcastOps(ops)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
defer.resolve()
|
||||||
|
})
|
||||||
|
})
|
||||||
|
} else if (message.type === 'sync done') {
|
||||||
|
var self = this
|
||||||
|
this.syncStep2.then(function () {
|
||||||
|
self._setSyncedWith(sender)
|
||||||
|
})
|
||||||
|
} else if (message.type === 'update') {
|
||||||
|
if (this.forwardToSyncingClients) {
|
||||||
|
for (var client of this.syncingClients) {
|
||||||
|
this.send(client, message)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (this.y.db.forwardAppliedOperations) {
|
||||||
|
var delops = message.ops.filter(function (o) {
|
||||||
|
return o.struct === 'Delete'
|
||||||
|
})
|
||||||
|
if (delops.length > 0) {
|
||||||
|
this.broadcastOps(delops)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
this.y.db.apply(message.ops)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
_setSyncedWith (user) {
|
_setSyncedWith (user) {
|
||||||
|
|||||||
@@ -24,21 +24,11 @@ module.exports = function (Y) {
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
whenTransactionsFinished: function () {
|
whenTransactionsFinished: function () {
|
||||||
var self = this
|
var ps = []
|
||||||
return new Promise(function (resolve, reject) {
|
for (var name in this.users) {
|
||||||
// The connector first has to send the messages to the db.
|
ps.push(this.users[name].y.db.whenTransactionsFinished())
|
||||||
// Wait for the checkAuth-function to resolve
|
}
|
||||||
// The test lib only has a simple checkAuth function: `() => Promise.resolve()`
|
return Promise.all(ps)
|
||||||
// Just add a function to the event-queue, in order to wait for the event.
|
|
||||||
// TODO: this may be buggy in test applications (but it isn't be for real-life apps)
|
|
||||||
setTimeout(function () {
|
|
||||||
var ps = []
|
|
||||||
for (var name in self.users) {
|
|
||||||
ps.push(self.users[name].y.db.whenTransactionsFinished())
|
|
||||||
}
|
|
||||||
Promise.all(ps).then(resolve, reject)
|
|
||||||
}, 10)
|
|
||||||
})
|
|
||||||
},
|
},
|
||||||
flushOne: function flushOne () {
|
flushOne: function flushOne () {
|
||||||
var bufs = []
|
var bufs = []
|
||||||
@@ -64,9 +54,8 @@ module.exports = function (Y) {
|
|||||||
delete buff[sender]
|
delete buff[sender]
|
||||||
}
|
}
|
||||||
var user = globalRoom.users[userId]
|
var user = globalRoom.users[userId]
|
||||||
return user.receiveMessage(m[0], m[1]).then(function () {
|
user.receiveMessage(m[0], m[1])
|
||||||
return user.y.db.whenTransactionsFinished()
|
return user.y.db.whenTransactionsFinished()
|
||||||
}, function () {})
|
|
||||||
} else {
|
} else {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
@@ -83,14 +72,16 @@ module.exports = function (Y) {
|
|||||||
}
|
}
|
||||||
globalRoom.whenTransactionsFinished().then(nextFlush)
|
globalRoom.whenTransactionsFinished().then(nextFlush)
|
||||||
} else {
|
} else {
|
||||||
c = globalRoom.flushOne()
|
setTimeout(function () {
|
||||||
if (c) {
|
var c = globalRoom.flushOne()
|
||||||
c.then(function () {
|
if (c) {
|
||||||
globalRoom.whenTransactionsFinished().then(nextFlush)
|
c.then(function () {
|
||||||
})
|
globalRoom.whenTransactionsFinished().then(nextFlush)
|
||||||
} else {
|
})
|
||||||
resolve()
|
} else {
|
||||||
}
|
resolve()
|
||||||
|
}
|
||||||
|
}, 0)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
globalRoom.whenTransactionsFinished().then(nextFlush)
|
globalRoom.whenTransactionsFinished().then(nextFlush)
|
||||||
@@ -116,7 +107,7 @@ module.exports = function (Y) {
|
|||||||
this.syncingClientDuration = 0
|
this.syncingClientDuration = 0
|
||||||
}
|
}
|
||||||
receiveMessage (sender, m) {
|
receiveMessage (sender, m) {
|
||||||
return super.receiveMessage(sender, JSON.parse(JSON.stringify(m)))
|
super.receiveMessage(sender, JSON.parse(JSON.stringify(m)))
|
||||||
}
|
}
|
||||||
send (userId, message) {
|
send (userId, message) {
|
||||||
var buffer = globalRoom.buffers[userId]
|
var buffer = globalRoom.buffers[userId]
|
||||||
@@ -147,15 +138,11 @@ module.exports = function (Y) {
|
|||||||
return Y.utils.globalRoom.flushAll()
|
return Y.utils.globalRoom.flushAll()
|
||||||
}
|
}
|
||||||
disconnect () {
|
disconnect () {
|
||||||
var waitForMe = Promise.resolve()
|
|
||||||
if (!this.isDisconnected()) {
|
if (!this.isDisconnected()) {
|
||||||
globalRoom.removeUser(this.userId)
|
globalRoom.removeUser(this.userId)
|
||||||
waitForMe = super.disconnect()
|
super.disconnect()
|
||||||
}
|
}
|
||||||
var self = this
|
return this.y.db.whenTransactionsFinished()
|
||||||
return waitForMe.then(function () {
|
|
||||||
return self.y.db.whenTransactionsFinished()
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
flush () {
|
flush () {
|
||||||
var self = this
|
var self = this
|
||||||
@@ -167,7 +154,7 @@ module.exports = function (Y) {
|
|||||||
if (buff[sender].length === 0) {
|
if (buff[sender].length === 0) {
|
||||||
delete buff[sender]
|
delete buff[sender]
|
||||||
}
|
}
|
||||||
yield this.receiveMessage(m[0], m[1])
|
this.receiveMessage(m[0], m[1])
|
||||||
}
|
}
|
||||||
yield self.whenTransactionsFinished()
|
yield self.whenTransactionsFinished()
|
||||||
})
|
})
|
||||||
|
|||||||
175
src/Database.js
175
src/Database.js
@@ -1,7 +1,7 @@
|
|||||||
/* @flow */
|
/* @flow */
|
||||||
'use strict'
|
'use strict'
|
||||||
|
|
||||||
export default function extendDatabase (Y /* :any */) {
|
module.exports = function (Y /* :any */) {
|
||||||
/*
|
/*
|
||||||
Partial definition of an OperationStore.
|
Partial definition of an OperationStore.
|
||||||
TODO: name it Database, operation store only holds operations.
|
TODO: name it Database, operation store only holds operations.
|
||||||
@@ -39,15 +39,13 @@ export default function extendDatabase (Y /* :any */) {
|
|||||||
*/
|
*/
|
||||||
constructor (y, opts) {
|
constructor (y, opts) {
|
||||||
this.y = y
|
this.y = y
|
||||||
opts.gc = opts.gc === true
|
|
||||||
this.dbOpts = opts
|
|
||||||
var os = this
|
var os = this
|
||||||
this.userId = null
|
this.userId = null
|
||||||
var resolve_
|
var resolve
|
||||||
this.userIdPromise = new Promise(function (resolve) {
|
this.userIdPromise = new Promise(function (r) {
|
||||||
resolve_ = resolve
|
resolve = r
|
||||||
})
|
})
|
||||||
this.userIdPromise.resolve = resolve_
|
this.userIdPromise.resolve = resolve
|
||||||
// whether to broadcast all applied operations (insert & delete hook)
|
// whether to broadcast all applied operations (insert & delete hook)
|
||||||
this.forwardAppliedOperations = false
|
this.forwardAppliedOperations = false
|
||||||
// E.g. this.listenersById[id] : Array<Listener>
|
// E.g. this.listenersById[id] : Array<Listener>
|
||||||
@@ -72,24 +70,24 @@ export default function extendDatabase (Y /* :any */) {
|
|||||||
this.waitingTransactions = []
|
this.waitingTransactions = []
|
||||||
this.transactionInProgress = false
|
this.transactionInProgress = false
|
||||||
this.transactionIsFlushed = false
|
this.transactionIsFlushed = false
|
||||||
if (typeof YConcurrencyTestingMode !== 'undefined') {
|
if (typeof YConcurrency_TestingMode !== 'undefined') {
|
||||||
this.executeOrder = []
|
this.executeOrder = []
|
||||||
}
|
}
|
||||||
this.gc1 = [] // first stage
|
this.gc1 = [] // first stage
|
||||||
this.gc2 = [] // second stage -> after that, remove the op
|
this.gc2 = [] // second stage -> after that, remove the op
|
||||||
|
this.gcTimeout = !opts.gcTimeout ? 50000 : opts.gcTimeouts
|
||||||
function garbageCollect () {
|
function garbageCollect () {
|
||||||
return os.whenTransactionsFinished().then(function () {
|
return os.whenTransactionsFinished().then(function () {
|
||||||
if (os.gcTimeout > 0 && (os.gc1.length > 0 || os.gc2.length > 0)) {
|
if (os.gc1.length > 0 || os.gc2.length > 0) {
|
||||||
if (!os.y.connector.isSynced) {
|
if (!os.y.isConnected()) {
|
||||||
console.warn('gc should be empty when not synced!')
|
console.warn('gc should be empty when disconnected!')
|
||||||
}
|
}
|
||||||
return new Promise((resolve) => {
|
return new Promise((resolve) => {
|
||||||
os.requestTransaction(function * () {
|
os.requestTransaction(function * () {
|
||||||
if (os.y.connector != null && os.y.connector.isSynced) {
|
if (os.y.connector != null && os.y.connector.isSynced) {
|
||||||
for (var i = 0; i < os.gc2.length; i++) {
|
for (var i = 0; i < os.gc2.length; i++) {
|
||||||
var oid = os.gc2[i]
|
var oid = os.gc2[i]
|
||||||
yield * this.garbageCollectOperation(oid)
|
yield* this.garbageCollectOperation(oid)
|
||||||
}
|
}
|
||||||
os.gc2 = os.gc1
|
os.gc2 = os.gc1
|
||||||
os.gc1 = []
|
os.gc1 = []
|
||||||
@@ -111,23 +109,13 @@ export default function extendDatabase (Y /* :any */) {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
this.garbageCollect = garbageCollect
|
this.garbageCollect = garbageCollect
|
||||||
this.startGarbageCollector()
|
if (this.gcTimeout > 0) {
|
||||||
|
garbageCollect()
|
||||||
|
}
|
||||||
this.repairCheckInterval = !opts.repairCheckInterval ? 6000 : opts.repairCheckInterval
|
this.repairCheckInterval = !opts.repairCheckInterval ? 6000 : opts.repairCheckInterval
|
||||||
this.opsReceivedTimestamp = new Date()
|
this.opsReceivedTimestamp = new Date()
|
||||||
this.startRepairCheck()
|
this.startRepairCheck()
|
||||||
}
|
}
|
||||||
startGarbageCollector () {
|
|
||||||
this.gc = this.dbOpts.gc
|
|
||||||
if (this.gc) {
|
|
||||||
this.gcTimeout = !this.dbOpts.gcTimeout ? 100000 : this.dbOpts.gcTimeout
|
|
||||||
} else {
|
|
||||||
this.gcTimeout = -1
|
|
||||||
}
|
|
||||||
if (this.gcTimeout > 0) {
|
|
||||||
this.garbageCollect()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
startRepairCheck () {
|
startRepairCheck () {
|
||||||
var os = this
|
var os = this
|
||||||
if (this.repairCheckInterval > 0) {
|
if (this.repairCheckInterval > 0) {
|
||||||
@@ -161,7 +149,7 @@ export default function extendDatabase (Y /* :any */) {
|
|||||||
clearInterval(this.repairCheckIntervalHandler)
|
clearInterval(this.repairCheckIntervalHandler)
|
||||||
}
|
}
|
||||||
queueGarbageCollector (id) {
|
queueGarbageCollector (id) {
|
||||||
if (this.y.connector.isSynced && this.gc) {
|
if (this.y.isConnected()) {
|
||||||
this.gc1.push(id)
|
this.gc1.push(id)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -178,7 +166,7 @@ export default function extendDatabase (Y /* :any */) {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
addToDebug () {
|
addToDebug () {
|
||||||
if (typeof YConcurrencyTestingMode !== 'undefined') {
|
if (typeof YConcurrency_TestingMode !== 'undefined') {
|
||||||
var command /* :string */ = Array.prototype.map.call(arguments, function (s) {
|
var command /* :string */ = Array.prototype.map.call(arguments, function (s) {
|
||||||
if (typeof s === 'string') {
|
if (typeof s === 'string') {
|
||||||
return s
|
return s
|
||||||
@@ -194,18 +182,16 @@ export default function extendDatabase (Y /* :any */) {
|
|||||||
}
|
}
|
||||||
stopGarbageCollector () {
|
stopGarbageCollector () {
|
||||||
var self = this
|
var self = this
|
||||||
this.gc = false
|
|
||||||
this.gcTimeout = -1
|
|
||||||
return new Promise(function (resolve) {
|
return new Promise(function (resolve) {
|
||||||
self.requestTransaction(function * () {
|
self.requestTransaction(function * () {
|
||||||
var ungc /* :Array<Struct> */ = self.gc1.concat(self.gc2)
|
var ungc /* :Array<Struct> */ = self.gc1.concat(self.gc2)
|
||||||
self.gc1 = []
|
self.gc1 = []
|
||||||
self.gc2 = []
|
self.gc2 = []
|
||||||
for (var i = 0; i < ungc.length; i++) {
|
for (var i = 0; i < ungc.length; i++) {
|
||||||
var op = yield * this.getOperation(ungc[i])
|
var op = yield* this.getOperation(ungc[i])
|
||||||
if (op != null) {
|
if (op != null) {
|
||||||
delete op.gc
|
delete op.gc
|
||||||
yield * this.setOperation(op)
|
yield* this.setOperation(op)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
resolve()
|
resolve()
|
||||||
@@ -218,7 +204,7 @@ export default function extendDatabase (Y /* :any */) {
|
|||||||
TODO: rename this function
|
TODO: rename this function
|
||||||
|
|
||||||
Rulez:
|
Rulez:
|
||||||
* Only gc if this user is online & gc turned on
|
* Only gc if this user is online
|
||||||
* The most left element in a list must not be gc'd.
|
* The most left element in a list must not be gc'd.
|
||||||
=> There is at least one element in the list
|
=> There is at least one element in the list
|
||||||
|
|
||||||
@@ -227,20 +213,18 @@ export default function extendDatabase (Y /* :any */) {
|
|||||||
* addToGarbageCollector (op, left) {
|
* addToGarbageCollector (op, left) {
|
||||||
if (
|
if (
|
||||||
op.gc == null &&
|
op.gc == null &&
|
||||||
op.deleted === true &&
|
op.deleted === true
|
||||||
this.store.gc &&
|
|
||||||
this.store.y.connector.isSynced
|
|
||||||
) {
|
) {
|
||||||
var gc = false
|
var gc = false
|
||||||
if (left != null && left.deleted === true) {
|
if (left != null && left.deleted === true) {
|
||||||
gc = true
|
gc = true
|
||||||
} else if (op.content != null && op.content.length > 1) {
|
} else if (op.content != null && op.content.length > 1) {
|
||||||
op = yield * this.getInsertionCleanStart([op.id[0], op.id[1] + 1])
|
op = yield* this.getInsertionCleanStart([op.id[0], op.id[1] + 1])
|
||||||
gc = true
|
gc = true
|
||||||
}
|
}
|
||||||
if (gc) {
|
if (gc) {
|
||||||
op.gc = true
|
op.gc = true
|
||||||
yield * this.setOperation(op)
|
yield* this.setOperation(op)
|
||||||
this.store.queueGarbageCollector(op.id)
|
this.store.queueGarbageCollector(op.id)
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
@@ -255,7 +239,10 @@ export default function extendDatabase (Y /* :any */) {
|
|||||||
this.gc2 = this.gc2.filter(filter)
|
this.gc2 = this.gc2.filter(filter)
|
||||||
delete op.gc
|
delete op.gc
|
||||||
}
|
}
|
||||||
destroyTypes () {
|
* destroy () {
|
||||||
|
clearInterval(this.gcInterval)
|
||||||
|
this.gcInterval = null
|
||||||
|
this.stopRepairCheck()
|
||||||
for (var key in this.initializedTypes) {
|
for (var key in this.initializedTypes) {
|
||||||
var type = this.initializedTypes[key]
|
var type = this.initializedTypes[key]
|
||||||
if (type._destroy != null) {
|
if (type._destroy != null) {
|
||||||
@@ -265,18 +252,13 @@ export default function extendDatabase (Y /* :any */) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
* destroy () {
|
|
||||||
clearTimeout(this.gcInterval)
|
|
||||||
this.gcInterval = null
|
|
||||||
this.stopRepairCheck()
|
|
||||||
}
|
|
||||||
setUserId (userId) {
|
setUserId (userId) {
|
||||||
if (!this.userIdPromise.inProgress) {
|
if (!this.userIdPromise.inProgress) {
|
||||||
this.userIdPromise.inProgress = true
|
this.userIdPromise.inProgress = true
|
||||||
var self = this
|
var self = this
|
||||||
self.requestTransaction(function * () {
|
self.requestTransaction(function * () {
|
||||||
self.userId = userId
|
self.userId = userId
|
||||||
var state = yield * this.getState(userId)
|
var state = yield* this.getState(userId)
|
||||||
self.opClock = state.clock
|
self.opClock = state.clock
|
||||||
self.userIdPromise.resolve(userId)
|
self.userIdPromise.resolve(userId)
|
||||||
})
|
})
|
||||||
@@ -364,7 +346,7 @@ export default function extendDatabase (Y /* :any */) {
|
|||||||
|
|
||||||
for (let key = 0; key < exeNow.length; key++) {
|
for (let key = 0; key < exeNow.length; key++) {
|
||||||
let o = exeNow[key].op
|
let o = exeNow[key].op
|
||||||
yield * store.tryExecute.call(this, o)
|
yield* store.tryExecute.call(this, o)
|
||||||
}
|
}
|
||||||
|
|
||||||
for (var sid in ls) {
|
for (var sid in ls) {
|
||||||
@@ -372,9 +354,9 @@ export default function extendDatabase (Y /* :any */) {
|
|||||||
var id = JSON.parse(sid)
|
var id = JSON.parse(sid)
|
||||||
var op
|
var op
|
||||||
if (typeof id[1] === 'string') {
|
if (typeof id[1] === 'string') {
|
||||||
op = yield * this.getOperation(id)
|
op = yield* this.getOperation(id)
|
||||||
} else {
|
} else {
|
||||||
op = yield * this.getInsertion(id)
|
op = yield* this.getInsertion(id)
|
||||||
}
|
}
|
||||||
if (op == null) {
|
if (op == null) {
|
||||||
store.listenersById[sid] = l
|
store.listenersById[sid] = l
|
||||||
@@ -383,7 +365,7 @@ export default function extendDatabase (Y /* :any */) {
|
|||||||
let listener = l[i]
|
let listener = l[i]
|
||||||
let o = listener.op
|
let o = listener.op
|
||||||
if (--listener.missing === 0) {
|
if (--listener.missing === 0) {
|
||||||
yield * store.tryExecute.call(this, o)
|
yield* store.tryExecute.call(this, o)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -401,14 +383,14 @@ export default function extendDatabase (Y /* :any */) {
|
|||||||
whenOperationsExist: any;
|
whenOperationsExist: any;
|
||||||
*/
|
*/
|
||||||
* tryExecute (op) {
|
* tryExecute (op) {
|
||||||
this.store.addToDebug('yield * this.store.tryExecute.call(this, ', JSON.stringify(op), ')')
|
this.store.addToDebug('yield* this.store.tryExecute.call(this, ', JSON.stringify(op), ')')
|
||||||
if (op.struct === 'Delete') {
|
if (op.struct === 'Delete') {
|
||||||
yield * Y.Struct.Delete.execute.call(this, op)
|
yield* Y.Struct.Delete.execute.call(this, op)
|
||||||
// this is now called in Transaction.deleteOperation!
|
// this is now called in Transaction.deleteOperation!
|
||||||
// yield * this.store.operationAdded(this, op)
|
// yield* this.store.operationAdded(this, op)
|
||||||
} else {
|
} else {
|
||||||
// check if this op was defined
|
// check if this op was defined
|
||||||
var defined = yield * this.getInsertion(op.id)
|
var defined = yield* this.getInsertion(op.id)
|
||||||
while (defined != null && defined.content != null) {
|
while (defined != null && defined.content != null) {
|
||||||
// check if this op has a longer content in the case it is defined
|
// check if this op has a longer content in the case it is defined
|
||||||
if (defined.id[1] + defined.content.length < op.id[1] + op.content.length) {
|
if (defined.id[1] + defined.content.length < op.id[1] + op.content.length) {
|
||||||
@@ -417,23 +399,23 @@ export default function extendDatabase (Y /* :any */) {
|
|||||||
op.id = [op.id[0], op.id[1] + overlapSize]
|
op.id = [op.id[0], op.id[1] + overlapSize]
|
||||||
op.left = Y.utils.getLastId(defined)
|
op.left = Y.utils.getLastId(defined)
|
||||||
op.origin = op.left
|
op.origin = op.left
|
||||||
defined = yield * this.getOperation(op.id) // getOperation suffices here
|
defined = yield* this.getOperation(op.id) // getOperation suffices here
|
||||||
} else {
|
} else {
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (defined == null) {
|
if (defined == null) {
|
||||||
var opid = op.id
|
var opid = op.id
|
||||||
var isGarbageCollected = yield * this.isGarbageCollected(opid)
|
var isGarbageCollected = yield* this.isGarbageCollected(opid)
|
||||||
if (!isGarbageCollected) {
|
if (!isGarbageCollected) {
|
||||||
// TODO: reduce number of get / put calls for op ..
|
// TODO: reduce number of get / put calls for op ..
|
||||||
yield * Y.Struct[op.struct].execute.call(this, op)
|
yield* Y.Struct[op.struct].execute.call(this, op)
|
||||||
yield * this.addOperation(op)
|
yield* this.addOperation(op)
|
||||||
yield * this.store.operationAdded(this, op)
|
yield* this.store.operationAdded(this, op)
|
||||||
// operationAdded can change op..
|
// operationAdded can change op..
|
||||||
op = yield * this.getOperation(opid)
|
op = yield* this.getOperation(opid)
|
||||||
// if insertion, try to combine with left
|
// if insertion, try to combine with left
|
||||||
yield * this.tryCombineWithLeft(op)
|
yield* this.tryCombineWithLeft(op)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -452,13 +434,14 @@ export default function extendDatabase (Y /* :any */) {
|
|||||||
*/
|
*/
|
||||||
* operationAdded (transaction, op) {
|
* operationAdded (transaction, op) {
|
||||||
if (op.struct === 'Delete') {
|
if (op.struct === 'Delete') {
|
||||||
var type = this.initializedTypes[JSON.stringify(op.targetParent)]
|
var target = yield* transaction.getInsertion(op.target)
|
||||||
|
var type = this.initializedTypes[JSON.stringify(target.parent)]
|
||||||
if (type != null) {
|
if (type != null) {
|
||||||
yield * type._changed(transaction, op)
|
yield* type._changed(transaction, op)
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// increase SS
|
// increase SS
|
||||||
yield * transaction.updateState(op.id[0])
|
yield* transaction.updateState(op.id[0])
|
||||||
var opLen = op.content != null ? op.content.length : 1
|
var opLen = op.content != null ? op.content.length : 1
|
||||||
for (let i = 0; i < opLen; i++) {
|
for (let i = 0; i < opLen; i++) {
|
||||||
// notify whenOperation listeners (by id)
|
// notify whenOperation listeners (by id)
|
||||||
@@ -478,9 +461,9 @@ export default function extendDatabase (Y /* :any */) {
|
|||||||
|
|
||||||
// if parent is deleted, mark as gc'd and return
|
// if parent is deleted, mark as gc'd and return
|
||||||
if (op.parent != null) {
|
if (op.parent != null) {
|
||||||
var parentIsDeleted = yield * transaction.isDeleted(op.parent)
|
var parentIsDeleted = yield* transaction.isDeleted(op.parent)
|
||||||
if (parentIsDeleted) {
|
if (parentIsDeleted) {
|
||||||
yield * transaction.deleteList(op.id)
|
yield* transaction.deleteList(op.id)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -488,7 +471,7 @@ export default function extendDatabase (Y /* :any */) {
|
|||||||
// notify parent, if it was instanciated as a custom type
|
// notify parent, if it was instanciated as a custom type
|
||||||
if (t != null) {
|
if (t != null) {
|
||||||
let o = Y.utils.copyOperation(op)
|
let o = Y.utils.copyOperation(op)
|
||||||
yield * t._changed(transaction, o)
|
yield* t._changed(transaction, o)
|
||||||
}
|
}
|
||||||
if (!op.deleted) {
|
if (!op.deleted) {
|
||||||
// Delete if DS says this is actually deleted
|
// Delete if DS says this is actually deleted
|
||||||
@@ -497,13 +480,13 @@ export default function extendDatabase (Y /* :any */) {
|
|||||||
// TODO: !! console.log('TODO: change this before commiting')
|
// TODO: !! console.log('TODO: change this before commiting')
|
||||||
for (let i = 0; i < len; i++) {
|
for (let i = 0; i < len; i++) {
|
||||||
var id = [startId[0], startId[1] + i]
|
var id = [startId[0], startId[1] + i]
|
||||||
var opIsDeleted = yield * transaction.isDeleted(id)
|
var opIsDeleted = yield* transaction.isDeleted(id)
|
||||||
if (opIsDeleted) {
|
if (opIsDeleted) {
|
||||||
var delop = {
|
var delop = {
|
||||||
struct: 'Delete',
|
struct: 'Delete',
|
||||||
target: id
|
target: id
|
||||||
}
|
}
|
||||||
yield * this.tryExecute.call(transaction, delop)
|
yield* this.tryExecute.call(transaction, delop)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -512,16 +495,18 @@ export default function extendDatabase (Y /* :any */) {
|
|||||||
whenTransactionsFinished () {
|
whenTransactionsFinished () {
|
||||||
if (this.transactionInProgress) {
|
if (this.transactionInProgress) {
|
||||||
if (this.transactionsFinished == null) {
|
if (this.transactionsFinished == null) {
|
||||||
var resolve_
|
var resolve
|
||||||
var promise = new Promise(function (resolve) {
|
var promise = new Promise(function (r) {
|
||||||
resolve_ = resolve
|
resolve = r
|
||||||
})
|
})
|
||||||
this.transactionsFinished = {
|
this.transactionsFinished = {
|
||||||
resolve: resolve_,
|
resolve: resolve,
|
||||||
promise: promise
|
promise: promise
|
||||||
}
|
}
|
||||||
|
return promise
|
||||||
|
} else {
|
||||||
|
return this.transactionsFinished.promise
|
||||||
}
|
}
|
||||||
return this.transactionsFinished.promise
|
|
||||||
} else {
|
} else {
|
||||||
return Promise.resolve()
|
return Promise.resolve()
|
||||||
}
|
}
|
||||||
@@ -541,7 +526,7 @@ export default function extendDatabase (Y /* :any */) {
|
|||||||
} else {
|
} else {
|
||||||
this.transactionIsFlushed = true
|
this.transactionIsFlushed = true
|
||||||
return function * () {
|
return function * () {
|
||||||
yield * this.flush()
|
yield* this.flush()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
@@ -558,48 +543,6 @@ export default function extendDatabase (Y /* :any */) {
|
|||||||
}, 0)
|
}, 0)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
/*
|
|
||||||
Get a created/initialized type.
|
|
||||||
*/
|
|
||||||
getType (id) {
|
|
||||||
return this.initializedTypes[JSON.stringify(id)]
|
|
||||||
}
|
|
||||||
/*
|
|
||||||
Init type. This is called when a remote operation is retrieved, and transformed to a type
|
|
||||||
TODO: delete type from store.initializedTypes[id] when corresponding id was deleted!
|
|
||||||
*/
|
|
||||||
* initType (id, args) {
|
|
||||||
var sid = JSON.stringify(id)
|
|
||||||
var t = this.store.initializedTypes[sid]
|
|
||||||
if (t == null) {
|
|
||||||
var op/* :MapStruct | ListStruct */ = yield * this.getOperation(id)
|
|
||||||
if (op != null) {
|
|
||||||
t = yield * Y[op.type].typeDefinition.initType.call(this, this.store, op, args)
|
|
||||||
this.store.initializedTypes[sid] = t
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return t
|
|
||||||
}
|
|
||||||
/*
|
|
||||||
Create type. This is called when the local user creates a type (which is a synchronous action)
|
|
||||||
*/
|
|
||||||
createType (typedefinition, id) {
|
|
||||||
var structname = typedefinition[0].struct
|
|
||||||
id = id || this.getNextOpId(1)
|
|
||||||
var op = Y.Struct[structname].create(id)
|
|
||||||
op.type = typedefinition[0].name
|
|
||||||
|
|
||||||
this.requestTransaction(function * () {
|
|
||||||
if (op.id[0] === '_') {
|
|
||||||
yield * this.setOperation(op)
|
|
||||||
} else {
|
|
||||||
yield * this.applyCreatedOperations([op])
|
|
||||||
}
|
|
||||||
})
|
|
||||||
var t = Y[op.type].typeDefinition.createType(this, op, typedefinition[1])
|
|
||||||
this.initializedTypes[JSON.stringify(op.id)] = t
|
|
||||||
return t
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
Y.AbstractDatabase = AbstractDatabase
|
Y.AbstractDatabase = AbstractDatabase
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -17,141 +17,141 @@ for (let database of databases) {
|
|||||||
})
|
})
|
||||||
afterEach(function (done) {
|
afterEach(function (done) {
|
||||||
store.requestTransaction(function * () {
|
store.requestTransaction(function * () {
|
||||||
yield * this.store.destroy()
|
yield* this.store.destroy()
|
||||||
done()
|
done()
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
it('Deleted operation is deleted', async(function * (done) {
|
it('Deleted operation is deleted', async(function * (done) {
|
||||||
store.requestTransaction(function * () {
|
store.requestTransaction(function * () {
|
||||||
yield * this.markDeleted(['u1', 10], 1)
|
yield* this.markDeleted(['u1', 10], 1)
|
||||||
expect(yield * this.isDeleted(['u1', 10])).toBeTruthy()
|
expect(yield* this.isDeleted(['u1', 10])).toBeTruthy()
|
||||||
expect(yield * this.getDeleteSet()).toEqual({'u1': [[10, 1, false]]})
|
expect(yield* this.getDeleteSet()).toEqual({'u1': [[10, 1, false]]})
|
||||||
done()
|
done()
|
||||||
})
|
})
|
||||||
}))
|
}))
|
||||||
it('Deleted operation extends other deleted operation', async(function * (done) {
|
it('Deleted operation extends other deleted operation', async(function * (done) {
|
||||||
store.requestTransaction(function * () {
|
store.requestTransaction(function * () {
|
||||||
yield * this.markDeleted(['u1', 10], 1)
|
yield* this.markDeleted(['u1', 10], 1)
|
||||||
yield * this.markDeleted(['u1', 11], 1)
|
yield* this.markDeleted(['u1', 11], 1)
|
||||||
expect(yield * this.isDeleted(['u1', 10])).toBeTruthy()
|
expect(yield* this.isDeleted(['u1', 10])).toBeTruthy()
|
||||||
expect(yield * this.isDeleted(['u1', 11])).toBeTruthy()
|
expect(yield* this.isDeleted(['u1', 11])).toBeTruthy()
|
||||||
expect(yield * this.getDeleteSet()).toEqual({'u1': [[10, 2, false]]})
|
expect(yield* this.getDeleteSet()).toEqual({'u1': [[10, 2, false]]})
|
||||||
done()
|
done()
|
||||||
})
|
})
|
||||||
}))
|
}))
|
||||||
it('Deleted operation extends other deleted operation', async(function * (done) {
|
it('Deleted operation extends other deleted operation', async(function * (done) {
|
||||||
store.requestTransaction(function * () {
|
store.requestTransaction(function * () {
|
||||||
yield * this.markDeleted(['0', 3], 1)
|
yield* this.markDeleted(['0', 3], 1)
|
||||||
yield * this.markDeleted(['0', 4], 1)
|
yield* this.markDeleted(['0', 4], 1)
|
||||||
yield * this.markDeleted(['0', 2], 1)
|
yield* this.markDeleted(['0', 2], 1)
|
||||||
expect(yield * this.getDeleteSet()).toEqual({'0': [[2, 3, false]]})
|
expect(yield* this.getDeleteSet()).toEqual({'0': [[2, 3, false]]})
|
||||||
done()
|
done()
|
||||||
})
|
})
|
||||||
}))
|
}))
|
||||||
it('Debug #1', async(function * (done) {
|
it('Debug #1', async(function * (done) {
|
||||||
store.requestTransaction(function * () {
|
store.requestTransaction(function * () {
|
||||||
yield * this.markDeleted(['166', 0], 1)
|
yield* this.markDeleted(['166', 0], 1)
|
||||||
yield * this.markDeleted(['166', 2], 1)
|
yield* this.markDeleted(['166', 2], 1)
|
||||||
yield * this.markDeleted(['166', 0], 1)
|
yield* this.markDeleted(['166', 0], 1)
|
||||||
yield * this.markDeleted(['166', 2], 1)
|
yield* this.markDeleted(['166', 2], 1)
|
||||||
yield * this.markGarbageCollected(['166', 2], 1)
|
yield* this.markGarbageCollected(['166', 2], 1)
|
||||||
yield * this.markDeleted(['166', 1], 1)
|
yield* this.markDeleted(['166', 1], 1)
|
||||||
yield * this.markDeleted(['166', 3], 1)
|
yield* this.markDeleted(['166', 3], 1)
|
||||||
yield * this.markGarbageCollected(['166', 3], 1)
|
yield* this.markGarbageCollected(['166', 3], 1)
|
||||||
yield * this.markDeleted(['166', 0], 1)
|
yield* this.markDeleted(['166', 0], 1)
|
||||||
expect(yield * this.getDeleteSet()).toEqual({'166': [[0, 2, false], [2, 2, true]]})
|
expect(yield* this.getDeleteSet()).toEqual({'166': [[0, 2, false], [2, 2, true]]})
|
||||||
done()
|
done()
|
||||||
})
|
})
|
||||||
}))
|
}))
|
||||||
it('Debug #2', async(function * (done) {
|
it('Debug #2', async(function * (done) {
|
||||||
store.requestTransaction(function * () {
|
store.requestTransaction(function * () {
|
||||||
yield * this.markDeleted(['293', 0], 1)
|
yield* this.markDeleted(['293', 0], 1)
|
||||||
yield * this.markDeleted(['291', 2], 1)
|
yield* this.markDeleted(['291', 2], 1)
|
||||||
yield * this.markDeleted(['291', 2], 1)
|
yield* this.markDeleted(['291', 2], 1)
|
||||||
yield * this.markGarbageCollected(['293', 0], 1)
|
yield* this.markGarbageCollected(['293', 0], 1)
|
||||||
yield * this.markDeleted(['293', 1], 1)
|
yield* this.markDeleted(['293', 1], 1)
|
||||||
yield * this.markGarbageCollected(['291', 2], 1)
|
yield* this.markGarbageCollected(['291', 2], 1)
|
||||||
expect(yield * this.getDeleteSet()).toEqual({'291': [[2, 1, true]], '293': [[0, 1, true], [1, 1, false]]})
|
expect(yield* this.getDeleteSet()).toEqual({'291': [[2, 1, true]], '293': [[0, 1, true], [1, 1, false]]})
|
||||||
done()
|
done()
|
||||||
})
|
})
|
||||||
}))
|
}))
|
||||||
it('Debug #3', async(function * (done) {
|
it('Debug #3', async(function * (done) {
|
||||||
store.requestTransaction(function * () {
|
store.requestTransaction(function * () {
|
||||||
yield * this.markDeleted(['581', 0], 1)
|
yield* this.markDeleted(['581', 0], 1)
|
||||||
yield * this.markDeleted(['581', 1], 1)
|
yield* this.markDeleted(['581', 1], 1)
|
||||||
yield * this.markDeleted(['580', 0], 1)
|
yield* this.markDeleted(['580', 0], 1)
|
||||||
yield * this.markDeleted(['580', 0], 1)
|
yield* this.markDeleted(['580', 0], 1)
|
||||||
yield * this.markGarbageCollected(['581', 0], 1)
|
yield* this.markGarbageCollected(['581', 0], 1)
|
||||||
yield * this.markDeleted(['581', 2], 1)
|
yield* this.markDeleted(['581', 2], 1)
|
||||||
yield * this.markDeleted(['580', 1], 1)
|
yield* this.markDeleted(['580', 1], 1)
|
||||||
yield * this.markDeleted(['580', 2], 1)
|
yield* this.markDeleted(['580', 2], 1)
|
||||||
yield * this.markDeleted(['580', 1], 1)
|
yield* this.markDeleted(['580', 1], 1)
|
||||||
yield * this.markDeleted(['580', 2], 1)
|
yield* this.markDeleted(['580', 2], 1)
|
||||||
yield * this.markGarbageCollected(['581', 2], 1)
|
yield* this.markGarbageCollected(['581', 2], 1)
|
||||||
yield * this.markGarbageCollected(['581', 1], 1)
|
yield* this.markGarbageCollected(['581', 1], 1)
|
||||||
yield * this.markGarbageCollected(['580', 1], 1)
|
yield* this.markGarbageCollected(['580', 1], 1)
|
||||||
expect(yield * this.getDeleteSet()).toEqual({'580': [[0, 1, false], [1, 1, true], [2, 1, false]], '581': [[0, 3, true]]})
|
expect(yield* this.getDeleteSet()).toEqual({'580': [[0, 1, false], [1, 1, true], [2, 1, false]], '581': [[0, 3, true]]})
|
||||||
done()
|
done()
|
||||||
})
|
})
|
||||||
}))
|
}))
|
||||||
it('Debug #4', async(function * (done) {
|
it('Debug #4', async(function * (done) {
|
||||||
store.requestTransaction(function * () {
|
store.requestTransaction(function * () {
|
||||||
yield * this.markDeleted(['544', 0], 1)
|
yield* this.markDeleted(['544', 0], 1)
|
||||||
yield * this.markDeleted(['543', 2], 1)
|
yield* this.markDeleted(['543', 2], 1)
|
||||||
yield * this.markDeleted(['544', 0], 1)
|
yield* this.markDeleted(['544', 0], 1)
|
||||||
yield * this.markDeleted(['543', 2], 1)
|
yield* this.markDeleted(['543', 2], 1)
|
||||||
yield * this.markGarbageCollected(['544', 0], 1)
|
yield* this.markGarbageCollected(['544', 0], 1)
|
||||||
yield * this.markDeleted(['545', 1], 1)
|
yield* this.markDeleted(['545', 1], 1)
|
||||||
yield * this.markDeleted(['543', 4], 1)
|
yield* this.markDeleted(['543', 4], 1)
|
||||||
yield * this.markDeleted(['543', 3], 1)
|
yield* this.markDeleted(['543', 3], 1)
|
||||||
yield * this.markDeleted(['544', 1], 1)
|
yield* this.markDeleted(['544', 1], 1)
|
||||||
yield * this.markDeleted(['544', 2], 1)
|
yield* this.markDeleted(['544', 2], 1)
|
||||||
yield * this.markDeleted(['544', 1], 1)
|
yield* this.markDeleted(['544', 1], 1)
|
||||||
yield * this.markDeleted(['544', 2], 1)
|
yield* this.markDeleted(['544', 2], 1)
|
||||||
yield * this.markGarbageCollected(['543', 2], 1)
|
yield* this.markGarbageCollected(['543', 2], 1)
|
||||||
yield * this.markGarbageCollected(['543', 4], 1)
|
yield* this.markGarbageCollected(['543', 4], 1)
|
||||||
yield * this.markGarbageCollected(['544', 2], 1)
|
yield* this.markGarbageCollected(['544', 2], 1)
|
||||||
yield * this.markGarbageCollected(['543', 3], 1)
|
yield* this.markGarbageCollected(['543', 3], 1)
|
||||||
expect(yield * this.getDeleteSet()).toEqual({'543': [[2, 3, true]], '544': [[0, 1, true], [1, 1, false], [2, 1, true]], '545': [[1, 1, false]]})
|
expect(yield* this.getDeleteSet()).toEqual({'543': [[2, 3, true]], '544': [[0, 1, true], [1, 1, false], [2, 1, true]], '545': [[1, 1, false]]})
|
||||||
done()
|
done()
|
||||||
})
|
})
|
||||||
}))
|
}))
|
||||||
it('Debug #5', async(function * (done) {
|
it('Debug #5', async(function * (done) {
|
||||||
store.requestTransaction(function * () {
|
store.requestTransaction(function * () {
|
||||||
yield * this.applyDeleteSet({'16': [[1, 2, false]], '17': [[0, 1, true], [1, 3, false]]})
|
yield* this.applyDeleteSet({'16': [[1, 2, false]], '17': [[0, 1, true], [1, 3, false]]})
|
||||||
expect(yield * this.getDeleteSet()).toEqual({'16': [[1, 2, false]], '17': [[0, 1, true], [1, 3, false]]})
|
expect(yield* this.getDeleteSet()).toEqual({'16': [[1, 2, false]], '17': [[0, 1, true], [1, 3, false]]})
|
||||||
yield * this.applyDeleteSet({'16': [[1, 2, false]], '17': [[0, 4, true]]})
|
yield* this.applyDeleteSet({'16': [[1, 2, false]], '17': [[0, 4, true]]})
|
||||||
expect(yield * this.getDeleteSet()).toEqual({'16': [[1, 2, false]], '17': [[0, 4, true]]})
|
expect(yield* this.getDeleteSet()).toEqual({'16': [[1, 2, false]], '17': [[0, 4, true]]})
|
||||||
done()
|
done()
|
||||||
})
|
})
|
||||||
}))
|
}))
|
||||||
it('Debug #6', async(function * (done) {
|
it('Debug #6', async(function * (done) {
|
||||||
store.requestTransaction(function * () {
|
store.requestTransaction(function * () {
|
||||||
yield * this.applyDeleteSet({'40': [[0, 3, false]]})
|
yield* this.applyDeleteSet({'40': [[0, 3, false]]})
|
||||||
expect(yield * this.getDeleteSet()).toEqual({'40': [[0, 3, false]]})
|
expect(yield* this.getDeleteSet()).toEqual({'40': [[0, 3, false]]})
|
||||||
yield * this.applyDeleteSet({'39': [[2, 2, false]], '40': [[0, 1, true], [1, 2, false]], '41': [[2, 1, false]]})
|
yield* this.applyDeleteSet({'39': [[2, 2, false]], '40': [[0, 1, true], [1, 2, false]], '41': [[2, 1, false]]})
|
||||||
expect(yield * this.getDeleteSet()).toEqual({'39': [[2, 2, false]], '40': [[0, 1, true], [1, 2, false]], '41': [[2, 1, false]]})
|
expect(yield* this.getDeleteSet()).toEqual({'39': [[2, 2, false]], '40': [[0, 1, true], [1, 2, false]], '41': [[2, 1, false]]})
|
||||||
done()
|
done()
|
||||||
})
|
})
|
||||||
}))
|
}))
|
||||||
it('Debug #7', async(function * (done) {
|
it('Debug #7', async(function * (done) {
|
||||||
store.requestTransaction(function * () {
|
store.requestTransaction(function * () {
|
||||||
yield * this.markDeleted(['9', 2], 1)
|
yield* this.markDeleted(['9', 2], 1)
|
||||||
yield * this.markDeleted(['11', 2], 1)
|
yield* this.markDeleted(['11', 2], 1)
|
||||||
yield * this.markDeleted(['11', 4], 1)
|
yield* this.markDeleted(['11', 4], 1)
|
||||||
yield * this.markDeleted(['11', 1], 1)
|
yield* this.markDeleted(['11', 1], 1)
|
||||||
yield * this.markDeleted(['9', 4], 1)
|
yield* this.markDeleted(['9', 4], 1)
|
||||||
yield * this.markDeleted(['10', 0], 1)
|
yield* this.markDeleted(['10', 0], 1)
|
||||||
yield * this.markGarbageCollected(['11', 2], 1)
|
yield* this.markGarbageCollected(['11', 2], 1)
|
||||||
yield * this.markDeleted(['11', 2], 1)
|
yield* this.markDeleted(['11', 2], 1)
|
||||||
yield * this.markGarbageCollected(['11', 3], 1)
|
yield* this.markGarbageCollected(['11', 3], 1)
|
||||||
yield * this.markDeleted(['11', 3], 1)
|
yield* this.markDeleted(['11', 3], 1)
|
||||||
yield * this.markDeleted(['11', 3], 1)
|
yield* this.markDeleted(['11', 3], 1)
|
||||||
yield * this.markDeleted(['9', 4], 1)
|
yield* this.markDeleted(['9', 4], 1)
|
||||||
yield * this.markDeleted(['10', 0], 1)
|
yield* this.markDeleted(['10', 0], 1)
|
||||||
yield * this.markGarbageCollected(['11', 1], 1)
|
yield* this.markGarbageCollected(['11', 1], 1)
|
||||||
yield * this.markDeleted(['11', 1], 1)
|
yield* this.markDeleted(['11', 1], 1)
|
||||||
expect(yield * this.getDeleteSet()).toEqual({'9': [[2, 1, false], [4, 1, false]], '10': [[0, 1, false]], '11': [[1, 3, true], [4, 1, false]]})
|
expect(yield* this.getDeleteSet()).toEqual({'9': [[2, 1, false], [4, 1, false]], '10': [[0, 1, false]], '11': [[1, 3, true], [4, 1, false]]})
|
||||||
done()
|
done()
|
||||||
})
|
})
|
||||||
}))
|
}))
|
||||||
@@ -167,54 +167,54 @@ for (let database of databases) {
|
|||||||
})
|
})
|
||||||
afterEach(function (done) {
|
afterEach(function (done) {
|
||||||
store.requestTransaction(function * () {
|
store.requestTransaction(function * () {
|
||||||
yield * this.store.destroy()
|
yield* this.store.destroy()
|
||||||
done()
|
done()
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
it('debug #1', function (done) {
|
it('debug #1', function (done) {
|
||||||
store.requestTransaction(function * () {
|
store.requestTransaction(function * () {
|
||||||
yield * this.os.put({id: [2]})
|
yield* this.os.put({id: [2]})
|
||||||
yield * this.os.put({id: [0]})
|
yield* this.os.put({id: [0]})
|
||||||
yield * this.os.delete([2])
|
yield* this.os.delete([2])
|
||||||
yield * this.os.put({id: [1]})
|
yield* this.os.put({id: [1]})
|
||||||
expect(yield * this.os.find([0])).toBeTruthy()
|
expect(yield* this.os.find([0])).toBeTruthy()
|
||||||
expect(yield * this.os.find([1])).toBeTruthy()
|
expect(yield* this.os.find([1])).toBeTruthy()
|
||||||
expect(yield * this.os.find([2])).toBeFalsy()
|
expect(yield* this.os.find([2])).toBeFalsy()
|
||||||
done()
|
done()
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
it('can add&retrieve 5 elements', function (done) {
|
it('can add&retrieve 5 elements', function (done) {
|
||||||
store.requestTransaction(function * () {
|
store.requestTransaction(function * () {
|
||||||
yield * this.os.put({val: 'four', id: [4]})
|
yield* this.os.put({val: 'four', id: [4]})
|
||||||
yield * this.os.put({val: 'one', id: [1]})
|
yield* this.os.put({val: 'one', id: [1]})
|
||||||
yield * this.os.put({val: 'three', id: [3]})
|
yield* this.os.put({val: 'three', id: [3]})
|
||||||
yield * this.os.put({val: 'two', id: [2]})
|
yield* this.os.put({val: 'two', id: [2]})
|
||||||
yield * this.os.put({val: 'five', id: [5]})
|
yield* this.os.put({val: 'five', id: [5]})
|
||||||
expect((yield * this.os.find([1])).val).toEqual('one')
|
expect((yield* this.os.find([1])).val).toEqual('one')
|
||||||
expect((yield * this.os.find([2])).val).toEqual('two')
|
expect((yield* this.os.find([2])).val).toEqual('two')
|
||||||
expect((yield * this.os.find([3])).val).toEqual('three')
|
expect((yield* this.os.find([3])).val).toEqual('three')
|
||||||
expect((yield * this.os.find([4])).val).toEqual('four')
|
expect((yield* this.os.find([4])).val).toEqual('four')
|
||||||
expect((yield * this.os.find([5])).val).toEqual('five')
|
expect((yield* this.os.find([5])).val).toEqual('five')
|
||||||
done()
|
done()
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
it('5 elements do not exist anymore after deleting them', function (done) {
|
it('5 elements do not exist anymore after deleting them', function (done) {
|
||||||
store.requestTransaction(function * () {
|
store.requestTransaction(function * () {
|
||||||
yield * this.os.put({val: 'four', id: [4]})
|
yield* this.os.put({val: 'four', id: [4]})
|
||||||
yield * this.os.put({val: 'one', id: [1]})
|
yield* this.os.put({val: 'one', id: [1]})
|
||||||
yield * this.os.put({val: 'three', id: [3]})
|
yield* this.os.put({val: 'three', id: [3]})
|
||||||
yield * this.os.put({val: 'two', id: [2]})
|
yield* this.os.put({val: 'two', id: [2]})
|
||||||
yield * this.os.put({val: 'five', id: [5]})
|
yield* this.os.put({val: 'five', id: [5]})
|
||||||
yield * this.os.delete([4])
|
yield* this.os.delete([4])
|
||||||
expect(yield * this.os.find([4])).not.toBeTruthy()
|
expect(yield* this.os.find([4])).not.toBeTruthy()
|
||||||
yield * this.os.delete([3])
|
yield* this.os.delete([3])
|
||||||
expect(yield * this.os.find([3])).not.toBeTruthy()
|
expect(yield* this.os.find([3])).not.toBeTruthy()
|
||||||
yield * this.os.delete([2])
|
yield* this.os.delete([2])
|
||||||
expect(yield * this.os.find([2])).not.toBeTruthy()
|
expect(yield* this.os.find([2])).not.toBeTruthy()
|
||||||
yield * this.os.delete([1])
|
yield* this.os.delete([1])
|
||||||
expect(yield * this.os.find([1])).not.toBeTruthy()
|
expect(yield* this.os.find([1])).not.toBeTruthy()
|
||||||
yield * this.os.delete([5])
|
yield* this.os.delete([5])
|
||||||
expect(yield * this.os.find([5])).not.toBeTruthy()
|
expect(yield* this.os.find([5])).not.toBeTruthy()
|
||||||
done()
|
done()
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
@@ -232,9 +232,9 @@ for (let database of databases) {
|
|||||||
var r = Math.random()
|
var r = Math.random()
|
||||||
if (r < 0.8) {
|
if (r < 0.8) {
|
||||||
var obj = [Math.floor(Math.random() * numberOfOSTests * 10000)]
|
var obj = [Math.floor(Math.random() * numberOfOSTests * 10000)]
|
||||||
if (!(yield * this.os.find(obj))) {
|
if (!(yield* this.os.find(obj))) {
|
||||||
elements.push(obj)
|
elements.push(obj)
|
||||||
yield * this.os.put({id: obj})
|
yield* this.os.put({id: obj})
|
||||||
}
|
}
|
||||||
} else if (elements.length > 0) {
|
} else if (elements.length > 0) {
|
||||||
var elemid = Math.floor(Math.random() * elements.length)
|
var elemid = Math.floor(Math.random() * elements.length)
|
||||||
@@ -242,7 +242,7 @@ for (let database of databases) {
|
|||||||
elements = elements.filter(function (e) {
|
elements = elements.filter(function (e) {
|
||||||
return !Y.utils.compareIds(e, elem)
|
return !Y.utils.compareIds(e, elem)
|
||||||
})
|
})
|
||||||
yield * this.os.delete(elem)
|
yield* this.os.delete(elem)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
done()
|
done()
|
||||||
@@ -250,14 +250,14 @@ for (let database of databases) {
|
|||||||
})
|
})
|
||||||
afterAll(function (done) {
|
afterAll(function (done) {
|
||||||
store.requestTransaction(function * () {
|
store.requestTransaction(function * () {
|
||||||
yield * this.store.destroy()
|
yield* this.store.destroy()
|
||||||
done()
|
done()
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
it('can find every object', function (done) {
|
it('can find every object', function (done) {
|
||||||
store.requestTransaction(function * () {
|
store.requestTransaction(function * () {
|
||||||
for (var id of elements) {
|
for (var id of elements) {
|
||||||
expect((yield * this.os.find(id)).id).toEqual(id)
|
expect((yield* this.os.find(id)).id).toEqual(id)
|
||||||
}
|
}
|
||||||
done()
|
done()
|
||||||
})
|
})
|
||||||
@@ -266,7 +266,7 @@ for (let database of databases) {
|
|||||||
it('can find every object with lower bound search', function (done) {
|
it('can find every object with lower bound search', function (done) {
|
||||||
store.requestTransaction(function * () {
|
store.requestTransaction(function * () {
|
||||||
for (var id of elements) {
|
for (var id of elements) {
|
||||||
var e = yield * this.os.findWithLowerBound(id)
|
var e = yield* this.os.findWithLowerBound(id)
|
||||||
expect(e.id).toEqual(id)
|
expect(e.id).toEqual(id)
|
||||||
}
|
}
|
||||||
done()
|
done()
|
||||||
@@ -281,7 +281,7 @@ for (let database of databases) {
|
|||||||
|
|
||||||
var actualResults = 0
|
var actualResults = 0
|
||||||
store.requestTransaction(function * () {
|
store.requestTransaction(function * () {
|
||||||
yield * this.os.iterate(this, lowerBound, null, function * (val) {
|
yield* this.os.iterate(this, lowerBound, null, function * (val) {
|
||||||
expect(val).toBeDefined()
|
expect(val).toBeDefined()
|
||||||
actualResults++
|
actualResults++
|
||||||
})
|
})
|
||||||
@@ -297,7 +297,7 @@ for (let database of databases) {
|
|||||||
}).length
|
}).length
|
||||||
var actualResults = 0
|
var actualResults = 0
|
||||||
store.requestTransaction(function * () {
|
store.requestTransaction(function * () {
|
||||||
yield * this.os.iterate(this, lowerBound, null, function * (val) {
|
yield* this.os.iterate(this, lowerBound, null, function * (val) {
|
||||||
expect(val).toBeDefined()
|
expect(val).toBeDefined()
|
||||||
actualResults++
|
actualResults++
|
||||||
})
|
})
|
||||||
@@ -314,7 +314,7 @@ for (let database of databases) {
|
|||||||
|
|
||||||
var actualResults = 0
|
var actualResults = 0
|
||||||
store.requestTransaction(function * () {
|
store.requestTransaction(function * () {
|
||||||
yield * this.os.iterate(this, null, upperBound, function * (val) {
|
yield* this.os.iterate(this, null, upperBound, function * (val) {
|
||||||
expect(val).toBeDefined()
|
expect(val).toBeDefined()
|
||||||
actualResults++
|
actualResults++
|
||||||
})
|
})
|
||||||
@@ -340,7 +340,7 @@ for (let database of databases) {
|
|||||||
}).length
|
}).length
|
||||||
var actualResults = 0
|
var actualResults = 0
|
||||||
store.requestTransaction(function * () {
|
store.requestTransaction(function * () {
|
||||||
yield * this.os.iterate(this, lowerBound, upperBound, function * (val) {
|
yield* this.os.iterate(this, lowerBound, upperBound, function * (val) {
|
||||||
expect(val).toBeDefined()
|
expect(val).toBeDefined()
|
||||||
actualResults++
|
actualResults++
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -11,7 +11,9 @@ require('../../y-memory/src/Memory.js')(Y)
|
|||||||
require('../../y-array/src/Array.js')(Y)
|
require('../../y-array/src/Array.js')(Y)
|
||||||
require('../../y-map/src/Map.js')(Y)
|
require('../../y-map/src/Map.js')(Y)
|
||||||
require('../../y-indexeddb/src/IndexedDB.js')(Y)
|
require('../../y-indexeddb/src/IndexedDB.js')(Y)
|
||||||
|
if (typeof window === 'undefined') {
|
||||||
|
require('../../y-leveldb/src/LevelDB.js')(Y)
|
||||||
|
}
|
||||||
module.exports = Y
|
module.exports = Y
|
||||||
|
|
||||||
var g
|
var g
|
||||||
@@ -46,7 +48,7 @@ g.setRandomSeed = function setRandomSeed (seed) {
|
|||||||
|
|
||||||
g.generateRandomSeed()
|
g.generateRandomSeed()
|
||||||
|
|
||||||
g.YConcurrencyTestingMode = true
|
g.YConcurrency_TestingMode = true
|
||||||
|
|
||||||
jasmine.DEFAULT_TIMEOUT_INTERVAL = 200000
|
jasmine.DEFAULT_TIMEOUT_INTERVAL = 200000
|
||||||
|
|
||||||
@@ -119,10 +121,6 @@ function * applyTransactions (relAmount, numberOfTransactions, objects, users, t
|
|||||||
// There will be an artificial delay until ops can be executed by the type,
|
// There will be an artificial delay until ops can be executed by the type,
|
||||||
// therefore, operations of the database will be (pre)transformed until user operations arrive
|
// therefore, operations of the database will be (pre)transformed until user operations arrive
|
||||||
yield (function simulateConcurrentUserInteractions (type) {
|
yield (function simulateConcurrentUserInteractions (type) {
|
||||||
if (!(type instanceof Y.utils.CustomType) && type.y instanceof Y.utils.CustomType) {
|
|
||||||
// usually we expect type to be a custom type. But in YXml we share an object {y: YXml, dom: Dom} instead
|
|
||||||
type = type.y
|
|
||||||
}
|
|
||||||
if (type.eventHandler.awaiting === 0 && type.eventHandler._debuggingAwaiting !== true) {
|
if (type.eventHandler.awaiting === 0 && type.eventHandler._debuggingAwaiting !== true) {
|
||||||
type.eventHandler.awaiting = 1
|
type.eventHandler.awaiting = 1
|
||||||
type.eventHandler._debuggingAwaiting = true
|
type.eventHandler._debuggingAwaiting = true
|
||||||
@@ -158,17 +156,13 @@ function * applyTransactions (relAmount, numberOfTransactions, objects, users, t
|
|||||||
}
|
}
|
||||||
|
|
||||||
function fixAwaitingInType (type) {
|
function fixAwaitingInType (type) {
|
||||||
if (!(type instanceof Y.utils.CustomType) && type.y instanceof Y.utils.CustomType) {
|
|
||||||
// usually we expect type to be a custom type. But in YXml we share an object {y: YXml, dom: Dom} instead
|
|
||||||
type = type.y
|
|
||||||
}
|
|
||||||
return new Promise(function (resolve) {
|
return new Promise(function (resolve) {
|
||||||
type.os.whenTransactionsFinished().then(function () {
|
type.os.whenTransactionsFinished().then(function () {
|
||||||
// _debuggingAwaiting artificially increases the awaiting property. We need to make sure that we only do that once / reverse the effect once
|
// _debuggingAwaiting artificially increases the awaiting property. We need to make sure that we only do that once / reverse the effect once
|
||||||
type.os.requestTransaction(function * () {
|
type.os.requestTransaction(function * () {
|
||||||
if (type.eventHandler.awaiting > 0 && type.eventHandler._debuggingAwaiting === true) {
|
if (type.eventHandler.awaiting > 0 && type.eventHandler._debuggingAwaiting === true) {
|
||||||
type.eventHandler._debuggingAwaiting = false
|
type.eventHandler._debuggingAwaiting = false
|
||||||
yield * type.eventHandler.awaitOps(this, function * () { /* mock function */ })
|
yield* type.eventHandler.awaitOps(this, function * () { /* mock function */ })
|
||||||
}
|
}
|
||||||
wait(50).then(type.os.whenTransactionsFinished()).then(wait(50)).then(resolve)
|
wait(50).then(type.os.whenTransactionsFinished()).then(wait(50)).then(resolve)
|
||||||
})
|
})
|
||||||
@@ -178,13 +172,13 @@ function fixAwaitingInType (type) {
|
|||||||
g.fixAwaitingInType = fixAwaitingInType
|
g.fixAwaitingInType = fixAwaitingInType
|
||||||
|
|
||||||
g.applyRandomTransactionsNoGCNoDisconnect = async(function * applyRandomTransactions (users, objects, transactions, numberOfTransactions) {
|
g.applyRandomTransactionsNoGCNoDisconnect = async(function * applyRandomTransactions (users, objects, transactions, numberOfTransactions) {
|
||||||
yield * applyTransactions(1, numberOfTransactions, objects, users, transactions, true)
|
yield* applyTransactions(1, numberOfTransactions, objects, users, transactions, true)
|
||||||
yield Y.utils.globalRoom.flushAll()
|
yield Y.utils.globalRoom.flushAll()
|
||||||
yield Promise.all(objects.map(fixAwaitingInType))
|
yield Promise.all(objects.map(fixAwaitingInType))
|
||||||
})
|
})
|
||||||
|
|
||||||
g.applyRandomTransactionsAllRejoinNoGC = async(function * applyRandomTransactions (users, objects, transactions, numberOfTransactions) {
|
g.applyRandomTransactionsAllRejoinNoGC = async(function * applyRandomTransactions (users, objects, transactions, numberOfTransactions) {
|
||||||
yield * applyTransactions(1, numberOfTransactions, objects, users, transactions)
|
yield* applyTransactions(1, numberOfTransactions, objects, users, transactions)
|
||||||
yield Promise.all(objects.map(fixAwaitingInType))
|
yield Promise.all(objects.map(fixAwaitingInType))
|
||||||
yield Y.utils.globalRoom.flushAll()
|
yield Y.utils.globalRoom.flushAll()
|
||||||
yield Promise.all(objects.map(fixAwaitingInType))
|
yield Promise.all(objects.map(fixAwaitingInType))
|
||||||
@@ -200,7 +194,7 @@ g.applyRandomTransactionsAllRejoinNoGC = async(function * applyRandomTransaction
|
|||||||
})
|
})
|
||||||
|
|
||||||
g.applyRandomTransactionsWithGC = async(function * applyRandomTransactions (users, objects, transactions, numberOfTransactions) {
|
g.applyRandomTransactionsWithGC = async(function * applyRandomTransactions (users, objects, transactions, numberOfTransactions) {
|
||||||
yield * applyTransactions(1, numberOfTransactions, objects, users.slice(1), transactions)
|
yield* applyTransactions(1, numberOfTransactions, objects, users.slice(1), transactions)
|
||||||
yield Y.utils.globalRoom.flushAll()
|
yield Y.utils.globalRoom.flushAll()
|
||||||
yield Promise.all(objects.map(fixAwaitingInType))
|
yield Promise.all(objects.map(fixAwaitingInType))
|
||||||
for (var u in users) {
|
for (var u in users) {
|
||||||
@@ -226,38 +220,26 @@ g.compareAllUsers = async(function * compareAllUsers (users) {
|
|||||||
var allDels1, allDels2 // all deletions
|
var allDels1, allDels2 // all deletions
|
||||||
var db1 = [] // operation store of user1
|
var db1 = [] // operation store of user1
|
||||||
|
|
||||||
yield Y.utils.globalRoom.flushAll()
|
|
||||||
yield g.garbageCollectAllUsers(users)
|
|
||||||
yield Y.utils.globalRoom.flushAll()
|
|
||||||
|
|
||||||
// disconnect, then reconnect all users
|
|
||||||
// We do this to make sure that the gc is updated by everyone
|
|
||||||
for (var i = 0; i < users.length; i++) {
|
|
||||||
yield users[i].disconnect()
|
|
||||||
yield wait()
|
|
||||||
yield users[i].reconnect()
|
|
||||||
}
|
|
||||||
yield wait()
|
|
||||||
yield Y.utils.globalRoom.flushAll()
|
|
||||||
|
|
||||||
// t1 and t2 basically do the same. They define t[1,2], ds[1,2], and allDels[1,2]
|
// t1 and t2 basically do the same. They define t[1,2], ds[1,2], and allDels[1,2]
|
||||||
function * t1 () {
|
function * t1 () {
|
||||||
s1 = yield * this.getStateSet()
|
s1 = yield* this.getStateSet()
|
||||||
ds1 = yield * this.getDeleteSet()
|
ds1 = yield* this.getDeleteSet()
|
||||||
allDels1 = []
|
allDels1 = []
|
||||||
yield * this.ds.iterate(this, null, null, function * (d) {
|
yield* this.ds.iterate(this, null, null, function * (d) {
|
||||||
allDels1.push(d)
|
allDels1.push(d)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
function * t2 () {
|
function * t2 () {
|
||||||
s2 = yield * this.getStateSet()
|
s2 = yield* this.getStateSet()
|
||||||
ds2 = yield * this.getDeleteSet()
|
ds2 = yield* this.getDeleteSet()
|
||||||
allDels2 = []
|
allDels2 = []
|
||||||
yield * this.ds.iterate(this, null, null, function * (d) {
|
yield* this.ds.iterate(this, null, null, function * (d) {
|
||||||
allDels2.push(d)
|
allDels2.push(d)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
yield Y.utils.globalRoom.flushAll()
|
||||||
|
yield g.garbageCollectAllUsers(users)
|
||||||
|
yield Y.utils.globalRoom.flushAll()
|
||||||
var buffer = Y.utils.globalRoom.buffers
|
var buffer = Y.utils.globalRoom.buffers
|
||||||
for (var name in buffer) {
|
for (var name in buffer) {
|
||||||
if (buffer[name].length > 0) {
|
if (buffer[name].length > 0) {
|
||||||
@@ -269,25 +251,25 @@ g.compareAllUsers = async(function * compareAllUsers (users) {
|
|||||||
for (var uid = 0; uid < users.length; uid++) {
|
for (var uid = 0; uid < users.length; uid++) {
|
||||||
var u = users[uid]
|
var u = users[uid]
|
||||||
u.db.requestTransaction(function * () {
|
u.db.requestTransaction(function * () {
|
||||||
var sv = yield * this.getStateVector()
|
var sv = yield* this.getStateVector()
|
||||||
for (var s of sv) {
|
for (var s of sv) {
|
||||||
yield * this.updateState(s.user)
|
yield* this.updateState(s.user)
|
||||||
}
|
}
|
||||||
// compare deleted ops against deleteStore
|
// compare deleted ops against deleteStore
|
||||||
yield * this.os.iterate(this, null, null, function * (o) {
|
yield* this.os.iterate(this, null, null, function * (o) {
|
||||||
if (o.deleted === true) {
|
if (o.deleted === true) {
|
||||||
expect(yield * this.isDeleted(o.id)).toBeTruthy()
|
expect(yield* this.isDeleted(o.id)).toBeTruthy()
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
// compare deleteStore against deleted ops
|
// compare deleteStore against deleted ops
|
||||||
var ds = []
|
var ds = []
|
||||||
yield * this.ds.iterate(this, null, null, function * (d) {
|
yield* this.ds.iterate(this, null, null, function * (d) {
|
||||||
ds.push(d)
|
ds.push(d)
|
||||||
})
|
})
|
||||||
for (var j in ds) {
|
for (var j in ds) {
|
||||||
var d = ds[j]
|
var d = ds[j]
|
||||||
for (var i = 0; i < d.len; i++) {
|
for (var i = 0; i < d.len; i++) {
|
||||||
var o = yield * this.getInsertion([d.id[0], d.id[1] + i])
|
var o = yield* this.getInsertion([d.id[0], d.id[1] + i])
|
||||||
// gc'd or deleted
|
// gc'd or deleted
|
||||||
if (d.gc) {
|
if (d.gc) {
|
||||||
expect(o).toBeFalsy()
|
expect(o).toBeFalsy()
|
||||||
@@ -300,8 +282,8 @@ g.compareAllUsers = async(function * compareAllUsers (users) {
|
|||||||
// compare allDels tree
|
// compare allDels tree
|
||||||
if (s1 == null) {
|
if (s1 == null) {
|
||||||
u.db.requestTransaction(function * () {
|
u.db.requestTransaction(function * () {
|
||||||
yield * t1.call(this)
|
yield* t1.call(this)
|
||||||
yield * this.os.iterate(this, null, null, function * (o) {
|
yield* this.os.iterate(this, null, null, function * (o) {
|
||||||
o = Y.utils.copyObject(o)
|
o = Y.utils.copyObject(o)
|
||||||
delete o.origin
|
delete o.origin
|
||||||
delete o.originOf
|
delete o.originOf
|
||||||
@@ -310,9 +292,9 @@ g.compareAllUsers = async(function * compareAllUsers (users) {
|
|||||||
})
|
})
|
||||||
} else {
|
} else {
|
||||||
u.db.requestTransaction(function * () {
|
u.db.requestTransaction(function * () {
|
||||||
yield * t2.call(this)
|
yield* t2.call(this)
|
||||||
var db2 = []
|
var db2 = []
|
||||||
yield * this.os.iterate(this, null, null, function * (o) {
|
yield* this.os.iterate(this, null, null, function * (o) {
|
||||||
o = Y.utils.copyObject(o)
|
o = Y.utils.copyObject(o)
|
||||||
delete o.origin
|
delete o.origin
|
||||||
delete o.originOf
|
delete o.originOf
|
||||||
@@ -348,7 +330,6 @@ g.createUsers = async(function * createUsers (self, numberOfUsers, database, ini
|
|||||||
namespace: 'User ' + i,
|
namespace: 'User ' + i,
|
||||||
cleanStart: true,
|
cleanStart: true,
|
||||||
gcTimeout: -1,
|
gcTimeout: -1,
|
||||||
gc: true,
|
|
||||||
repairCheckInterval: -1
|
repairCheckInterval: -1
|
||||||
},
|
},
|
||||||
connector: {
|
connector: {
|
||||||
|
|||||||
@@ -19,7 +19,7 @@
|
|||||||
* requiredOps
|
* requiredOps
|
||||||
- Operations that are required to execute this operation.
|
- Operations that are required to execute this operation.
|
||||||
*/
|
*/
|
||||||
export default function extendStruct (Y) {
|
module.exports = function (Y/* :any */) {
|
||||||
var Struct = {
|
var Struct = {
|
||||||
/* This is the only operation that is actually not a structure, because
|
/* This is the only operation that is actually not a structure, because
|
||||||
it is not stored in the OS. This is why it _does not_ have an id
|
it is not stored in the OS. This is why it _does not_ have an id
|
||||||
@@ -30,17 +30,13 @@ export default function extendStruct (Y) {
|
|||||||
*/
|
*/
|
||||||
Delete: {
|
Delete: {
|
||||||
encode: function (op) {
|
encode: function (op) {
|
||||||
return {
|
return op
|
||||||
target: op.target,
|
|
||||||
length: op.length || 0,
|
|
||||||
struct: 'Delete'
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
requiredOps: function (op) {
|
requiredOps: function (op) {
|
||||||
return [] // [op.target]
|
return [] // [op.target]
|
||||||
},
|
},
|
||||||
execute: function * (op) {
|
execute: function * (op) {
|
||||||
return yield * this.deleteOperation(op.target, op.length || 1)
|
return yield* this.deleteOperation(op.target, op.length || 1)
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
Insert: {
|
Insert: {
|
||||||
@@ -101,13 +97,13 @@ export default function extendStruct (Y) {
|
|||||||
return 0
|
return 0
|
||||||
} else {
|
} else {
|
||||||
var d = 0
|
var d = 0
|
||||||
var o = yield * this.getInsertion(op.left)
|
var o = yield* this.getInsertion(op.left)
|
||||||
while (!Y.utils.matchesId(o, op.origin)) {
|
while (!Y.utils.matchesId(o, op.origin)) {
|
||||||
d++
|
d++
|
||||||
if (o.left == null) {
|
if (o.left == null) {
|
||||||
break
|
break
|
||||||
} else {
|
} else {
|
||||||
o = yield * this.getInsertion(o.left)
|
o = yield* this.getInsertion(o.left)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return d
|
return d
|
||||||
@@ -138,17 +134,17 @@ export default function extendStruct (Y) {
|
|||||||
if (op.origin != null) { // TODO: !== instead of !=
|
if (op.origin != null) { // TODO: !== instead of !=
|
||||||
// we save in origin that op originates in it
|
// we save in origin that op originates in it
|
||||||
// we need that later when we eventually garbage collect origin (see transaction)
|
// we need that later when we eventually garbage collect origin (see transaction)
|
||||||
var origin = yield * this.getInsertionCleanEnd(op.origin)
|
var origin = yield* this.getInsertionCleanEnd(op.origin)
|
||||||
if (origin.originOf == null) {
|
if (origin.originOf == null) {
|
||||||
origin.originOf = []
|
origin.originOf = []
|
||||||
}
|
}
|
||||||
origin.originOf.push(op.id)
|
origin.originOf.push(op.id)
|
||||||
yield * this.setOperation(origin)
|
yield* this.setOperation(origin)
|
||||||
if (origin.right != null) {
|
if (origin.right != null) {
|
||||||
tryToRemergeLater.push(origin.right)
|
tryToRemergeLater.push(origin.right)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
var distanceToOrigin = i = yield * Struct.Insert.getDistanceToOrigin.call(this, op) // most cases: 0 (starts from 0)
|
var distanceToOrigin = i = yield* Struct.Insert.getDistanceToOrigin.call(this, op) // most cases: 0 (starts from 0)
|
||||||
|
|
||||||
// now we begin to insert op in the list of insertions..
|
// now we begin to insert op in the list of insertions..
|
||||||
var o
|
var o
|
||||||
@@ -157,29 +153,29 @@ export default function extendStruct (Y) {
|
|||||||
|
|
||||||
// find o. o is the first conflicting operation
|
// find o. o is the first conflicting operation
|
||||||
if (op.left != null) {
|
if (op.left != null) {
|
||||||
o = yield * this.getInsertionCleanEnd(op.left)
|
o = yield* this.getInsertionCleanEnd(op.left)
|
||||||
if (!Y.utils.compareIds(op.left, op.origin) && o.right != null) {
|
if (!Y.utils.compareIds(op.left, op.origin) && o.right != null) {
|
||||||
// only if not added previously
|
// only if not added previously
|
||||||
tryToRemergeLater.push(o.right)
|
tryToRemergeLater.push(o.right)
|
||||||
}
|
}
|
||||||
o = (o.right == null) ? null : yield * this.getOperation(o.right)
|
o = (o.right == null) ? null : yield* this.getOperation(o.right)
|
||||||
} else { // left == null
|
} else { // left == null
|
||||||
parent = yield * this.getOperation(op.parent)
|
parent = yield* this.getOperation(op.parent)
|
||||||
let startId = op.parentSub ? parent.map[op.parentSub] : parent.start
|
let startId = op.parentSub ? parent.map[op.parentSub] : parent.start
|
||||||
start = startId == null ? null : yield * this.getOperation(startId)
|
start = startId == null ? null : yield* this.getOperation(startId)
|
||||||
o = start
|
o = start
|
||||||
}
|
}
|
||||||
|
|
||||||
// make sure to split op.right if necessary (also add to tryCombineWithLeft)
|
// make sure to split op.right if necessary (also add to tryCombineWithLeft)
|
||||||
if (op.right != null) {
|
if (op.right != null) {
|
||||||
tryToRemergeLater.push(op.right)
|
tryToRemergeLater.push(op.right)
|
||||||
yield * this.getInsertionCleanStart(op.right)
|
yield* this.getInsertionCleanStart(op.right)
|
||||||
}
|
}
|
||||||
|
|
||||||
// handle conflicts
|
// handle conflicts
|
||||||
while (true) {
|
while (true) {
|
||||||
if (o != null && !Y.utils.compareIds(o.id, op.right)) {
|
if (o != null && !Y.utils.compareIds(o.id, op.right)) {
|
||||||
var oOriginDistance = yield * Struct.Insert.getDistanceToOrigin.call(this, o)
|
var oOriginDistance = yield* Struct.Insert.getDistanceToOrigin.call(this, o)
|
||||||
if (oOriginDistance === i) {
|
if (oOriginDistance === i) {
|
||||||
// case 1
|
// case 1
|
||||||
if (o.id[0] < op.id[0]) {
|
if (o.id[0] < op.id[0]) {
|
||||||
@@ -197,7 +193,7 @@ export default function extendStruct (Y) {
|
|||||||
}
|
}
|
||||||
i++
|
i++
|
||||||
if (o.right != null) {
|
if (o.right != null) {
|
||||||
o = yield * this.getInsertion(o.right)
|
o = yield* this.getInsertion(o.right)
|
||||||
} else {
|
} else {
|
||||||
o = null
|
o = null
|
||||||
}
|
}
|
||||||
@@ -210,17 +206,17 @@ export default function extendStruct (Y) {
|
|||||||
var left = null
|
var left = null
|
||||||
var right = null
|
var right = null
|
||||||
if (parent == null) {
|
if (parent == null) {
|
||||||
parent = yield * this.getOperation(op.parent)
|
parent = yield* this.getOperation(op.parent)
|
||||||
}
|
}
|
||||||
|
|
||||||
// reconnect left and set right of op
|
// reconnect left and set right of op
|
||||||
if (op.left != null) {
|
if (op.left != null) {
|
||||||
left = yield * this.getInsertion(op.left)
|
left = yield* this.getInsertion(op.left)
|
||||||
// link left
|
// link left
|
||||||
op.right = left.right
|
op.right = left.right
|
||||||
left.right = op.id
|
left.right = op.id
|
||||||
|
|
||||||
yield * this.setOperation(left)
|
yield* this.setOperation(left)
|
||||||
} else {
|
} else {
|
||||||
// set op.right from parent, if necessary
|
// set op.right from parent, if necessary
|
||||||
op.right = op.parentSub ? parent.map[op.parentSub] || null : parent.start
|
op.right = op.parentSub ? parent.map[op.parentSub] || null : parent.start
|
||||||
@@ -228,33 +224,33 @@ export default function extendStruct (Y) {
|
|||||||
// reconnect right
|
// reconnect right
|
||||||
if (op.right != null) {
|
if (op.right != null) {
|
||||||
// TODO: wanna connect right too?
|
// TODO: wanna connect right too?
|
||||||
right = yield * this.getOperation(op.right)
|
right = yield* this.getOperation(op.right)
|
||||||
right.left = Y.utils.getLastId(op)
|
right.left = Y.utils.getLastId(op)
|
||||||
|
|
||||||
// if right exists, and it is supposed to be gc'd. Remove it from the gc
|
// if right exists, and it is supposed to be gc'd. Remove it from the gc
|
||||||
if (right.gc != null) {
|
if (right.gc != null) {
|
||||||
if (right.content != null && right.content.length > 1) {
|
if (right.content != null && right.content.length > 1) {
|
||||||
right = yield * this.getInsertionCleanEnd(right.id)
|
right = yield* this.getInsertionCleanEnd(right.id)
|
||||||
}
|
}
|
||||||
this.store.removeFromGarbageCollector(right)
|
this.store.removeFromGarbageCollector(right)
|
||||||
}
|
}
|
||||||
yield * this.setOperation(right)
|
yield* this.setOperation(right)
|
||||||
}
|
}
|
||||||
|
|
||||||
// update parents .map/start/end properties
|
// update parents .map/start/end properties
|
||||||
if (op.parentSub != null) {
|
if (op.parentSub != null) {
|
||||||
if (left == null) {
|
if (left == null) {
|
||||||
parent.map[op.parentSub] = op.id
|
parent.map[op.parentSub] = op.id
|
||||||
yield * this.setOperation(parent)
|
yield* this.setOperation(parent)
|
||||||
}
|
}
|
||||||
// is a child of a map struct.
|
// is a child of a map struct.
|
||||||
// Then also make sure that only the most left element is not deleted
|
// Then also make sure that only the most left element is not deleted
|
||||||
// We do not call the type in this case (this is what the third parameter is for)
|
// We do not call the type in this case (this is what the third parameter is for)
|
||||||
if (op.right != null) {
|
if (op.right != null) {
|
||||||
yield * this.deleteOperation(op.right, 1, true)
|
yield* this.deleteOperation(op.right, 1, true)
|
||||||
}
|
}
|
||||||
if (op.left != null) {
|
if (op.left != null) {
|
||||||
yield * this.deleteOperation(op.id, 1, true)
|
yield* this.deleteOperation(op.id, 1, true)
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
if (right == null || left == null) {
|
if (right == null || left == null) {
|
||||||
@@ -264,14 +260,14 @@ export default function extendStruct (Y) {
|
|||||||
if (left == null) {
|
if (left == null) {
|
||||||
parent.start = op.id
|
parent.start = op.id
|
||||||
}
|
}
|
||||||
yield * this.setOperation(parent)
|
yield* this.setOperation(parent)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// try to merge original op.left and op.origin
|
// try to merge original op.left and op.origin
|
||||||
for (i = 0; i < tryToRemergeLater.length; i++) {
|
for (let i = 0; i < tryToRemergeLater.length; i++) {
|
||||||
var m = yield * this.getOperation(tryToRemergeLater[i])
|
var m = yield* this.getOperation(tryToRemergeLater[i])
|
||||||
yield * this.tryCombineWithLeft(m)
|
yield* this.tryCombineWithLeft(m)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@@ -329,7 +325,7 @@ export default function extendStruct (Y) {
|
|||||||
return null
|
return null
|
||||||
}
|
}
|
||||||
var res = null
|
var res = null
|
||||||
var o = yield * this.getOperation(op.start)
|
var o = yield* this.getOperation(op.start)
|
||||||
|
|
||||||
while (true) {
|
while (true) {
|
||||||
if (!o.deleted) {
|
if (!o.deleted) {
|
||||||
@@ -337,7 +333,7 @@ export default function extendStruct (Y) {
|
|||||||
pos--
|
pos--
|
||||||
}
|
}
|
||||||
if (pos >= 0 && o.right != null) {
|
if (pos >= 0 && o.right != null) {
|
||||||
o = yield * this.getOperation(o.right)
|
o = yield* this.getOperation(o.right)
|
||||||
} else {
|
} else {
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
@@ -348,7 +344,7 @@ export default function extendStruct (Y) {
|
|||||||
o = o.start
|
o = o.start
|
||||||
var res = []
|
var res = []
|
||||||
while (o != null) { // TODO: change to != (at least some convention)
|
while (o != null) { // TODO: change to != (at least some convention)
|
||||||
var operation = yield * this.getOperation(o)
|
var operation = yield* this.getOperation(o)
|
||||||
if (!operation.deleted) {
|
if (!operation.deleted) {
|
||||||
res.push(f(operation))
|
res.push(f(operation))
|
||||||
}
|
}
|
||||||
@@ -398,13 +394,13 @@ export default function extendStruct (Y) {
|
|||||||
get: function * (op, name) {
|
get: function * (op, name) {
|
||||||
var oid = op.map[name]
|
var oid = op.map[name]
|
||||||
if (oid != null) {
|
if (oid != null) {
|
||||||
var res = yield * this.getOperation(oid)
|
var res = yield* this.getOperation(oid)
|
||||||
if (res == null || res.deleted) {
|
if (res == null || res.deleted) {
|
||||||
return void 0
|
return void 0
|
||||||
} else if (res.opContent == null) {
|
} else if (res.opContent == null) {
|
||||||
return res.content[0]
|
return res.content[0]
|
||||||
} else {
|
} else {
|
||||||
return yield * this.getType(res.opContent)
|
return yield* this.getType(res.opContent)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -74,7 +74,7 @@
|
|||||||
- this is called only by `getOperations(startSS)`. It makes an operation
|
- this is called only by `getOperations(startSS)`. It makes an operation
|
||||||
applyable on a given SS.
|
applyable on a given SS.
|
||||||
*/
|
*/
|
||||||
export default function extendTransaction (Y) {
|
module.exports = function (Y/* :any */) {
|
||||||
class TransactionInterface {
|
class TransactionInterface {
|
||||||
/* ::
|
/* ::
|
||||||
store: Y.AbstractDatabase;
|
store: Y.AbstractDatabase;
|
||||||
@@ -82,6 +82,57 @@ export default function extendTransaction (Y) {
|
|||||||
os: Store;
|
os: Store;
|
||||||
ss: Store;
|
ss: Store;
|
||||||
*/
|
*/
|
||||||
|
/*
|
||||||
|
Get a type based on the id of its model.
|
||||||
|
If it does not exist yes, create it.
|
||||||
|
TODO: delete type from store.initializedTypes[id] when corresponding id was deleted!
|
||||||
|
*/
|
||||||
|
* getType (id, args) {
|
||||||
|
var sid = JSON.stringify(id)
|
||||||
|
var t = this.store.initializedTypes[sid]
|
||||||
|
if (t == null) {
|
||||||
|
var op/* :MapStruct | ListStruct */ = yield* this.getOperation(id)
|
||||||
|
if (op != null) {
|
||||||
|
t = yield* Y[op.type].typeDefinition.initType.call(this, this.store, op, args)
|
||||||
|
this.store.initializedTypes[sid] = t
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return t
|
||||||
|
}
|
||||||
|
* createType (typedefinition, id) {
|
||||||
|
var structname = typedefinition[0].struct
|
||||||
|
id = id || this.store.getNextOpId(1)
|
||||||
|
var op
|
||||||
|
if (id[0] === '_') {
|
||||||
|
op = yield* this.getOperation(id)
|
||||||
|
} else {
|
||||||
|
op = Y.Struct[structname].create(id)
|
||||||
|
op.type = typedefinition[0].name
|
||||||
|
}
|
||||||
|
if (typedefinition[0].appendAdditionalInfo != null) {
|
||||||
|
yield* typedefinition[0].appendAdditionalInfo.call(this, op, typedefinition[1])
|
||||||
|
}
|
||||||
|
if (op[0] === '_') {
|
||||||
|
yield* this.setOperation(op)
|
||||||
|
} else {
|
||||||
|
yield* this.applyCreatedOperations([op])
|
||||||
|
}
|
||||||
|
return yield* this.getType(id, typedefinition[1])
|
||||||
|
}
|
||||||
|
/* createType (typedefinition, id) {
|
||||||
|
var structname = typedefinition[0].struct
|
||||||
|
id = id || this.store.getNextOpId(1)
|
||||||
|
var op = Y.Struct[structname].create(id)
|
||||||
|
op.type = typedefinition[0].name
|
||||||
|
if (typedefinition[0].appendAdditionalInfo != null) {
|
||||||
|
yield* typedefinition[0].appendAdditionalInfo.call(this, op, typedefinition[1])
|
||||||
|
}
|
||||||
|
// yield* this.applyCreatedOperations([op])
|
||||||
|
yield* Y.Struct[op.struct].execute.call(this, op)
|
||||||
|
yield* this.addOperation(op)
|
||||||
|
yield* this.store.operationAdded(this, op)
|
||||||
|
return yield* this.getType(id, typedefinition[1])
|
||||||
|
}*/
|
||||||
/*
|
/*
|
||||||
Apply operations that this user created (no remote ones!)
|
Apply operations that this user created (no remote ones!)
|
||||||
* does not check for Struct.*.requiredOps()
|
* does not check for Struct.*.requiredOps()
|
||||||
@@ -91,12 +142,12 @@ export default function extendTransaction (Y) {
|
|||||||
var send = []
|
var send = []
|
||||||
for (var i = 0; i < ops.length; i++) {
|
for (var i = 0; i < ops.length; i++) {
|
||||||
var op = ops[i]
|
var op = ops[i]
|
||||||
yield * this.store.tryExecute.call(this, op)
|
yield* this.store.tryExecute.call(this, op)
|
||||||
if (op.id == null || typeof op.id[1] !== 'string') {
|
if (op.id == null || typeof op.id[1] !== 'string') {
|
||||||
send.push(Y.Struct[op.struct].encode(op))
|
send.push(Y.Struct[op.struct].encode(op))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (this.store.y.connector.isSynced && send.length > 0) { // TODO: && !this.store.forwardAppliedOperations (but then i don't send delete ops)
|
if (!this.store.y.connector.isDisconnected() && send.length > 0) { // TODO: && !this.store.forwardAppliedOperations (but then i don't send delete ops)
|
||||||
// is connected, and this is not going to be send in addOperation
|
// is connected, and this is not going to be send in addOperation
|
||||||
this.store.y.connector.broadcastOps(send)
|
this.store.y.connector.broadcastOps(send)
|
||||||
}
|
}
|
||||||
@@ -104,15 +155,15 @@ export default function extendTransaction (Y) {
|
|||||||
|
|
||||||
* deleteList (start) {
|
* deleteList (start) {
|
||||||
while (start != null) {
|
while (start != null) {
|
||||||
start = yield * this.getOperation(start)
|
start = yield* this.getOperation(start)
|
||||||
if (!start.gc) {
|
if (!start.gc) {
|
||||||
start.gc = true
|
start.gc = true
|
||||||
start.deleted = true
|
start.deleted = true
|
||||||
yield * this.setOperation(start)
|
yield* this.setOperation(start)
|
||||||
var delLength = start.content != null ? start.content.length : 1
|
var delLength = start.content != null ? start.content.length : 1
|
||||||
yield * this.markDeleted(start.id, delLength)
|
yield* this.markDeleted(start.id, delLength)
|
||||||
if (start.opContent != null) {
|
if (start.opContent != null) {
|
||||||
yield * this.deleteOperation(start.opContent)
|
yield* this.deleteOperation(start.opContent)
|
||||||
}
|
}
|
||||||
this.store.queueGarbageCollector(start.id)
|
this.store.queueGarbageCollector(start.id)
|
||||||
}
|
}
|
||||||
@@ -127,10 +178,10 @@ export default function extendTransaction (Y) {
|
|||||||
if (length == null) {
|
if (length == null) {
|
||||||
length = 1
|
length = 1
|
||||||
}
|
}
|
||||||
yield * this.markDeleted(targetId, length)
|
yield* this.markDeleted(targetId, length)
|
||||||
while (length > 0) {
|
while (length > 0) {
|
||||||
var callType = false
|
var callType = false
|
||||||
var target = yield * this.os.findWithUpperBound([targetId[0], targetId[1] + length - 1])
|
var target = yield* this.os.findWithUpperBound([targetId[0], targetId[1] + length - 1])
|
||||||
var targetLength = target != null && target.content != null ? target.content.length : 1
|
var targetLength = target != null && target.content != null ? target.content.length : 1
|
||||||
if (target == null || target.id[0] !== targetId[0] || target.id[1] + targetLength <= targetId[1]) {
|
if (target == null || target.id[0] !== targetId[0] || target.id[1] + targetLength <= targetId[1]) {
|
||||||
// does not exist or is not in the range of the deletion
|
// does not exist or is not in the range of the deletion
|
||||||
@@ -141,12 +192,12 @@ export default function extendTransaction (Y) {
|
|||||||
if (!target.deleted) {
|
if (!target.deleted) {
|
||||||
if (target.id[1] < targetId[1]) {
|
if (target.id[1] < targetId[1]) {
|
||||||
// starts to the left of the deletion range
|
// starts to the left of the deletion range
|
||||||
target = yield * this.getInsertionCleanStart(targetId)
|
target = yield* this.getInsertionCleanStart(targetId)
|
||||||
targetLength = target.content.length // must have content property!
|
targetLength = target.content.length // must have content property!
|
||||||
}
|
}
|
||||||
if (target.id[1] + targetLength > targetId[1] + length) {
|
if (target.id[1] + targetLength > targetId[1] + length) {
|
||||||
// ends to the right of the deletion range
|
// ends to the right of the deletion range
|
||||||
target = yield * this.getInsertionCleanEnd([targetId[0], targetId[1] + length - 1])
|
target = yield* this.getInsertionCleanEnd([targetId[0], targetId[1] + length - 1])
|
||||||
targetLength = target.content.length
|
targetLength = target.content.length
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -161,35 +212,35 @@ export default function extendTransaction (Y) {
|
|||||||
// delete containing lists
|
// delete containing lists
|
||||||
if (target.start != null) {
|
if (target.start != null) {
|
||||||
// TODO: don't do it like this .. -.-
|
// TODO: don't do it like this .. -.-
|
||||||
yield * this.deleteList(target.start)
|
yield* this.deleteList(target.start)
|
||||||
// yield * this.deleteList(target.id) -- do not gc itself because this may still get referenced
|
// yield* this.deleteList(target.id) -- do not gc itself because this may still get referenced
|
||||||
}
|
}
|
||||||
if (target.map != null) {
|
if (target.map != null) {
|
||||||
for (var name in target.map) {
|
for (var name in target.map) {
|
||||||
yield * this.deleteList(target.map[name])
|
yield* this.deleteList(target.map[name])
|
||||||
}
|
}
|
||||||
// TODO: here to.. (see above)
|
// TODO: here to.. (see above)
|
||||||
// yield * this.deleteList(target.id) -- see above
|
// yield* this.deleteList(target.id) -- see above
|
||||||
}
|
}
|
||||||
if (target.opContent != null) {
|
if (target.opContent != null) {
|
||||||
yield * this.deleteOperation(target.opContent)
|
yield* this.deleteOperation(target.opContent)
|
||||||
// target.opContent = null
|
// target.opContent = null
|
||||||
}
|
}
|
||||||
if (target.requires != null) {
|
if (target.requires != null) {
|
||||||
for (var i = 0; i < target.requires.length; i++) {
|
for (var i = 0; i < target.requires.length; i++) {
|
||||||
yield * this.deleteOperation(target.requires[i])
|
yield* this.deleteOperation(target.requires[i])
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
var left
|
var left
|
||||||
if (target.left != null) {
|
if (target.left != null) {
|
||||||
left = yield * this.getInsertion(target.left)
|
left = yield* this.getInsertion(target.left)
|
||||||
} else {
|
} else {
|
||||||
left = null
|
left = null
|
||||||
}
|
}
|
||||||
|
|
||||||
// set here because it was deleted and/or gc'd
|
// set here because it was deleted and/or gc'd
|
||||||
yield * this.setOperation(target)
|
yield* this.setOperation(target)
|
||||||
|
|
||||||
/*
|
/*
|
||||||
Check if it is possible to add right to the gc.
|
Check if it is possible to add right to the gc.
|
||||||
@@ -198,22 +249,21 @@ export default function extendTransaction (Y) {
|
|||||||
*/
|
*/
|
||||||
var right
|
var right
|
||||||
if (target.right != null) {
|
if (target.right != null) {
|
||||||
right = yield * this.getOperation(target.right)
|
right = yield* this.getOperation(target.right)
|
||||||
} else {
|
} else {
|
||||||
right = null
|
right = null
|
||||||
}
|
}
|
||||||
if (callType && !preventCallType) {
|
if (callType && !preventCallType) {
|
||||||
yield * this.store.operationAdded(this, {
|
yield* this.store.operationAdded(this, {
|
||||||
struct: 'Delete',
|
struct: 'Delete',
|
||||||
target: target.id,
|
target: target.id,
|
||||||
length: targetLength,
|
length: targetLength
|
||||||
targetParent: target.parent
|
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
// need to gc in the end!
|
// need to gc in the end!
|
||||||
yield * this.store.addToGarbageCollector.call(this, target, left)
|
yield* this.store.addToGarbageCollector.call(this, target, left)
|
||||||
if (right != null) {
|
if (right != null) {
|
||||||
yield * this.store.addToGarbageCollector.call(this, right, target)
|
yield* this.store.addToGarbageCollector.call(this, right, target)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -223,23 +273,23 @@ export default function extendTransaction (Y) {
|
|||||||
*/
|
*/
|
||||||
* markGarbageCollected (id, len) {
|
* markGarbageCollected (id, len) {
|
||||||
// this.mem.push(["gc", id]);
|
// this.mem.push(["gc", id]);
|
||||||
this.store.addToDebug('yield * this.markGarbageCollected(', id, ', ', len, ')')
|
this.store.addToDebug('yield* this.markGarbageCollected(', id, ', ', len, ')')
|
||||||
var n = yield * this.markDeleted(id, len)
|
var n = yield* this.markDeleted(id, len)
|
||||||
if (n.id[1] < id[1] && !n.gc) {
|
if (n.id[1] < id[1] && !n.gc) {
|
||||||
// un-extend left
|
// un-extend left
|
||||||
var newlen = n.len - (id[1] - n.id[1])
|
var newlen = n.len - (id[1] - n.id[1])
|
||||||
n.len -= newlen
|
n.len -= newlen
|
||||||
yield * this.ds.put(n)
|
yield* this.ds.put(n)
|
||||||
n = {id: id, len: newlen, gc: false}
|
n = {id: id, len: newlen, gc: false}
|
||||||
yield * this.ds.put(n)
|
yield* this.ds.put(n)
|
||||||
}
|
}
|
||||||
// get prev&next before adding a new operation
|
// get prev&next before adding a new operation
|
||||||
var prev = yield * this.ds.findPrev(id)
|
var prev = yield* this.ds.findPrev(id)
|
||||||
var next = yield * this.ds.findNext(id)
|
var next = yield* this.ds.findNext(id)
|
||||||
|
|
||||||
if (id[1] + len < n.id[1] + n.len && !n.gc) {
|
if (id[1] + len < n.id[1] + n.len && !n.gc) {
|
||||||
// un-extend right
|
// un-extend right
|
||||||
yield * this.ds.put({id: [id[0], id[1] + len], len: n.len - len, gc: false})
|
yield* this.ds.put({id: [id[0], id[1] + len], len: n.len - len, gc: false})
|
||||||
n.len = len
|
n.len = len
|
||||||
}
|
}
|
||||||
// set gc'd
|
// set gc'd
|
||||||
@@ -251,7 +301,7 @@ export default function extendTransaction (Y) {
|
|||||||
Y.utils.compareIds([prev.id[0], prev.id[1] + prev.len], n.id)
|
Y.utils.compareIds([prev.id[0], prev.id[1] + prev.len], n.id)
|
||||||
) {
|
) {
|
||||||
prev.len += n.len
|
prev.len += n.len
|
||||||
yield * this.ds.delete(n.id)
|
yield* this.ds.delete(n.id)
|
||||||
n = prev
|
n = prev
|
||||||
// ds.put n here?
|
// ds.put n here?
|
||||||
}
|
}
|
||||||
@@ -262,10 +312,10 @@ export default function extendTransaction (Y) {
|
|||||||
Y.utils.compareIds([n.id[0], n.id[1] + n.len], next.id)
|
Y.utils.compareIds([n.id[0], n.id[1] + n.len], next.id)
|
||||||
) {
|
) {
|
||||||
n.len += next.len
|
n.len += next.len
|
||||||
yield * this.ds.delete(next.id)
|
yield* this.ds.delete(next.id)
|
||||||
}
|
}
|
||||||
yield * this.ds.put(n)
|
yield* this.ds.put(n)
|
||||||
yield * this.updateState(n.id[0])
|
yield* this.updateState(n.id[0])
|
||||||
}
|
}
|
||||||
/*
|
/*
|
||||||
Mark an operation as deleted.
|
Mark an operation as deleted.
|
||||||
@@ -277,7 +327,7 @@ export default function extendTransaction (Y) {
|
|||||||
length = 1
|
length = 1
|
||||||
}
|
}
|
||||||
// this.mem.push(["del", id]);
|
// this.mem.push(["del", id]);
|
||||||
var n = yield * this.ds.findWithUpperBound(id)
|
var n = yield* this.ds.findWithUpperBound(id)
|
||||||
if (n != null && n.id[0] === id[0]) {
|
if (n != null && n.id[0] === id[0]) {
|
||||||
if (n.id[1] <= id[1] && id[1] <= n.id[1] + n.len) {
|
if (n.id[1] <= id[1] && id[1] <= n.id[1] + n.len) {
|
||||||
// id is in n's range
|
// id is in n's range
|
||||||
@@ -291,12 +341,11 @@ export default function extendTransaction (Y) {
|
|||||||
if (diff < length) {
|
if (diff < length) {
|
||||||
// a partial deletion
|
// a partial deletion
|
||||||
n = {id: [id[0], id[1] + diff], len: length - diff, gc: false}
|
n = {id: [id[0], id[1] + diff], len: length - diff, gc: false}
|
||||||
yield * this.ds.put(n)
|
yield* this.ds.put(n)
|
||||||
} else {
|
} else {
|
||||||
// already gc'd
|
// already gc'd
|
||||||
throw new Error(
|
throw new Error('Cannot happen! (it dit though.. :()')
|
||||||
'DS reached an inconsistent state. Please report this issue!'
|
// return n
|
||||||
)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
@@ -306,15 +355,15 @@ export default function extendTransaction (Y) {
|
|||||||
} else {
|
} else {
|
||||||
// cannot extend left (there is no left!)
|
// cannot extend left (there is no left!)
|
||||||
n = {id: id, len: length, gc: false}
|
n = {id: id, len: length, gc: false}
|
||||||
yield * this.ds.put(n) // TODO: you double-put !!
|
yield* this.ds.put(n) // TODO: you double-put !!
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// cannot extend left
|
// cannot extend left
|
||||||
n = {id: id, len: length, gc: false}
|
n = {id: id, len: length, gc: false}
|
||||||
yield * this.ds.put(n)
|
yield* this.ds.put(n)
|
||||||
}
|
}
|
||||||
// can extend right?
|
// can extend right?
|
||||||
var next = yield * this.ds.findNext(n.id)
|
var next = yield* this.ds.findNext(n.id)
|
||||||
if (
|
if (
|
||||||
next != null &&
|
next != null &&
|
||||||
n.id[0] === next.id[0] &&
|
n.id[0] === next.id[0] &&
|
||||||
@@ -330,8 +379,8 @@ export default function extendTransaction (Y) {
|
|||||||
// delete the missing range after next
|
// delete the missing range after next
|
||||||
diff = diff - next.len // missing range after next
|
diff = diff - next.len // missing range after next
|
||||||
if (diff > 0) {
|
if (diff > 0) {
|
||||||
yield * this.ds.put(n) // unneccessary? TODO!
|
yield* this.ds.put(n) // unneccessary? TODO!
|
||||||
yield * this.markDeleted([next.id[0], next.id[1] + next.len], diff)
|
yield* this.markDeleted([next.id[0], next.id[1] + next.len], diff)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
break
|
break
|
||||||
@@ -340,8 +389,8 @@ export default function extendTransaction (Y) {
|
|||||||
if (diff > next.len) {
|
if (diff > next.len) {
|
||||||
// n is even longer than next
|
// n is even longer than next
|
||||||
// get next.next, and try to extend it
|
// get next.next, and try to extend it
|
||||||
var _next = yield * this.ds.findNext(next.id)
|
var _next = yield* this.ds.findNext(next.id)
|
||||||
yield * this.ds.delete(next.id)
|
yield* this.ds.delete(next.id)
|
||||||
if (_next == null || n.id[0] !== _next.id[0]) {
|
if (_next == null || n.id[0] !== _next.id[0]) {
|
||||||
break
|
break
|
||||||
} else {
|
} else {
|
||||||
@@ -352,13 +401,13 @@ export default function extendTransaction (Y) {
|
|||||||
} else {
|
} else {
|
||||||
// n just partially overlaps with next. extend n, delete next, and break this loop
|
// n just partially overlaps with next. extend n, delete next, and break this loop
|
||||||
n.len += next.len - diff
|
n.len += next.len - diff
|
||||||
yield * this.ds.delete(next.id)
|
yield* this.ds.delete(next.id)
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
yield * this.ds.put(n)
|
yield* this.ds.put(n)
|
||||||
return n
|
return n
|
||||||
}
|
}
|
||||||
/*
|
/*
|
||||||
@@ -367,35 +416,31 @@ export default function extendTransaction (Y) {
|
|||||||
operations that can be gc'd and add them to the garbage collector.
|
operations that can be gc'd and add them to the garbage collector.
|
||||||
*/
|
*/
|
||||||
* garbageCollectAfterSync () {
|
* garbageCollectAfterSync () {
|
||||||
// debugger
|
|
||||||
if (this.store.gc1.length > 0 || this.store.gc2.length > 0) {
|
if (this.store.gc1.length > 0 || this.store.gc2.length > 0) {
|
||||||
console.warn('gc should be empty after sync')
|
console.warn('gc should be empty after sync')
|
||||||
}
|
}
|
||||||
if (!this.store.gc) {
|
yield* this.os.iterate(this, null, null, function * (op) {
|
||||||
return
|
|
||||||
}
|
|
||||||
yield * this.os.iterate(this, null, null, function * (op) {
|
|
||||||
if (op.gc) {
|
if (op.gc) {
|
||||||
delete op.gc
|
delete op.gc
|
||||||
yield * this.setOperation(op)
|
yield* this.setOperation(op)
|
||||||
}
|
}
|
||||||
if (op.parent != null) {
|
if (op.parent != null) {
|
||||||
var parentDeleted = yield * this.isDeleted(op.parent)
|
var parentDeleted = yield* this.isDeleted(op.parent)
|
||||||
if (parentDeleted) {
|
if (parentDeleted) {
|
||||||
op.gc = true
|
op.gc = true
|
||||||
if (!op.deleted) {
|
if (!op.deleted) {
|
||||||
yield * this.markDeleted(op.id, op.content != null ? op.content.length : 1)
|
yield* this.markDeleted(op.id, op.content != null ? op.content.length : 1)
|
||||||
op.deleted = true
|
op.deleted = true
|
||||||
if (op.opContent != null) {
|
if (op.opContent != null) {
|
||||||
yield * this.deleteOperation(op.opContent)
|
yield* this.deleteOperation(op.opContent)
|
||||||
}
|
}
|
||||||
if (op.requires != null) {
|
if (op.requires != null) {
|
||||||
for (var i = 0; i < op.requires.length; i++) {
|
for (var i = 0; i < op.requires.length; i++) {
|
||||||
yield * this.deleteOperation(op.requires[i])
|
yield* this.deleteOperation(op.requires[i])
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
yield * this.setOperation(op)
|
yield* this.setOperation(op)
|
||||||
this.store.gc1.push(op.id) // this is ok becaues its shortly before sync (otherwise use queueGarbageCollector!)
|
this.store.gc1.push(op.id) // this is ok becaues its shortly before sync (otherwise use queueGarbageCollector!)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -403,9 +448,9 @@ export default function extendTransaction (Y) {
|
|||||||
if (op.deleted) {
|
if (op.deleted) {
|
||||||
var left = null
|
var left = null
|
||||||
if (op.left != null) {
|
if (op.left != null) {
|
||||||
left = yield * this.getInsertion(op.left)
|
left = yield* this.getInsertion(op.left)
|
||||||
}
|
}
|
||||||
yield * this.store.addToGarbageCollector.call(this, op, left)
|
yield* this.store.addToGarbageCollector.call(this, op, left)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
@@ -419,9 +464,9 @@ export default function extendTransaction (Y) {
|
|||||||
* reset origins of all right ops
|
* reset origins of all right ops
|
||||||
*/
|
*/
|
||||||
* garbageCollectOperation (id) {
|
* garbageCollectOperation (id) {
|
||||||
this.store.addToDebug('yield * this.garbageCollectOperation(', id, ')')
|
this.store.addToDebug('yield* this.garbageCollectOperation(', id, ')')
|
||||||
var o = yield * this.getOperation(id)
|
var o = yield* this.getOperation(id)
|
||||||
yield * this.markGarbageCollected(id, (o != null && o.content != null) ? o.content.length : 1) // always mark gc'd
|
yield* this.markGarbageCollected(id, (o != null && o.content != null) ? o.content.length : 1) // always mark gc'd
|
||||||
// if op exists, then clean that mess up..
|
// if op exists, then clean that mess up..
|
||||||
if (o != null) {
|
if (o != null) {
|
||||||
var deps = []
|
var deps = []
|
||||||
@@ -432,37 +477,45 @@ export default function extendTransaction (Y) {
|
|||||||
deps = deps.concat(o.requires)
|
deps = deps.concat(o.requires)
|
||||||
}
|
}
|
||||||
for (var i = 0; i < deps.length; i++) {
|
for (var i = 0; i < deps.length; i++) {
|
||||||
var dep = yield * this.getOperation(deps[i])
|
var dep = yield* this.getOperation(deps[i])
|
||||||
if (dep != null) {
|
if (dep != null) {
|
||||||
if (!dep.deleted) {
|
if (!dep.deleted) {
|
||||||
yield * this.deleteOperation(dep.id)
|
yield* this.deleteOperation(dep.id)
|
||||||
dep = yield * this.getOperation(dep.id)
|
dep = yield* this.getOperation(dep.id)
|
||||||
}
|
}
|
||||||
dep.gc = true
|
dep.gc = true
|
||||||
yield * this.setOperation(dep)
|
yield* this.setOperation(dep)
|
||||||
this.store.queueGarbageCollector(dep.id)
|
this.store.queueGarbageCollector(dep.id)
|
||||||
} else {
|
} else {
|
||||||
yield * this.markGarbageCollected(deps[i], 1)
|
yield* this.markGarbageCollected(deps[i], 1)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// remove gc'd op from the left op, if it exists
|
// remove gc'd op from the left op, if it exists
|
||||||
if (o.left != null) {
|
if (o.left != null) {
|
||||||
var left = yield * this.getInsertion(o.left)
|
var left = yield* this.getInsertion(o.left)
|
||||||
left.right = o.right
|
left.right = o.right
|
||||||
yield * this.setOperation(left)
|
yield* this.setOperation(left)
|
||||||
}
|
}
|
||||||
// remove gc'd op from the right op, if it exists
|
// remove gc'd op from the right op, if it exists
|
||||||
// also reset origins of right ops
|
// also reset origins of right ops
|
||||||
if (o.right != null) {
|
if (o.right != null) {
|
||||||
var right = yield * this.getOperation(o.right)
|
var right = yield* this.getOperation(o.right)
|
||||||
right.left = o.left
|
right.left = o.left
|
||||||
yield * this.setOperation(right)
|
yield* this.setOperation(right)
|
||||||
|
|
||||||
if (o.originOf != null && o.originOf.length > 0) {
|
if (o.originOf != null && o.originOf.length > 0) {
|
||||||
// find new origin of right ops
|
// find new origin of right ops
|
||||||
// origin is the first left operation
|
// origin is the first left deleted operation
|
||||||
var neworigin = o.left
|
var neworigin = o.left
|
||||||
|
var neworigin_ = null
|
||||||
|
while (neworigin != null) {
|
||||||
|
neworigin_ = yield* this.getInsertion(neworigin)
|
||||||
|
if (neworigin_.deleted) {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
neworigin = neworigin_.left
|
||||||
|
}
|
||||||
|
|
||||||
// reset origin of all right ops (except first right - duh!),
|
// reset origin of all right ops (except first right - duh!),
|
||||||
|
|
||||||
@@ -476,7 +529,7 @@ export default function extendTransaction (Y) {
|
|||||||
right.origin = neworigin
|
right.origin = neworigin
|
||||||
// search until you find origin pointer to the left of o
|
// search until you find origin pointer to the left of o
|
||||||
if (right.right != null) {
|
if (right.right != null) {
|
||||||
var i = yield * this.getOperation(right.right)
|
var i = yield* this.getOperation(right.right)
|
||||||
var ids = [o.id, o.right]
|
var ids = [o.id, o.right]
|
||||||
while (ids.some(function (id) {
|
while (ids.some(function (id) {
|
||||||
return Y.utils.compareIds(id, i.origin)
|
return Y.utils.compareIds(id, i.origin)
|
||||||
@@ -484,14 +537,14 @@ export default function extendTransaction (Y) {
|
|||||||
if (Y.utils.compareIds(i.origin, o.id)) {
|
if (Y.utils.compareIds(i.origin, o.id)) {
|
||||||
// reset origin of i
|
// reset origin of i
|
||||||
i.origin = neworigin
|
i.origin = neworigin
|
||||||
yield * this.setOperation(i)
|
yield* this.setOperation(i)
|
||||||
}
|
}
|
||||||
// get next i
|
// get next i
|
||||||
if (i.right == null) {
|
if (i.right == null) {
|
||||||
break
|
break
|
||||||
} else {
|
} else {
|
||||||
ids.push(i.id)
|
ids.push(i.id)
|
||||||
i = yield * this.getOperation(i.right)
|
i = yield* this.getOperation(i.right)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -500,20 +553,19 @@ export default function extendTransaction (Y) {
|
|||||||
// ** Now the new implementation starts **
|
// ** Now the new implementation starts **
|
||||||
// reset neworigin of all originOf[*]
|
// reset neworigin of all originOf[*]
|
||||||
for (var _i in o.originOf) {
|
for (var _i in o.originOf) {
|
||||||
var originsIn = yield * this.getOperation(o.originOf[_i])
|
var originsIn = yield* this.getOperation(o.originOf[_i])
|
||||||
if (originsIn != null) {
|
if (originsIn != null) {
|
||||||
originsIn.origin = neworigin
|
originsIn.origin = neworigin
|
||||||
yield * this.setOperation(originsIn)
|
yield* this.setOperation(originsIn)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (neworigin != null) {
|
if (neworigin != null) {
|
||||||
var neworigin_ = yield * this.getInsertion(neworigin)
|
|
||||||
if (neworigin_.originOf == null) {
|
if (neworigin_.originOf == null) {
|
||||||
neworigin_.originOf = o.originOf
|
neworigin_.originOf = o.originOf
|
||||||
} else {
|
} else {
|
||||||
neworigin_.originOf = o.originOf.concat(neworigin_.originOf)
|
neworigin_.originOf = o.originOf.concat(neworigin_.originOf)
|
||||||
}
|
}
|
||||||
yield * this.setOperation(neworigin_)
|
yield* this.setOperation(neworigin_)
|
||||||
}
|
}
|
||||||
// we don't need to set right here, because
|
// we don't need to set right here, because
|
||||||
// right should be in o.originOf => it is set it the previous for loop
|
// right should be in o.originOf => it is set it the previous for loop
|
||||||
@@ -522,15 +574,15 @@ export default function extendTransaction (Y) {
|
|||||||
// o may originate in another operation.
|
// o may originate in another operation.
|
||||||
// Since o is deleted, we have to reset o.origin's `originOf` property
|
// Since o is deleted, we have to reset o.origin's `originOf` property
|
||||||
if (o.origin != null) {
|
if (o.origin != null) {
|
||||||
var origin = yield * this.getInsertion(o.origin)
|
var origin = yield* this.getInsertion(o.origin)
|
||||||
origin.originOf = origin.originOf.filter(function (_id) {
|
origin.originOf = origin.originOf.filter(function (_id) {
|
||||||
return !Y.utils.compareIds(id, _id)
|
return !Y.utils.compareIds(id, _id)
|
||||||
})
|
})
|
||||||
yield * this.setOperation(origin)
|
yield* this.setOperation(origin)
|
||||||
}
|
}
|
||||||
var parent
|
var parent
|
||||||
if (o.parent != null) {
|
if (o.parent != null) {
|
||||||
parent = yield * this.getOperation(o.parent)
|
parent = yield* this.getOperation(o.parent)
|
||||||
}
|
}
|
||||||
// remove gc'd op from parent, if it exists
|
// remove gc'd op from parent, if it exists
|
||||||
if (parent != null) {
|
if (parent != null) {
|
||||||
@@ -557,32 +609,32 @@ export default function extendTransaction (Y) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (setParent) {
|
if (setParent) {
|
||||||
yield * this.setOperation(parent)
|
yield* this.setOperation(parent)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// finally remove it from the os
|
// finally remove it from the os
|
||||||
yield * this.removeOperation(o.id)
|
yield* this.removeOperation(o.id)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
* checkDeleteStoreForState (state) {
|
* checkDeleteStoreForState (state) {
|
||||||
var n = yield * this.ds.findWithUpperBound([state.user, state.clock])
|
var n = yield* this.ds.findWithUpperBound([state.user, state.clock])
|
||||||
if (n != null && n.id[0] === state.user && n.gc) {
|
if (n != null && n.id[0] === state.user && n.gc) {
|
||||||
state.clock = Math.max(state.clock, n.id[1] + n.len)
|
state.clock = Math.max(state.clock, n.id[1] + n.len)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
* updateState (user) {
|
* updateState (user) {
|
||||||
var state = yield * this.getState(user)
|
var state = yield* this.getState(user)
|
||||||
yield * this.checkDeleteStoreForState(state)
|
yield* this.checkDeleteStoreForState(state)
|
||||||
var o = yield * this.getInsertion([user, state.clock])
|
var o = yield* this.getInsertion([user, state.clock])
|
||||||
var oLength = (o != null && o.content != null) ? o.content.length : 1
|
var oLength = (o != null && o.content != null) ? o.content.length : 1
|
||||||
while (o != null && user === o.id[0] && o.id[1] <= state.clock && o.id[1] + oLength > state.clock) {
|
while (o != null && user === o.id[0] && o.id[1] <= state.clock && o.id[1] + oLength > state.clock) {
|
||||||
// either its a new operation (1. case), or it is an operation that was deleted, but is not yet in the OS
|
// either its a new operation (1. case), or it is an operation that was deleted, but is not yet in the OS
|
||||||
state.clock += oLength
|
state.clock += oLength
|
||||||
yield * this.checkDeleteStoreForState(state)
|
yield* this.checkDeleteStoreForState(state)
|
||||||
o = yield * this.os.findNext(o.id)
|
o = yield* this.os.findNext(o.id)
|
||||||
oLength = (o != null && o.content != null) ? o.content.length : 1
|
oLength = (o != null && o.content != null) ? o.content.length : 1
|
||||||
}
|
}
|
||||||
yield * this.setState(state)
|
yield* this.setState(state)
|
||||||
}
|
}
|
||||||
/*
|
/*
|
||||||
apply a delete set in order to get
|
apply a delete set in order to get
|
||||||
@@ -595,7 +647,7 @@ export default function extendTransaction (Y) {
|
|||||||
var dv = ds[user]
|
var dv = ds[user]
|
||||||
var pos = 0
|
var pos = 0
|
||||||
var d = dv[pos]
|
var d = dv[pos]
|
||||||
yield * this.ds.iterate(this, [user, 0], [user, Number.MAX_VALUE], function * (n) {
|
yield* this.ds.iterate(this, [user, 0], [user, Number.MAX_VALUE], function * (n) {
|
||||||
// cases:
|
// cases:
|
||||||
// 1. d deletes something to the right of n
|
// 1. d deletes something to the right of n
|
||||||
// => go to next n (break)
|
// => go to next n (break)
|
||||||
@@ -644,14 +696,14 @@ export default function extendTransaction (Y) {
|
|||||||
for (var i = 0; i < deletions.length; i++) {
|
for (var i = 0; i < deletions.length; i++) {
|
||||||
var del = deletions[i]
|
var del = deletions[i]
|
||||||
// always try to delete..
|
// always try to delete..
|
||||||
yield * this.deleteOperation([del[0], del[1]], del[2])
|
yield* this.deleteOperation([del[0], del[1]], del[2])
|
||||||
if (del[3]) {
|
if (del[3]) {
|
||||||
// gc..
|
// gc..
|
||||||
yield * this.markGarbageCollected([del[0], del[1]], del[2]) // always mark gc'd
|
yield* this.markGarbageCollected([del[0], del[1]], del[2]) // always mark gc'd
|
||||||
// remove operation..
|
// remove operation..
|
||||||
var counter = del[1] + del[2]
|
var counter = del[1] + del[2]
|
||||||
while (counter >= del[1]) {
|
while (counter >= del[1]) {
|
||||||
var o = yield * this.os.findWithUpperBound([del[0], counter - 1])
|
var o = yield* this.os.findWithUpperBound([del[0], counter - 1])
|
||||||
if (o == null) {
|
if (o == null) {
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
@@ -662,25 +714,25 @@ export default function extendTransaction (Y) {
|
|||||||
}
|
}
|
||||||
if (o.id[1] + oLen > del[1] + del[2]) {
|
if (o.id[1] + oLen > del[1] + del[2]) {
|
||||||
// overlaps right
|
// overlaps right
|
||||||
o = yield * this.getInsertionCleanEnd([del[0], del[1] + del[2] - 1])
|
o = yield* this.getInsertionCleanEnd([del[0], del[1] + del[2] - 1])
|
||||||
}
|
}
|
||||||
if (o.id[1] < del[1]) {
|
if (o.id[1] < del[1]) {
|
||||||
// overlaps left
|
// overlaps left
|
||||||
o = yield * this.getInsertionCleanStart([del[0], del[1]])
|
o = yield* this.getInsertionCleanStart([del[0], del[1]])
|
||||||
}
|
}
|
||||||
counter = o.id[1]
|
counter = o.id[1]
|
||||||
yield * this.garbageCollectOperation(o.id)
|
yield* this.garbageCollectOperation(o.id)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (this.store.forwardAppliedOperations) {
|
if (this.store.forwardAppliedOperations) {
|
||||||
var ops = []
|
var ops = []
|
||||||
ops.push({struct: 'Delete', target: [del[0], del[1]], length: del[2]})
|
ops.push({struct: 'Delete', target: [d[0], d[1]], length: del[2]})
|
||||||
this.store.y.connector.broadcastOps(ops)
|
this.store.y.connector.broadcastOps(ops)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
* isGarbageCollected (id) {
|
* isGarbageCollected (id) {
|
||||||
var n = yield * this.ds.findWithUpperBound(id)
|
var n = yield* this.ds.findWithUpperBound(id)
|
||||||
return n != null && n.id[0] === id[0] && id[1] < n.id[1] + n.len && n.gc
|
return n != null && n.id[0] === id[0] && id[1] < n.id[1] + n.len && n.gc
|
||||||
}
|
}
|
||||||
/*
|
/*
|
||||||
@@ -688,7 +740,7 @@ export default function extendTransaction (Y) {
|
|||||||
*/
|
*/
|
||||||
* getDeleteSet () {
|
* getDeleteSet () {
|
||||||
var ds = {}
|
var ds = {}
|
||||||
yield * this.ds.iterate(this, null, null, function * (n) {
|
yield* this.ds.iterate(this, null, null, function * (n) {
|
||||||
var user = n.id[0]
|
var user = n.id[0]
|
||||||
var counter = n.id[1]
|
var counter = n.id[1]
|
||||||
var len = n.len
|
var len = n.len
|
||||||
@@ -703,16 +755,16 @@ export default function extendTransaction (Y) {
|
|||||||
return ds
|
return ds
|
||||||
}
|
}
|
||||||
* isDeleted (id) {
|
* isDeleted (id) {
|
||||||
var n = yield * this.ds.findWithUpperBound(id)
|
var n = yield* this.ds.findWithUpperBound(id)
|
||||||
return n != null && n.id[0] === id[0] && id[1] < n.id[1] + n.len
|
return n != null && n.id[0] === id[0] && id[1] < n.id[1] + n.len
|
||||||
}
|
}
|
||||||
* setOperation (op) {
|
* setOperation (op) {
|
||||||
yield * this.os.put(op)
|
yield* this.os.put(op)
|
||||||
return op
|
return op
|
||||||
}
|
}
|
||||||
* addOperation (op) {
|
* addOperation (op) {
|
||||||
yield * this.os.put(op)
|
yield* this.os.put(op)
|
||||||
if (this.store.y.connector.isSynced && this.store.forwardAppliedOperations && typeof op.id[1] !== 'string') {
|
if (!this.store.y.connector.isDisconnected() && this.store.forwardAppliedOperations && typeof op.id[1] !== 'string') {
|
||||||
// is connected, and this is not going to be send in addOperation
|
// is connected, and this is not going to be send in addOperation
|
||||||
this.store.y.connector.broadcastOps([op])
|
this.store.y.connector.broadcastOps([op])
|
||||||
}
|
}
|
||||||
@@ -726,7 +778,7 @@ export default function extendTransaction (Y) {
|
|||||||
op.left[0] === op.id[0] &&
|
op.left[0] === op.id[0] &&
|
||||||
Y.utils.compareIds(op.left, op.origin)
|
Y.utils.compareIds(op.left, op.origin)
|
||||||
) {
|
) {
|
||||||
var left = yield * this.getInsertion(op.left)
|
var left = yield* this.getInsertion(op.left)
|
||||||
if (left.content != null &&
|
if (left.content != null &&
|
||||||
left.id[1] + left.content.length === op.id[1] &&
|
left.id[1] + left.content.length === op.id[1] &&
|
||||||
left.originOf.length === 1 &&
|
left.originOf.length === 1 &&
|
||||||
@@ -741,13 +793,13 @@ export default function extendTransaction (Y) {
|
|||||||
}
|
}
|
||||||
left.content = left.content.concat(op.content)
|
left.content = left.content.concat(op.content)
|
||||||
left.right = op.right
|
left.right = op.right
|
||||||
yield * this.os.delete(op.id)
|
yield* this.os.delete(op.id)
|
||||||
yield * this.setOperation(left)
|
yield* this.setOperation(left)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
* getInsertion (id) {
|
* getInsertion (id) {
|
||||||
var ins = yield * this.os.findWithUpperBound(id)
|
var ins = yield* this.os.findWithUpperBound(id)
|
||||||
if (ins == null) {
|
if (ins == null) {
|
||||||
return null
|
return null
|
||||||
} else {
|
} else {
|
||||||
@@ -760,13 +812,13 @@ export default function extendTransaction (Y) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
* getInsertionCleanStartEnd (id) {
|
* getInsertionCleanStartEnd (id) {
|
||||||
yield * this.getInsertionCleanStart(id)
|
yield* this.getInsertionCleanStart(id)
|
||||||
return yield * this.getInsertionCleanEnd(id)
|
return yield* this.getInsertionCleanEnd(id)
|
||||||
}
|
}
|
||||||
// Return an insertion such that id is the first element of content
|
// Return an insertion such that id is the first element of content
|
||||||
// This function manipulates an operation, if necessary
|
// This function manipulates an operation, if necessary
|
||||||
* getInsertionCleanStart (id) {
|
* getInsertionCleanStart (id) {
|
||||||
var ins = yield * this.getInsertion(id)
|
var ins = yield* this.getInsertion(id)
|
||||||
if (ins != null) {
|
if (ins != null) {
|
||||||
if (ins.id[1] === id[1]) {
|
if (ins.id[1] === id[1]) {
|
||||||
return ins
|
return ins
|
||||||
@@ -780,8 +832,8 @@ export default function extendTransaction (Y) {
|
|||||||
left.right = ins.id
|
left.right = ins.id
|
||||||
ins.left = leftLid
|
ins.left = leftLid
|
||||||
// debugger // check
|
// debugger // check
|
||||||
yield * this.setOperation(left)
|
yield* this.setOperation(left)
|
||||||
yield * this.setOperation(ins)
|
yield* this.setOperation(ins)
|
||||||
if (left.gc) {
|
if (left.gc) {
|
||||||
this.store.queueGarbageCollector(ins.id)
|
this.store.queueGarbageCollector(ins.id)
|
||||||
}
|
}
|
||||||
@@ -794,7 +846,7 @@ export default function extendTransaction (Y) {
|
|||||||
// Return an insertion such that id is the last element of content
|
// Return an insertion such that id is the last element of content
|
||||||
// This function manipulates an operation, if necessary
|
// This function manipulates an operation, if necessary
|
||||||
* getInsertionCleanEnd (id) {
|
* getInsertionCleanEnd (id) {
|
||||||
var ins = yield * this.getInsertion(id)
|
var ins = yield* this.getInsertion(id)
|
||||||
if (ins != null) {
|
if (ins != null) {
|
||||||
if (ins.content == null || (ins.id[1] + ins.content.length - 1 === id[1])) {
|
if (ins.content == null || (ins.id[1] + ins.content.length - 1 === id[1])) {
|
||||||
return ins
|
return ins
|
||||||
@@ -808,8 +860,8 @@ export default function extendTransaction (Y) {
|
|||||||
ins.right = right.id
|
ins.right = right.id
|
||||||
right.left = insLid
|
right.left = insLid
|
||||||
// debugger // check
|
// debugger // check
|
||||||
yield * this.setOperation(right)
|
yield* this.setOperation(right)
|
||||||
yield * this.setOperation(ins)
|
yield* this.setOperation(ins)
|
||||||
if (ins.gc) {
|
if (ins.gc) {
|
||||||
this.store.queueGarbageCollector(right.id)
|
this.store.queueGarbageCollector(right.id)
|
||||||
}
|
}
|
||||||
@@ -820,7 +872,7 @@ export default function extendTransaction (Y) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
* getOperation (id/* :any */)/* :Transaction<any> */ {
|
* getOperation (id/* :any */)/* :Transaction<any> */ {
|
||||||
var o = yield * this.os.find(id)
|
var o = yield* this.os.find(id)
|
||||||
if (id[0] !== '_' || o != null) {
|
if (id[0] !== '_' || o != null) {
|
||||||
return o
|
return o
|
||||||
} else { // type is string
|
} else { // type is string
|
||||||
@@ -830,28 +882,28 @@ export default function extendTransaction (Y) {
|
|||||||
var struct = comp[0]
|
var struct = comp[0]
|
||||||
var op = Y.Struct[struct].create(id)
|
var op = Y.Struct[struct].create(id)
|
||||||
op.type = comp[1]
|
op.type = comp[1]
|
||||||
yield * this.setOperation(op)
|
yield* this.setOperation(op)
|
||||||
return op
|
return op
|
||||||
} else {
|
} else {
|
||||||
throw new Error(
|
// won't be called. but just in case..
|
||||||
'Unexpected case. Operation cannot be generated correctly!' +
|
console.error('Unexpected case. How can this happen?')
|
||||||
'Incompatible Yjs version?'
|
debugger // eslint-disable-line
|
||||||
)
|
return null
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
* removeOperation (id) {
|
* removeOperation (id) {
|
||||||
yield * this.os.delete(id)
|
yield* this.os.delete(id)
|
||||||
}
|
}
|
||||||
* setState (state) {
|
* setState (state) {
|
||||||
var val = {
|
var val = {
|
||||||
id: [state.user],
|
id: [state.user],
|
||||||
clock: state.clock
|
clock: state.clock
|
||||||
}
|
}
|
||||||
yield * this.ss.put(val)
|
yield* this.ss.put(val)
|
||||||
}
|
}
|
||||||
* getState (user) {
|
* getState (user) {
|
||||||
var n = yield * this.ss.find([user])
|
var n = yield* this.ss.find([user])
|
||||||
var clock = n == null ? null : n.clock
|
var clock = n == null ? null : n.clock
|
||||||
if (clock == null) {
|
if (clock == null) {
|
||||||
clock = 0
|
clock = 0
|
||||||
@@ -863,7 +915,7 @@ export default function extendTransaction (Y) {
|
|||||||
}
|
}
|
||||||
* getStateVector () {
|
* getStateVector () {
|
||||||
var stateVector = []
|
var stateVector = []
|
||||||
yield * this.ss.iterate(this, null, null, function * (n) {
|
yield* this.ss.iterate(this, null, null, function * (n) {
|
||||||
stateVector.push({
|
stateVector.push({
|
||||||
user: n.id[0],
|
user: n.id[0],
|
||||||
clock: n.clock
|
clock: n.clock
|
||||||
@@ -873,7 +925,7 @@ export default function extendTransaction (Y) {
|
|||||||
}
|
}
|
||||||
* getStateSet () {
|
* getStateSet () {
|
||||||
var ss = {}
|
var ss = {}
|
||||||
yield * this.ss.iterate(this, null, null, function * (n) {
|
yield* this.ss.iterate(this, null, null, function * (n) {
|
||||||
ss[n.id[0]] = n.clock
|
ss[n.id[0]] = n.clock
|
||||||
})
|
})
|
||||||
return ss
|
return ss
|
||||||
@@ -931,65 +983,53 @@ export default function extendTransaction (Y) {
|
|||||||
}
|
}
|
||||||
var send = []
|
var send = []
|
||||||
|
|
||||||
var endSV = yield * this.getStateVector()
|
var endSV = yield* this.getStateVector()
|
||||||
for (let endState of endSV) {
|
for (var endState of endSV) {
|
||||||
let user = endState.user
|
var user = endState.user
|
||||||
if (user === '_') {
|
if (user === '_') {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
let startPos = startSS[user] || 0
|
var startPos = startSS[user] || 0
|
||||||
if (startPos > 0) {
|
if (startPos > 0) {
|
||||||
// There is a change that [user, startPos] is in a composed Insertion (with a smaller counter)
|
// There is a change that [user, startPos] is in a composed Insertion (with a smaller counter)
|
||||||
// find out if that is the case
|
// find out if that is the case
|
||||||
let firstMissing = yield * this.getInsertion([user, startPos])
|
var firstMissing = yield* this.getInsertion([user, startPos])
|
||||||
if (firstMissing != null) {
|
if (firstMissing != null) {
|
||||||
// update startPos
|
// update startPos
|
||||||
startPos = firstMissing.id[1]
|
startPos = firstMissing.id[1]
|
||||||
|
startSS[user] = startPos
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
startSS[user] = startPos
|
yield* this.os.iterate(this, [user, startPos], [user, Number.MAX_VALUE], function * (op) {
|
||||||
}
|
|
||||||
for (let endState of endSV) {
|
|
||||||
let user = endState.user
|
|
||||||
let startPos = startSS[user]
|
|
||||||
if (user === '_') {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
yield * this.os.iterate(this, [user, startPos], [user, Number.MAX_VALUE], function * (op) {
|
|
||||||
op = Y.Struct[op.struct].encode(op)
|
op = Y.Struct[op.struct].encode(op)
|
||||||
if (op.struct !== 'Insert') {
|
if (op.struct !== 'Insert') {
|
||||||
send.push(op)
|
send.push(op)
|
||||||
} else if (op.right == null || op.right[1] < (startSS[op.right[0]] || 0)) {
|
} else if (op.right == null || op.right[1] < (startSS[op.right[0]] || 0)) {
|
||||||
// case 1. op.right is known
|
// case 1. op.right is known
|
||||||
// this case is only reached if op.right is known.
|
var o = op
|
||||||
// => this is not called for op.left, as op.right is unknown
|
|
||||||
let o = op
|
|
||||||
// Remember: ?
|
// Remember: ?
|
||||||
// -> set op.right
|
// -> set op.right
|
||||||
// 1. to the first operation that is known (according to startSS)
|
// 1. to the first operation that is known (according to startSS)
|
||||||
// 2. or to the first operation that has an origin that is not to the
|
// 2. or to the first operation that has an origin that is not to the
|
||||||
// right of op.
|
// right of op.
|
||||||
// For this we maintain a list of ops which origins are not found yet.
|
// For this we maintain a list of ops which origins are not found yet.
|
||||||
var missingOrigins = [op]
|
var missing_origins = [op]
|
||||||
var newright = op.right
|
var newright = op.right
|
||||||
while (true) {
|
while (true) {
|
||||||
if (o.left == null) {
|
if (o.left == null) {
|
||||||
op.left = null
|
op.left = null
|
||||||
send.push(op)
|
send.push(op)
|
||||||
/* not necessary, as o is already sent..
|
if (!Y.utils.compareIds(o.id, op.id)) {
|
||||||
if (!Y.utils.compareIds(o.id, op.id) && o.id[1] >= (startSS[o.id[0]] || 0)) {
|
|
||||||
// o is not op && o is unknown
|
|
||||||
o = Y.Struct[op.struct].encode(o)
|
o = Y.Struct[op.struct].encode(o)
|
||||||
o.right = missingOrigins[missingOrigins.length - 1].id
|
o.right = missing_origins[missing_origins.length - 1].id
|
||||||
send.push(o)
|
send.push(o)
|
||||||
}
|
}
|
||||||
*/
|
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
o = yield * this.getInsertion(o.left)
|
o = yield* this.getInsertion(o.left)
|
||||||
// we set another o, check if we can reduce $missingOrigins
|
// we set another o, check if we can reduce $missing_origins
|
||||||
while (missingOrigins.length > 0 && Y.utils.matchesId(o, missingOrigins[missingOrigins.length - 1].origin)) {
|
while (missing_origins.length > 0 && Y.utils.matchesId(o, missing_origins[missing_origins.length - 1].origin)) {
|
||||||
missingOrigins.pop()
|
missing_origins.pop()
|
||||||
}
|
}
|
||||||
if (o.id[1] < (startSS[o.id[0]] || 0)) {
|
if (o.id[1] < (startSS[o.id[0]] || 0)) {
|
||||||
// case 2. o is known
|
// case 2. o is known
|
||||||
@@ -1002,20 +1042,17 @@ export default function extendTransaction (Y) {
|
|||||||
send.push(op)
|
send.push(op)
|
||||||
op = Y.Struct[op.struct].encode(o)
|
op = Y.Struct[op.struct].encode(o)
|
||||||
op.right = newright
|
op.right = newright
|
||||||
if (missingOrigins.length > 0) {
|
if (missing_origins.length > 0) {
|
||||||
throw new Error(
|
console.log('This should not happen .. :( please report this')
|
||||||
'Reached inconsistent OS state.' +
|
|
||||||
'Operations are not correctly connected.'
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
missingOrigins = [op]
|
missing_origins = [op]
|
||||||
} else {
|
} else {
|
||||||
// case 4. send o, continue to find op.origin
|
// case 4. send o, continue to find op.origin
|
||||||
var s = Y.Struct[op.struct].encode(o)
|
var s = Y.Struct[op.struct].encode(o)
|
||||||
s.right = missingOrigins[missingOrigins.length - 1].id
|
s.right = missing_origins[missing_origins.length - 1].id
|
||||||
s.left = s.origin
|
s.left = s.origin
|
||||||
send.push(s)
|
send.push(s)
|
||||||
missingOrigins.push(o)
|
missing_origins.push(o)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1023,56 +1060,6 @@ export default function extendTransaction (Y) {
|
|||||||
}
|
}
|
||||||
return send.reverse()
|
return send.reverse()
|
||||||
}
|
}
|
||||||
/*
|
|
||||||
* Get the plain untransformed operations from the database.
|
|
||||||
* You can apply these operations using .applyOperationsUntransformed(ops)
|
|
||||||
*
|
|
||||||
*/
|
|
||||||
* getOperationsUntransformed () {
|
|
||||||
var ops = []
|
|
||||||
yield * this.os.iterate(this, null, null, function * (op) {
|
|
||||||
if (op.id[0] !== '_') {
|
|
||||||
ops.push(op)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
return {
|
|
||||||
untransformed: ops
|
|
||||||
}
|
|
||||||
}
|
|
||||||
* applyOperationsUntransformed (m, stateSet) {
|
|
||||||
var ops = m.untransformed
|
|
||||||
for (var i = 0; i < ops.length; i++) {
|
|
||||||
var op = ops[i]
|
|
||||||
// create, and modify parent, if it is created implicitly
|
|
||||||
if (op.parent != null && op.parent[0] === '_') {
|
|
||||||
if (op.struct === 'Insert') {
|
|
||||||
// update parents .map/start/end properties
|
|
||||||
if (op.parentSub != null && op.left == null) {
|
|
||||||
// op is child of Map
|
|
||||||
let parent = yield * this.getOperation(op.parent)
|
|
||||||
parent.map[op.parentSub] = op.id
|
|
||||||
yield * this.setOperation(parent)
|
|
||||||
} else if (op.right == null || op.left == null) {
|
|
||||||
let parent = yield * this.getOperation(op.parent)
|
|
||||||
if (op.right == null) {
|
|
||||||
parent.end = Y.utils.getLastId(op)
|
|
||||||
}
|
|
||||||
if (op.left == null) {
|
|
||||||
parent.start = op.id
|
|
||||||
}
|
|
||||||
yield * this.setOperation(parent)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
yield * this.os.put(op)
|
|
||||||
}
|
|
||||||
for (var user in stateSet) {
|
|
||||||
yield * this.ss.put({
|
|
||||||
id: [user],
|
|
||||||
clock: stateSet[user]
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
/* this is what we used before.. use this as a reference..
|
/* this is what we used before.. use this as a reference..
|
||||||
* makeOperationReady (startSS, op) {
|
* makeOperationReady (startSS, op) {
|
||||||
op = Y.Struct[op.struct].encode(op)
|
op = Y.Struct[op.struct].encode(op)
|
||||||
@@ -1084,7 +1071,7 @@ export default function extendTransaction (Y) {
|
|||||||
// or the o that has no origin to the right of op
|
// or the o that has no origin to the right of op
|
||||||
// (this is why we use the ids array)
|
// (this is why we use the ids array)
|
||||||
while (o.right != null) {
|
while (o.right != null) {
|
||||||
var right = yield * this.getOperation(o.right)
|
var right = yield* this.getOperation(o.right)
|
||||||
if (o.right[1] < (startSS[o.right[0]] || 0) || !ids.some(function (id) {
|
if (o.right[1] < (startSS[o.right[0]] || 0) || !ids.some(function (id) {
|
||||||
return Y.utils.compareIds(id, right.origin)
|
return Y.utils.compareIds(id, right.origin)
|
||||||
})) {
|
})) {
|
||||||
@@ -1099,9 +1086,9 @@ export default function extendTransaction (Y) {
|
|||||||
}
|
}
|
||||||
*/
|
*/
|
||||||
* flush () {
|
* flush () {
|
||||||
yield * this.os.flush()
|
yield* this.os.flush()
|
||||||
yield * this.ss.flush()
|
yield* this.ss.flush()
|
||||||
yield * this.ds.flush()
|
yield* this.ds.flush()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Y.Transaction = TransactionInterface
|
Y.Transaction = TransactionInterface
|
||||||
|
|||||||
161
src/Utils.js
161
src/Utils.js
@@ -1,3 +1,6 @@
|
|||||||
|
/* @flow */
|
||||||
|
'use strict'
|
||||||
|
|
||||||
/*
|
/*
|
||||||
EventHandler is an helper class for constructing custom types.
|
EventHandler is an helper class for constructing custom types.
|
||||||
|
|
||||||
@@ -20,54 +23,9 @@
|
|||||||
database request to finish). EventHandler helps you to make your type
|
database request to finish). EventHandler helps you to make your type
|
||||||
synchronous.
|
synchronous.
|
||||||
*/
|
*/
|
||||||
|
module.exports = function (Y /* : any*/) {
|
||||||
export default function Utils (Y) {
|
|
||||||
Y.utils = {}
|
Y.utils = {}
|
||||||
|
|
||||||
Y.utils.bubbleEvent = function (type, event) {
|
|
||||||
type.eventHandler.callEventListeners(event)
|
|
||||||
event.path = []
|
|
||||||
while (type != null && type._deepEventHandler != null) {
|
|
||||||
type._deepEventHandler.callEventListeners(event)
|
|
||||||
var parent = null
|
|
||||||
if (type._parent != null) {
|
|
||||||
parent = type.os.getType(type._parent)
|
|
||||||
}
|
|
||||||
if (parent != null && parent._getPathToChild != null) {
|
|
||||||
event.path = [parent._getPathToChild(type._model)].concat(event.path)
|
|
||||||
type = parent
|
|
||||||
} else {
|
|
||||||
type = null
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
class NamedEventHandler {
|
|
||||||
constructor () {
|
|
||||||
this._eventListener = {}
|
|
||||||
}
|
|
||||||
on (name, f) {
|
|
||||||
if (this._eventListener[name] == null) {
|
|
||||||
this._eventListener[name] = []
|
|
||||||
}
|
|
||||||
this._eventListener[name].push(f)
|
|
||||||
}
|
|
||||||
off (name, f) {
|
|
||||||
if (name == null || f == null) {
|
|
||||||
throw new Error('You must specify event name and function!')
|
|
||||||
}
|
|
||||||
let listener = this._eventListener[name] || []
|
|
||||||
this._eventListener[name] = listener.filter(e => e !== f)
|
|
||||||
}
|
|
||||||
emit (name, value) {
|
|
||||||
(this._eventListener[name] || []).forEach(l => l(value))
|
|
||||||
}
|
|
||||||
destroy () {
|
|
||||||
this._eventListener = null
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Y.utils.NamedEventHandler = NamedEventHandler
|
|
||||||
|
|
||||||
class EventListenerHandler {
|
class EventListenerHandler {
|
||||||
constructor () {
|
constructor () {
|
||||||
this.eventListeners = []
|
this.eventListeners = []
|
||||||
@@ -92,18 +50,9 @@ export default function Utils (Y) {
|
|||||||
callEventListeners (event) {
|
callEventListeners (event) {
|
||||||
for (var i = 0; i < this.eventListeners.length; i++) {
|
for (var i = 0; i < this.eventListeners.length; i++) {
|
||||||
try {
|
try {
|
||||||
var _event = {}
|
this.eventListeners[i](event)
|
||||||
for (var name in event) {
|
|
||||||
_event[name] = event[name]
|
|
||||||
}
|
|
||||||
this.eventListeners[i](_event)
|
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
/*
|
console.error('User events must not throw Errors!')
|
||||||
Your observer threw an error. This error was caught so that Yjs
|
|
||||||
can ensure data consistency! In order to debug this error you
|
|
||||||
have to check "Pause On Caught Exceptions" in developer tools.
|
|
||||||
*/
|
|
||||||
console.error(e)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -133,6 +82,7 @@ export default function Utils (Y) {
|
|||||||
destroy () {
|
destroy () {
|
||||||
super.destroy()
|
super.destroy()
|
||||||
this.waiting = null
|
this.waiting = null
|
||||||
|
this.awaiting = null
|
||||||
this.onevent = null
|
this.onevent = null
|
||||||
}
|
}
|
||||||
/*
|
/*
|
||||||
@@ -288,13 +238,7 @@ export default function Utils (Y) {
|
|||||||
// finished with remaining operations
|
// finished with remaining operations
|
||||||
self.waiting.push(d)
|
self.waiting.push(d)
|
||||||
}
|
}
|
||||||
if (op.key == null) {
|
checkDelete(op)
|
||||||
// deletes in list
|
|
||||||
checkDelete(op)
|
|
||||||
} else {
|
|
||||||
// deletes in map
|
|
||||||
this.waiting.push(op)
|
|
||||||
}
|
|
||||||
} else {
|
} else {
|
||||||
this.waiting.push(op)
|
this.waiting.push(op)
|
||||||
}
|
}
|
||||||
@@ -332,7 +276,7 @@ export default function Utils (Y) {
|
|||||||
}
|
}
|
||||||
var before = this.waiting.length
|
var before = this.waiting.length
|
||||||
// somehow create new operations
|
// somehow create new operations
|
||||||
yield * f.apply(transaction, args)
|
yield* f.apply(transaction, args)
|
||||||
// remove all appended ops / awaited ops
|
// remove all appended ops / awaited ops
|
||||||
this.waiting.splice(before)
|
this.waiting.splice(before)
|
||||||
if (this.awaiting > 0) this.awaiting--
|
if (this.awaiting > 0) this.awaiting--
|
||||||
@@ -342,22 +286,18 @@ export default function Utils (Y) {
|
|||||||
for (let i = 0; i < this.waiting.length; i++) {
|
for (let i = 0; i < this.waiting.length; i++) {
|
||||||
var o = this.waiting[i]
|
var o = this.waiting[i]
|
||||||
if (o.struct === 'Insert') {
|
if (o.struct === 'Insert') {
|
||||||
var _o = yield * transaction.getInsertion(o.id)
|
var _o = yield* transaction.getInsertion(o.id)
|
||||||
if (_o.parentSub != null && _o.left != null) {
|
if (!Y.utils.compareIds(_o.id, o.id)) {
|
||||||
// if o is an insertion of a map struc (parentSub is defined), then it shouldn't be necessary to compute left
|
|
||||||
this.waiting.splice(i, 1)
|
|
||||||
i-- // update index
|
|
||||||
} else if (!Y.utils.compareIds(_o.id, o.id)) {
|
|
||||||
// o got extended
|
// o got extended
|
||||||
o.left = [o.id[0], o.id[1] - 1]
|
o.left = [o.id[0], o.id[1] - 1]
|
||||||
} else if (_o.left == null) {
|
} else if (_o.left == null) {
|
||||||
o.left = null
|
o.left = null
|
||||||
} else {
|
} else {
|
||||||
// find next undeleted op
|
// find next undeleted op
|
||||||
var left = yield * transaction.getInsertion(_o.left)
|
var left = yield* transaction.getInsertion(_o.left)
|
||||||
while (left.deleted != null) {
|
while (left.deleted != null) {
|
||||||
if (left.left != null) {
|
if (left.left != null) {
|
||||||
left = yield * transaction.getInsertion(left.left)
|
left = yield* transaction.getInsertion(left.left)
|
||||||
} else {
|
} else {
|
||||||
left = null
|
left = null
|
||||||
break
|
break
|
||||||
@@ -507,27 +447,6 @@ export default function Utils (Y) {
|
|||||||
}
|
}
|
||||||
Y.utils.EventHandler = EventHandler
|
Y.utils.EventHandler = EventHandler
|
||||||
|
|
||||||
/*
|
|
||||||
Default class of custom types!
|
|
||||||
*/
|
|
||||||
class CustomType {
|
|
||||||
getPath () {
|
|
||||||
var parent = null
|
|
||||||
if (this._parent != null) {
|
|
||||||
parent = this.os.getType(this._parent)
|
|
||||||
}
|
|
||||||
if (parent != null && parent._getPathToChild != null) {
|
|
||||||
var firstKey = parent._getPathToChild(this._model)
|
|
||||||
var parentKeys = parent.getPath()
|
|
||||||
parentKeys.push(firstKey)
|
|
||||||
return parentKeys
|
|
||||||
} else {
|
|
||||||
return []
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Y.utils.CustomType = CustomType
|
|
||||||
|
|
||||||
/*
|
/*
|
||||||
A wrapper for the definition of a custom type.
|
A wrapper for the definition of a custom type.
|
||||||
Every custom type must have three properties:
|
Every custom type must have three properties:
|
||||||
@@ -539,7 +458,7 @@ export default function Utils (Y) {
|
|||||||
* class
|
* class
|
||||||
- the constructor of the custom type (e.g. in order to inherit from a type)
|
- the constructor of the custom type (e.g. in order to inherit from a type)
|
||||||
*/
|
*/
|
||||||
class CustomTypeDefinition { // eslint-disable-line
|
class CustomType { // eslint-disable-line
|
||||||
/* ::
|
/* ::
|
||||||
struct: any;
|
struct: any;
|
||||||
initType: any;
|
initType: any;
|
||||||
@@ -550,14 +469,12 @@ export default function Utils (Y) {
|
|||||||
if (def.struct == null ||
|
if (def.struct == null ||
|
||||||
def.initType == null ||
|
def.initType == null ||
|
||||||
def.class == null ||
|
def.class == null ||
|
||||||
def.name == null ||
|
def.name == null
|
||||||
def.createType == null
|
|
||||||
) {
|
) {
|
||||||
throw new Error('Custom type was not initialized correctly!')
|
throw new Error('Custom type was not initialized correctly!')
|
||||||
}
|
}
|
||||||
this.struct = def.struct
|
this.struct = def.struct
|
||||||
this.initType = def.initType
|
this.initType = def.initType
|
||||||
this.createType = def.createType
|
|
||||||
this.class = def.class
|
this.class = def.class
|
||||||
this.name = def.name
|
this.name = def.name
|
||||||
if (def.appendAdditionalInfo != null) {
|
if (def.appendAdditionalInfo != null) {
|
||||||
@@ -569,13 +486,13 @@ export default function Utils (Y) {
|
|||||||
this.parseArguments.typeDefinition = this
|
this.parseArguments.typeDefinition = this
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Y.utils.CustomTypeDefinition = CustomTypeDefinition
|
Y.utils.CustomType = CustomType
|
||||||
|
|
||||||
Y.utils.isTypeDefinition = function isTypeDefinition (v) {
|
Y.utils.isTypeDefinition = function isTypeDefinition (v) {
|
||||||
if (v != null) {
|
if (v != null) {
|
||||||
if (v instanceof Y.utils.CustomTypeDefinition) return [v]
|
if (v instanceof Y.utils.CustomType) return [v]
|
||||||
else if (v.constructor === Array && v[0] instanceof Y.utils.CustomTypeDefinition) return v
|
else if (v.constructor === Array && v[0] instanceof Y.utils.CustomType) return v
|
||||||
else if (v instanceof Function && v.typeDefinition instanceof Y.utils.CustomTypeDefinition) return [v.typeDefinition]
|
else if (v instanceof Function && v.typeDefinition instanceof Y.utils.CustomType) return [v.typeDefinition]
|
||||||
}
|
}
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
@@ -641,7 +558,6 @@ export default function Utils (Y) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return false
|
|
||||||
}
|
}
|
||||||
Y.utils.matchesId = matchesId
|
Y.utils.matchesId = matchesId
|
||||||
|
|
||||||
@@ -709,7 +625,7 @@ export default function Utils (Y) {
|
|||||||
if (i < 0 && noSuperCall === undefined) {
|
if (i < 0 && noSuperCall === undefined) {
|
||||||
// did not reach break in last loop
|
// did not reach break in last loop
|
||||||
// read id and put it to the end of readBuffer
|
// read id and put it to the end of readBuffer
|
||||||
o = yield * super.find(id)
|
o = yield* super.find(id)
|
||||||
}
|
}
|
||||||
if (o != null) {
|
if (o != null) {
|
||||||
for (i = 0; i < this.readBuffer.length - 1; i++) {
|
for (i = 0; i < this.readBuffer.length - 1; i++) {
|
||||||
@@ -739,7 +655,7 @@ export default function Utils (Y) {
|
|||||||
// write writeBuffer[0]
|
// write writeBuffer[0]
|
||||||
var write = this.writeBuffer[0]
|
var write = this.writeBuffer[0]
|
||||||
if (write.id[0] !== null) {
|
if (write.id[0] !== null) {
|
||||||
yield * super.put(write)
|
yield* super.put(write)
|
||||||
}
|
}
|
||||||
// put o to the end of writeBuffer
|
// put o to the end of writeBuffer
|
||||||
for (i = 0; i < this.writeBuffer.length - 1; i++) {
|
for (i = 0; i < this.writeBuffer.length - 1; i++) {
|
||||||
@@ -769,44 +685,44 @@ export default function Utils (Y) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
yield * this.flush()
|
yield* this.flush()
|
||||||
yield * super.delete(id)
|
yield* super.delete(id)
|
||||||
}
|
}
|
||||||
* findWithLowerBound (id) {
|
* findWithLowerBound (id) {
|
||||||
var o = yield * this.find(id, true)
|
var o = yield* this.find(id, true)
|
||||||
if (o != null) {
|
if (o != null) {
|
||||||
return o
|
return o
|
||||||
} else {
|
} else {
|
||||||
yield * this.flush()
|
yield* this.flush()
|
||||||
return yield * super.findWithLowerBound.apply(this, arguments)
|
return yield* super.findWithLowerBound.apply(this, arguments)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
* findWithUpperBound (id) {
|
* findWithUpperBound (id) {
|
||||||
var o = yield * this.find(id, true)
|
var o = yield* this.find(id, true)
|
||||||
if (o != null) {
|
if (o != null) {
|
||||||
return o
|
return o
|
||||||
} else {
|
} else {
|
||||||
yield * this.flush()
|
yield* this.flush()
|
||||||
return yield * super.findWithUpperBound.apply(this, arguments)
|
return yield* super.findWithUpperBound.apply(this, arguments)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
* findNext () {
|
* findNext () {
|
||||||
yield * this.flush()
|
yield* this.flush()
|
||||||
return yield * super.findNext.apply(this, arguments)
|
return yield* super.findNext.apply(this, arguments)
|
||||||
}
|
}
|
||||||
* findPrev () {
|
* findPrev () {
|
||||||
yield * this.flush()
|
yield* this.flush()
|
||||||
return yield * super.findPrev.apply(this, arguments)
|
return yield* super.findPrev.apply(this, arguments)
|
||||||
}
|
}
|
||||||
* iterate () {
|
* iterate () {
|
||||||
yield * this.flush()
|
yield* this.flush()
|
||||||
yield * super.iterate.apply(this, arguments)
|
yield* super.iterate.apply(this, arguments)
|
||||||
}
|
}
|
||||||
* flush () {
|
* flush () {
|
||||||
for (var i = 0; i < this.writeBuffer.length; i++) {
|
for (var i = 0; i < this.writeBuffer.length; i++) {
|
||||||
var write = this.writeBuffer[i]
|
var write = this.writeBuffer[i]
|
||||||
if (write.id[0] !== null) {
|
if (write.id[0] !== null) {
|
||||||
yield * super.put(write)
|
yield* super.put(write)
|
||||||
this.writeBuffer[i] = {
|
this.writeBuffer[i] = {
|
||||||
id: [null, null]
|
id: [null, null]
|
||||||
}
|
}
|
||||||
@@ -817,9 +733,4 @@ export default function Utils (Y) {
|
|||||||
return SmallLookupBuffer
|
return SmallLookupBuffer
|
||||||
}
|
}
|
||||||
Y.utils.createSmallLookupBuffer = createSmallLookupBuffer
|
Y.utils.createSmallLookupBuffer = createSmallLookupBuffer
|
||||||
|
|
||||||
// Generates a unique id, for use as a user id.
|
|
||||||
// Thx to @jed for this script https://gist.github.com/jed/982883
|
|
||||||
function generateGuid(a){return a?(a^Math.random()*16>>a/4).toString(16):([1e7]+-1e3+-4e3+-8e3+-1e11).replace(/[018]/g,generateGuid)} // eslint-disable-line
|
|
||||||
Y.utils.generateGuid = generateGuid
|
|
||||||
}
|
}
|
||||||
|
|||||||
161
src/y.js
161
src/y.js
@@ -1,53 +1,32 @@
|
|||||||
import debug from 'debug'
|
/* @flow */
|
||||||
import extendConnector from './Connector.js'
|
'use strict'
|
||||||
import extendDatabase from './Database.js'
|
|
||||||
import extendTransaction from './Transaction.js'
|
|
||||||
import extendStruct from './Struct.js'
|
|
||||||
import extendUtils from './Utils.js'
|
|
||||||
|
|
||||||
extendConnector(Y)
|
require('./Connector.js')(Y)
|
||||||
extendDatabase(Y)
|
require('./Database.js')(Y)
|
||||||
extendTransaction(Y)
|
require('./Transaction.js')(Y)
|
||||||
extendStruct(Y)
|
require('./Struct.js')(Y)
|
||||||
extendUtils(Y)
|
require('./Utils.js')(Y)
|
||||||
|
require('./Connectors/Test.js')(Y)
|
||||||
Y.debug = debug
|
|
||||||
|
|
||||||
var requiringModules = {}
|
var requiringModules = {}
|
||||||
|
|
||||||
|
module.exports = Y
|
||||||
Y.requiringModules = requiringModules
|
Y.requiringModules = requiringModules
|
||||||
|
|
||||||
Y.extend = function (name, value) {
|
Y.extend = function (name, value) {
|
||||||
if (arguments.length === 2 && typeof name === 'string') {
|
if (value instanceof Y.utils.CustomType) {
|
||||||
if (value instanceof Y.utils.CustomTypeDefinition) {
|
Y[name] = value.parseArguments
|
||||||
Y[name] = value.parseArguments
|
|
||||||
} else {
|
|
||||||
Y[name] = value
|
|
||||||
}
|
|
||||||
if (requiringModules[name] != null) {
|
|
||||||
requiringModules[name].resolve()
|
|
||||||
delete requiringModules[name]
|
|
||||||
}
|
|
||||||
} else {
|
} else {
|
||||||
for (var i = 0; i < arguments.length; i++) {
|
Y[name] = value
|
||||||
var f = arguments[i]
|
}
|
||||||
if (typeof f === 'function') {
|
if (requiringModules[name] != null) {
|
||||||
f(Y)
|
requiringModules[name].resolve()
|
||||||
} else {
|
delete requiringModules[name]
|
||||||
throw new Error('Expected function!')
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Y.requestModules = requestModules
|
Y.requestModules = requestModules
|
||||||
function requestModules (modules) {
|
function requestModules (modules) {
|
||||||
var sourceDir
|
|
||||||
if (Y.sourceDir === null) {
|
|
||||||
sourceDir = null
|
|
||||||
} else {
|
|
||||||
sourceDir = Y.sourceDir || '/bower_components'
|
|
||||||
}
|
|
||||||
// determine if this module was compiled for es5 or es6 (y.js vs. y.es6)
|
// determine if this module was compiled for es5 or es6 (y.js vs. y.es6)
|
||||||
// if Insert.execute is a Function, then it isnt a generator..
|
// if Insert.execute is a Function, then it isnt a generator..
|
||||||
// then load the es5(.js) files..
|
// then load the es5(.js) files..
|
||||||
@@ -60,11 +39,10 @@ function requestModules (modules) {
|
|||||||
if (requiringModules[module] == null) {
|
if (requiringModules[module] == null) {
|
||||||
// module does not exist
|
// module does not exist
|
||||||
if (typeof window !== 'undefined' && window.Y !== 'undefined') {
|
if (typeof window !== 'undefined' && window.Y !== 'undefined') {
|
||||||
if (sourceDir != null) {
|
var imported = document.createElement('script')
|
||||||
var imported = document.createElement('script')
|
imported.src = Y.sourceDir + '/' + modulename + '/' + modulename + extention
|
||||||
imported.src = sourceDir + '/' + modulename + '/' + modulename + extention
|
document.head.appendChild(imported)
|
||||||
document.head.appendChild(imported)
|
|
||||||
}
|
|
||||||
let requireModule = {}
|
let requireModule = {}
|
||||||
requiringModules[module] = requireModule
|
requiringModules[module] = requireModule
|
||||||
requireModule.promise = new Promise(function (resolve) {
|
requireModule.promise = new Promise(function (resolve) {
|
||||||
@@ -112,39 +90,36 @@ type YOptions = {
|
|||||||
}
|
}
|
||||||
*/
|
*/
|
||||||
|
|
||||||
export default function Y (opts/* :YOptions */) /* :Promise<YConfig> */ {
|
function Y (opts/* :YOptions */) /* :Promise<YConfig> */ {
|
||||||
if (opts.hasOwnProperty('sourceDir')) {
|
|
||||||
Y.sourceDir = opts.sourceDir
|
|
||||||
}
|
|
||||||
opts.types = opts.types != null ? opts.types : []
|
opts.types = opts.types != null ? opts.types : []
|
||||||
var modules = [opts.db.name, opts.connector.name].concat(opts.types)
|
var modules = [opts.db.name, opts.connector.name].concat(opts.types)
|
||||||
for (var name in opts.share) {
|
for (var name in opts.share) {
|
||||||
modules.push(opts.share[name])
|
modules.push(opts.share[name])
|
||||||
}
|
}
|
||||||
|
Y.sourceDir = opts.sourceDir
|
||||||
return new Promise(function (resolve, reject) {
|
return new Promise(function (resolve, reject) {
|
||||||
if (opts == null) reject(new Error('An options object is expected!'))
|
setTimeout(function () {
|
||||||
else if (opts.connector == null) reject(new Error('You must specify a connector! (missing connector property)'))
|
|
||||||
else if (opts.connector.name == null) reject(new Error('You must specify connector name! (missing connector.name property)'))
|
|
||||||
else if (opts.db == null) reject(new Error('You must specify a database! (missing db property)'))
|
|
||||||
else if (opts.connector.name == null) reject(new Error('You must specify db name! (missing db.name property)'))
|
|
||||||
else {
|
|
||||||
opts = Y.utils.copyObject(opts)
|
|
||||||
opts.connector = Y.utils.copyObject(opts.connector)
|
|
||||||
opts.db = Y.utils.copyObject(opts.db)
|
|
||||||
opts.share = Y.utils.copyObject(opts.share)
|
|
||||||
Y.requestModules(modules).then(function () {
|
Y.requestModules(modules).then(function () {
|
||||||
var yconfig = new YConfig(opts)
|
if (opts == null) reject('An options object is expected! ')
|
||||||
yconfig.db.whenUserIdSet(function () {
|
else if (opts.connector == null) reject('You must specify a connector! (missing connector property)')
|
||||||
yconfig.init(function () {
|
else if (opts.connector.name == null) reject('You must specify connector name! (missing connector.name property)')
|
||||||
resolve(yconfig)
|
else if (opts.db == null) reject('You must specify a database! (missing db property)')
|
||||||
|
else if (opts.connector.name == null) reject('You must specify db name! (missing db.name property)')
|
||||||
|
else if (opts.share == null) reject('You must specify a set of shared types!')
|
||||||
|
else {
|
||||||
|
var yconfig = new YConfig(opts)
|
||||||
|
yconfig.db.whenUserIdSet(function () {
|
||||||
|
yconfig.init(function () {
|
||||||
|
resolve(yconfig)
|
||||||
|
})
|
||||||
})
|
})
|
||||||
})
|
}
|
||||||
}).catch(reject)
|
}).catch(reject)
|
||||||
}
|
}, 0)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
class YConfig extends Y.utils.NamedEventHandler {
|
class YConfig {
|
||||||
/* ::
|
/* ::
|
||||||
db: Y.AbstractDatabase;
|
db: Y.AbstractDatabase;
|
||||||
connector: Y.AbstractConnector;
|
connector: Y.AbstractConnector;
|
||||||
@@ -152,11 +127,9 @@ class YConfig extends Y.utils.NamedEventHandler {
|
|||||||
options: Object;
|
options: Object;
|
||||||
*/
|
*/
|
||||||
constructor (opts, callback) {
|
constructor (opts, callback) {
|
||||||
super()
|
|
||||||
this.options = opts
|
this.options = opts
|
||||||
this.db = new Y[opts.db.name](this, opts.db)
|
this.db = new Y[opts.db.name](this, opts.db)
|
||||||
this.connector = new Y[opts.connector.name](this, opts.connector)
|
this.connector = new Y[opts.connector.name](this, opts.connector)
|
||||||
this.connected = true
|
|
||||||
}
|
}
|
||||||
init (callback) {
|
init (callback) {
|
||||||
var opts = this.options
|
var opts = this.options
|
||||||
@@ -167,9 +140,6 @@ class YConfig extends Y.utils.NamedEventHandler {
|
|||||||
for (var propertyname in opts.share) {
|
for (var propertyname in opts.share) {
|
||||||
var typeConstructor = opts.share[propertyname].split('(')
|
var typeConstructor = opts.share[propertyname].split('(')
|
||||||
var typeName = typeConstructor.splice(0, 1)
|
var typeName = typeConstructor.splice(0, 1)
|
||||||
var type = Y[typeName]
|
|
||||||
var typedef = type.typeDefinition
|
|
||||||
var id = ['_', typedef.struct + '_' + typeName + '_' + propertyname + '_' + typeConstructor]
|
|
||||||
var args = []
|
var args = []
|
||||||
if (typeConstructor.length === 1) {
|
if (typeConstructor.length === 1) {
|
||||||
try {
|
try {
|
||||||
@@ -177,13 +147,11 @@ class YConfig extends Y.utils.NamedEventHandler {
|
|||||||
} catch (e) {
|
} catch (e) {
|
||||||
throw new Error('Was not able to parse type definition! (share.' + propertyname + ')')
|
throw new Error('Was not able to parse type definition! (share.' + propertyname + ')')
|
||||||
}
|
}
|
||||||
if (type.typeDefinition.parseArguments == null) {
|
|
||||||
throw new Error(typeName + ' does not expect arguments!')
|
|
||||||
} else {
|
|
||||||
args = typedef.parseArguments(args[0])[1]
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
share[propertyname] = yield * this.store.initType.call(this, id, args)
|
var type = Y[typeName]
|
||||||
|
var typedef = type.typeDefinition
|
||||||
|
var id = ['_', typedef.struct + '_' + typeName + '_' + propertyname + '_' + typeConstructor]
|
||||||
|
share[propertyname] = yield* this.createType(type.apply(typedef, args), id)
|
||||||
}
|
}
|
||||||
this.store.whenTransactionsFinished()
|
this.store.whenTransactionsFinished()
|
||||||
.then(callback)
|
.then(callback)
|
||||||
@@ -193,49 +161,26 @@ class YConfig extends Y.utils.NamedEventHandler {
|
|||||||
return this.connector.isSynced
|
return this.connector.isSynced
|
||||||
}
|
}
|
||||||
disconnect () {
|
disconnect () {
|
||||||
if (this.connected) {
|
return this.connector.disconnect()
|
||||||
this.connected = false
|
|
||||||
return this.connector.disconnect()
|
|
||||||
} else {
|
|
||||||
return Promise.resolve()
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
reconnect () {
|
reconnect () {
|
||||||
if (!this.connected) {
|
return this.connector.reconnect()
|
||||||
this.connected = true
|
|
||||||
return this.connector.reconnect()
|
|
||||||
} else {
|
|
||||||
return Promise.resolve()
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
destroy () {
|
destroy () {
|
||||||
var self = this
|
|
||||||
return this.close().then(function () {
|
|
||||||
if (self.db.deleteDB != null) {
|
|
||||||
return self.db.deleteDB()
|
|
||||||
} else {
|
|
||||||
return Promise.resolve()
|
|
||||||
}
|
|
||||||
}).then(() => {
|
|
||||||
// remove existing event listener
|
|
||||||
super.destroy()
|
|
||||||
})
|
|
||||||
}
|
|
||||||
close () {
|
|
||||||
var self = this
|
|
||||||
this.share = null
|
|
||||||
if (this.connector.destroy != null) {
|
if (this.connector.destroy != null) {
|
||||||
this.connector.destroy()
|
this.connector.destroy()
|
||||||
} else {
|
} else {
|
||||||
this.connector.disconnect()
|
this.connector.disconnect()
|
||||||
}
|
}
|
||||||
return this.db.whenTransactionsFinished().then(function () {
|
var self = this
|
||||||
self.db.destroyTypes()
|
this.db.requestTransaction(function * () {
|
||||||
// make sure to wait for all transactions before destroying the db
|
yield* self.db.destroy()
|
||||||
self.db.requestTransaction(function * () {
|
self.connector = null
|
||||||
yield * self.db.destroy()
|
self.db = null
|
||||||
})
|
|
||||||
return self.db.whenTransactionsFinished()
|
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (typeof window !== 'undefined') {
|
||||||
|
window.Y = Y
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,191 +0,0 @@
|
|||||||
|
|
||||||
import _Y from '../../yjs/src/y.js'
|
|
||||||
|
|
||||||
import yMemory from '../../y-memory/src/y-memory.js'
|
|
||||||
import yArray from '../../y-array/src/y-array.js'
|
|
||||||
import yMap from '../../y-map/src/Map.js'
|
|
||||||
import yTest from './test-connector.js'
|
|
||||||
|
|
||||||
import Chance from 'chance'
|
|
||||||
|
|
||||||
export let Y = _Y
|
|
||||||
|
|
||||||
Y.extend(yMemory, yArray, yMap, yTest)
|
|
||||||
|
|
||||||
export async function garbageCollectUsers (t, users) {
|
|
||||||
await flushAll(t, users)
|
|
||||||
await Promise.all(users.map(u => u.db.emptyGarbageCollector()))
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
* 1. reconnect and flush all
|
|
||||||
* 2. user 0 gc
|
|
||||||
* 3. get type content
|
|
||||||
* 4. disconnect & reconnect all (so gc is propagated)
|
|
||||||
* 5. compare os, ds, ss
|
|
||||||
*/
|
|
||||||
export async function compareUsers (t, users) {
|
|
||||||
await Promise.all(users.map(u => u.reconnect()))
|
|
||||||
if (users[0].connector.testRoom == null) {
|
|
||||||
await wait(100)
|
|
||||||
}
|
|
||||||
await flushAll(t, users)
|
|
||||||
await wait()
|
|
||||||
await flushAll(t, users)
|
|
||||||
|
|
||||||
var userTypeContents = users.map(u => u.share.array._content.map(c => c.val || JSON.stringify(c.type)))
|
|
||||||
|
|
||||||
await users[0].db.garbageCollect()
|
|
||||||
await users[0].db.garbageCollect()
|
|
||||||
|
|
||||||
// disconnect all except user 0
|
|
||||||
await Promise.all(users.slice(1).map(async u =>
|
|
||||||
u.disconnect()
|
|
||||||
))
|
|
||||||
if (users[0].connector.testRoom == null) {
|
|
||||||
await wait(100)
|
|
||||||
}
|
|
||||||
// reconnect all
|
|
||||||
await Promise.all(users.map(u => u.reconnect()))
|
|
||||||
if (users[0].connector.testRoom == null) {
|
|
||||||
await wait(100)
|
|
||||||
}
|
|
||||||
await users[0].connector.testRoom.flushAll(users)
|
|
||||||
await Promise.all(users.map(u =>
|
|
||||||
new Promise(function (resolve) {
|
|
||||||
u.connector.whenSynced(resolve)
|
|
||||||
})
|
|
||||||
))
|
|
||||||
let filterDeletedOps = users.every(u => u.db.gc === false)
|
|
||||||
var data = await Promise.all(users.map(async (u) => {
|
|
||||||
var data = {}
|
|
||||||
u.db.requestTransaction(function * () {
|
|
||||||
var os = yield * this.getOperationsUntransformed()
|
|
||||||
data.os = {}
|
|
||||||
for (let i = 0; i < os.untransformed.length; i++) {
|
|
||||||
let op = os.untransformed[i]
|
|
||||||
op = Y.Struct[op.struct].encode(op)
|
|
||||||
delete op.origin
|
|
||||||
/*
|
|
||||||
If gc = false, it is necessary to filter deleted ops
|
|
||||||
as they might have been split up differently..
|
|
||||||
*/
|
|
||||||
if (filterDeletedOps) {
|
|
||||||
let opIsDeleted = yield * this.isDeleted(op.id)
|
|
||||||
if (!opIsDeleted) {
|
|
||||||
data.os[JSON.stringify(op.id)] = op
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
data.os[JSON.stringify(op.id)] = op
|
|
||||||
}
|
|
||||||
}
|
|
||||||
data.ds = yield * this.getDeleteSet()
|
|
||||||
data.ss = yield * this.getStateSet()
|
|
||||||
})
|
|
||||||
await u.db.whenTransactionsFinished()
|
|
||||||
return data
|
|
||||||
}))
|
|
||||||
for (var i = 0; i < data.length - 1; i++) {
|
|
||||||
await t.asyncGroup(async () => {
|
|
||||||
t.compare(userTypeContents[i], userTypeContents[i + 1], 'types')
|
|
||||||
t.compare(data[i].os, data[i + 1].os, 'os')
|
|
||||||
t.compare(data[i].ds, data[i + 1].ds, 'ds')
|
|
||||||
t.compare(data[i].ss, data[i + 1].ss, 'ss')
|
|
||||||
}, `Compare user${i} with user${i + 1}`)
|
|
||||||
}
|
|
||||||
await Promise.all(users.map(async (u) => {
|
|
||||||
await u.close()
|
|
||||||
}))
|
|
||||||
}
|
|
||||||
|
|
||||||
export async function initArrays (t, opts) {
|
|
||||||
var result = {
|
|
||||||
users: []
|
|
||||||
}
|
|
||||||
var share = Object.assign({ flushHelper: 'Map', array: 'Array' }, opts.share)
|
|
||||||
var chance = opts.chance || new Chance(t.getSeed() * 1000000000)
|
|
||||||
var connector = Object.assign({ room: 'debugging_' + t.name, testContext: t, chance }, opts.connector)
|
|
||||||
for (let i = 0; i < opts.users; i++) {
|
|
||||||
let dbOpts
|
|
||||||
let connOpts
|
|
||||||
if (i === 0) {
|
|
||||||
// Only one instance can gc!
|
|
||||||
dbOpts = Object.assign({ gc: true }, opts.db)
|
|
||||||
connOpts = Object.assign({ role: 'master' }, connector)
|
|
||||||
} else {
|
|
||||||
dbOpts = Object.assign({ gc: false }, opts.db)
|
|
||||||
connOpts = Object.assign({ role: 'slave' }, connector)
|
|
||||||
}
|
|
||||||
let y = await Y({
|
|
||||||
connector: connOpts,
|
|
||||||
db: dbOpts,
|
|
||||||
share: share
|
|
||||||
})
|
|
||||||
result.users.push(y)
|
|
||||||
for (let name in share) {
|
|
||||||
result[name + i] = y.share[name]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
result.array0.delete(0, result.array0.length)
|
|
||||||
if (result.users[0].connector.testRoom != null) {
|
|
||||||
// flush for sync if test-connector
|
|
||||||
await result.users[0].connector.testRoom.flushAll(result.users)
|
|
||||||
}
|
|
||||||
await Promise.all(result.users.map(u => {
|
|
||||||
return new Promise(function (resolve) {
|
|
||||||
u.connector.whenSynced(resolve)
|
|
||||||
})
|
|
||||||
}))
|
|
||||||
await flushAll(t, result.users)
|
|
||||||
return result
|
|
||||||
}
|
|
||||||
|
|
||||||
export async function flushAll (t, users) {
|
|
||||||
// users = users.filter(u => u.connector.isSynced)
|
|
||||||
if (users.length === 0) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
await wait(0)
|
|
||||||
if (users[0].connector.testRoom != null) {
|
|
||||||
// use flushAll method specified in Test Connector
|
|
||||||
await users[0].connector.testRoom.flushAll(users)
|
|
||||||
} else {
|
|
||||||
// flush for any connector
|
|
||||||
await Promise.all(users.map(u => { return u.db.whenTransactionsFinished() }))
|
|
||||||
|
|
||||||
var flushCounter = users[0].share.flushHelper.get('0') || 0
|
|
||||||
flushCounter++
|
|
||||||
await Promise.all(users.map(async (u, i) => {
|
|
||||||
// wait for all users to set the flush counter to the same value
|
|
||||||
await new Promise(resolve => {
|
|
||||||
function observer () {
|
|
||||||
var allUsersReceivedUpdate = true
|
|
||||||
for (var i = 0; i < users.length; i++) {
|
|
||||||
if (u.share.flushHelper.get(i + '') !== flushCounter) {
|
|
||||||
allUsersReceivedUpdate = false
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (allUsersReceivedUpdate) {
|
|
||||||
resolve()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
u.share.flushHelper.observe(observer)
|
|
||||||
u.share.flushHelper.set(i + '', flushCounter)
|
|
||||||
})
|
|
||||||
}))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export async function flushSome (t, users) {
|
|
||||||
if (users[0].connector.testRoom == null) {
|
|
||||||
// if not test-connector, wait for some time for operations to arrive
|
|
||||||
await wait(100)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export function wait (t) {
|
|
||||||
return new Promise(function (resolve) {
|
|
||||||
setTimeout(resolve, t != null ? t : 100)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
@@ -1,156 +0,0 @@
|
|||||||
/* global Y */
|
|
||||||
import { wait } from './helper.js'
|
|
||||||
|
|
||||||
var rooms = {}
|
|
||||||
|
|
||||||
export class TestRoom {
|
|
||||||
constructor (roomname) {
|
|
||||||
this.room = roomname
|
|
||||||
this.users = {}
|
|
||||||
this.nextUserId = 0
|
|
||||||
}
|
|
||||||
join (connector) {
|
|
||||||
if (connector.userId == null) {
|
|
||||||
connector.setUserId('' + (this.nextUserId++))
|
|
||||||
}
|
|
||||||
Object.keys(this.users).forEach(uid => {
|
|
||||||
let user = this.users[uid]
|
|
||||||
if (user.role === 'master' || connector.role === 'master') {
|
|
||||||
this.users[uid].userJoined(connector.userId, connector.role)
|
|
||||||
connector.userJoined(uid, this.users[uid].role)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
this.users[connector.userId] = connector
|
|
||||||
}
|
|
||||||
leave (connector) {
|
|
||||||
delete this.users[connector.userId]
|
|
||||||
Object.keys(this.users).forEach(uid => {
|
|
||||||
this.users[uid].userLeft(connector.userId)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
send (sender, receiver, m) {
|
|
||||||
m = JSON.parse(JSON.stringify(m))
|
|
||||||
var user = this.users[receiver]
|
|
||||||
if (user != null) {
|
|
||||||
user.receiveMessage(sender, m)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
broadcast (sender, m) {
|
|
||||||
Object.keys(this.users).forEach(receiver => {
|
|
||||||
this.send(sender, receiver, m)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
async flushAll (users) {
|
|
||||||
let flushing = true
|
|
||||||
let allUserIds = Object.keys(this.users)
|
|
||||||
if (users == null) {
|
|
||||||
users = allUserIds.map(id => this.users[id].y)
|
|
||||||
}
|
|
||||||
while (flushing) {
|
|
||||||
await wait(10)
|
|
||||||
let res = await Promise.all(allUserIds.map(id => this.users[id]._flushAll(users)))
|
|
||||||
flushing = res.some(status => status === 'flushing')
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function getTestRoom (roomname) {
|
|
||||||
if (rooms[roomname] == null) {
|
|
||||||
rooms[roomname] = new TestRoom(roomname)
|
|
||||||
}
|
|
||||||
return rooms[roomname]
|
|
||||||
}
|
|
||||||
|
|
||||||
export default function extendTestConnector (Y) {
|
|
||||||
class TestConnector extends Y.AbstractConnector {
|
|
||||||
constructor (y, options) {
|
|
||||||
if (options === undefined) {
|
|
||||||
throw new Error('Options must not be undefined!')
|
|
||||||
}
|
|
||||||
if (options.room == null) {
|
|
||||||
throw new Error('You must define a room name!')
|
|
||||||
}
|
|
||||||
options.forwardAppliedOperations = options.role === 'master'
|
|
||||||
super(y, options)
|
|
||||||
this.options = options
|
|
||||||
this.room = options.room
|
|
||||||
this.chance = options.chance
|
|
||||||
this.testRoom = getTestRoom(this.room)
|
|
||||||
this.testRoom.join(this)
|
|
||||||
}
|
|
||||||
disconnect () {
|
|
||||||
this.testRoom.leave(this)
|
|
||||||
return super.disconnect()
|
|
||||||
}
|
|
||||||
reconnect () {
|
|
||||||
this.testRoom.join(this)
|
|
||||||
return super.reconnect()
|
|
||||||
}
|
|
||||||
send (uid, message) {
|
|
||||||
this.testRoom.send(this.userId, uid, message)
|
|
||||||
}
|
|
||||||
broadcast (message) {
|
|
||||||
this.testRoom.broadcast(this.userId, message)
|
|
||||||
}
|
|
||||||
async whenSynced (f) {
|
|
||||||
var synced = false
|
|
||||||
var periodicFlushTillSync = () => {
|
|
||||||
if (synced) {
|
|
||||||
f()
|
|
||||||
} else {
|
|
||||||
this.testRoom.flushAll([this.y]).then(function () {
|
|
||||||
setTimeout(periodicFlushTillSync, 10)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
periodicFlushTillSync()
|
|
||||||
return super.whenSynced(function () {
|
|
||||||
synced = true
|
|
||||||
})
|
|
||||||
}
|
|
||||||
receiveMessage (sender, m) {
|
|
||||||
if (this.userId !== sender && this.connections[sender] != null) {
|
|
||||||
var buffer = this.connections[sender].buffer
|
|
||||||
if (buffer == null) {
|
|
||||||
buffer = this.connections[sender].buffer = []
|
|
||||||
}
|
|
||||||
buffer.push(m)
|
|
||||||
if (this.chance.bool({likelihood: 30})) {
|
|
||||||
// flush 1/2 with 30% chance
|
|
||||||
var flushLength = Math.round(buffer.length / 2)
|
|
||||||
buffer.splice(0, flushLength).forEach(m => {
|
|
||||||
super.receiveMessage(sender, m)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
async _flushAll (flushUsers) {
|
|
||||||
if (flushUsers.some(u => u.connector.userId === this.userId)) {
|
|
||||||
// this one needs to sync with every other user
|
|
||||||
flushUsers = Object.keys(this.connections).map(id => this.testRoom.users[id].y)
|
|
||||||
}
|
|
||||||
var finished = []
|
|
||||||
for (let i = 0; i < flushUsers.length; i++) {
|
|
||||||
let userId = flushUsers[i].connector.userId
|
|
||||||
if (userId !== this.userId && this.connections[userId] != null) {
|
|
||||||
let buffer = this.connections[userId].buffer
|
|
||||||
if (buffer != null) {
|
|
||||||
var messages = buffer.splice(0)
|
|
||||||
for (let j = 0; j < messages.length; j++) {
|
|
||||||
let p = super.receiveMessage(userId, messages[j])
|
|
||||||
finished.push(p)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
await Promise.all(finished)
|
|
||||||
await this.y.db.whenTransactionsFinished()
|
|
||||||
return finished.length > 0 ? 'flushing' : 'done'
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Y.extend('test', TestConnector)
|
|
||||||
}
|
|
||||||
|
|
||||||
if (typeof Y !== 'undefined') {
|
|
||||||
extendTestConnector(Y)
|
|
||||||
}
|
|
||||||
File diff suppressed because one or more lines are too long
Reference in New Issue
Block a user