Compare commits
6 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
03685ac84a | ||
|
|
6fa79320a5 | ||
|
|
4e3a297efc | ||
|
|
ef2cbf210b | ||
|
|
3a1c7a71d4 | ||
|
|
dd6c196135 |
12
.babelrc
12
.babelrc
@@ -1,12 +0,0 @@
|
||||
{
|
||||
"presets": [
|
||||
["latest", {
|
||||
"es2015": {
|
||||
"modules": false
|
||||
}
|
||||
}]
|
||||
],
|
||||
"plugins": [
|
||||
"external-helpers"
|
||||
]
|
||||
}
|
||||
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
|
||||
34
README.md
34
README.md
@@ -22,6 +22,7 @@ is a list of the modules we know of:
|
||||
|[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))|
|
||||
|[ipfs](https://github.com/ipfs-labs/y-ipfs-connector) | Connector for the [Interplanetary File System](https://ipfs.io/)!|
|
||||
|[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
|
||||
@@ -54,23 +55,23 @@ Install Yjs, and its modules with [bower](http://bower.io/), or
|
||||
[npm](https://www.npmjs.org/package/yjs).
|
||||
|
||||
### Bower
|
||||
```
|
||||
```sh
|
||||
bower install --save yjs y-array % add all y-* modules you want to use
|
||||
```
|
||||
You only need to include the `y.js` file. Yjs is able to automatically require
|
||||
missing modules.
|
||||
```
|
||||
```html
|
||||
<script src="./bower_components/yjs/y.js"></script>
|
||||
```
|
||||
|
||||
### Npm
|
||||
```
|
||||
```sh
|
||||
npm install --save yjs % add all y-* modules you want to use
|
||||
```
|
||||
|
||||
If you don't include via script tag, you have to explicitly include all modules!
|
||||
(Same goes for other module systems)
|
||||
```
|
||||
```js
|
||||
var Y = require('yjs')
|
||||
require('y-array')(Y) // add the y-array type to Yjs
|
||||
require('y-websockets-client')(Y)
|
||||
@@ -83,7 +84,7 @@ require('y-text')(Y)
|
||||
```
|
||||
|
||||
### ES6 Syntax
|
||||
```
|
||||
```js
|
||||
import Y from 'yjs'
|
||||
import yArray from 'y-array'
|
||||
import yWebsocketsClient from 'y-webrtc'
|
||||
@@ -97,7 +98,7 @@ Y.extend(yArray, yWebsocketsClient, yMemory, yArray, yMap, yText /*, .. */)
|
||||
|
||||
# Text editing example
|
||||
Install dependencies
|
||||
```
|
||||
```sh
|
||||
bower i yjs y-memory y-webrtc y-array y-text
|
||||
```
|
||||
|
||||
@@ -165,6 +166,25 @@ soon, if possible.
|
||||
endpoint of the used connector.
|
||||
* All of our connectors also have a default connection endpoint that you can
|
||||
use for development.
|
||||
* We provide basic authentification for all connectors. The value of
|
||||
`options.connector.auth` (this can be a passphase) is sent to all connected
|
||||
Yjs instances. `options.connector.checkAuth` may grant read or write access
|
||||
depending on the `auth` information.
|
||||
Example: A client specifies `options.connector.auth = 'superSecretPassword`.
|
||||
A server specifies
|
||||
```js
|
||||
options.connector.checkAuth = function (auth, yjsInstance, sender) {
|
||||
return new Promise(function (resolve, reject){
|
||||
if (auth === 'superSecretPassword') {
|
||||
resolve('write') // grant read-write access
|
||||
} else if (auth === 'different password') {
|
||||
resolve('read') // grant read-only access
|
||||
} else {
|
||||
reject('wrong password!') // reject connection
|
||||
}
|
||||
})
|
||||
}
|
||||
```
|
||||
* 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
|
||||
@@ -180,7 +200,7 @@ soon, if possible.
|
||||
* Defaults to `/bower_components`
|
||||
* Not required when running on `nodejs` / `iojs`
|
||||
* When using nodejs you need to manually extend Yjs:
|
||||
```
|
||||
```js
|
||||
var Y = require('yjs')
|
||||
// you have to require a db, connector, and *all* types you use!
|
||||
require('y-memory')(Y)
|
||||
|
||||
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 8739fd3a9c
@@ -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)
|
||||
})
|
||||
214
gulpfile.helper.js
Normal file
214
gulpfile.helper.js
Normal file
@@ -0,0 +1,214 @@
|
||||
|
||||
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]
|
||||
}
|
||||
|
||||
var header = require('gulp-header')
|
||||
var banner = ['/**',
|
||||
' * <%= pkg.name %> - <%= pkg.description %>',
|
||||
' * @version v<%= pkg.version %>',
|
||||
' * @link <%= pkg.homepage %>',
|
||||
' * @license <%= pkg.license %>',
|
||||
' */',
|
||||
''].join('\n')
|
||||
|
||||
gulp.task('dist:es5', function () {
|
||||
var babelOptions = {
|
||||
presets: ['es2015']
|
||||
}
|
||||
return (browserify({
|
||||
entries: files.distEs5,
|
||||
debug: true,
|
||||
standalone: options.moduleName
|
||||
}).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(header(banner, { pkg: require('./package.json') }))
|
||||
.pipe($.sourcemaps.write('.'))
|
||||
.pipe(gulp.dest('./dist/')))
|
||||
})
|
||||
|
||||
gulp.task('dist:es6', function () {
|
||||
return (browserify({
|
||||
entries: files.dist,
|
||||
debug: true,
|
||||
standalone: options.moduleName
|
||||
}).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(header(banner, { pkg: require('./package.json') }))
|
||||
.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: 'Y',
|
||||
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
75
package.json
75
package.json
@@ -1,23 +1,24 @@
|
||||
{
|
||||
"name": "yjs",
|
||||
"version": "13.0.0-2",
|
||||
"version": "12.3.3",
|
||||
"description": "A framework for real-time p2p shared editing on any data",
|
||||
"main": "./src/y.js",
|
||||
"scripts": {
|
||||
"test": "npm run lint",
|
||||
"lint": "standard",
|
||||
"dist": "rollup -c rollup.dist.js",
|
||||
"serve": "concurrently 'serve ..' 'rollup -wc rollup.dist.js -o examples/bower_components/yjs/y.js'",
|
||||
"postversion": "npm run dist",
|
||||
"postpublish": "tag-dist-files --overwrite-existing-tag"
|
||||
"test": "node --harmony ./node_modules/.bin/gulp test",
|
||||
"lint": "./node_modules/.bin/standard"
|
||||
},
|
||||
"files": [
|
||||
"y.*"
|
||||
"pre-commit": [
|
||||
"lint",
|
||||
"test"
|
||||
],
|
||||
"standard": {
|
||||
"parser": "babel-eslint",
|
||||
"ignore": [
|
||||
"/y.js",
|
||||
"/y.js.map"
|
||||
"build/**",
|
||||
"dist/**",
|
||||
"declarations/**",
|
||||
"./y.js",
|
||||
"./y.js.map"
|
||||
]
|
||||
},
|
||||
"repository": {
|
||||
@@ -41,25 +42,41 @@
|
||||
},
|
||||
"homepage": "http://y-js.org",
|
||||
"devDependencies": {
|
||||
"babel-cli": "^6.24.1",
|
||||
"babel-plugin-external-helpers": "^6.22.0",
|
||||
"babel-plugin-transform-regenerator": "^6.24.1",
|
||||
"babel-plugin-transform-runtime": "^6.23.0",
|
||||
"babel-preset-latest": "^6.24.1",
|
||||
"chance": "^1.0.9",
|
||||
"concurrently": "^3.4.0",
|
||||
"rollup-plugin-babel": "^2.7.1",
|
||||
"rollup-plugin-commonjs": "^8.0.2",
|
||||
"rollup-plugin-inject": "^2.0.0",
|
||||
"rollup-plugin-multi-entry": "^2.0.1",
|
||||
"rollup-plugin-node-resolve": "^3.0.0",
|
||||
"rollup-plugin-uglify": "^1.0.2",
|
||||
"rollup-regenerator-runtime": "^6.23.1",
|
||||
"rollup-watch": "^3.2.2",
|
||||
"standard": "^10.0.2",
|
||||
"tag-dist-files": "^0.1.6"
|
||||
"babel-eslint": "^5.0.0-beta6",
|
||||
"babel-plugin-transform-runtime": "^6.1.18",
|
||||
"babel-preset-es2015": "^6.1.18",
|
||||
"babelify": "^7.2.0",
|
||||
"browserify": "^12.0.1",
|
||||
"eslint": "^1.10.2",
|
||||
"gulp": "^3.9.0",
|
||||
"gulp-bump": "^1.0.0",
|
||||
"gulp-concat": "^2.6.0",
|
||||
"gulp-filter": "^3.0.1",
|
||||
"gulp-git": "^1.6.0",
|
||||
"gulp-header": "^1.8.8",
|
||||
"gulp-if": "^2.0.0",
|
||||
"gulp-jasmine": "^2.0.1",
|
||||
"gulp-jasmine-browser": "^0.2.3",
|
||||
"gulp-load-plugins": "^1.0.0",
|
||||
"gulp-prompt": "^0.1.2",
|
||||
"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": "^2.0.0",
|
||||
"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": {
|
||||
"debug": "^2.6.8"
|
||||
"debug": "^2.6.3"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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,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
|
||||
}
|
||||
154
src/Connector.js
154
src/Connector.js
@@ -1,10 +1,7 @@
|
||||
/* @flow */
|
||||
'use strict'
|
||||
|
||||
function canRead (auth) { return auth === 'read' || auth === 'write' }
|
||||
function canWrite (auth) { return auth === 'write' }
|
||||
|
||||
export default function extendConnector (Y/* :any */) {
|
||||
module.exports = function (Y/* :any */) {
|
||||
class AbstractConnector {
|
||||
/* ::
|
||||
y: YConfig;
|
||||
@@ -39,7 +36,6 @@ export default function extendConnector (Y/* :any */) {
|
||||
// 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') {
|
||||
this.role = 'master'
|
||||
} else if (opts.role === 'slave') {
|
||||
@@ -59,11 +55,12 @@ export default function extendConnector (Y/* :any */) {
|
||||
this.syncingClients = []
|
||||
this.forwardToSyncingClients = opts.forwardToSyncingClients !== false
|
||||
this.debug = opts.debug === true
|
||||
this.syncStep2 = Promise.resolve()
|
||||
this.broadcastOpBuffer = []
|
||||
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) {
|
||||
if (opts.generateUserId === true) {
|
||||
this.setUserId(Y.utils.generateGuid())
|
||||
}
|
||||
}
|
||||
@@ -112,7 +109,7 @@ export default function extendConnector (Y/* :any */) {
|
||||
this.userEventListeners.push(f)
|
||||
}
|
||||
removeUserEventListener (f) {
|
||||
this.userEventListeners = this.userEventListeners.filter(g => f !== g)
|
||||
this.userEventListeners = this.userEventListeners.filter(g => { f !== g })
|
||||
}
|
||||
userLeft (user) {
|
||||
if (this.connections[user] != null) {
|
||||
@@ -145,9 +142,6 @@ export default function extendConnector (Y/* :any */) {
|
||||
isSynced: false,
|
||||
role: role
|
||||
}
|
||||
let defer = {}
|
||||
defer.promise = new Promise(function (resolve) { defer.resolve = resolve })
|
||||
this.connections[user].syncStep2 = defer
|
||||
for (var f of this.userEventListeners) {
|
||||
f({
|
||||
action: 'userJoined',
|
||||
@@ -183,29 +177,25 @@ export default function extendConnector (Y/* :any */) {
|
||||
var conn = this
|
||||
if (syncUser != null) {
|
||||
this.currentSyncTarget = syncUser
|
||||
this.y.db.requestTransaction(function * () {
|
||||
var stateSet = yield * this.getStateSet()
|
||||
// var deleteSet = yield * this.getDeleteSet()
|
||||
this.y.db.requestTransaction(function *() {
|
||||
var stateSet = yield* this.getStateSet()
|
||||
var deleteSet = yield* this.getDeleteSet()
|
||||
var answer = {
|
||||
type: 'sync step 1',
|
||||
stateSet: stateSet,
|
||||
// deleteSet: deleteSet,
|
||||
deleteSet: deleteSet,
|
||||
protocolVersion: conn.protocolVersion,
|
||||
auth: conn.authInfo
|
||||
}
|
||||
if (conn.preferUntransformed && Object.keys(stateSet).length === 0) {
|
||||
answer.preferUntransformed = true
|
||||
}
|
||||
conn.send(syncUser, answer)
|
||||
})
|
||||
} else {
|
||||
if (!conn.isSynced) {
|
||||
this.y.db.requestTransaction(function * () {
|
||||
this.y.db.requestTransaction(function *() {
|
||||
if (!conn.isSynced) {
|
||||
// it is crucial that isSynced is set at the time garbageCollectAfterSync is called
|
||||
conn.isSynced = true
|
||||
// It is safer to remove this!
|
||||
// TODO: remove: yield * this.garbageCollectAfterSync()
|
||||
yield* this.garbageCollectAfterSync()
|
||||
// call whensynced listeners
|
||||
for (var f of conn.whenSyncedListeners) {
|
||||
f()
|
||||
@@ -243,7 +233,11 @@ export default function extendConnector (Y/* :any */) {
|
||||
}
|
||||
if (this.broadcastOpBuffer.length === 0) {
|
||||
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 {
|
||||
this.broadcastOpBuffer = this.broadcastOpBuffer.concat(ops)
|
||||
}
|
||||
@@ -267,11 +261,11 @@ export default function extendConnector (Y/* :any */) {
|
||||
type: 'sync stop',
|
||||
protocolVersion: this.protocolVersion
|
||||
})
|
||||
return Promise.reject(new Error('Incompatible protocol version'))
|
||||
return Promise.reject('Incompatible protocol version')
|
||||
}
|
||||
if (message.auth != null && this.connections[sender] != null) {
|
||||
// authenticate using auth in message
|
||||
var auth = this.checkAuth(message.auth, this.y)
|
||||
var auth = this.checkAuth(message.auth, this.y, sender)
|
||||
this.connections[sender].auth = auth
|
||||
auth.then(auth => {
|
||||
for (var f of this.userEventListeners) {
|
||||
@@ -284,87 +278,85 @@ export default function extendConnector (Y/* :any */) {
|
||||
})
|
||||
} else if (this.connections[sender] != null && this.connections[sender].auth == null) {
|
||||
// authenticate without otherwise
|
||||
this.connections[sender].auth = this.checkAuth(null, this.y)
|
||||
this.connections[sender].auth = this.checkAuth(null, this.y, sender)
|
||||
}
|
||||
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 answer = {
|
||||
type: 'sync step 2',
|
||||
stateSet: currentStateSet,
|
||||
deleteSet: ds,
|
||||
protocolVersion: this.protocolVersion,
|
||||
auth: this.authInfo
|
||||
}
|
||||
if (message.preferUntransformed === true && Object.keys(m.stateSet).length === 0) {
|
||||
answer.osUntransformed = yield * this.getOperationsUntransformed()
|
||||
} else {
|
||||
answer.os = yield * this.getOperations(m.stateSet)
|
||||
}
|
||||
conn.send(sender, answer)
|
||||
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 {
|
||||
this.y.db.requestTransaction(function *() {
|
||||
var currentStateSet = yield* this.getStateSet()
|
||||
if (canWrite(auth)) {
|
||||
yield* this.applyDeleteSet(m.deleteSet)
|
||||
}
|
||||
|
||||
var ds = yield* this.getDeleteSet()
|
||||
var answer = {
|
||||
type: 'sync step 2',
|
||||
stateSet: currentStateSet,
|
||||
deleteSet: ds,
|
||||
protocolVersion: this.protocolVersion,
|
||||
auth: this.authInfo
|
||||
}
|
||||
answer.os = yield* this.getOperations(m.stateSet)
|
||||
conn.send(sender, answer)
|
||||
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'
|
||||
})
|
||||
}
|
||||
})
|
||||
} else if (message.type === 'sync step 2' && canWrite(auth)) {
|
||||
var db = this.y.db
|
||||
let defer = this.connections[sender].syncStep2
|
||||
let m = message
|
||||
// apply operations first
|
||||
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)
|
||||
yield* this.applyDeleteSet(m.deleteSet)
|
||||
if (m.osUntransformed != null) {
|
||||
yield * this.applyOperationsUntransformed(m.osUntransformed, m.stateSet)
|
||||
yield* this.applyOperationsUntransformed(m.osUntransformed, m.stateSet)
|
||||
} else {
|
||||
this.store.apply(m.os)
|
||||
}
|
||||
// defer.resolve()
|
||||
})
|
||||
// then apply ds
|
||||
db.whenTransactionsFinished().then(() => {
|
||||
/*
|
||||
* This just sends the complete hb after some time
|
||||
* Mostly for debugging..
|
||||
*
|
||||
db.requestTransaction(function * () {
|
||||
yield * this.applyDeleteSet(m.deleteSet)
|
||||
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()
|
||||
})
|
||||
return defer.promise
|
||||
} else if (message.type === 'sync done') {
|
||||
var self = this
|
||||
this.connections[sender].syncStep2.promise.then(function () {
|
||||
this.syncStep2.then(function () {
|
||||
self._setSyncedWith(sender)
|
||||
})
|
||||
} else if (message.type === 'update' && canWrite(auth)) {
|
||||
@@ -385,7 +377,7 @@ export default function extendConnector (Y/* :any */) {
|
||||
}
|
||||
})
|
||||
} else {
|
||||
return Promise.reject(new Error('Unable to deliver message'))
|
||||
return Promise.reject('Unable to deliver message')
|
||||
}
|
||||
}
|
||||
_setSyncedWith (user) {
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
/* @flow */
|
||||
'use strict'
|
||||
|
||||
export default function extendDatabase (Y /* :any */) {
|
||||
module.exports = function (Y /* :any */) {
|
||||
/*
|
||||
Partial definition of an OperationStore.
|
||||
TODO: name it Database, operation store only holds operations.
|
||||
@@ -39,15 +39,14 @@ export default function extendDatabase (Y /* :any */) {
|
||||
*/
|
||||
constructor (y, opts) {
|
||||
this.y = y
|
||||
opts.gc = opts.gc === true
|
||||
this.dbOpts = opts
|
||||
var os = this
|
||||
this.userId = null
|
||||
var resolve_
|
||||
this.userIdPromise = new Promise(function (resolve) {
|
||||
resolve_ = resolve
|
||||
var resolve
|
||||
this.userIdPromise = new Promise(function (r) {
|
||||
resolve = r
|
||||
})
|
||||
this.userIdPromise.resolve = resolve_
|
||||
this.userIdPromise.resolve = resolve
|
||||
// whether to broadcast all applied operations (insert & delete hook)
|
||||
this.forwardAppliedOperations = false
|
||||
// E.g. this.listenersById[id] : Array<Listener>
|
||||
@@ -72,7 +71,7 @@ export default function extendDatabase (Y /* :any */) {
|
||||
this.waitingTransactions = []
|
||||
this.transactionInProgress = false
|
||||
this.transactionIsFlushed = false
|
||||
if (typeof YConcurrencyTestingMode !== 'undefined') {
|
||||
if (typeof YConcurrency_TestingMode !== 'undefined') {
|
||||
this.executeOrder = []
|
||||
}
|
||||
this.gc1 = [] // first stage
|
||||
@@ -80,7 +79,7 @@ export default function extendDatabase (Y /* :any */) {
|
||||
|
||||
function garbageCollect () {
|
||||
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) {
|
||||
console.warn('gc should be empty when not synced!')
|
||||
}
|
||||
@@ -89,7 +88,7 @@ export default function extendDatabase (Y /* :any */) {
|
||||
if (os.y.connector != null && os.y.connector.isSynced) {
|
||||
for (var i = 0; i < os.gc2.length; i++) {
|
||||
var oid = os.gc2[i]
|
||||
yield * this.garbageCollectOperation(oid)
|
||||
yield* this.garbageCollectOperation(oid)
|
||||
}
|
||||
os.gc2 = os.gc1
|
||||
os.gc1 = []
|
||||
@@ -118,9 +117,9 @@ export default function extendDatabase (Y /* :any */) {
|
||||
this.startRepairCheck()
|
||||
}
|
||||
startGarbageCollector () {
|
||||
this.gc = this.dbOpts.gc
|
||||
this.gc = this.dbOpts.gc == null || this.dbOpts.gc
|
||||
if (this.gc) {
|
||||
this.gcTimeout = !this.dbOpts.gcTimeout ? 100000 : this.dbOpts.gcTimeout
|
||||
this.gcTimeout = !this.dbOpts.gcTimeout ? 50000 : this.dbOpts.gcTimeout
|
||||
} else {
|
||||
this.gcTimeout = -1
|
||||
}
|
||||
@@ -178,7 +177,7 @@ export default function extendDatabase (Y /* :any */) {
|
||||
})
|
||||
}
|
||||
addToDebug () {
|
||||
if (typeof YConcurrencyTestingMode !== 'undefined') {
|
||||
if (typeof YConcurrency_TestingMode !== 'undefined') {
|
||||
var command /* :string */ = Array.prototype.map.call(arguments, function (s) {
|
||||
if (typeof s === 'string') {
|
||||
return s
|
||||
@@ -202,10 +201,10 @@ export default function extendDatabase (Y /* :any */) {
|
||||
self.gc1 = []
|
||||
self.gc2 = []
|
||||
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) {
|
||||
delete op.gc
|
||||
yield * this.setOperation(op)
|
||||
yield* this.setOperation(op)
|
||||
}
|
||||
}
|
||||
resolve()
|
||||
@@ -235,12 +234,12 @@ export default function extendDatabase (Y /* :any */) {
|
||||
if (left != null && left.deleted === true) {
|
||||
gc = true
|
||||
} 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
|
||||
}
|
||||
if (gc) {
|
||||
op.gc = true
|
||||
yield * this.setOperation(op)
|
||||
yield* this.setOperation(op)
|
||||
this.store.queueGarbageCollector(op.id)
|
||||
return true
|
||||
}
|
||||
@@ -266,7 +265,7 @@ export default function extendDatabase (Y /* :any */) {
|
||||
}
|
||||
}
|
||||
* destroy () {
|
||||
clearTimeout(this.gcInterval)
|
||||
clearInterval(this.gcInterval)
|
||||
this.gcInterval = null
|
||||
this.stopRepairCheck()
|
||||
}
|
||||
@@ -276,7 +275,7 @@ export default function extendDatabase (Y /* :any */) {
|
||||
var self = this
|
||||
self.requestTransaction(function * () {
|
||||
self.userId = userId
|
||||
var state = yield * this.getState(userId)
|
||||
var state = yield* this.getState(userId)
|
||||
self.opClock = state.clock
|
||||
self.userIdPromise.resolve(userId)
|
||||
})
|
||||
@@ -364,7 +363,7 @@ export default function extendDatabase (Y /* :any */) {
|
||||
|
||||
for (let key = 0; key < exeNow.length; key++) {
|
||||
let o = exeNow[key].op
|
||||
yield * store.tryExecute.call(this, o)
|
||||
yield* store.tryExecute.call(this, o)
|
||||
}
|
||||
|
||||
for (var sid in ls) {
|
||||
@@ -372,9 +371,9 @@ export default function extendDatabase (Y /* :any */) {
|
||||
var id = JSON.parse(sid)
|
||||
var op
|
||||
if (typeof id[1] === 'string') {
|
||||
op = yield * this.getOperation(id)
|
||||
op = yield* this.getOperation(id)
|
||||
} else {
|
||||
op = yield * this.getInsertion(id)
|
||||
op = yield* this.getInsertion(id)
|
||||
}
|
||||
if (op == null) {
|
||||
store.listenersById[sid] = l
|
||||
@@ -383,7 +382,7 @@ export default function extendDatabase (Y /* :any */) {
|
||||
let listener = l[i]
|
||||
let o = listener.op
|
||||
if (--listener.missing === 0) {
|
||||
yield * store.tryExecute.call(this, o)
|
||||
yield* store.tryExecute.call(this, o)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -401,14 +400,14 @@ export default function extendDatabase (Y /* :any */) {
|
||||
whenOperationsExist: any;
|
||||
*/
|
||||
* 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') {
|
||||
yield * Y.Struct.Delete.execute.call(this, op)
|
||||
yield* Y.Struct.Delete.execute.call(this, op)
|
||||
// this is now called in Transaction.deleteOperation!
|
||||
// yield * this.store.operationAdded(this, op)
|
||||
// yield* this.store.operationAdded(this, op)
|
||||
} else {
|
||||
// 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) {
|
||||
// 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) {
|
||||
@@ -417,23 +416,23 @@ export default function extendDatabase (Y /* :any */) {
|
||||
op.id = [op.id[0], op.id[1] + overlapSize]
|
||||
op.left = Y.utils.getLastId(defined)
|
||||
op.origin = op.left
|
||||
defined = yield * this.getOperation(op.id) // getOperation suffices here
|
||||
defined = yield* this.getOperation(op.id) // getOperation suffices here
|
||||
} else {
|
||||
break
|
||||
}
|
||||
}
|
||||
if (defined == null) {
|
||||
var opid = op.id
|
||||
var isGarbageCollected = yield * this.isGarbageCollected(opid)
|
||||
var isGarbageCollected = yield* this.isGarbageCollected(opid)
|
||||
if (!isGarbageCollected) {
|
||||
// TODO: reduce number of get / put calls for op ..
|
||||
yield * Y.Struct[op.struct].execute.call(this, op)
|
||||
yield * this.addOperation(op)
|
||||
yield * this.store.operationAdded(this, op)
|
||||
yield* Y.Struct[op.struct].execute.call(this, op)
|
||||
yield* this.addOperation(op)
|
||||
yield* this.store.operationAdded(this, op)
|
||||
// operationAdded can change op..
|
||||
op = yield * this.getOperation(opid)
|
||||
op = yield* this.getOperation(opid)
|
||||
// if insertion, try to combine with left
|
||||
yield * this.tryCombineWithLeft(op)
|
||||
yield* this.tryCombineWithLeft(op)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -454,11 +453,11 @@ export default function extendDatabase (Y /* :any */) {
|
||||
if (op.struct === 'Delete') {
|
||||
var type = this.initializedTypes[JSON.stringify(op.targetParent)]
|
||||
if (type != null) {
|
||||
yield * type._changed(transaction, op)
|
||||
yield* type._changed(transaction, op)
|
||||
}
|
||||
} else {
|
||||
// increase SS
|
||||
yield * transaction.updateState(op.id[0])
|
||||
yield* transaction.updateState(op.id[0])
|
||||
var opLen = op.content != null ? op.content.length : 1
|
||||
for (let i = 0; i < opLen; i++) {
|
||||
// notify whenOperation listeners (by id)
|
||||
@@ -478,9 +477,9 @@ export default function extendDatabase (Y /* :any */) {
|
||||
|
||||
// if parent is deleted, mark as gc'd and return
|
||||
if (op.parent != null) {
|
||||
var parentIsDeleted = yield * transaction.isDeleted(op.parent)
|
||||
var parentIsDeleted = yield* transaction.isDeleted(op.parent)
|
||||
if (parentIsDeleted) {
|
||||
yield * transaction.deleteList(op.id)
|
||||
yield* transaction.deleteList(op.id)
|
||||
return
|
||||
}
|
||||
}
|
||||
@@ -488,7 +487,7 @@ export default function extendDatabase (Y /* :any */) {
|
||||
// notify parent, if it was instanciated as a custom type
|
||||
if (t != null) {
|
||||
let o = Y.utils.copyOperation(op)
|
||||
yield * t._changed(transaction, o)
|
||||
yield* t._changed(transaction, o)
|
||||
}
|
||||
if (!op.deleted) {
|
||||
// Delete if DS says this is actually deleted
|
||||
@@ -497,13 +496,13 @@ export default function extendDatabase (Y /* :any */) {
|
||||
// TODO: !! console.log('TODO: change this before commiting')
|
||||
for (let i = 0; i < len; i++) {
|
||||
var id = [startId[0], startId[1] + i]
|
||||
var opIsDeleted = yield * transaction.isDeleted(id)
|
||||
var opIsDeleted = yield* transaction.isDeleted(id)
|
||||
if (opIsDeleted) {
|
||||
var delop = {
|
||||
struct: 'Delete',
|
||||
target: id
|
||||
}
|
||||
yield * this.tryExecute.call(transaction, delop)
|
||||
yield* this.tryExecute.call(transaction, delop)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -512,12 +511,12 @@ export default function extendDatabase (Y /* :any */) {
|
||||
whenTransactionsFinished () {
|
||||
if (this.transactionInProgress) {
|
||||
if (this.transactionsFinished == null) {
|
||||
var resolve_
|
||||
var promise = new Promise(function (resolve) {
|
||||
resolve_ = resolve
|
||||
var resolve
|
||||
var promise = new Promise(function (r) {
|
||||
resolve = r
|
||||
})
|
||||
this.transactionsFinished = {
|
||||
resolve: resolve_,
|
||||
resolve: resolve,
|
||||
promise: promise
|
||||
}
|
||||
}
|
||||
@@ -541,7 +540,7 @@ export default function extendDatabase (Y /* :any */) {
|
||||
} else {
|
||||
this.transactionIsFlushed = true
|
||||
return function * () {
|
||||
yield * this.flush()
|
||||
yield* this.flush()
|
||||
}
|
||||
}
|
||||
} else {
|
||||
@@ -572,9 +571,9 @@ export default function extendDatabase (Y /* :any */) {
|
||||
var sid = JSON.stringify(id)
|
||||
var t = this.store.initializedTypes[sid]
|
||||
if (t == null) {
|
||||
var op/* :MapStruct | ListStruct */ = yield * this.getOperation(id)
|
||||
var op/* :MapStruct | ListStruct */ = yield* this.getOperation(id)
|
||||
if (op != null) {
|
||||
t = yield * Y[op.type].typeDefinition.initType.call(this, this.store, op, args)
|
||||
t = yield* Y[op.type].typeDefinition.initType.call(this, this.store, op, args)
|
||||
this.store.initializedTypes[sid] = t
|
||||
}
|
||||
}
|
||||
@@ -591,9 +590,9 @@ export default function extendDatabase (Y /* :any */) {
|
||||
|
||||
this.requestTransaction(function * () {
|
||||
if (op.id[0] === '_') {
|
||||
yield * this.setOperation(op)
|
||||
yield* this.setOperation(op)
|
||||
} else {
|
||||
yield * this.applyCreatedOperations([op])
|
||||
yield* this.applyCreatedOperations([op])
|
||||
}
|
||||
})
|
||||
var t = Y[op.type].typeDefinition.createType(this, op, typedefinition[1])
|
||||
|
||||
@@ -17,141 +17,141 @@ for (let database of databases) {
|
||||
})
|
||||
afterEach(function (done) {
|
||||
store.requestTransaction(function * () {
|
||||
yield * this.store.destroy()
|
||||
yield* this.store.destroy()
|
||||
done()
|
||||
})
|
||||
})
|
||||
it('Deleted operation is deleted', async(function * (done) {
|
||||
store.requestTransaction(function * () {
|
||||
yield * this.markDeleted(['u1', 10], 1)
|
||||
expect(yield * this.isDeleted(['u1', 10])).toBeTruthy()
|
||||
expect(yield * this.getDeleteSet()).toEqual({'u1': [[10, 1, false]]})
|
||||
yield* this.markDeleted(['u1', 10], 1)
|
||||
expect(yield* this.isDeleted(['u1', 10])).toBeTruthy()
|
||||
expect(yield* this.getDeleteSet()).toEqual({'u1': [[10, 1, false]]})
|
||||
done()
|
||||
})
|
||||
}))
|
||||
it('Deleted operation extends other deleted operation', async(function * (done) {
|
||||
store.requestTransaction(function * () {
|
||||
yield * this.markDeleted(['u1', 10], 1)
|
||||
yield * this.markDeleted(['u1', 11], 1)
|
||||
expect(yield * this.isDeleted(['u1', 10])).toBeTruthy()
|
||||
expect(yield * this.isDeleted(['u1', 11])).toBeTruthy()
|
||||
expect(yield * this.getDeleteSet()).toEqual({'u1': [[10, 2, false]]})
|
||||
yield* this.markDeleted(['u1', 10], 1)
|
||||
yield* this.markDeleted(['u1', 11], 1)
|
||||
expect(yield* this.isDeleted(['u1', 10])).toBeTruthy()
|
||||
expect(yield* this.isDeleted(['u1', 11])).toBeTruthy()
|
||||
expect(yield* this.getDeleteSet()).toEqual({'u1': [[10, 2, false]]})
|
||||
done()
|
||||
})
|
||||
}))
|
||||
it('Deleted operation extends other deleted operation', async(function * (done) {
|
||||
store.requestTransaction(function * () {
|
||||
yield * this.markDeleted(['0', 3], 1)
|
||||
yield * this.markDeleted(['0', 4], 1)
|
||||
yield * this.markDeleted(['0', 2], 1)
|
||||
expect(yield * this.getDeleteSet()).toEqual({'0': [[2, 3, false]]})
|
||||
yield* this.markDeleted(['0', 3], 1)
|
||||
yield* this.markDeleted(['0', 4], 1)
|
||||
yield* this.markDeleted(['0', 2], 1)
|
||||
expect(yield* this.getDeleteSet()).toEqual({'0': [[2, 3, false]]})
|
||||
done()
|
||||
})
|
||||
}))
|
||||
it('Debug #1', async(function * (done) {
|
||||
store.requestTransaction(function * () {
|
||||
yield * this.markDeleted(['166', 0], 1)
|
||||
yield * this.markDeleted(['166', 2], 1)
|
||||
yield * this.markDeleted(['166', 0], 1)
|
||||
yield * this.markDeleted(['166', 2], 1)
|
||||
yield * this.markGarbageCollected(['166', 2], 1)
|
||||
yield * this.markDeleted(['166', 1], 1)
|
||||
yield * this.markDeleted(['166', 3], 1)
|
||||
yield * this.markGarbageCollected(['166', 3], 1)
|
||||
yield * this.markDeleted(['166', 0], 1)
|
||||
expect(yield * this.getDeleteSet()).toEqual({'166': [[0, 2, false], [2, 2, true]]})
|
||||
yield* this.markDeleted(['166', 0], 1)
|
||||
yield* this.markDeleted(['166', 2], 1)
|
||||
yield* this.markDeleted(['166', 0], 1)
|
||||
yield* this.markDeleted(['166', 2], 1)
|
||||
yield* this.markGarbageCollected(['166', 2], 1)
|
||||
yield* this.markDeleted(['166', 1], 1)
|
||||
yield* this.markDeleted(['166', 3], 1)
|
||||
yield* this.markGarbageCollected(['166', 3], 1)
|
||||
yield* this.markDeleted(['166', 0], 1)
|
||||
expect(yield* this.getDeleteSet()).toEqual({'166': [[0, 2, false], [2, 2, true]]})
|
||||
done()
|
||||
})
|
||||
}))
|
||||
it('Debug #2', async(function * (done) {
|
||||
store.requestTransaction(function * () {
|
||||
yield * this.markDeleted(['293', 0], 1)
|
||||
yield * this.markDeleted(['291', 2], 1)
|
||||
yield * this.markDeleted(['291', 2], 1)
|
||||
yield * this.markGarbageCollected(['293', 0], 1)
|
||||
yield * this.markDeleted(['293', 1], 1)
|
||||
yield * this.markGarbageCollected(['291', 2], 1)
|
||||
expect(yield * this.getDeleteSet()).toEqual({'291': [[2, 1, true]], '293': [[0, 1, true], [1, 1, false]]})
|
||||
yield* this.markDeleted(['293', 0], 1)
|
||||
yield* this.markDeleted(['291', 2], 1)
|
||||
yield* this.markDeleted(['291', 2], 1)
|
||||
yield* this.markGarbageCollected(['293', 0], 1)
|
||||
yield* this.markDeleted(['293', 1], 1)
|
||||
yield* this.markGarbageCollected(['291', 2], 1)
|
||||
expect(yield* this.getDeleteSet()).toEqual({'291': [[2, 1, true]], '293': [[0, 1, true], [1, 1, false]]})
|
||||
done()
|
||||
})
|
||||
}))
|
||||
it('Debug #3', async(function * (done) {
|
||||
store.requestTransaction(function * () {
|
||||
yield * this.markDeleted(['581', 0], 1)
|
||||
yield * this.markDeleted(['581', 1], 1)
|
||||
yield * this.markDeleted(['580', 0], 1)
|
||||
yield * this.markDeleted(['580', 0], 1)
|
||||
yield * this.markGarbageCollected(['581', 0], 1)
|
||||
yield * this.markDeleted(['581', 2], 1)
|
||||
yield * this.markDeleted(['580', 1], 1)
|
||||
yield * this.markDeleted(['580', 2], 1)
|
||||
yield * this.markDeleted(['580', 1], 1)
|
||||
yield * this.markDeleted(['580', 2], 1)
|
||||
yield * this.markGarbageCollected(['581', 2], 1)
|
||||
yield * this.markGarbageCollected(['581', 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]]})
|
||||
yield* this.markDeleted(['581', 0], 1)
|
||||
yield* this.markDeleted(['581', 1], 1)
|
||||
yield* this.markDeleted(['580', 0], 1)
|
||||
yield* this.markDeleted(['580', 0], 1)
|
||||
yield* this.markGarbageCollected(['581', 0], 1)
|
||||
yield* this.markDeleted(['581', 2], 1)
|
||||
yield* this.markDeleted(['580', 1], 1)
|
||||
yield* this.markDeleted(['580', 2], 1)
|
||||
yield* this.markDeleted(['580', 1], 1)
|
||||
yield* this.markDeleted(['580', 2], 1)
|
||||
yield* this.markGarbageCollected(['581', 2], 1)
|
||||
yield* this.markGarbageCollected(['581', 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]]})
|
||||
done()
|
||||
})
|
||||
}))
|
||||
it('Debug #4', async(function * (done) {
|
||||
store.requestTransaction(function * () {
|
||||
yield * this.markDeleted(['544', 0], 1)
|
||||
yield * this.markDeleted(['543', 2], 1)
|
||||
yield * this.markDeleted(['544', 0], 1)
|
||||
yield * this.markDeleted(['543', 2], 1)
|
||||
yield * this.markGarbageCollected(['544', 0], 1)
|
||||
yield * this.markDeleted(['545', 1], 1)
|
||||
yield * this.markDeleted(['543', 4], 1)
|
||||
yield * this.markDeleted(['543', 3], 1)
|
||||
yield * this.markDeleted(['544', 1], 1)
|
||||
yield * this.markDeleted(['544', 2], 1)
|
||||
yield * this.markDeleted(['544', 1], 1)
|
||||
yield * this.markDeleted(['544', 2], 1)
|
||||
yield * this.markGarbageCollected(['543', 2], 1)
|
||||
yield * this.markGarbageCollected(['543', 4], 1)
|
||||
yield * this.markGarbageCollected(['544', 2], 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]]})
|
||||
yield* this.markDeleted(['544', 0], 1)
|
||||
yield* this.markDeleted(['543', 2], 1)
|
||||
yield* this.markDeleted(['544', 0], 1)
|
||||
yield* this.markDeleted(['543', 2], 1)
|
||||
yield* this.markGarbageCollected(['544', 0], 1)
|
||||
yield* this.markDeleted(['545', 1], 1)
|
||||
yield* this.markDeleted(['543', 4], 1)
|
||||
yield* this.markDeleted(['543', 3], 1)
|
||||
yield* this.markDeleted(['544', 1], 1)
|
||||
yield* this.markDeleted(['544', 2], 1)
|
||||
yield* this.markDeleted(['544', 1], 1)
|
||||
yield* this.markDeleted(['544', 2], 1)
|
||||
yield* this.markGarbageCollected(['543', 2], 1)
|
||||
yield* this.markGarbageCollected(['543', 4], 1)
|
||||
yield* this.markGarbageCollected(['544', 2], 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]]})
|
||||
done()
|
||||
})
|
||||
}))
|
||||
it('Debug #5', async(function * (done) {
|
||||
store.requestTransaction(function * () {
|
||||
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]]})
|
||||
yield * this.applyDeleteSet({'16': [[1, 2, false]], '17': [[0, 4, true]]})
|
||||
expect(yield * this.getDeleteSet()).toEqual({'16': [[1, 2, false]], '17': [[0, 4, true]]})
|
||||
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]]})
|
||||
yield* this.applyDeleteSet({'16': [[1, 2, false]], '17': [[0, 4, true]]})
|
||||
expect(yield* this.getDeleteSet()).toEqual({'16': [[1, 2, false]], '17': [[0, 4, true]]})
|
||||
done()
|
||||
})
|
||||
}))
|
||||
it('Debug #6', async(function * (done) {
|
||||
store.requestTransaction(function * () {
|
||||
yield * this.applyDeleteSet({'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]]})
|
||||
expect(yield * this.getDeleteSet()).toEqual({'39': [[2, 2, false]], '40': [[0, 1, true], [1, 2, false]], '41': [[2, 1, false]]})
|
||||
yield* this.applyDeleteSet({'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]]})
|
||||
expect(yield* this.getDeleteSet()).toEqual({'39': [[2, 2, false]], '40': [[0, 1, true], [1, 2, false]], '41': [[2, 1, false]]})
|
||||
done()
|
||||
})
|
||||
}))
|
||||
it('Debug #7', async(function * (done) {
|
||||
store.requestTransaction(function * () {
|
||||
yield * this.markDeleted(['9', 2], 1)
|
||||
yield * this.markDeleted(['11', 2], 1)
|
||||
yield * this.markDeleted(['11', 4], 1)
|
||||
yield * this.markDeleted(['11', 1], 1)
|
||||
yield * this.markDeleted(['9', 4], 1)
|
||||
yield * this.markDeleted(['10', 0], 1)
|
||||
yield * this.markGarbageCollected(['11', 2], 1)
|
||||
yield * this.markDeleted(['11', 2], 1)
|
||||
yield * this.markGarbageCollected(['11', 3], 1)
|
||||
yield * this.markDeleted(['11', 3], 1)
|
||||
yield * this.markDeleted(['11', 3], 1)
|
||||
yield * this.markDeleted(['9', 4], 1)
|
||||
yield * this.markDeleted(['10', 0], 1)
|
||||
yield * this.markGarbageCollected(['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]]})
|
||||
yield* this.markDeleted(['9', 2], 1)
|
||||
yield* this.markDeleted(['11', 2], 1)
|
||||
yield* this.markDeleted(['11', 4], 1)
|
||||
yield* this.markDeleted(['11', 1], 1)
|
||||
yield* this.markDeleted(['9', 4], 1)
|
||||
yield* this.markDeleted(['10', 0], 1)
|
||||
yield* this.markGarbageCollected(['11', 2], 1)
|
||||
yield* this.markDeleted(['11', 2], 1)
|
||||
yield* this.markGarbageCollected(['11', 3], 1)
|
||||
yield* this.markDeleted(['11', 3], 1)
|
||||
yield* this.markDeleted(['11', 3], 1)
|
||||
yield* this.markDeleted(['9', 4], 1)
|
||||
yield* this.markDeleted(['10', 0], 1)
|
||||
yield* this.markGarbageCollected(['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]]})
|
||||
done()
|
||||
})
|
||||
}))
|
||||
@@ -167,54 +167,54 @@ for (let database of databases) {
|
||||
})
|
||||
afterEach(function (done) {
|
||||
store.requestTransaction(function * () {
|
||||
yield * this.store.destroy()
|
||||
yield* this.store.destroy()
|
||||
done()
|
||||
})
|
||||
})
|
||||
it('debug #1', function (done) {
|
||||
store.requestTransaction(function * () {
|
||||
yield * this.os.put({id: [2]})
|
||||
yield * this.os.put({id: [0]})
|
||||
yield * this.os.delete([2])
|
||||
yield * this.os.put({id: [1]})
|
||||
expect(yield * this.os.find([0])).toBeTruthy()
|
||||
expect(yield * this.os.find([1])).toBeTruthy()
|
||||
expect(yield * this.os.find([2])).toBeFalsy()
|
||||
yield* this.os.put({id: [2]})
|
||||
yield* this.os.put({id: [0]})
|
||||
yield* this.os.delete([2])
|
||||
yield* this.os.put({id: [1]})
|
||||
expect(yield* this.os.find([0])).toBeTruthy()
|
||||
expect(yield* this.os.find([1])).toBeTruthy()
|
||||
expect(yield* this.os.find([2])).toBeFalsy()
|
||||
done()
|
||||
})
|
||||
})
|
||||
it('can add&retrieve 5 elements', function (done) {
|
||||
store.requestTransaction(function * () {
|
||||
yield * this.os.put({val: 'four', id: [4]})
|
||||
yield * this.os.put({val: 'one', id: [1]})
|
||||
yield * this.os.put({val: 'three', id: [3]})
|
||||
yield * this.os.put({val: 'two', id: [2]})
|
||||
yield * this.os.put({val: 'five', id: [5]})
|
||||
expect((yield * this.os.find([1])).val).toEqual('one')
|
||||
expect((yield * this.os.find([2])).val).toEqual('two')
|
||||
expect((yield * this.os.find([3])).val).toEqual('three')
|
||||
expect((yield * this.os.find([4])).val).toEqual('four')
|
||||
expect((yield * this.os.find([5])).val).toEqual('five')
|
||||
yield* this.os.put({val: 'four', id: [4]})
|
||||
yield* this.os.put({val: 'one', id: [1]})
|
||||
yield* this.os.put({val: 'three', id: [3]})
|
||||
yield* this.os.put({val: 'two', id: [2]})
|
||||
yield* this.os.put({val: 'five', id: [5]})
|
||||
expect((yield* this.os.find([1])).val).toEqual('one')
|
||||
expect((yield* this.os.find([2])).val).toEqual('two')
|
||||
expect((yield* this.os.find([3])).val).toEqual('three')
|
||||
expect((yield* this.os.find([4])).val).toEqual('four')
|
||||
expect((yield* this.os.find([5])).val).toEqual('five')
|
||||
done()
|
||||
})
|
||||
})
|
||||
it('5 elements do not exist anymore after deleting them', function (done) {
|
||||
store.requestTransaction(function * () {
|
||||
yield * this.os.put({val: 'four', id: [4]})
|
||||
yield * this.os.put({val: 'one', id: [1]})
|
||||
yield * this.os.put({val: 'three', id: [3]})
|
||||
yield * this.os.put({val: 'two', id: [2]})
|
||||
yield * this.os.put({val: 'five', id: [5]})
|
||||
yield * this.os.delete([4])
|
||||
expect(yield * this.os.find([4])).not.toBeTruthy()
|
||||
yield * this.os.delete([3])
|
||||
expect(yield * this.os.find([3])).not.toBeTruthy()
|
||||
yield * this.os.delete([2])
|
||||
expect(yield * this.os.find([2])).not.toBeTruthy()
|
||||
yield * this.os.delete([1])
|
||||
expect(yield * this.os.find([1])).not.toBeTruthy()
|
||||
yield * this.os.delete([5])
|
||||
expect(yield * this.os.find([5])).not.toBeTruthy()
|
||||
yield* this.os.put({val: 'four', id: [4]})
|
||||
yield* this.os.put({val: 'one', id: [1]})
|
||||
yield* this.os.put({val: 'three', id: [3]})
|
||||
yield* this.os.put({val: 'two', id: [2]})
|
||||
yield* this.os.put({val: 'five', id: [5]})
|
||||
yield* this.os.delete([4])
|
||||
expect(yield* this.os.find([4])).not.toBeTruthy()
|
||||
yield* this.os.delete([3])
|
||||
expect(yield* this.os.find([3])).not.toBeTruthy()
|
||||
yield* this.os.delete([2])
|
||||
expect(yield* this.os.find([2])).not.toBeTruthy()
|
||||
yield* this.os.delete([1])
|
||||
expect(yield* this.os.find([1])).not.toBeTruthy()
|
||||
yield* this.os.delete([5])
|
||||
expect(yield* this.os.find([5])).not.toBeTruthy()
|
||||
done()
|
||||
})
|
||||
})
|
||||
@@ -232,9 +232,9 @@ for (let database of databases) {
|
||||
var r = Math.random()
|
||||
if (r < 0.8) {
|
||||
var obj = [Math.floor(Math.random() * numberOfOSTests * 10000)]
|
||||
if (!(yield * this.os.find(obj))) {
|
||||
if (!(yield* this.os.find(obj))) {
|
||||
elements.push(obj)
|
||||
yield * this.os.put({id: obj})
|
||||
yield* this.os.put({id: obj})
|
||||
}
|
||||
} else if (elements.length > 0) {
|
||||
var elemid = Math.floor(Math.random() * elements.length)
|
||||
@@ -242,7 +242,7 @@ for (let database of databases) {
|
||||
elements = elements.filter(function (e) {
|
||||
return !Y.utils.compareIds(e, elem)
|
||||
})
|
||||
yield * this.os.delete(elem)
|
||||
yield* this.os.delete(elem)
|
||||
}
|
||||
}
|
||||
done()
|
||||
@@ -250,14 +250,14 @@ for (let database of databases) {
|
||||
})
|
||||
afterAll(function (done) {
|
||||
store.requestTransaction(function * () {
|
||||
yield * this.store.destroy()
|
||||
yield* this.store.destroy()
|
||||
done()
|
||||
})
|
||||
})
|
||||
it('can find every object', function (done) {
|
||||
store.requestTransaction(function * () {
|
||||
for (var id of elements) {
|
||||
expect((yield * this.os.find(id)).id).toEqual(id)
|
||||
expect((yield* this.os.find(id)).id).toEqual(id)
|
||||
}
|
||||
done()
|
||||
})
|
||||
@@ -266,7 +266,7 @@ for (let database of databases) {
|
||||
it('can find every object with lower bound search', function (done) {
|
||||
store.requestTransaction(function * () {
|
||||
for (var id of elements) {
|
||||
var e = yield * this.os.findWithLowerBound(id)
|
||||
var e = yield* this.os.findWithLowerBound(id)
|
||||
expect(e.id).toEqual(id)
|
||||
}
|
||||
done()
|
||||
@@ -281,7 +281,7 @@ for (let database of databases) {
|
||||
|
||||
var actualResults = 0
|
||||
store.requestTransaction(function * () {
|
||||
yield * this.os.iterate(this, lowerBound, null, function * (val) {
|
||||
yield* this.os.iterate(this, lowerBound, null, function * (val) {
|
||||
expect(val).toBeDefined()
|
||||
actualResults++
|
||||
})
|
||||
@@ -297,7 +297,7 @@ for (let database of databases) {
|
||||
}).length
|
||||
var actualResults = 0
|
||||
store.requestTransaction(function * () {
|
||||
yield * this.os.iterate(this, lowerBound, null, function * (val) {
|
||||
yield* this.os.iterate(this, lowerBound, null, function * (val) {
|
||||
expect(val).toBeDefined()
|
||||
actualResults++
|
||||
})
|
||||
@@ -314,7 +314,7 @@ for (let database of databases) {
|
||||
|
||||
var actualResults = 0
|
||||
store.requestTransaction(function * () {
|
||||
yield * this.os.iterate(this, null, upperBound, function * (val) {
|
||||
yield* this.os.iterate(this, null, upperBound, function * (val) {
|
||||
expect(val).toBeDefined()
|
||||
actualResults++
|
||||
})
|
||||
@@ -340,7 +340,7 @@ for (let database of databases) {
|
||||
}).length
|
||||
var actualResults = 0
|
||||
store.requestTransaction(function * () {
|
||||
yield * this.os.iterate(this, lowerBound, upperBound, function * (val) {
|
||||
yield* this.os.iterate(this, lowerBound, upperBound, function * (val) {
|
||||
expect(val).toBeDefined()
|
||||
actualResults++
|
||||
})
|
||||
|
||||
@@ -46,7 +46,7 @@ g.setRandomSeed = function setRandomSeed (seed) {
|
||||
|
||||
g.generateRandomSeed()
|
||||
|
||||
g.YConcurrencyTestingMode = true
|
||||
g.YConcurrency_TestingMode = true
|
||||
|
||||
jasmine.DEFAULT_TIMEOUT_INTERVAL = 200000
|
||||
|
||||
@@ -168,7 +168,7 @@ function fixAwaitingInType (type) {
|
||||
type.os.requestTransaction(function * () {
|
||||
if (type.eventHandler.awaiting > 0 && type.eventHandler._debuggingAwaiting === true) {
|
||||
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)
|
||||
})
|
||||
@@ -178,13 +178,13 @@ function fixAwaitingInType (type) {
|
||||
g.fixAwaitingInType = fixAwaitingInType
|
||||
|
||||
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 Promise.all(objects.map(fixAwaitingInType))
|
||||
})
|
||||
|
||||
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 Y.utils.globalRoom.flushAll()
|
||||
yield Promise.all(objects.map(fixAwaitingInType))
|
||||
@@ -200,7 +200,7 @@ g.applyRandomTransactionsAllRejoinNoGC = async(function * applyRandomTransaction
|
||||
})
|
||||
|
||||
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 Promise.all(objects.map(fixAwaitingInType))
|
||||
for (var u in users) {
|
||||
@@ -242,18 +242,18 @@ g.compareAllUsers = async(function * compareAllUsers (users) {
|
||||
|
||||
// t1 and t2 basically do the same. They define t[1,2], ds[1,2], and allDels[1,2]
|
||||
function * t1 () {
|
||||
s1 = yield * this.getStateSet()
|
||||
ds1 = yield * this.getDeleteSet()
|
||||
s1 = yield* this.getStateSet()
|
||||
ds1 = yield* this.getDeleteSet()
|
||||
allDels1 = []
|
||||
yield * this.ds.iterate(this, null, null, function * (d) {
|
||||
yield* this.ds.iterate(this, null, null, function * (d) {
|
||||
allDels1.push(d)
|
||||
})
|
||||
}
|
||||
function * t2 () {
|
||||
s2 = yield * this.getStateSet()
|
||||
ds2 = yield * this.getDeleteSet()
|
||||
s2 = yield* this.getStateSet()
|
||||
ds2 = yield* this.getDeleteSet()
|
||||
allDels2 = []
|
||||
yield * this.ds.iterate(this, null, null, function * (d) {
|
||||
yield* this.ds.iterate(this, null, null, function * (d) {
|
||||
allDels2.push(d)
|
||||
})
|
||||
}
|
||||
@@ -269,25 +269,25 @@ g.compareAllUsers = async(function * compareAllUsers (users) {
|
||||
for (var uid = 0; uid < users.length; uid++) {
|
||||
var u = users[uid]
|
||||
u.db.requestTransaction(function * () {
|
||||
var sv = yield * this.getStateVector()
|
||||
var sv = yield* this.getStateVector()
|
||||
for (var s of sv) {
|
||||
yield * this.updateState(s.user)
|
||||
yield* this.updateState(s.user)
|
||||
}
|
||||
// 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) {
|
||||
expect(yield * this.isDeleted(o.id)).toBeTruthy()
|
||||
expect(yield* this.isDeleted(o.id)).toBeTruthy()
|
||||
}
|
||||
})
|
||||
// compare deleteStore against deleted ops
|
||||
var ds = []
|
||||
yield * this.ds.iterate(this, null, null, function * (d) {
|
||||
yield* this.ds.iterate(this, null, null, function * (d) {
|
||||
ds.push(d)
|
||||
})
|
||||
for (var j in ds) {
|
||||
var d = ds[j]
|
||||
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
|
||||
if (d.gc) {
|
||||
expect(o).toBeFalsy()
|
||||
@@ -300,8 +300,8 @@ g.compareAllUsers = async(function * compareAllUsers (users) {
|
||||
// compare allDels tree
|
||||
if (s1 == null) {
|
||||
u.db.requestTransaction(function * () {
|
||||
yield * t1.call(this)
|
||||
yield * this.os.iterate(this, null, null, function * (o) {
|
||||
yield* t1.call(this)
|
||||
yield* this.os.iterate(this, null, null, function * (o) {
|
||||
o = Y.utils.copyObject(o)
|
||||
delete o.origin
|
||||
delete o.originOf
|
||||
@@ -310,9 +310,9 @@ g.compareAllUsers = async(function * compareAllUsers (users) {
|
||||
})
|
||||
} else {
|
||||
u.db.requestTransaction(function * () {
|
||||
yield * t2.call(this)
|
||||
yield* t2.call(this)
|
||||
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)
|
||||
delete o.origin
|
||||
delete o.originOf
|
||||
|
||||
@@ -19,7 +19,7 @@
|
||||
* requiredOps
|
||||
- Operations that are required to execute this operation.
|
||||
*/
|
||||
export default function extendStruct (Y) {
|
||||
module.exports = function (Y/* :any */) {
|
||||
var Struct = {
|
||||
/* 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
|
||||
@@ -40,7 +40,7 @@ export default function extendStruct (Y) {
|
||||
return [] // [op.target]
|
||||
},
|
||||
execute: function * (op) {
|
||||
return yield * this.deleteOperation(op.target, op.length || 1)
|
||||
return yield* this.deleteOperation(op.target, op.length || 1)
|
||||
}
|
||||
},
|
||||
Insert: {
|
||||
@@ -101,13 +101,13 @@ export default function extendStruct (Y) {
|
||||
return 0
|
||||
} else {
|
||||
var d = 0
|
||||
var o = yield * this.getInsertion(op.left)
|
||||
var o = yield* this.getInsertion(op.left)
|
||||
while (!Y.utils.matchesId(o, op.origin)) {
|
||||
d++
|
||||
if (o.left == null) {
|
||||
break
|
||||
} else {
|
||||
o = yield * this.getInsertion(o.left)
|
||||
o = yield* this.getInsertion(o.left)
|
||||
}
|
||||
}
|
||||
return d
|
||||
@@ -138,17 +138,17 @@ export default function extendStruct (Y) {
|
||||
if (op.origin != null) { // TODO: !== instead of !=
|
||||
// we save in origin that op originates in it
|
||||
// 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) {
|
||||
origin.originOf = []
|
||||
}
|
||||
origin.originOf.push(op.id)
|
||||
yield * this.setOperation(origin)
|
||||
yield* this.setOperation(origin)
|
||||
if (origin.right != null) {
|
||||
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..
|
||||
var o
|
||||
@@ -157,29 +157,29 @@ export default function extendStruct (Y) {
|
||||
|
||||
// find o. o is the first conflicting operation
|
||||
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) {
|
||||
// only if not added previously
|
||||
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
|
||||
parent = yield * this.getOperation(op.parent)
|
||||
parent = yield* this.getOperation(op.parent)
|
||||
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
|
||||
}
|
||||
|
||||
// make sure to split op.right if necessary (also add to tryCombineWithLeft)
|
||||
if (op.right != null) {
|
||||
tryToRemergeLater.push(op.right)
|
||||
yield * this.getInsertionCleanStart(op.right)
|
||||
yield* this.getInsertionCleanStart(op.right)
|
||||
}
|
||||
|
||||
// handle conflicts
|
||||
while (true) {
|
||||
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) {
|
||||
// case 1
|
||||
if (o.id[0] < op.id[0]) {
|
||||
@@ -197,7 +197,7 @@ export default function extendStruct (Y) {
|
||||
}
|
||||
i++
|
||||
if (o.right != null) {
|
||||
o = yield * this.getInsertion(o.right)
|
||||
o = yield* this.getInsertion(o.right)
|
||||
} else {
|
||||
o = null
|
||||
}
|
||||
@@ -210,17 +210,17 @@ export default function extendStruct (Y) {
|
||||
var left = null
|
||||
var right = null
|
||||
if (parent == null) {
|
||||
parent = yield * this.getOperation(op.parent)
|
||||
parent = yield* this.getOperation(op.parent)
|
||||
}
|
||||
|
||||
// reconnect left and set right of op
|
||||
if (op.left != null) {
|
||||
left = yield * this.getInsertion(op.left)
|
||||
left = yield* this.getInsertion(op.left)
|
||||
// link left
|
||||
op.right = left.right
|
||||
left.right = op.id
|
||||
|
||||
yield * this.setOperation(left)
|
||||
yield* this.setOperation(left)
|
||||
} else {
|
||||
// set op.right from parent, if necessary
|
||||
op.right = op.parentSub ? parent.map[op.parentSub] || null : parent.start
|
||||
@@ -228,33 +228,33 @@ export default function extendStruct (Y) {
|
||||
// reconnect right
|
||||
if (op.right != null) {
|
||||
// TODO: wanna connect right too?
|
||||
right = yield * this.getOperation(op.right)
|
||||
right = yield* this.getOperation(op.right)
|
||||
right.left = Y.utils.getLastId(op)
|
||||
|
||||
// if right exists, and it is supposed to be gc'd. Remove it from the gc
|
||||
if (right.gc != null) {
|
||||
if (right.content != null && right.content.length > 1) {
|
||||
right = yield * this.getInsertionCleanEnd(right.id)
|
||||
right = yield* this.getInsertionCleanEnd(right.id)
|
||||
}
|
||||
this.store.removeFromGarbageCollector(right)
|
||||
}
|
||||
yield * this.setOperation(right)
|
||||
yield* this.setOperation(right)
|
||||
}
|
||||
|
||||
// update parents .map/start/end properties
|
||||
if (op.parentSub != null) {
|
||||
if (left == null) {
|
||||
parent.map[op.parentSub] = op.id
|
||||
yield * this.setOperation(parent)
|
||||
yield* this.setOperation(parent)
|
||||
}
|
||||
// is a child of a map struct.
|
||||
// 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)
|
||||
if (op.right != null) {
|
||||
yield * this.deleteOperation(op.right, 1, true)
|
||||
yield* this.deleteOperation(op.right, 1, true)
|
||||
}
|
||||
if (op.left != null) {
|
||||
yield * this.deleteOperation(op.id, 1, true)
|
||||
yield* this.deleteOperation(op.id, 1, true)
|
||||
}
|
||||
} else {
|
||||
if (right == null || left == null) {
|
||||
@@ -264,14 +264,14 @@ export default function extendStruct (Y) {
|
||||
if (left == null) {
|
||||
parent.start = op.id
|
||||
}
|
||||
yield * this.setOperation(parent)
|
||||
yield* this.setOperation(parent)
|
||||
}
|
||||
}
|
||||
|
||||
// try to merge original op.left and op.origin
|
||||
for (i = 0; i < tryToRemergeLater.length; i++) {
|
||||
var m = yield * this.getOperation(tryToRemergeLater[i])
|
||||
yield * this.tryCombineWithLeft(m)
|
||||
var m = yield* this.getOperation(tryToRemergeLater[i])
|
||||
yield* this.tryCombineWithLeft(m)
|
||||
}
|
||||
}
|
||||
},
|
||||
@@ -329,7 +329,7 @@ export default function extendStruct (Y) {
|
||||
return null
|
||||
}
|
||||
var res = null
|
||||
var o = yield * this.getOperation(op.start)
|
||||
var o = yield* this.getOperation(op.start)
|
||||
|
||||
while (true) {
|
||||
if (!o.deleted) {
|
||||
@@ -337,7 +337,7 @@ export default function extendStruct (Y) {
|
||||
pos--
|
||||
}
|
||||
if (pos >= 0 && o.right != null) {
|
||||
o = yield * this.getOperation(o.right)
|
||||
o = yield* this.getOperation(o.right)
|
||||
} else {
|
||||
break
|
||||
}
|
||||
@@ -348,7 +348,7 @@ export default function extendStruct (Y) {
|
||||
o = o.start
|
||||
var res = []
|
||||
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) {
|
||||
res.push(f(operation))
|
||||
}
|
||||
@@ -398,13 +398,13 @@ export default function extendStruct (Y) {
|
||||
get: function * (op, name) {
|
||||
var oid = op.map[name]
|
||||
if (oid != null) {
|
||||
var res = yield * this.getOperation(oid)
|
||||
var res = yield* this.getOperation(oid)
|
||||
if (res == null || res.deleted) {
|
||||
return void 0
|
||||
} else if (res.opContent == null) {
|
||||
return res.content[0]
|
||||
} 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
|
||||
applyable on a given SS.
|
||||
*/
|
||||
export default function extendTransaction (Y) {
|
||||
module.exports = function (Y/* :any */) {
|
||||
class TransactionInterface {
|
||||
/* ::
|
||||
store: Y.AbstractDatabase;
|
||||
@@ -91,12 +91,12 @@ export default function extendTransaction (Y) {
|
||||
var send = []
|
||||
for (var i = 0; i < ops.length; 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') {
|
||||
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 (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
|
||||
this.store.y.connector.broadcastOps(send)
|
||||
}
|
||||
@@ -104,15 +104,15 @@ export default function extendTransaction (Y) {
|
||||
|
||||
* deleteList (start) {
|
||||
while (start != null) {
|
||||
start = yield * this.getOperation(start)
|
||||
start = yield* this.getOperation(start)
|
||||
if (!start.gc) {
|
||||
start.gc = true
|
||||
start.deleted = true
|
||||
yield * this.setOperation(start)
|
||||
yield* this.setOperation(start)
|
||||
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) {
|
||||
yield * this.deleteOperation(start.opContent)
|
||||
yield* this.deleteOperation(start.opContent)
|
||||
}
|
||||
this.store.queueGarbageCollector(start.id)
|
||||
}
|
||||
@@ -127,10 +127,10 @@ export default function extendTransaction (Y) {
|
||||
if (length == null) {
|
||||
length = 1
|
||||
}
|
||||
yield * this.markDeleted(targetId, length)
|
||||
yield* this.markDeleted(targetId, length)
|
||||
while (length > 0) {
|
||||
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
|
||||
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
|
||||
@@ -141,12 +141,12 @@ export default function extendTransaction (Y) {
|
||||
if (!target.deleted) {
|
||||
if (target.id[1] < targetId[1]) {
|
||||
// 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!
|
||||
}
|
||||
if (target.id[1] + targetLength > targetId[1] + length) {
|
||||
// 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
|
||||
}
|
||||
}
|
||||
@@ -161,35 +161,35 @@ export default function extendTransaction (Y) {
|
||||
// delete containing lists
|
||||
if (target.start != null) {
|
||||
// TODO: don't do it like this .. -.-
|
||||
yield * this.deleteList(target.start)
|
||||
// yield * this.deleteList(target.id) -- do not gc itself because this may still get referenced
|
||||
yield* this.deleteList(target.start)
|
||||
// yield* this.deleteList(target.id) -- do not gc itself because this may still get referenced
|
||||
}
|
||||
if (target.map != null) {
|
||||
for (var name in target.map) {
|
||||
yield * this.deleteList(target.map[name])
|
||||
yield* this.deleteList(target.map[name])
|
||||
}
|
||||
// TODO: here to.. (see above)
|
||||
// yield * this.deleteList(target.id) -- see above
|
||||
// yield* this.deleteList(target.id) -- see above
|
||||
}
|
||||
if (target.opContent != null) {
|
||||
yield * this.deleteOperation(target.opContent)
|
||||
yield* this.deleteOperation(target.opContent)
|
||||
// target.opContent = null
|
||||
}
|
||||
if (target.requires != null) {
|
||||
for (var i = 0; i < target.requires.length; i++) {
|
||||
yield * this.deleteOperation(target.requires[i])
|
||||
yield* this.deleteOperation(target.requires[i])
|
||||
}
|
||||
}
|
||||
}
|
||||
var left
|
||||
if (target.left != null) {
|
||||
left = yield * this.getInsertion(target.left)
|
||||
left = yield* this.getInsertion(target.left)
|
||||
} else {
|
||||
left = null
|
||||
}
|
||||
|
||||
// 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.
|
||||
@@ -198,12 +198,12 @@ export default function extendTransaction (Y) {
|
||||
*/
|
||||
var right
|
||||
if (target.right != null) {
|
||||
right = yield * this.getOperation(target.right)
|
||||
right = yield* this.getOperation(target.right)
|
||||
} else {
|
||||
right = null
|
||||
}
|
||||
if (callType && !preventCallType) {
|
||||
yield * this.store.operationAdded(this, {
|
||||
yield* this.store.operationAdded(this, {
|
||||
struct: 'Delete',
|
||||
target: target.id,
|
||||
length: targetLength,
|
||||
@@ -211,9 +211,9 @@ export default function extendTransaction (Y) {
|
||||
})
|
||||
}
|
||||
// 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) {
|
||||
yield * this.store.addToGarbageCollector.call(this, right, target)
|
||||
yield* this.store.addToGarbageCollector.call(this, right, target)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -223,23 +223,23 @@ export default function extendTransaction (Y) {
|
||||
*/
|
||||
* markGarbageCollected (id, len) {
|
||||
// this.mem.push(["gc", id]);
|
||||
this.store.addToDebug('yield * this.markGarbageCollected(', id, ', ', len, ')')
|
||||
var n = yield * this.markDeleted(id, len)
|
||||
this.store.addToDebug('yield* this.markGarbageCollected(', id, ', ', len, ')')
|
||||
var n = yield* this.markDeleted(id, len)
|
||||
if (n.id[1] < id[1] && !n.gc) {
|
||||
// un-extend left
|
||||
var newlen = n.len - (id[1] - n.id[1])
|
||||
n.len -= newlen
|
||||
yield * this.ds.put(n)
|
||||
yield* this.ds.put(n)
|
||||
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
|
||||
var prev = yield * this.ds.findPrev(id)
|
||||
var next = yield * this.ds.findNext(id)
|
||||
var prev = yield* this.ds.findPrev(id)
|
||||
var next = yield* this.ds.findNext(id)
|
||||
|
||||
if (id[1] + len < n.id[1] + n.len && !n.gc) {
|
||||
// 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
|
||||
}
|
||||
// set gc'd
|
||||
@@ -251,7 +251,7 @@ export default function extendTransaction (Y) {
|
||||
Y.utils.compareIds([prev.id[0], prev.id[1] + prev.len], n.id)
|
||||
) {
|
||||
prev.len += n.len
|
||||
yield * this.ds.delete(n.id)
|
||||
yield* this.ds.delete(n.id)
|
||||
n = prev
|
||||
// ds.put n here?
|
||||
}
|
||||
@@ -262,10 +262,10 @@ export default function extendTransaction (Y) {
|
||||
Y.utils.compareIds([n.id[0], n.id[1] + n.len], next.id)
|
||||
) {
|
||||
n.len += next.len
|
||||
yield * this.ds.delete(next.id)
|
||||
yield* this.ds.delete(next.id)
|
||||
}
|
||||
yield * this.ds.put(n)
|
||||
yield * this.updateState(n.id[0])
|
||||
yield* this.ds.put(n)
|
||||
yield* this.updateState(n.id[0])
|
||||
}
|
||||
/*
|
||||
Mark an operation as deleted.
|
||||
@@ -277,7 +277,7 @@ export default function extendTransaction (Y) {
|
||||
length = 1
|
||||
}
|
||||
// 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.id[1] <= id[1] && id[1] <= n.id[1] + n.len) {
|
||||
// id is in n's range
|
||||
@@ -291,12 +291,11 @@ export default function extendTransaction (Y) {
|
||||
if (diff < length) {
|
||||
// a partial deletion
|
||||
n = {id: [id[0], id[1] + diff], len: length - diff, gc: false}
|
||||
yield * this.ds.put(n)
|
||||
yield* this.ds.put(n)
|
||||
} else {
|
||||
// already gc'd
|
||||
throw new Error(
|
||||
'DS reached an inconsistent state. Please report this issue!'
|
||||
)
|
||||
throw new Error('Cannot happen! (it dit though.. :()')
|
||||
// return n
|
||||
}
|
||||
}
|
||||
} else {
|
||||
@@ -306,15 +305,15 @@ export default function extendTransaction (Y) {
|
||||
} else {
|
||||
// cannot extend left (there is no left!)
|
||||
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 {
|
||||
// cannot extend left
|
||||
n = {id: id, len: length, gc: false}
|
||||
yield * this.ds.put(n)
|
||||
yield* this.ds.put(n)
|
||||
}
|
||||
// can extend right?
|
||||
var next = yield * this.ds.findNext(n.id)
|
||||
var next = yield* this.ds.findNext(n.id)
|
||||
if (
|
||||
next != null &&
|
||||
n.id[0] === next.id[0] &&
|
||||
@@ -330,8 +329,8 @@ export default function extendTransaction (Y) {
|
||||
// delete the missing range after next
|
||||
diff = diff - next.len // missing range after next
|
||||
if (diff > 0) {
|
||||
yield * this.ds.put(n) // unneccessary? TODO!
|
||||
yield * this.markDeleted([next.id[0], next.id[1] + next.len], diff)
|
||||
yield* this.ds.put(n) // unneccessary? TODO!
|
||||
yield* this.markDeleted([next.id[0], next.id[1] + next.len], diff)
|
||||
}
|
||||
}
|
||||
break
|
||||
@@ -340,8 +339,8 @@ export default function extendTransaction (Y) {
|
||||
if (diff > next.len) {
|
||||
// n is even longer than next
|
||||
// get next.next, and try to extend it
|
||||
var _next = yield * this.ds.findNext(next.id)
|
||||
yield * this.ds.delete(next.id)
|
||||
var _next = yield* this.ds.findNext(next.id)
|
||||
yield* this.ds.delete(next.id)
|
||||
if (_next == null || n.id[0] !== _next.id[0]) {
|
||||
break
|
||||
} else {
|
||||
@@ -352,13 +351,13 @@ export default function extendTransaction (Y) {
|
||||
} else {
|
||||
// n just partially overlaps with next. extend n, delete next, and break this loop
|
||||
n.len += next.len - diff
|
||||
yield * this.ds.delete(next.id)
|
||||
yield* this.ds.delete(next.id)
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
yield * this.ds.put(n)
|
||||
yield* this.ds.put(n)
|
||||
return n
|
||||
}
|
||||
/*
|
||||
@@ -367,35 +366,34 @@ export default function extendTransaction (Y) {
|
||||
operations that can be gc'd and add them to the garbage collector.
|
||||
*/
|
||||
* garbageCollectAfterSync () {
|
||||
// debugger
|
||||
if (this.store.gc1.length > 0 || this.store.gc2.length > 0) {
|
||||
console.warn('gc should be empty after sync')
|
||||
}
|
||||
if (!this.store.gc) {
|
||||
return
|
||||
}
|
||||
yield * this.os.iterate(this, null, null, function * (op) {
|
||||
yield* this.os.iterate(this, null, null, function * (op) {
|
||||
if (op.gc) {
|
||||
delete op.gc
|
||||
yield * this.setOperation(op)
|
||||
yield* this.setOperation(op)
|
||||
}
|
||||
if (op.parent != null) {
|
||||
var parentDeleted = yield * this.isDeleted(op.parent)
|
||||
var parentDeleted = yield* this.isDeleted(op.parent)
|
||||
if (parentDeleted) {
|
||||
op.gc = true
|
||||
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
|
||||
if (op.opContent != null) {
|
||||
yield * this.deleteOperation(op.opContent)
|
||||
yield* this.deleteOperation(op.opContent)
|
||||
}
|
||||
if (op.requires != null) {
|
||||
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!)
|
||||
return
|
||||
}
|
||||
@@ -403,9 +401,9 @@ export default function extendTransaction (Y) {
|
||||
if (op.deleted) {
|
||||
var 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 +417,9 @@ export default function extendTransaction (Y) {
|
||||
* reset origins of all right ops
|
||||
*/
|
||||
* garbageCollectOperation (id) {
|
||||
this.store.addToDebug('yield * this.garbageCollectOperation(', id, ')')
|
||||
var o = yield * this.getOperation(id)
|
||||
yield * this.markGarbageCollected(id, (o != null && o.content != null) ? o.content.length : 1) // always mark gc'd
|
||||
this.store.addToDebug('yield* this.garbageCollectOperation(', id, ')')
|
||||
var o = yield* this.getOperation(id)
|
||||
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 (o != null) {
|
||||
var deps = []
|
||||
@@ -432,37 +430,45 @@ export default function extendTransaction (Y) {
|
||||
deps = deps.concat(o.requires)
|
||||
}
|
||||
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.deleted) {
|
||||
yield * this.deleteOperation(dep.id)
|
||||
dep = yield * this.getOperation(dep.id)
|
||||
yield* this.deleteOperation(dep.id)
|
||||
dep = yield* this.getOperation(dep.id)
|
||||
}
|
||||
dep.gc = true
|
||||
yield * this.setOperation(dep)
|
||||
yield* this.setOperation(dep)
|
||||
this.store.queueGarbageCollector(dep.id)
|
||||
} else {
|
||||
yield * this.markGarbageCollected(deps[i], 1)
|
||||
yield* this.markGarbageCollected(deps[i], 1)
|
||||
}
|
||||
}
|
||||
|
||||
// remove gc'd op from the left op, if it exists
|
||||
if (o.left != null) {
|
||||
var left = yield * this.getInsertion(o.left)
|
||||
var left = yield* this.getInsertion(o.left)
|
||||
left.right = o.right
|
||||
yield * this.setOperation(left)
|
||||
yield* this.setOperation(left)
|
||||
}
|
||||
// remove gc'd op from the right op, if it exists
|
||||
// also reset origins of right ops
|
||||
if (o.right != null) {
|
||||
var right = yield * this.getOperation(o.right)
|
||||
var right = yield* this.getOperation(o.right)
|
||||
right.left = o.left
|
||||
yield * this.setOperation(right)
|
||||
yield* this.setOperation(right)
|
||||
|
||||
if (o.originOf != null && o.originOf.length > 0) {
|
||||
// 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_ = 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!),
|
||||
|
||||
@@ -476,7 +482,7 @@ export default function extendTransaction (Y) {
|
||||
right.origin = neworigin
|
||||
// search until you find origin pointer to the left of o
|
||||
if (right.right != null) {
|
||||
var i = yield * this.getOperation(right.right)
|
||||
var i = yield* this.getOperation(right.right)
|
||||
var ids = [o.id, o.right]
|
||||
while (ids.some(function (id) {
|
||||
return Y.utils.compareIds(id, i.origin)
|
||||
@@ -484,14 +490,14 @@ export default function extendTransaction (Y) {
|
||||
if (Y.utils.compareIds(i.origin, o.id)) {
|
||||
// reset origin of i
|
||||
i.origin = neworigin
|
||||
yield * this.setOperation(i)
|
||||
yield* this.setOperation(i)
|
||||
}
|
||||
// get next i
|
||||
if (i.right == null) {
|
||||
break
|
||||
} else {
|
||||
ids.push(i.id)
|
||||
i = yield * this.getOperation(i.right)
|
||||
i = yield* this.getOperation(i.right)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -500,20 +506,19 @@ export default function extendTransaction (Y) {
|
||||
// ** Now the new implementation starts **
|
||||
// reset neworigin of all 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) {
|
||||
originsIn.origin = neworigin
|
||||
yield * this.setOperation(originsIn)
|
||||
yield* this.setOperation(originsIn)
|
||||
}
|
||||
}
|
||||
if (neworigin != null) {
|
||||
var neworigin_ = yield * this.getInsertion(neworigin)
|
||||
if (neworigin_.originOf == null) {
|
||||
neworigin_.originOf = o.originOf
|
||||
} else {
|
||||
neworigin_.originOf = o.originOf.concat(neworigin_.originOf)
|
||||
}
|
||||
yield * this.setOperation(neworigin_)
|
||||
yield* this.setOperation(neworigin_)
|
||||
}
|
||||
// we don't need to set right here, because
|
||||
// right should be in o.originOf => it is set it the previous for loop
|
||||
@@ -522,15 +527,15 @@ export default function extendTransaction (Y) {
|
||||
// o may originate in another operation.
|
||||
// Since o is deleted, we have to reset o.origin's `originOf` property
|
||||
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) {
|
||||
return !Y.utils.compareIds(id, _id)
|
||||
})
|
||||
yield * this.setOperation(origin)
|
||||
yield* this.setOperation(origin)
|
||||
}
|
||||
var parent
|
||||
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
|
||||
if (parent != null) {
|
||||
@@ -557,32 +562,32 @@ export default function extendTransaction (Y) {
|
||||
}
|
||||
}
|
||||
if (setParent) {
|
||||
yield * this.setOperation(parent)
|
||||
yield* this.setOperation(parent)
|
||||
}
|
||||
}
|
||||
// finally remove it from the os
|
||||
yield * this.removeOperation(o.id)
|
||||
yield* this.removeOperation(o.id)
|
||||
}
|
||||
}
|
||||
* 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) {
|
||||
state.clock = Math.max(state.clock, n.id[1] + n.len)
|
||||
}
|
||||
}
|
||||
* updateState (user) {
|
||||
var state = yield * this.getState(user)
|
||||
yield * this.checkDeleteStoreForState(state)
|
||||
var o = yield * this.getInsertion([user, state.clock])
|
||||
var state = yield* this.getState(user)
|
||||
yield* this.checkDeleteStoreForState(state)
|
||||
var o = yield* this.getInsertion([user, state.clock])
|
||||
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) {
|
||||
// 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
|
||||
yield * this.checkDeleteStoreForState(state)
|
||||
o = yield * this.os.findNext(o.id)
|
||||
yield* this.checkDeleteStoreForState(state)
|
||||
o = yield* this.os.findNext(o.id)
|
||||
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
|
||||
@@ -595,7 +600,7 @@ export default function extendTransaction (Y) {
|
||||
var dv = ds[user]
|
||||
var pos = 0
|
||||
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:
|
||||
// 1. d deletes something to the right of n
|
||||
// => go to next n (break)
|
||||
@@ -644,14 +649,14 @@ export default function extendTransaction (Y) {
|
||||
for (var i = 0; i < deletions.length; i++) {
|
||||
var del = deletions[i]
|
||||
// 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]) {
|
||||
// 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..
|
||||
var counter = del[1] + del[2]
|
||||
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) {
|
||||
break
|
||||
}
|
||||
@@ -662,14 +667,14 @@ export default function extendTransaction (Y) {
|
||||
}
|
||||
if (o.id[1] + oLen > del[1] + del[2]) {
|
||||
// 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]) {
|
||||
// overlaps left
|
||||
o = yield * this.getInsertionCleanStart([del[0], del[1]])
|
||||
o = yield* this.getInsertionCleanStart([del[0], del[1]])
|
||||
}
|
||||
counter = o.id[1]
|
||||
yield * this.garbageCollectOperation(o.id)
|
||||
yield* this.garbageCollectOperation(o.id)
|
||||
}
|
||||
}
|
||||
if (this.store.forwardAppliedOperations) {
|
||||
@@ -680,7 +685,7 @@ export default function extendTransaction (Y) {
|
||||
}
|
||||
}
|
||||
* 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
|
||||
}
|
||||
/*
|
||||
@@ -688,7 +693,7 @@ export default function extendTransaction (Y) {
|
||||
*/
|
||||
* getDeleteSet () {
|
||||
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 counter = n.id[1]
|
||||
var len = n.len
|
||||
@@ -703,16 +708,16 @@ export default function extendTransaction (Y) {
|
||||
return ds
|
||||
}
|
||||
* 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
|
||||
}
|
||||
* setOperation (op) {
|
||||
yield * this.os.put(op)
|
||||
yield* this.os.put(op)
|
||||
return op
|
||||
}
|
||||
* addOperation (op) {
|
||||
yield * this.os.put(op)
|
||||
if (this.store.y.connector.isSynced && this.store.forwardAppliedOperations && typeof op.id[1] !== 'string') {
|
||||
yield* this.os.put(op)
|
||||
if (this.store.forwardAppliedOperations && typeof op.id[1] !== 'string') {
|
||||
// is connected, and this is not going to be send in addOperation
|
||||
this.store.y.connector.broadcastOps([op])
|
||||
}
|
||||
@@ -726,7 +731,7 @@ export default function extendTransaction (Y) {
|
||||
op.left[0] === op.id[0] &&
|
||||
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 &&
|
||||
left.id[1] + left.content.length === op.id[1] &&
|
||||
left.originOf.length === 1 &&
|
||||
@@ -741,13 +746,13 @@ export default function extendTransaction (Y) {
|
||||
}
|
||||
left.content = left.content.concat(op.content)
|
||||
left.right = op.right
|
||||
yield * this.os.delete(op.id)
|
||||
yield * this.setOperation(left)
|
||||
yield* this.os.delete(op.id)
|
||||
yield* this.setOperation(left)
|
||||
}
|
||||
}
|
||||
}
|
||||
* getInsertion (id) {
|
||||
var ins = yield * this.os.findWithUpperBound(id)
|
||||
var ins = yield* this.os.findWithUpperBound(id)
|
||||
if (ins == null) {
|
||||
return null
|
||||
} else {
|
||||
@@ -760,13 +765,13 @@ export default function extendTransaction (Y) {
|
||||
}
|
||||
}
|
||||
* getInsertionCleanStartEnd (id) {
|
||||
yield * this.getInsertionCleanStart(id)
|
||||
return yield * this.getInsertionCleanEnd(id)
|
||||
yield* this.getInsertionCleanStart(id)
|
||||
return yield* this.getInsertionCleanEnd(id)
|
||||
}
|
||||
// Return an insertion such that id is the first element of content
|
||||
// This function manipulates an operation, if necessary
|
||||
* getInsertionCleanStart (id) {
|
||||
var ins = yield * this.getInsertion(id)
|
||||
var ins = yield* this.getInsertion(id)
|
||||
if (ins != null) {
|
||||
if (ins.id[1] === id[1]) {
|
||||
return ins
|
||||
@@ -780,8 +785,8 @@ export default function extendTransaction (Y) {
|
||||
left.right = ins.id
|
||||
ins.left = leftLid
|
||||
// debugger // check
|
||||
yield * this.setOperation(left)
|
||||
yield * this.setOperation(ins)
|
||||
yield* this.setOperation(left)
|
||||
yield* this.setOperation(ins)
|
||||
if (left.gc) {
|
||||
this.store.queueGarbageCollector(ins.id)
|
||||
}
|
||||
@@ -794,7 +799,7 @@ export default function extendTransaction (Y) {
|
||||
// Return an insertion such that id is the last element of content
|
||||
// This function manipulates an operation, if necessary
|
||||
* getInsertionCleanEnd (id) {
|
||||
var ins = yield * this.getInsertion(id)
|
||||
var ins = yield* this.getInsertion(id)
|
||||
if (ins != null) {
|
||||
if (ins.content == null || (ins.id[1] + ins.content.length - 1 === id[1])) {
|
||||
return ins
|
||||
@@ -808,8 +813,8 @@ export default function extendTransaction (Y) {
|
||||
ins.right = right.id
|
||||
right.left = insLid
|
||||
// debugger // check
|
||||
yield * this.setOperation(right)
|
||||
yield * this.setOperation(ins)
|
||||
yield* this.setOperation(right)
|
||||
yield* this.setOperation(ins)
|
||||
if (ins.gc) {
|
||||
this.store.queueGarbageCollector(right.id)
|
||||
}
|
||||
@@ -820,7 +825,7 @@ export default function extendTransaction (Y) {
|
||||
}
|
||||
}
|
||||
* getOperation (id/* :any */)/* :Transaction<any> */ {
|
||||
var o = yield * this.os.find(id)
|
||||
var o = yield* this.os.find(id)
|
||||
if (id[0] !== '_' || o != null) {
|
||||
return o
|
||||
} else { // type is string
|
||||
@@ -830,28 +835,28 @@ export default function extendTransaction (Y) {
|
||||
var struct = comp[0]
|
||||
var op = Y.Struct[struct].create(id)
|
||||
op.type = comp[1]
|
||||
yield * this.setOperation(op)
|
||||
yield* this.setOperation(op)
|
||||
return op
|
||||
} else {
|
||||
throw new Error(
|
||||
'Unexpected case. Operation cannot be generated correctly!' +
|
||||
'Incompatible Yjs version?'
|
||||
)
|
||||
// won't be called. but just in case..
|
||||
console.error('Unexpected case. How can this happen?')
|
||||
debugger // eslint-disable-line
|
||||
return null
|
||||
}
|
||||
}
|
||||
}
|
||||
* removeOperation (id) {
|
||||
yield * this.os.delete(id)
|
||||
yield* this.os.delete(id)
|
||||
}
|
||||
* setState (state) {
|
||||
var val = {
|
||||
id: [state.user],
|
||||
clock: state.clock
|
||||
}
|
||||
yield * this.ss.put(val)
|
||||
yield* this.ss.put(val)
|
||||
}
|
||||
* getState (user) {
|
||||
var n = yield * this.ss.find([user])
|
||||
var n = yield* this.ss.find([user])
|
||||
var clock = n == null ? null : n.clock
|
||||
if (clock == null) {
|
||||
clock = 0
|
||||
@@ -863,7 +868,7 @@ export default function extendTransaction (Y) {
|
||||
}
|
||||
* getStateVector () {
|
||||
var stateVector = []
|
||||
yield * this.ss.iterate(this, null, null, function * (n) {
|
||||
yield* this.ss.iterate(this, null, null, function * (n) {
|
||||
stateVector.push({
|
||||
user: n.id[0],
|
||||
clock: n.clock
|
||||
@@ -873,7 +878,7 @@ export default function extendTransaction (Y) {
|
||||
}
|
||||
* getStateSet () {
|
||||
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
|
||||
})
|
||||
return ss
|
||||
@@ -931,65 +936,53 @@ export default function extendTransaction (Y) {
|
||||
}
|
||||
var send = []
|
||||
|
||||
var endSV = yield * this.getStateVector()
|
||||
for (let endState of endSV) {
|
||||
let user = endState.user
|
||||
var endSV = yield* this.getStateVector()
|
||||
for (var endState of endSV) {
|
||||
var user = endState.user
|
||||
if (user === '_') {
|
||||
continue
|
||||
}
|
||||
let startPos = startSS[user] || 0
|
||||
var startPos = startSS[user] || 0
|
||||
if (startPos > 0) {
|
||||
// There is a change that [user, startPos] is in a composed Insertion (with a smaller counter)
|
||||
// find out if that is the case
|
||||
let firstMissing = yield * this.getInsertion([user, startPos])
|
||||
var firstMissing = yield* this.getInsertion([user, startPos])
|
||||
if (firstMissing != null) {
|
||||
// update startPos
|
||||
startPos = firstMissing.id[1]
|
||||
startSS[user] = startPos
|
||||
}
|
||||
}
|
||||
startSS[user] = startPos
|
||||
}
|
||||
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) {
|
||||
yield* this.os.iterate(this, [user, startPos], [user, Number.MAX_VALUE], function * (op) {
|
||||
op = Y.Struct[op.struct].encode(op)
|
||||
if (op.struct !== 'Insert') {
|
||||
send.push(op)
|
||||
} else if (op.right == null || op.right[1] < (startSS[op.right[0]] || 0)) {
|
||||
// case 1. op.right is known
|
||||
// this case is only reached if op.right is known.
|
||||
// => this is not called for op.left, as op.right is unknown
|
||||
let o = op
|
||||
var o = op
|
||||
// Remember: ?
|
||||
// -> set op.right
|
||||
// 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
|
||||
// right of op.
|
||||
// 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
|
||||
while (true) {
|
||||
if (o.left == null) {
|
||||
op.left = null
|
||||
send.push(op)
|
||||
/* not necessary, as o is already sent..
|
||||
if (!Y.utils.compareIds(o.id, op.id) && o.id[1] >= (startSS[o.id[0]] || 0)) {
|
||||
// o is not op && o is unknown
|
||||
if (!Y.utils.compareIds(o.id, op.id)) {
|
||||
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)
|
||||
}
|
||||
*/
|
||||
break
|
||||
}
|
||||
o = yield * this.getInsertion(o.left)
|
||||
// we set another o, check if we can reduce $missingOrigins
|
||||
while (missingOrigins.length > 0 && Y.utils.matchesId(o, missingOrigins[missingOrigins.length - 1].origin)) {
|
||||
missingOrigins.pop()
|
||||
o = yield* this.getInsertion(o.left)
|
||||
// we set another o, check if we can reduce $missing_origins
|
||||
while (missing_origins.length > 0 && Y.utils.matchesId(o, missing_origins[missing_origins.length - 1].origin)) {
|
||||
missing_origins.pop()
|
||||
}
|
||||
if (o.id[1] < (startSS[o.id[0]] || 0)) {
|
||||
// case 2. o is known
|
||||
@@ -1002,20 +995,17 @@ export default function extendTransaction (Y) {
|
||||
send.push(op)
|
||||
op = Y.Struct[op.struct].encode(o)
|
||||
op.right = newright
|
||||
if (missingOrigins.length > 0) {
|
||||
throw new Error(
|
||||
'Reached inconsistent OS state.' +
|
||||
'Operations are not correctly connected.'
|
||||
)
|
||||
if (missing_origins.length > 0) {
|
||||
console.log('This should not happen .. :( please report this')
|
||||
}
|
||||
missingOrigins = [op]
|
||||
missing_origins = [op]
|
||||
} else {
|
||||
// case 4. send o, continue to find op.origin
|
||||
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
|
||||
send.push(s)
|
||||
missingOrigins.push(o)
|
||||
missing_origins.push(o)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1030,7 +1020,7 @@ export default function extendTransaction (Y) {
|
||||
*/
|
||||
* getOperationsUntransformed () {
|
||||
var ops = []
|
||||
yield * this.os.iterate(this, null, null, function * (op) {
|
||||
yield* this.os.iterate(this, null, null, function * (op) {
|
||||
if (op.id[0] !== '_') {
|
||||
ops.push(op)
|
||||
}
|
||||
@@ -1049,25 +1039,25 @@ export default function extendTransaction (Y) {
|
||||
// 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)
|
||||
let parent = yield* this.getOperation(op.parent)
|
||||
parent.map[op.parentSub] = op.id
|
||||
yield * this.setOperation(parent)
|
||||
yield* this.setOperation(parent)
|
||||
} else if (op.right == null || op.left == null) {
|
||||
let parent = yield * this.getOperation(op.parent)
|
||||
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.setOperation(parent)
|
||||
}
|
||||
}
|
||||
}
|
||||
yield * this.os.put(op)
|
||||
yield* this.os.put(op)
|
||||
}
|
||||
for (var user in stateSet) {
|
||||
yield * this.ss.put({
|
||||
yield* this.ss.put({
|
||||
id: [user],
|
||||
clock: stateSet[user]
|
||||
})
|
||||
@@ -1084,7 +1074,7 @@ export default function extendTransaction (Y) {
|
||||
// or the o that has no origin to the right of op
|
||||
// (this is why we use the ids array)
|
||||
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) {
|
||||
return Y.utils.compareIds(id, right.origin)
|
||||
})) {
|
||||
@@ -1099,9 +1089,9 @@ export default function extendTransaction (Y) {
|
||||
}
|
||||
*/
|
||||
* flush () {
|
||||
yield * this.os.flush()
|
||||
yield * this.ss.flush()
|
||||
yield * this.ds.flush()
|
||||
yield* this.os.flush()
|
||||
yield* this.ss.flush()
|
||||
yield* this.ds.flush()
|
||||
}
|
||||
}
|
||||
Y.Transaction = TransactionInterface
|
||||
|
||||
82
src/Utils.js
82
src/Utils.js
@@ -1,3 +1,6 @@
|
||||
/* @flow */
|
||||
'use strict'
|
||||
|
||||
/*
|
||||
EventHandler is an helper class for constructing custom types.
|
||||
|
||||
@@ -20,8 +23,7 @@
|
||||
database request to finish). EventHandler helps you to make your type
|
||||
synchronous.
|
||||
*/
|
||||
|
||||
export default function Utils (Y) {
|
||||
module.exports = function (Y /* : any*/) {
|
||||
Y.utils = {}
|
||||
|
||||
Y.utils.bubbleEvent = function (type, event) {
|
||||
@@ -42,32 +44,6 @@ export default function Utils (Y) {
|
||||
}
|
||||
}
|
||||
|
||||
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 {
|
||||
constructor () {
|
||||
this.eventListeners = []
|
||||
@@ -98,12 +74,7 @@ export default function Utils (Y) {
|
||||
}
|
||||
this.eventListeners[i](_event)
|
||||
} catch (e) {
|
||||
/*
|
||||
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)
|
||||
console.error('Your observer threw an error. This error was caught so that Yjs still can ensure data consistency! In order to debug this error you have to check "Pause On Caught Exceptions"', e)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -332,7 +303,7 @@ export default function Utils (Y) {
|
||||
}
|
||||
var before = this.waiting.length
|
||||
// somehow create new operations
|
||||
yield * f.apply(transaction, args)
|
||||
yield* f.apply(transaction, args)
|
||||
// remove all appended ops / awaited ops
|
||||
this.waiting.splice(before)
|
||||
if (this.awaiting > 0) this.awaiting--
|
||||
@@ -342,7 +313,7 @@ export default function Utils (Y) {
|
||||
for (let i = 0; i < this.waiting.length; i++) {
|
||||
var o = this.waiting[i]
|
||||
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 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)
|
||||
@@ -354,10 +325,10 @@ export default function Utils (Y) {
|
||||
o.left = null
|
||||
} else {
|
||||
// find next undeleted op
|
||||
var left = yield * transaction.getInsertion(_o.left)
|
||||
var left = yield* transaction.getInsertion(_o.left)
|
||||
while (left.deleted != null) {
|
||||
if (left.left != null) {
|
||||
left = yield * transaction.getInsertion(left.left)
|
||||
left = yield* transaction.getInsertion(left.left)
|
||||
} else {
|
||||
left = null
|
||||
break
|
||||
@@ -641,7 +612,6 @@ export default function Utils (Y) {
|
||||
}
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
Y.utils.matchesId = matchesId
|
||||
|
||||
@@ -709,7 +679,7 @@ export default function Utils (Y) {
|
||||
if (i < 0 && noSuperCall === undefined) {
|
||||
// did not reach break in last loop
|
||||
// read id and put it to the end of readBuffer
|
||||
o = yield * super.find(id)
|
||||
o = yield* super.find(id)
|
||||
}
|
||||
if (o != null) {
|
||||
for (i = 0; i < this.readBuffer.length - 1; i++) {
|
||||
@@ -739,7 +709,7 @@ export default function Utils (Y) {
|
||||
// write writeBuffer[0]
|
||||
var write = this.writeBuffer[0]
|
||||
if (write.id[0] !== null) {
|
||||
yield * super.put(write)
|
||||
yield* super.put(write)
|
||||
}
|
||||
// put o to the end of writeBuffer
|
||||
for (i = 0; i < this.writeBuffer.length - 1; i++) {
|
||||
@@ -769,44 +739,44 @@ export default function Utils (Y) {
|
||||
}
|
||||
}
|
||||
}
|
||||
yield * this.flush()
|
||||
yield * super.delete(id)
|
||||
yield* this.flush()
|
||||
yield* super.delete(id)
|
||||
}
|
||||
* findWithLowerBound (id) {
|
||||
var o = yield * this.find(id, true)
|
||||
var o = yield* this.find(id, true)
|
||||
if (o != null) {
|
||||
return o
|
||||
} else {
|
||||
yield * this.flush()
|
||||
return yield * super.findWithLowerBound.apply(this, arguments)
|
||||
yield* this.flush()
|
||||
return yield* super.findWithLowerBound.apply(this, arguments)
|
||||
}
|
||||
}
|
||||
* findWithUpperBound (id) {
|
||||
var o = yield * this.find(id, true)
|
||||
var o = yield* this.find(id, true)
|
||||
if (o != null) {
|
||||
return o
|
||||
} else {
|
||||
yield * this.flush()
|
||||
return yield * super.findWithUpperBound.apply(this, arguments)
|
||||
yield* this.flush()
|
||||
return yield* super.findWithUpperBound.apply(this, arguments)
|
||||
}
|
||||
}
|
||||
* findNext () {
|
||||
yield * this.flush()
|
||||
return yield * super.findNext.apply(this, arguments)
|
||||
yield* this.flush()
|
||||
return yield* super.findNext.apply(this, arguments)
|
||||
}
|
||||
* findPrev () {
|
||||
yield * this.flush()
|
||||
return yield * super.findPrev.apply(this, arguments)
|
||||
yield* this.flush()
|
||||
return yield* super.findPrev.apply(this, arguments)
|
||||
}
|
||||
* iterate () {
|
||||
yield * this.flush()
|
||||
yield * super.iterate.apply(this, arguments)
|
||||
yield* this.flush()
|
||||
yield* super.iterate.apply(this, arguments)
|
||||
}
|
||||
* flush () {
|
||||
for (var i = 0; i < this.writeBuffer.length; i++) {
|
||||
var write = this.writeBuffer[i]
|
||||
if (write.id[0] !== null) {
|
||||
yield * super.put(write)
|
||||
yield* super.put(write)
|
||||
this.writeBuffer[i] = {
|
||||
id: [null, null]
|
||||
}
|
||||
|
||||
68
src/y.js
68
src/y.js
@@ -1,20 +1,18 @@
|
||||
import debug from 'debug'
|
||||
import extendConnector from './Connector.js'
|
||||
import extendDatabase from './Database.js'
|
||||
import extendTransaction from './Transaction.js'
|
||||
import extendStruct from './Struct.js'
|
||||
import extendUtils from './Utils.js'
|
||||
/* @flow */
|
||||
'use strict'
|
||||
|
||||
extendConnector(Y)
|
||||
extendDatabase(Y)
|
||||
extendTransaction(Y)
|
||||
extendStruct(Y)
|
||||
extendUtils(Y)
|
||||
require('./Connector.js')(Y)
|
||||
require('./Database.js')(Y)
|
||||
require('./Transaction.js')(Y)
|
||||
require('./Struct.js')(Y)
|
||||
require('./Utils.js')(Y)
|
||||
require('./Connectors/Test.js')(Y)
|
||||
|
||||
Y.debug = debug
|
||||
Y.debug = require('debug')
|
||||
|
||||
var requiringModules = {}
|
||||
|
||||
module.exports = Y
|
||||
Y.requiringModules = requiringModules
|
||||
|
||||
Y.extend = function (name, value) {
|
||||
@@ -112,7 +110,7 @@ type YOptions = {
|
||||
}
|
||||
*/
|
||||
|
||||
export default function Y (opts/* :YOptions */) /* :Promise<YConfig> */ {
|
||||
function Y (opts/* :YOptions */) /* :Promise<YConfig> */ {
|
||||
if (opts.hasOwnProperty('sourceDir')) {
|
||||
Y.sourceDir = opts.sourceDir
|
||||
}
|
||||
@@ -122,29 +120,31 @@ export default function Y (opts/* :YOptions */) /* :Promise<YConfig> */ {
|
||||
modules.push(opts.share[name])
|
||||
}
|
||||
return new Promise(function (resolve, reject) {
|
||||
if (opts == null) reject(new Error('An options object is expected!'))
|
||||
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)'))
|
||||
if (opts == null) reject('An options object is expected! ')
|
||||
else if (opts.connector == null) reject('You must specify a connector! (missing connector property)')
|
||||
else if (opts.connector.name == null) reject('You must specify connector name! (missing connector.name property)')
|
||||
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 {
|
||||
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 () {
|
||||
var yconfig = new YConfig(opts)
|
||||
yconfig.db.whenUserIdSet(function () {
|
||||
yconfig.init(function () {
|
||||
resolve(yconfig)
|
||||
setTimeout(function () {
|
||||
Y.requestModules(modules).then(function () {
|
||||
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;
|
||||
connector: Y.AbstractConnector;
|
||||
@@ -152,7 +152,6 @@ class YConfig extends Y.utils.NamedEventHandler {
|
||||
options: Object;
|
||||
*/
|
||||
constructor (opts, callback) {
|
||||
super()
|
||||
this.options = opts
|
||||
this.db = new Y[opts.db.name](this, opts.db)
|
||||
this.connector = new Y[opts.connector.name](this, opts.connector)
|
||||
@@ -183,7 +182,7 @@ class YConfig extends Y.utils.NamedEventHandler {
|
||||
args = typedef.parseArguments(args[0])[1]
|
||||
}
|
||||
}
|
||||
share[propertyname] = yield * this.store.initType.call(this, id, args)
|
||||
share[propertyname] = yield* this.store.initType.call(this, id, args)
|
||||
}
|
||||
this.store.whenTransactionsFinished()
|
||||
.then(callback)
|
||||
@@ -216,9 +215,6 @@ class YConfig extends Y.utils.NamedEventHandler {
|
||||
} else {
|
||||
return Promise.resolve()
|
||||
}
|
||||
}).then(() => {
|
||||
// remove existing event listener
|
||||
super.destroy()
|
||||
})
|
||||
}
|
||||
close () {
|
||||
@@ -229,13 +225,13 @@ class YConfig extends Y.utils.NamedEventHandler {
|
||||
} else {
|
||||
this.connector.disconnect()
|
||||
}
|
||||
return this.db.whenTransactionsFinished().then(function () {
|
||||
self.db.destroyTypes()
|
||||
return this.db.whenTransactionsFinished(function () {
|
||||
this.db.destroyTypes()
|
||||
// make sure to wait for all transactions before destroying the db
|
||||
self.db.requestTransaction(function * () {
|
||||
yield * self.db.destroy()
|
||||
this.db.requestTransaction(function * () {
|
||||
yield* self.db.destroy()
|
||||
})
|
||||
return self.db.whenTransactionsFinished()
|
||||
return this.db.whenTransactionsFinished()
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
Reference in New Issue
Block a user