Compare commits

..

6 Commits

Author SHA1 Message Date
Kevin Jahns
03685ac84a Release 12.3.3 2017-08-04 20:36:43 +02:00
Kevin Jahns
6fa79320a5 disable preferUntransformed 2017-08-04 20:36:07 +02:00
Kevin Jahns
4e3a297efc created doc for the auth method 2017-07-19 19:17:36 +02:00
Kevin Jahns
ef2cbf210b Release 12.3.2 2017-07-19 18:50:53 +02:00
Kevin Jahns
3a1c7a71d4 Provide sender information in checkAuth. Related to #79 2017-07-19 18:50:18 +02:00
Kevin Jahns
dd6c196135 link to the IPFS connector 2017-07-13 19:51:29 +02:00
45 changed files with 981 additions and 4804 deletions

View File

@@ -1,12 +0,0 @@
{
"presets": [
["latest", {
"es2015": {
"modules": false
}
}]
],
"plugins": [
"external-helpers"
]
}

View File

@@ -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
View 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
View File

34
declarations/Y.js Normal file
View 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>;
}

2
dist

Submodule dist updated: 42aa7ec5c9...8739fd3a9c

View File

@@ -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>

View File

@@ -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)
})

View File

@@ -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"
}
}

View File

@@ -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>

View File

@@ -1,75 +0,0 @@
/* @flow */
/* global Y */
// 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 () {
var len
while ((len = y.share.chat.length) > 7) {
y.share.chat.delete(0)
}
}
// 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 (var i = 0; i < event.length; i++) {
appendMessage(event.values[i], event.index + i)
}
} else if (event.type === 'delete') {
for (var 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
}
})

View File

@@ -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>

View File

@@ -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)
})

View File

@@ -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>

View File

@@ -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
}
})

View File

@@ -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>

View File

@@ -1,69 +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 + ")"
})
})
})
})

View File

@@ -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>

View File

@@ -1,31 +0,0 @@
/* global Y */
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)
})
})

View File

@@ -1,10 +0,0 @@
{
"name": "examples",
"version": "0.0.0",
"description": "",
"author": "Kevin Jahns",
"license": "MIT",
"dependencies": {
"monaco-editor": "^0.8.3"
}
}

View File

@@ -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>

View File

@@ -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)
})

View File

@@ -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>

View File

@@ -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)
})

View File

@@ -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'
)

View File

@@ -1,8 +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="../bower_components/yjs/y.js"></script>
<script src="./index.js"></script>
</body>
</html>

View File

@@ -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..
})

View File

@@ -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>

View File

@@ -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
View 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
View 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')
}
}))
})

3119
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@@ -1,19 +1,22 @@
{
"name": "yjs",
"version": "13.0.0-0",
"version": "12.3.3",
"description": "A framework for real-time p2p shared editing on any data",
"main": "./src/y.js",
"scripts": {
"lint": "standard",
"dist": "rollup -c rollup.dist.js",
"serve": "concurrently 'serve examples' 'rollup -wc rollup.dist.js -o examples/bower_components/yjs/y.js'"
"test": "node --harmony ./node_modules/.bin/gulp test",
"lint": "./node_modules/.bin/standard"
},
"pre-commit": [
"lint",
"test"
],
"standard": {
"parser": "babel-eslint",
"ignore": [
"build/**",
"dist/**",
"declarations/**",
"./y.js",
"./y.js.map"
]
@@ -39,24 +42,41 @@
},
"homepage": "http://y-js.org",
"devDependencies": {
"babel-cli": "^6.24.1",
"babel-preset-latest": "^6.24.1",
"babel-plugin-external-helpers": "^6.22.0",
"babel-plugin-transform-regenerator": "^6.24.1",
"babel-plugin-transform-runtime": "^6.23.0",
"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"
"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"
}
}

View File

@@ -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}
*/
`
}

View File

@@ -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
}

View File

@@ -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,6 +55,7 @@ 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
@@ -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)
}
/*
* This just sends the complete hb after some time
* Mostly for debugging..
*
db.requestTransaction(function * () {
var ops = yield* this.getOperations(m.stateSet)
if (ops.length > 0) {
if (!broadcastHB) { // TODO: consider to broadcast here..
conn.send(sender, {
type: 'update',
ops: ops
})
} else {
// broadcast only once!
conn.broadcastOps(ops)
}
}
})
*/
defer.resolve()
})
/*/ then apply ds
db.whenTransactionsFinished().then(() => {
db.requestTransaction(function * () {
yield * this.applyDeleteSet(m.deleteSet)
})
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) {

View File

@@ -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,11 +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)) {
// debug
if (os.y.connector.isSynced === false) {
debugger
}
if (os.gc1.length > 0 || os.gc2.length > 0) {
if (!os.y.connector.isSynced) {
console.warn('gc should be empty when not synced!')
}
@@ -93,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 = []
@@ -122,7 +117,7 @@ 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 ? 50000 : this.dbOpts.gcTimeout
} else {
@@ -182,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
@@ -206,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()
@@ -239,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
}
@@ -270,7 +265,7 @@ export default function extendDatabase (Y /* :any */) {
}
}
* destroy () {
clearTimeout(this.gcInterval)
clearInterval(this.gcInterval)
this.gcInterval = null
this.stopRepairCheck()
}
@@ -280,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)
})
@@ -368,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) {
@@ -376,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
@@ -387,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)
}
}
}
@@ -407,12 +402,12 @@ export default function extendDatabase (Y /* :any */) {
* tryExecute (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)
} 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) {
@@ -421,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)
}
}
}
@@ -458,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)
@@ -482,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
}
}
@@ -492,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
@@ -501,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)
}
}
}
@@ -516,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
}
}
@@ -545,7 +540,7 @@ export default function extendDatabase (Y /* :any */) {
} else {
this.transactionIsFlushed = true
return function * () {
yield * this.flush()
yield* this.flush()
}
}
} else {
@@ -576,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
}
}
@@ -595,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])

View File

@@ -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++
})

View File

@@ -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

View File

@@ -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)
}
}
}

View File

@@ -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.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
}
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)
}
}
}
@@ -224,22 +224,22 @@ 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)
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,7 +291,7 @@ 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('Cannot happen! (it dit though.. :()')
@@ -305,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] &&
@@ -329,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
@@ -339,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 {
@@ -351,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
}
/*
@@ -372,28 +372,28 @@ export default function extendTransaction (Y) {
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
}
@@ -401,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)
}
})
}
@@ -418,8 +418,8 @@ export default function extendTransaction (Y) {
*/
* 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
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 = []
@@ -430,32 +430,32 @@ 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
@@ -463,7 +463,7 @@ export default function extendTransaction (Y) {
var neworigin = o.left
var neworigin_ = null
while (neworigin != null) {
neworigin_ = yield * this.getInsertion(neworigin)
neworigin_ = yield* this.getInsertion(neworigin)
if (neworigin_.deleted) {
break
}
@@ -506,10 +506,10 @@ 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) {
@@ -518,7 +518,7 @@ export default function extendTransaction (Y) {
} 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
@@ -527,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) {
@@ -562,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
@@ -600,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)
@@ -649,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
}
@@ -667,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) {
@@ -685,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
}
/*
@@ -693,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
@@ -708,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])
}
@@ -731,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 &&
@@ -746,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 {
@@ -765,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
@@ -785,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)
}
@@ -799,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
@@ -813,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)
}
@@ -825,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
@@ -835,7 +835,7 @@ 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 {
// won't be called. but just in case..
@@ -846,17 +846,17 @@ export default function extendTransaction (Y) {
}
}
* 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
@@ -868,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
@@ -878,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
@@ -936,7 +936,7 @@ export default function extendTransaction (Y) {
}
var send = []
var endSV = yield * this.getStateVector()
var endSV = yield* this.getStateVector()
for (var endState of endSV) {
var user = endState.user
if (user === '_') {
@@ -946,14 +946,14 @@ export default function extendTransaction (Y) {
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
var 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
}
}
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)
@@ -966,7 +966,7 @@ export default function extendTransaction (Y) {
// 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) {
@@ -974,15 +974,15 @@ export default function extendTransaction (Y) {
send.push(op)
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
@@ -995,18 +995,17 @@ export default function extendTransaction (Y) {
send.push(op)
op = Y.Struct[op.struct].encode(o)
op.right = newright
if (missingOrigins.length > 0) {
debugger
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)
}
}
}
@@ -1021,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)
}
@@ -1040,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]
})
@@ -1090,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

View File

@@ -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) {
@@ -301,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--
@@ -311,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)
@@ -323,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
@@ -677,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++) {
@@ -707,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++) {
@@ -737,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]
}

View File

@@ -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,24 +120,26 @@ 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)
}
})
}
@@ -182,7 +182,7 @@ class YConfig {
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)
@@ -225,13 +225,13 @@ class YConfig {
} 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()
})
}
}

View File

@@ -1,178 +0,0 @@
import _Y from '../../yjs/src/y.js'
import yMemory from '../../y-memory/src/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 users[0].db.garbageCollect()
await users[0].db.garbageCollect()
var userTypeContents = users.map(u => u.share.array._content.map(c => c.val || JSON.stringify(c.type)))
// 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)
})
))
var data = await Promise.all(users.map(async (u) => {
var data = {}
u.db.requestTransaction(function * () {
var os = yield * this.getOperationsUntransformed()
data.os = {}
os.untransformed.forEach((op) => {
op = Y.Struct[op.struct].encode(op)
delete op.origin
data.os[JSON.stringify(op.id)] = op
return 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)
})
}

View File

@@ -1,151 +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 => {
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!')
}
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) continue
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)
}