Compare commits

...

13 Commits

Author SHA1 Message Date
Kevin Jahns
c8bca15d72 13.0.0-0 2017-06-30 09:16:58 -07:00
Kevin Jahns
a64730e651 fix several sync issues. improve performance a bit by removing ds from first sync step 2017-06-29 15:04:36 -07:00
Kevin Jahns
409a9414f1 fix the "gc state" warning 2017-06-27 02:07:03 +02:00
Kevin Jahns
24facaab09 fix os comparison in compareUsers 2017-06-21 16:29:51 +02:00
Kevin Jahns
060549f2cb enable gc in random tests 2017-06-19 21:16:42 +02:00
Kevin Jahns
dfe3b0b1d1 Merge branch 'master' into v13 2017-06-19 10:48:16 +02:00
Kevin Jahns
e23154bec2 update dependencies 2017-06-16 01:04:58 +02:00
Kevin Jahns
1682d43c26 added chancejs to dependencies 2017-06-07 21:43:52 +02:00
Kevin Jahns
68c417fe6f fix gc timeout 2017-05-24 16:34:57 +02:00
Kevin Jahns
2ea163a5cf outsourced helper and test-connector 2017-05-22 13:57:16 +02:00
Kevin Jahns
020dacdad4 removed some unnecessary setTimeouts 2017-05-21 00:31:16 +02:00
Kevin Jahns
42abcc897c added examples 2017-05-19 02:22:00 +02:00
Kevin Jahns
0a321610aa use rollup for yjs 2017-05-16 18:35:30 +02:00
43 changed files with 4785 additions and 953 deletions

12
.babelrc Normal file
View File

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

View File

@@ -1,72 +0,0 @@
/* @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

View File

View File

@@ -1,34 +0,0 @@
/* @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>;
}

32
examples/ace/index.html Normal file
View File

@@ -0,0 +1,32 @@
<!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>

24
examples/ace/index.js Normal file
View File

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

29
examples/bower.json Normal file
View File

@@ -0,0 +1,29 @@
{
"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"
}
}

18
examples/chat/index.html Normal file
View File

@@ -0,0 +1,18 @@
<!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>

75
examples/chat/index.js Normal file
View File

@@ -0,0 +1,75 @@
/* @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

@@ -0,0 +1,23 @@
<!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

@@ -0,0 +1,24 @@
/* 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

@@ -0,0 +1,19 @@
<!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>

84
examples/drawing/index.js Normal file
View File

@@ -0,0 +1,84 @@
/* 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

@@ -0,0 +1,23 @@
<!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>

69
examples/jigsaw/index.js Normal file
View File

@@ -0,0 +1,69 @@
/* @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

@@ -0,0 +1,24 @@
<!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>

31
examples/monaco/index.js Normal file
View File

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

10
examples/package.json Normal file
View File

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

31
examples/quill/index.html Normal file
View File

@@ -0,0 +1,31 @@
<!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>

39
examples/quill/index.js Normal file
View File

@@ -0,0 +1,39 @@
/* 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

@@ -0,0 +1,31 @@
<!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

@@ -0,0 +1,49 @@
/* 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

@@ -0,0 +1,22 @@
/* 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

@@ -0,0 +1,8 @@
<!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

@@ -0,0 +1,23 @@
/* 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..
})

39
examples/xml/index.html Normal file
View File

@@ -0,0 +1,39 @@
<!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>

21
examples/xml/index.js Normal file
View File

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

View File

@@ -1,214 +0,0 @@
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)
})
}

View File

@@ -1,104 +0,0 @@
/* 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 Normal file

File diff suppressed because it is too large Load Diff

View File

@@ -1,22 +1,19 @@
{ {
"name": "yjs", "name": "yjs",
"version": "12.3.1", "version": "13.0.0-0",
"description": "A framework for real-time p2p shared editing on any data", "description": "A framework for real-time p2p shared editing on any data",
"main": "./src/y.js", "main": "./src/y.js",
"scripts": { "scripts": {
"test": "node --harmony ./node_modules/.bin/gulp test", "lint": "standard",
"lint": "./node_modules/.bin/standard" "dist": "rollup -c rollup.dist.js",
"serve": "concurrently 'serve examples' 'rollup -wc rollup.dist.js -o examples/bower_components/yjs/y.js'"
}, },
"pre-commit": [ "pre-commit": [
"lint", "lint",
"test" "test"
], ],
"standard": { "standard": {
"parser": "babel-eslint",
"ignore": [ "ignore": [
"build/**",
"dist/**",
"declarations/**",
"./y.js", "./y.js",
"./y.js.map" "./y.js.map"
] ]
@@ -42,41 +39,24 @@
}, },
"homepage": "http://y-js.org", "homepage": "http://y-js.org",
"devDependencies": { "devDependencies": {
"babel-eslint": "^5.0.0-beta6", "babel-cli": "^6.24.1",
"babel-plugin-transform-runtime": "^6.1.18", "babel-preset-latest": "^6.24.1",
"babel-preset-es2015": "^6.1.18", "babel-plugin-external-helpers": "^6.22.0",
"babelify": "^7.2.0", "babel-plugin-transform-regenerator": "^6.24.1",
"browserify": "^12.0.1", "babel-plugin-transform-runtime": "^6.23.0",
"eslint": "^1.10.2", "chance": "^1.0.9",
"gulp": "^3.9.0", "concurrently": "^3.4.0",
"gulp-bump": "^1.0.0", "rollup-plugin-babel": "^2.7.1",
"gulp-concat": "^2.6.0", "rollup-plugin-commonjs": "^8.0.2",
"gulp-filter": "^3.0.1", "rollup-plugin-inject": "^2.0.0",
"gulp-git": "^1.6.0", "rollup-plugin-multi-entry": "^2.0.1",
"gulp-header": "^1.8.8", "rollup-plugin-node-resolve": "^3.0.0",
"gulp-if": "^2.0.0", "rollup-plugin-uglify": "^1.0.2",
"gulp-jasmine": "^2.0.1", "rollup-regenerator-runtime": "^6.23.1",
"gulp-jasmine-browser": "^0.2.3", "rollup-watch": "^3.2.2",
"gulp-load-plugins": "^1.0.0", "standard": "^10.0.2"
"gulp-prompt": "^0.1.2",
"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": { "dependencies": {
"debug": "^2.6.3" "debug": "^2.6.8"
} }
} }

47
rollup.dist.js Normal file
View File

@@ -0,0 +1,47 @@
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}
*/
`
}

20
rollup.test.js Normal file
View File

@@ -0,0 +1,20 @@
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

@@ -4,7 +4,7 @@
function canRead (auth) { return auth === 'read' || auth === 'write' } function canRead (auth) { return auth === 'read' || auth === 'write' }
function canWrite (auth) { return auth === 'write' } function canWrite (auth) { return auth === 'write' }
module.exports = function (Y/* :any */) { export default function extendConnector (Y/* :any */) {
class AbstractConnector { class AbstractConnector {
/* :: /* ::
y: YConfig; y: YConfig;
@@ -59,7 +59,6 @@ module.exports = function (Y/* :any */) {
this.syncingClients = [] this.syncingClients = []
this.forwardToSyncingClients = opts.forwardToSyncingClients !== false this.forwardToSyncingClients = opts.forwardToSyncingClients !== false
this.debug = opts.debug === true this.debug = opts.debug === true
this.syncStep2 = Promise.resolve()
this.broadcastOpBuffer = [] this.broadcastOpBuffer = []
this.protocolVersion = 11 this.protocolVersion = 11
this.authInfo = opts.auth || null this.authInfo = opts.auth || null
@@ -113,7 +112,7 @@ module.exports = function (Y/* :any */) {
this.userEventListeners.push(f) this.userEventListeners.push(f)
} }
removeUserEventListener (f) { removeUserEventListener (f) {
this.userEventListeners = this.userEventListeners.filter(g => { f !== g }) this.userEventListeners = this.userEventListeners.filter(g => f !== g)
} }
userLeft (user) { userLeft (user) {
if (this.connections[user] != null) { if (this.connections[user] != null) {
@@ -146,6 +145,9 @@ module.exports = function (Y/* :any */) {
isSynced: false, isSynced: false,
role: role role: role
} }
let defer = {}
defer.promise = new Promise(function (resolve) { defer.resolve = resolve })
this.connections[user].syncStep2 = defer
for (var f of this.userEventListeners) { for (var f of this.userEventListeners) {
f({ f({
action: 'userJoined', action: 'userJoined',
@@ -181,13 +183,13 @@ module.exports = function (Y/* :any */) {
var conn = this var conn = this
if (syncUser != null) { if (syncUser != null) {
this.currentSyncTarget = syncUser this.currentSyncTarget = syncUser
this.y.db.requestTransaction(function *() { this.y.db.requestTransaction(function * () {
var stateSet = yield* this.getStateSet() var stateSet = yield * this.getStateSet()
var deleteSet = yield* this.getDeleteSet() var deleteSet = yield * this.getDeleteSet()
var answer = { var answer = {
type: 'sync step 1', type: 'sync step 1',
stateSet: stateSet, stateSet: stateSet,
deleteSet: deleteSet, // deleteSet: deleteSet,
protocolVersion: conn.protocolVersion, protocolVersion: conn.protocolVersion,
auth: conn.authInfo auth: conn.authInfo
} }
@@ -198,11 +200,12 @@ module.exports = function (Y/* :any */) {
}) })
} else { } else {
if (!conn.isSynced) { if (!conn.isSynced) {
this.y.db.requestTransaction(function *() { this.y.db.requestTransaction(function * () {
if (!conn.isSynced) { if (!conn.isSynced) {
// it is crucial that isSynced is set at the time garbageCollectAfterSync is called // it is crucial that isSynced is set at the time garbageCollectAfterSync is called
conn.isSynced = true conn.isSynced = true
yield* this.garbageCollectAfterSync() // It is safer to remove this!
// TODO: remove: yield * this.garbageCollectAfterSync()
// call whensynced listeners // call whensynced listeners
for (var f of conn.whenSyncedListeners) { for (var f of conn.whenSyncedListeners) {
f() f()
@@ -240,11 +243,7 @@ module.exports = function (Y/* :any */) {
} }
if (this.broadcastOpBuffer.length === 0) { if (this.broadcastOpBuffer.length === 0) {
this.broadcastOpBuffer = ops this.broadcastOpBuffer = ops
if (this.y.db.transactionInProgress) { this.y.db.whenTransactionsFinished().then(broadcastOperations)
this.y.db.whenTransactionsFinished().then(broadcastOperations)
} else {
setTimeout(broadcastOperations, 0)
}
} else { } else {
this.broadcastOpBuffer = this.broadcastOpBuffer.concat(ops) this.broadcastOpBuffer = this.broadcastOpBuffer.concat(ops)
} }
@@ -268,7 +267,7 @@ module.exports = function (Y/* :any */) {
type: 'sync stop', type: 'sync stop',
protocolVersion: this.protocolVersion protocolVersion: this.protocolVersion
}) })
return Promise.reject('Incompatible protocol version') return Promise.reject(new Error('Incompatible protocol version'))
} }
if (message.auth != null && this.connections[sender] != null) { if (message.auth != null && this.connections[sender] != null) {
// authenticate using auth in message // authenticate using auth in message
@@ -292,82 +291,80 @@ module.exports = function (Y/* :any */) {
if (message.type === 'sync step 1' && canRead(auth)) { if (message.type === 'sync step 1' && canRead(auth)) {
let conn = this let conn = this
let m = message 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)
// }
this.y.db.requestTransaction(function *() { var ds = yield * this.getDeleteSet()
var currentStateSet = yield* this.getStateSet() var answer = {
if (canWrite(auth)) { type: 'sync step 2',
yield* this.applyDeleteSet(m.deleteSet) stateSet: currentStateSet,
} deleteSet: ds,
protocolVersion: this.protocolVersion,
var ds = yield* this.getDeleteSet() auth: this.authInfo
var answer = { }
type: 'sync step 2', if (message.preferUntransformed === true && Object.keys(m.stateSet).length === 0) {
stateSet: currentStateSet, answer.osUntransformed = yield * this.getOperationsUntransformed()
deleteSet: ds, } else {
protocolVersion: this.protocolVersion, answer.os = yield * this.getOperations(m.stateSet)
auth: this.authInfo }
} conn.send(sender, answer)
if (message.preferUntransformed === true && Object.keys(m.stateSet).length === 0) { if (this.forwardToSyncingClients) {
answer.osUntransformed = yield* this.getOperationsUntransformed() conn.syncingClients.push(sender)
} else { setTimeout(function () {
answer.os = yield* this.getOperations(m.stateSet) conn.syncingClients = conn.syncingClients.filter(function (cli) {
} return cli !== sender
conn.send(sender, answer) })
if (this.forwardToSyncingClients) { conn.send(sender, {
conn.syncingClients.push(sender) type: 'sync done'
setTimeout(function () { })
conn.syncingClients = conn.syncingClients.filter(function (cli) { }, 5000) // TODO: conn.syncingClientDuration)
return cli !== sender } else {
})
conn.send(sender, { conn.send(sender, {
type: 'sync done' type: 'sync done'
}) })
}, 5000) // TODO: conn.syncingClientDuration) }
} else { })
conn.send(sender, {
type: 'sync done'
})
}
}) })
} else if (message.type === 'sync step 2' && canWrite(auth)) { } else if (message.type === 'sync step 2' && canWrite(auth)) {
var db = this.y.db var db = this.y.db
var defer = {} let defer = this.connections[sender].syncStep2
defer.promise = new Promise(function (resolve) { let m = message
defer.resolve = resolve // apply operations first
})
this.syncStep2 = defer.promise
let m /* :MessageSyncStep2 */ = message
db.requestTransaction(function * () { db.requestTransaction(function * () {
yield* this.applyDeleteSet(m.deleteSet) yield * this.applyDeleteSet(m.deleteSet)
if (m.osUntransformed != null) { if (m.osUntransformed != null) {
yield* this.applyOperationsUntransformed(m.osUntransformed, m.stateSet) yield * this.applyOperationsUntransformed(m.osUntransformed, m.stateSet)
} else { } else {
this.store.apply(m.os) 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() 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') { } else if (message.type === 'sync done') {
var self = this var self = this
this.syncStep2.then(function () { this.connections[sender].syncStep2.promise.then(function () {
self._setSyncedWith(sender) self._setSyncedWith(sender)
}) })
} else if (message.type === 'update' && canWrite(auth)) { } else if (message.type === 'update' && canWrite(auth)) {
@@ -388,7 +385,7 @@ module.exports = function (Y/* :any */) {
} }
}) })
} else { } else {
return Promise.reject('Unable to deliver message') return Promise.reject(new Error('Unable to deliver message'))
} }
} }
_setSyncedWith (user) { _setSyncedWith (user) {

View File

@@ -1,7 +1,7 @@
/* @flow */ /* @flow */
'use strict' 'use strict'
module.exports = function (Y /* :any */) { export default function extendDatabase (Y /* :any */) {
/* /*
Partial definition of an OperationStore. Partial definition of an OperationStore.
TODO: name it Database, operation store only holds operations. TODO: name it Database, operation store only holds operations.
@@ -39,14 +39,15 @@ module.exports = function (Y /* :any */) {
*/ */
constructor (y, opts) { constructor (y, opts) {
this.y = y this.y = y
opts.gc = opts.gc === true
this.dbOpts = opts this.dbOpts = opts
var os = this var os = this
this.userId = null this.userId = null
var resolve var resolve_
this.userIdPromise = new Promise(function (r) { this.userIdPromise = new Promise(function (resolve) {
resolve = r resolve_ = resolve
}) })
this.userIdPromise.resolve = resolve this.userIdPromise.resolve = resolve_
// whether to broadcast all applied operations (insert & delete hook) // whether to broadcast all applied operations (insert & delete hook)
this.forwardAppliedOperations = false this.forwardAppliedOperations = false
// E.g. this.listenersById[id] : Array<Listener> // E.g. this.listenersById[id] : Array<Listener>
@@ -71,7 +72,7 @@ module.exports = function (Y /* :any */) {
this.waitingTransactions = [] this.waitingTransactions = []
this.transactionInProgress = false this.transactionInProgress = false
this.transactionIsFlushed = false this.transactionIsFlushed = false
if (typeof YConcurrency_TestingMode !== 'undefined') { if (typeof YConcurrencyTestingMode !== 'undefined') {
this.executeOrder = [] this.executeOrder = []
} }
this.gc1 = [] // first stage this.gc1 = [] // first stage
@@ -79,7 +80,11 @@ module.exports = function (Y /* :any */) {
function garbageCollect () { function garbageCollect () {
return os.whenTransactionsFinished().then(function () { return os.whenTransactionsFinished().then(function () {
if (os.gc1.length > 0 || os.gc2.length > 0) { if (os.gcTimeout > 0 && (os.gc1.length > 0 || os.gc2.length > 0)) {
// debug
if (os.y.connector.isSynced === false) {
debugger
}
if (!os.y.connector.isSynced) { if (!os.y.connector.isSynced) {
console.warn('gc should be empty when not synced!') console.warn('gc should be empty when not synced!')
} }
@@ -88,7 +93,7 @@ module.exports = function (Y /* :any */) {
if (os.y.connector != null && os.y.connector.isSynced) { if (os.y.connector != null && os.y.connector.isSynced) {
for (var i = 0; i < os.gc2.length; i++) { for (var i = 0; i < os.gc2.length; i++) {
var oid = os.gc2[i] var oid = os.gc2[i]
yield* this.garbageCollectOperation(oid) yield * this.garbageCollectOperation(oid)
} }
os.gc2 = os.gc1 os.gc2 = os.gc1
os.gc1 = [] os.gc1 = []
@@ -117,7 +122,7 @@ module.exports = function (Y /* :any */) {
this.startRepairCheck() this.startRepairCheck()
} }
startGarbageCollector () { startGarbageCollector () {
this.gc = this.dbOpts.gc == null || this.dbOpts.gc this.gc = this.dbOpts.gc
if (this.gc) { if (this.gc) {
this.gcTimeout = !this.dbOpts.gcTimeout ? 50000 : this.dbOpts.gcTimeout this.gcTimeout = !this.dbOpts.gcTimeout ? 50000 : this.dbOpts.gcTimeout
} else { } else {
@@ -177,7 +182,7 @@ module.exports = function (Y /* :any */) {
}) })
} }
addToDebug () { addToDebug () {
if (typeof YConcurrency_TestingMode !== 'undefined') { if (typeof YConcurrencyTestingMode !== 'undefined') {
var command /* :string */ = Array.prototype.map.call(arguments, function (s) { var command /* :string */ = Array.prototype.map.call(arguments, function (s) {
if (typeof s === 'string') { if (typeof s === 'string') {
return s return s
@@ -201,10 +206,10 @@ module.exports = function (Y /* :any */) {
self.gc1 = [] self.gc1 = []
self.gc2 = [] self.gc2 = []
for (var i = 0; i < ungc.length; i++) { for (var i = 0; i < ungc.length; i++) {
var op = yield* this.getOperation(ungc[i]) var op = yield * this.getOperation(ungc[i])
if (op != null) { if (op != null) {
delete op.gc delete op.gc
yield* this.setOperation(op) yield * this.setOperation(op)
} }
} }
resolve() resolve()
@@ -234,12 +239,12 @@ module.exports = function (Y /* :any */) {
if (left != null && left.deleted === true) { if (left != null && left.deleted === true) {
gc = true gc = true
} else if (op.content != null && op.content.length > 1) { } else if (op.content != null && op.content.length > 1) {
op = yield* this.getInsertionCleanStart([op.id[0], op.id[1] + 1]) op = yield * this.getInsertionCleanStart([op.id[0], op.id[1] + 1])
gc = true gc = true
} }
if (gc) { if (gc) {
op.gc = true op.gc = true
yield* this.setOperation(op) yield * this.setOperation(op)
this.store.queueGarbageCollector(op.id) this.store.queueGarbageCollector(op.id)
return true return true
} }
@@ -265,7 +270,7 @@ module.exports = function (Y /* :any */) {
} }
} }
* destroy () { * destroy () {
clearInterval(this.gcInterval) clearTimeout(this.gcInterval)
this.gcInterval = null this.gcInterval = null
this.stopRepairCheck() this.stopRepairCheck()
} }
@@ -275,7 +280,7 @@ module.exports = function (Y /* :any */) {
var self = this var self = this
self.requestTransaction(function * () { self.requestTransaction(function * () {
self.userId = userId self.userId = userId
var state = yield* this.getState(userId) var state = yield * this.getState(userId)
self.opClock = state.clock self.opClock = state.clock
self.userIdPromise.resolve(userId) self.userIdPromise.resolve(userId)
}) })
@@ -363,7 +368,7 @@ module.exports = function (Y /* :any */) {
for (let key = 0; key < exeNow.length; key++) { for (let key = 0; key < exeNow.length; key++) {
let o = exeNow[key].op let o = exeNow[key].op
yield* store.tryExecute.call(this, o) yield * store.tryExecute.call(this, o)
} }
for (var sid in ls) { for (var sid in ls) {
@@ -371,9 +376,9 @@ module.exports = function (Y /* :any */) {
var id = JSON.parse(sid) var id = JSON.parse(sid)
var op var op
if (typeof id[1] === 'string') { if (typeof id[1] === 'string') {
op = yield* this.getOperation(id) op = yield * this.getOperation(id)
} else { } else {
op = yield* this.getInsertion(id) op = yield * this.getInsertion(id)
} }
if (op == null) { if (op == null) {
store.listenersById[sid] = l store.listenersById[sid] = l
@@ -382,7 +387,7 @@ module.exports = function (Y /* :any */) {
let listener = l[i] let listener = l[i]
let o = listener.op let o = listener.op
if (--listener.missing === 0) { if (--listener.missing === 0) {
yield* store.tryExecute.call(this, o) yield * store.tryExecute.call(this, o)
} }
} }
} }
@@ -402,12 +407,12 @@ module.exports = function (Y /* :any */) {
* tryExecute (op) { * tryExecute (op) {
this.store.addToDebug('yield* this.store.tryExecute.call(this, ', JSON.stringify(op), ')') this.store.addToDebug('yield* this.store.tryExecute.call(this, ', JSON.stringify(op), ')')
if (op.struct === 'Delete') { if (op.struct === 'Delete') {
yield* Y.Struct.Delete.execute.call(this, op) yield * Y.Struct.Delete.execute.call(this, op)
// this is now called in Transaction.deleteOperation! // this is now called in Transaction.deleteOperation!
// yield* this.store.operationAdded(this, op) // yield* this.store.operationAdded(this, op)
} else { } else {
// check if this op was defined // check if this op was defined
var defined = yield* this.getInsertion(op.id) var defined = yield * this.getInsertion(op.id)
while (defined != null && defined.content != null) { while (defined != null && defined.content != null) {
// check if this op has a longer content in the case it is defined // check if this op has a longer content in the case it is defined
if (defined.id[1] + defined.content.length < op.id[1] + op.content.length) { if (defined.id[1] + defined.content.length < op.id[1] + op.content.length) {
@@ -416,23 +421,23 @@ module.exports = function (Y /* :any */) {
op.id = [op.id[0], op.id[1] + overlapSize] op.id = [op.id[0], op.id[1] + overlapSize]
op.left = Y.utils.getLastId(defined) op.left = Y.utils.getLastId(defined)
op.origin = op.left op.origin = op.left
defined = yield* this.getOperation(op.id) // getOperation suffices here defined = yield * this.getOperation(op.id) // getOperation suffices here
} else { } else {
break break
} }
} }
if (defined == null) { if (defined == null) {
var opid = op.id var opid = op.id
var isGarbageCollected = yield* this.isGarbageCollected(opid) var isGarbageCollected = yield * this.isGarbageCollected(opid)
if (!isGarbageCollected) { if (!isGarbageCollected) {
// TODO: reduce number of get / put calls for op .. // TODO: reduce number of get / put calls for op ..
yield* Y.Struct[op.struct].execute.call(this, op) yield * Y.Struct[op.struct].execute.call(this, op)
yield* this.addOperation(op) yield * this.addOperation(op)
yield* this.store.operationAdded(this, op) yield * this.store.operationAdded(this, op)
// operationAdded can change op.. // operationAdded can change op..
op = yield* this.getOperation(opid) op = yield * this.getOperation(opid)
// if insertion, try to combine with left // if insertion, try to combine with left
yield* this.tryCombineWithLeft(op) yield * this.tryCombineWithLeft(op)
} }
} }
} }
@@ -453,11 +458,11 @@ module.exports = function (Y /* :any */) {
if (op.struct === 'Delete') { if (op.struct === 'Delete') {
var type = this.initializedTypes[JSON.stringify(op.targetParent)] var type = this.initializedTypes[JSON.stringify(op.targetParent)]
if (type != null) { if (type != null) {
yield* type._changed(transaction, op) yield * type._changed(transaction, op)
} }
} else { } else {
// increase SS // increase SS
yield* transaction.updateState(op.id[0]) yield * transaction.updateState(op.id[0])
var opLen = op.content != null ? op.content.length : 1 var opLen = op.content != null ? op.content.length : 1
for (let i = 0; i < opLen; i++) { for (let i = 0; i < opLen; i++) {
// notify whenOperation listeners (by id) // notify whenOperation listeners (by id)
@@ -477,9 +482,9 @@ module.exports = function (Y /* :any */) {
// if parent is deleted, mark as gc'd and return // if parent is deleted, mark as gc'd and return
if (op.parent != null) { if (op.parent != null) {
var parentIsDeleted = yield* transaction.isDeleted(op.parent) var parentIsDeleted = yield * transaction.isDeleted(op.parent)
if (parentIsDeleted) { if (parentIsDeleted) {
yield* transaction.deleteList(op.id) yield * transaction.deleteList(op.id)
return return
} }
} }
@@ -487,7 +492,7 @@ module.exports = function (Y /* :any */) {
// notify parent, if it was instanciated as a custom type // notify parent, if it was instanciated as a custom type
if (t != null) { if (t != null) {
let o = Y.utils.copyOperation(op) let o = Y.utils.copyOperation(op)
yield* t._changed(transaction, o) yield * t._changed(transaction, o)
} }
if (!op.deleted) { if (!op.deleted) {
// Delete if DS says this is actually deleted // Delete if DS says this is actually deleted
@@ -496,13 +501,13 @@ module.exports = function (Y /* :any */) {
// TODO: !! console.log('TODO: change this before commiting') // TODO: !! console.log('TODO: change this before commiting')
for (let i = 0; i < len; i++) { for (let i = 0; i < len; i++) {
var id = [startId[0], startId[1] + i] var id = [startId[0], startId[1] + i]
var opIsDeleted = yield* transaction.isDeleted(id) var opIsDeleted = yield * transaction.isDeleted(id)
if (opIsDeleted) { if (opIsDeleted) {
var delop = { var delop = {
struct: 'Delete', struct: 'Delete',
target: id target: id
} }
yield* this.tryExecute.call(transaction, delop) yield * this.tryExecute.call(transaction, delop)
} }
} }
} }
@@ -511,12 +516,12 @@ module.exports = function (Y /* :any */) {
whenTransactionsFinished () { whenTransactionsFinished () {
if (this.transactionInProgress) { if (this.transactionInProgress) {
if (this.transactionsFinished == null) { if (this.transactionsFinished == null) {
var resolve var resolve_
var promise = new Promise(function (r) { var promise = new Promise(function (resolve) {
resolve = r resolve_ = resolve
}) })
this.transactionsFinished = { this.transactionsFinished = {
resolve: resolve, resolve: resolve_,
promise: promise promise: promise
} }
} }
@@ -540,7 +545,7 @@ module.exports = function (Y /* :any */) {
} else { } else {
this.transactionIsFlushed = true this.transactionIsFlushed = true
return function * () { return function * () {
yield* this.flush() yield * this.flush()
} }
} }
} else { } else {
@@ -571,9 +576,9 @@ module.exports = function (Y /* :any */) {
var sid = JSON.stringify(id) var sid = JSON.stringify(id)
var t = this.store.initializedTypes[sid] var t = this.store.initializedTypes[sid]
if (t == null) { if (t == null) {
var op/* :MapStruct | ListStruct */ = yield* this.getOperation(id) var op/* :MapStruct | ListStruct */ = yield * this.getOperation(id)
if (op != null) { 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 this.store.initializedTypes[sid] = t
} }
} }
@@ -590,9 +595,9 @@ module.exports = function (Y /* :any */) {
this.requestTransaction(function * () { this.requestTransaction(function * () {
if (op.id[0] === '_') { if (op.id[0] === '_') {
yield* this.setOperation(op) yield * this.setOperation(op)
} else { } else {
yield* this.applyCreatedOperations([op]) yield * this.applyCreatedOperations([op])
} }
}) })
var t = Y[op.type].typeDefinition.createType(this, op, typedefinition[1]) 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) { afterEach(function (done) {
store.requestTransaction(function * () { store.requestTransaction(function * () {
yield* this.store.destroy() yield * this.store.destroy()
done() done()
}) })
}) })
it('Deleted operation is deleted', async(function * (done) { it('Deleted operation is deleted', async(function * (done) {
store.requestTransaction(function * () { store.requestTransaction(function * () {
yield* this.markDeleted(['u1', 10], 1) yield * this.markDeleted(['u1', 10], 1)
expect(yield* this.isDeleted(['u1', 10])).toBeTruthy() expect(yield * this.isDeleted(['u1', 10])).toBeTruthy()
expect(yield* this.getDeleteSet()).toEqual({'u1': [[10, 1, false]]}) expect(yield * this.getDeleteSet()).toEqual({'u1': [[10, 1, false]]})
done() done()
}) })
})) }))
it('Deleted operation extends other deleted operation', async(function * (done) { it('Deleted operation extends other deleted operation', async(function * (done) {
store.requestTransaction(function * () { store.requestTransaction(function * () {
yield* this.markDeleted(['u1', 10], 1) yield * this.markDeleted(['u1', 10], 1)
yield* this.markDeleted(['u1', 11], 1) yield * this.markDeleted(['u1', 11], 1)
expect(yield* this.isDeleted(['u1', 10])).toBeTruthy() expect(yield * this.isDeleted(['u1', 10])).toBeTruthy()
expect(yield* this.isDeleted(['u1', 11])).toBeTruthy() expect(yield * this.isDeleted(['u1', 11])).toBeTruthy()
expect(yield* this.getDeleteSet()).toEqual({'u1': [[10, 2, false]]}) expect(yield * this.getDeleteSet()).toEqual({'u1': [[10, 2, false]]})
done() done()
}) })
})) }))
it('Deleted operation extends other deleted operation', async(function * (done) { it('Deleted operation extends other deleted operation', async(function * (done) {
store.requestTransaction(function * () { store.requestTransaction(function * () {
yield* this.markDeleted(['0', 3], 1) yield * this.markDeleted(['0', 3], 1)
yield* this.markDeleted(['0', 4], 1) yield * this.markDeleted(['0', 4], 1)
yield* this.markDeleted(['0', 2], 1) yield * this.markDeleted(['0', 2], 1)
expect(yield* this.getDeleteSet()).toEqual({'0': [[2, 3, false]]}) expect(yield * this.getDeleteSet()).toEqual({'0': [[2, 3, false]]})
done() done()
}) })
})) }))
it('Debug #1', async(function * (done) { it('Debug #1', async(function * (done) {
store.requestTransaction(function * () { store.requestTransaction(function * () {
yield* this.markDeleted(['166', 0], 1) yield * this.markDeleted(['166', 0], 1)
yield* this.markDeleted(['166', 2], 1) yield * this.markDeleted(['166', 2], 1)
yield* this.markDeleted(['166', 0], 1) yield * this.markDeleted(['166', 0], 1)
yield* this.markDeleted(['166', 2], 1) yield * this.markDeleted(['166', 2], 1)
yield* this.markGarbageCollected(['166', 2], 1) yield * this.markGarbageCollected(['166', 2], 1)
yield* this.markDeleted(['166', 1], 1) yield * this.markDeleted(['166', 1], 1)
yield* this.markDeleted(['166', 3], 1) yield * this.markDeleted(['166', 3], 1)
yield* this.markGarbageCollected(['166', 3], 1) yield * this.markGarbageCollected(['166', 3], 1)
yield* this.markDeleted(['166', 0], 1) yield * this.markDeleted(['166', 0], 1)
expect(yield* this.getDeleteSet()).toEqual({'166': [[0, 2, false], [2, 2, true]]}) expect(yield * this.getDeleteSet()).toEqual({'166': [[0, 2, false], [2, 2, true]]})
done() done()
}) })
})) }))
it('Debug #2', async(function * (done) { it('Debug #2', async(function * (done) {
store.requestTransaction(function * () { store.requestTransaction(function * () {
yield* this.markDeleted(['293', 0], 1) yield * this.markDeleted(['293', 0], 1)
yield* this.markDeleted(['291', 2], 1) yield * this.markDeleted(['291', 2], 1)
yield* this.markDeleted(['291', 2], 1) yield * this.markDeleted(['291', 2], 1)
yield* this.markGarbageCollected(['293', 0], 1) yield * this.markGarbageCollected(['293', 0], 1)
yield* this.markDeleted(['293', 1], 1) yield * this.markDeleted(['293', 1], 1)
yield* this.markGarbageCollected(['291', 2], 1) yield * this.markGarbageCollected(['291', 2], 1)
expect(yield* this.getDeleteSet()).toEqual({'291': [[2, 1, true]], '293': [[0, 1, true], [1, 1, false]]}) expect(yield * this.getDeleteSet()).toEqual({'291': [[2, 1, true]], '293': [[0, 1, true], [1, 1, false]]})
done() done()
}) })
})) }))
it('Debug #3', async(function * (done) { it('Debug #3', async(function * (done) {
store.requestTransaction(function * () { store.requestTransaction(function * () {
yield* this.markDeleted(['581', 0], 1) yield * this.markDeleted(['581', 0], 1)
yield* this.markDeleted(['581', 1], 1) yield * this.markDeleted(['581', 1], 1)
yield* this.markDeleted(['580', 0], 1) yield * this.markDeleted(['580', 0], 1)
yield* this.markDeleted(['580', 0], 1) yield * this.markDeleted(['580', 0], 1)
yield* this.markGarbageCollected(['581', 0], 1) yield * this.markGarbageCollected(['581', 0], 1)
yield* this.markDeleted(['581', 2], 1) yield * this.markDeleted(['581', 2], 1)
yield* this.markDeleted(['580', 1], 1) yield * this.markDeleted(['580', 1], 1)
yield* this.markDeleted(['580', 2], 1) yield * this.markDeleted(['580', 2], 1)
yield* this.markDeleted(['580', 1], 1) yield * this.markDeleted(['580', 1], 1)
yield* this.markDeleted(['580', 2], 1) yield * this.markDeleted(['580', 2], 1)
yield* this.markGarbageCollected(['581', 2], 1) yield * this.markGarbageCollected(['581', 2], 1)
yield* this.markGarbageCollected(['581', 1], 1) yield * this.markGarbageCollected(['581', 1], 1)
yield* this.markGarbageCollected(['580', 1], 1) yield * this.markGarbageCollected(['580', 1], 1)
expect(yield* this.getDeleteSet()).toEqual({'580': [[0, 1, false], [1, 1, true], [2, 1, false]], '581': [[0, 3, true]]}) expect(yield * this.getDeleteSet()).toEqual({'580': [[0, 1, false], [1, 1, true], [2, 1, false]], '581': [[0, 3, true]]})
done() done()
}) })
})) }))
it('Debug #4', async(function * (done) { it('Debug #4', async(function * (done) {
store.requestTransaction(function * () { store.requestTransaction(function * () {
yield* this.markDeleted(['544', 0], 1) yield * this.markDeleted(['544', 0], 1)
yield* this.markDeleted(['543', 2], 1) yield * this.markDeleted(['543', 2], 1)
yield* this.markDeleted(['544', 0], 1) yield * this.markDeleted(['544', 0], 1)
yield* this.markDeleted(['543', 2], 1) yield * this.markDeleted(['543', 2], 1)
yield* this.markGarbageCollected(['544', 0], 1) yield * this.markGarbageCollected(['544', 0], 1)
yield* this.markDeleted(['545', 1], 1) yield * this.markDeleted(['545', 1], 1)
yield* this.markDeleted(['543', 4], 1) yield * this.markDeleted(['543', 4], 1)
yield* this.markDeleted(['543', 3], 1) yield * this.markDeleted(['543', 3], 1)
yield* this.markDeleted(['544', 1], 1) yield * this.markDeleted(['544', 1], 1)
yield* this.markDeleted(['544', 2], 1) yield * this.markDeleted(['544', 2], 1)
yield* this.markDeleted(['544', 1], 1) yield * this.markDeleted(['544', 1], 1)
yield* this.markDeleted(['544', 2], 1) yield * this.markDeleted(['544', 2], 1)
yield* this.markGarbageCollected(['543', 2], 1) yield * this.markGarbageCollected(['543', 2], 1)
yield* this.markGarbageCollected(['543', 4], 1) yield * this.markGarbageCollected(['543', 4], 1)
yield* this.markGarbageCollected(['544', 2], 1) yield * this.markGarbageCollected(['544', 2], 1)
yield* this.markGarbageCollected(['543', 3], 1) yield * this.markGarbageCollected(['543', 3], 1)
expect(yield* this.getDeleteSet()).toEqual({'543': [[2, 3, true]], '544': [[0, 1, true], [1, 1, false], [2, 1, true]], '545': [[1, 1, false]]}) expect(yield * this.getDeleteSet()).toEqual({'543': [[2, 3, true]], '544': [[0, 1, true], [1, 1, false], [2, 1, true]], '545': [[1, 1, false]]})
done() done()
}) })
})) }))
it('Debug #5', async(function * (done) { it('Debug #5', async(function * (done) {
store.requestTransaction(function * () { store.requestTransaction(function * () {
yield* this.applyDeleteSet({'16': [[1, 2, false]], '17': [[0, 1, true], [1, 3, false]]}) yield * this.applyDeleteSet({'16': [[1, 2, false]], '17': [[0, 1, true], [1, 3, false]]})
expect(yield* this.getDeleteSet()).toEqual({'16': [[1, 2, false]], '17': [[0, 1, true], [1, 3, false]]}) expect(yield * this.getDeleteSet()).toEqual({'16': [[1, 2, false]], '17': [[0, 1, true], [1, 3, false]]})
yield* this.applyDeleteSet({'16': [[1, 2, false]], '17': [[0, 4, true]]}) yield * this.applyDeleteSet({'16': [[1, 2, false]], '17': [[0, 4, true]]})
expect(yield* this.getDeleteSet()).toEqual({'16': [[1, 2, false]], '17': [[0, 4, true]]}) expect(yield * this.getDeleteSet()).toEqual({'16': [[1, 2, false]], '17': [[0, 4, true]]})
done() done()
}) })
})) }))
it('Debug #6', async(function * (done) { it('Debug #6', async(function * (done) {
store.requestTransaction(function * () { store.requestTransaction(function * () {
yield* this.applyDeleteSet({'40': [[0, 3, false]]}) yield * this.applyDeleteSet({'40': [[0, 3, false]]})
expect(yield* this.getDeleteSet()).toEqual({'40': [[0, 3, false]]}) expect(yield * this.getDeleteSet()).toEqual({'40': [[0, 3, false]]})
yield* this.applyDeleteSet({'39': [[2, 2, false]], '40': [[0, 1, true], [1, 2, false]], '41': [[2, 1, false]]}) yield * this.applyDeleteSet({'39': [[2, 2, false]], '40': [[0, 1, true], [1, 2, false]], '41': [[2, 1, false]]})
expect(yield* this.getDeleteSet()).toEqual({'39': [[2, 2, false]], '40': [[0, 1, true], [1, 2, false]], '41': [[2, 1, false]]}) expect(yield * this.getDeleteSet()).toEqual({'39': [[2, 2, false]], '40': [[0, 1, true], [1, 2, false]], '41': [[2, 1, false]]})
done() done()
}) })
})) }))
it('Debug #7', async(function * (done) { it('Debug #7', async(function * (done) {
store.requestTransaction(function * () { store.requestTransaction(function * () {
yield* this.markDeleted(['9', 2], 1) yield * this.markDeleted(['9', 2], 1)
yield* this.markDeleted(['11', 2], 1) yield * this.markDeleted(['11', 2], 1)
yield* this.markDeleted(['11', 4], 1) yield * this.markDeleted(['11', 4], 1)
yield* this.markDeleted(['11', 1], 1) yield * this.markDeleted(['11', 1], 1)
yield* this.markDeleted(['9', 4], 1) yield * this.markDeleted(['9', 4], 1)
yield* this.markDeleted(['10', 0], 1) yield * this.markDeleted(['10', 0], 1)
yield* this.markGarbageCollected(['11', 2], 1) yield * this.markGarbageCollected(['11', 2], 1)
yield* this.markDeleted(['11', 2], 1) yield * this.markDeleted(['11', 2], 1)
yield* this.markGarbageCollected(['11', 3], 1) yield * this.markGarbageCollected(['11', 3], 1)
yield* this.markDeleted(['11', 3], 1) yield * this.markDeleted(['11', 3], 1)
yield* this.markDeleted(['11', 3], 1) yield * this.markDeleted(['11', 3], 1)
yield* this.markDeleted(['9', 4], 1) yield * this.markDeleted(['9', 4], 1)
yield* this.markDeleted(['10', 0], 1) yield * this.markDeleted(['10', 0], 1)
yield* this.markGarbageCollected(['11', 1], 1) yield * this.markGarbageCollected(['11', 1], 1)
yield* this.markDeleted(['11', 1], 1) yield * this.markDeleted(['11', 1], 1)
expect(yield* this.getDeleteSet()).toEqual({'9': [[2, 1, false], [4, 1, false]], '10': [[0, 1, false]], '11': [[1, 3, true], [4, 1, false]]}) expect(yield * this.getDeleteSet()).toEqual({'9': [[2, 1, false], [4, 1, false]], '10': [[0, 1, false]], '11': [[1, 3, true], [4, 1, false]]})
done() done()
}) })
})) }))
@@ -167,54 +167,54 @@ for (let database of databases) {
}) })
afterEach(function (done) { afterEach(function (done) {
store.requestTransaction(function * () { store.requestTransaction(function * () {
yield* this.store.destroy() yield * this.store.destroy()
done() done()
}) })
}) })
it('debug #1', function (done) { it('debug #1', function (done) {
store.requestTransaction(function * () { store.requestTransaction(function * () {
yield* this.os.put({id: [2]}) yield * this.os.put({id: [2]})
yield* this.os.put({id: [0]}) yield * this.os.put({id: [0]})
yield* this.os.delete([2]) yield * this.os.delete([2])
yield* this.os.put({id: [1]}) yield * this.os.put({id: [1]})
expect(yield* this.os.find([0])).toBeTruthy() expect(yield * this.os.find([0])).toBeTruthy()
expect(yield* this.os.find([1])).toBeTruthy() expect(yield * this.os.find([1])).toBeTruthy()
expect(yield* this.os.find([2])).toBeFalsy() expect(yield * this.os.find([2])).toBeFalsy()
done() done()
}) })
}) })
it('can add&retrieve 5 elements', function (done) { it('can add&retrieve 5 elements', function (done) {
store.requestTransaction(function * () { store.requestTransaction(function * () {
yield* this.os.put({val: 'four', id: [4]}) yield * this.os.put({val: 'four', id: [4]})
yield* this.os.put({val: 'one', id: [1]}) yield * this.os.put({val: 'one', id: [1]})
yield* this.os.put({val: 'three', id: [3]}) yield * this.os.put({val: 'three', id: [3]})
yield* this.os.put({val: 'two', id: [2]}) yield * this.os.put({val: 'two', id: [2]})
yield* this.os.put({val: 'five', id: [5]}) yield * this.os.put({val: 'five', id: [5]})
expect((yield* this.os.find([1])).val).toEqual('one') expect((yield * this.os.find([1])).val).toEqual('one')
expect((yield* this.os.find([2])).val).toEqual('two') expect((yield * this.os.find([2])).val).toEqual('two')
expect((yield* this.os.find([3])).val).toEqual('three') expect((yield * this.os.find([3])).val).toEqual('three')
expect((yield* this.os.find([4])).val).toEqual('four') expect((yield * this.os.find([4])).val).toEqual('four')
expect((yield* this.os.find([5])).val).toEqual('five') expect((yield * this.os.find([5])).val).toEqual('five')
done() done()
}) })
}) })
it('5 elements do not exist anymore after deleting them', function (done) { it('5 elements do not exist anymore after deleting them', function (done) {
store.requestTransaction(function * () { store.requestTransaction(function * () {
yield* this.os.put({val: 'four', id: [4]}) yield * this.os.put({val: 'four', id: [4]})
yield* this.os.put({val: 'one', id: [1]}) yield * this.os.put({val: 'one', id: [1]})
yield* this.os.put({val: 'three', id: [3]}) yield * this.os.put({val: 'three', id: [3]})
yield* this.os.put({val: 'two', id: [2]}) yield * this.os.put({val: 'two', id: [2]})
yield* this.os.put({val: 'five', id: [5]}) yield * this.os.put({val: 'five', id: [5]})
yield* this.os.delete([4]) yield * this.os.delete([4])
expect(yield* this.os.find([4])).not.toBeTruthy() expect(yield * this.os.find([4])).not.toBeTruthy()
yield* this.os.delete([3]) yield * this.os.delete([3])
expect(yield* this.os.find([3])).not.toBeTruthy() expect(yield * this.os.find([3])).not.toBeTruthy()
yield* this.os.delete([2]) yield * this.os.delete([2])
expect(yield* this.os.find([2])).not.toBeTruthy() expect(yield * this.os.find([2])).not.toBeTruthy()
yield* this.os.delete([1]) yield * this.os.delete([1])
expect(yield* this.os.find([1])).not.toBeTruthy() expect(yield * this.os.find([1])).not.toBeTruthy()
yield* this.os.delete([5]) yield * this.os.delete([5])
expect(yield* this.os.find([5])).not.toBeTruthy() expect(yield * this.os.find([5])).not.toBeTruthy()
done() done()
}) })
}) })
@@ -232,9 +232,9 @@ for (let database of databases) {
var r = Math.random() var r = Math.random()
if (r < 0.8) { if (r < 0.8) {
var obj = [Math.floor(Math.random() * numberOfOSTests * 10000)] var obj = [Math.floor(Math.random() * numberOfOSTests * 10000)]
if (!(yield* this.os.find(obj))) { if (!(yield * this.os.find(obj))) {
elements.push(obj) elements.push(obj)
yield* this.os.put({id: obj}) yield * this.os.put({id: obj})
} }
} else if (elements.length > 0) { } else if (elements.length > 0) {
var elemid = Math.floor(Math.random() * elements.length) var elemid = Math.floor(Math.random() * elements.length)
@@ -242,7 +242,7 @@ for (let database of databases) {
elements = elements.filter(function (e) { elements = elements.filter(function (e) {
return !Y.utils.compareIds(e, elem) return !Y.utils.compareIds(e, elem)
}) })
yield* this.os.delete(elem) yield * this.os.delete(elem)
} }
} }
done() done()
@@ -250,14 +250,14 @@ for (let database of databases) {
}) })
afterAll(function (done) { afterAll(function (done) {
store.requestTransaction(function * () { store.requestTransaction(function * () {
yield* this.store.destroy() yield * this.store.destroy()
done() done()
}) })
}) })
it('can find every object', function (done) { it('can find every object', function (done) {
store.requestTransaction(function * () { store.requestTransaction(function * () {
for (var id of elements) { for (var id of elements) {
expect((yield* this.os.find(id)).id).toEqual(id) expect((yield * this.os.find(id)).id).toEqual(id)
} }
done() done()
}) })
@@ -266,7 +266,7 @@ for (let database of databases) {
it('can find every object with lower bound search', function (done) { it('can find every object with lower bound search', function (done) {
store.requestTransaction(function * () { store.requestTransaction(function * () {
for (var id of elements) { for (var id of elements) {
var e = yield* this.os.findWithLowerBound(id) var e = yield * this.os.findWithLowerBound(id)
expect(e.id).toEqual(id) expect(e.id).toEqual(id)
} }
done() done()
@@ -281,7 +281,7 @@ for (let database of databases) {
var actualResults = 0 var actualResults = 0
store.requestTransaction(function * () { store.requestTransaction(function * () {
yield* this.os.iterate(this, lowerBound, null, function * (val) { yield * this.os.iterate(this, lowerBound, null, function * (val) {
expect(val).toBeDefined() expect(val).toBeDefined()
actualResults++ actualResults++
}) })
@@ -297,7 +297,7 @@ for (let database of databases) {
}).length }).length
var actualResults = 0 var actualResults = 0
store.requestTransaction(function * () { store.requestTransaction(function * () {
yield* this.os.iterate(this, lowerBound, null, function * (val) { yield * this.os.iterate(this, lowerBound, null, function * (val) {
expect(val).toBeDefined() expect(val).toBeDefined()
actualResults++ actualResults++
}) })
@@ -314,7 +314,7 @@ for (let database of databases) {
var actualResults = 0 var actualResults = 0
store.requestTransaction(function * () { store.requestTransaction(function * () {
yield* this.os.iterate(this, null, upperBound, function * (val) { yield * this.os.iterate(this, null, upperBound, function * (val) {
expect(val).toBeDefined() expect(val).toBeDefined()
actualResults++ actualResults++
}) })
@@ -340,7 +340,7 @@ for (let database of databases) {
}).length }).length
var actualResults = 0 var actualResults = 0
store.requestTransaction(function * () { store.requestTransaction(function * () {
yield* this.os.iterate(this, lowerBound, upperBound, function * (val) { yield * this.os.iterate(this, lowerBound, upperBound, function * (val) {
expect(val).toBeDefined() expect(val).toBeDefined()
actualResults++ actualResults++
}) })

View File

@@ -46,7 +46,7 @@ g.setRandomSeed = function setRandomSeed (seed) {
g.generateRandomSeed() g.generateRandomSeed()
g.YConcurrency_TestingMode = true g.YConcurrencyTestingMode = true
jasmine.DEFAULT_TIMEOUT_INTERVAL = 200000 jasmine.DEFAULT_TIMEOUT_INTERVAL = 200000
@@ -168,7 +168,7 @@ function fixAwaitingInType (type) {
type.os.requestTransaction(function * () { type.os.requestTransaction(function * () {
if (type.eventHandler.awaiting > 0 && type.eventHandler._debuggingAwaiting === true) { if (type.eventHandler.awaiting > 0 && type.eventHandler._debuggingAwaiting === true) {
type.eventHandler._debuggingAwaiting = false type.eventHandler._debuggingAwaiting = false
yield* type.eventHandler.awaitOps(this, function * () { /* mock function */ }) yield * type.eventHandler.awaitOps(this, function * () { /* mock function */ })
} }
wait(50).then(type.os.whenTransactionsFinished()).then(wait(50)).then(resolve) wait(50).then(type.os.whenTransactionsFinished()).then(wait(50)).then(resolve)
}) })
@@ -178,13 +178,13 @@ function fixAwaitingInType (type) {
g.fixAwaitingInType = fixAwaitingInType g.fixAwaitingInType = fixAwaitingInType
g.applyRandomTransactionsNoGCNoDisconnect = async(function * applyRandomTransactions (users, objects, transactions, numberOfTransactions) { g.applyRandomTransactionsNoGCNoDisconnect = async(function * applyRandomTransactions (users, objects, transactions, numberOfTransactions) {
yield* applyTransactions(1, numberOfTransactions, objects, users, transactions, true) yield * applyTransactions(1, numberOfTransactions, objects, users, transactions, true)
yield Y.utils.globalRoom.flushAll() yield Y.utils.globalRoom.flushAll()
yield Promise.all(objects.map(fixAwaitingInType)) yield Promise.all(objects.map(fixAwaitingInType))
}) })
g.applyRandomTransactionsAllRejoinNoGC = async(function * applyRandomTransactions (users, objects, transactions, numberOfTransactions) { g.applyRandomTransactionsAllRejoinNoGC = async(function * applyRandomTransactions (users, objects, transactions, numberOfTransactions) {
yield* applyTransactions(1, numberOfTransactions, objects, users, transactions) yield * applyTransactions(1, numberOfTransactions, objects, users, transactions)
yield Promise.all(objects.map(fixAwaitingInType)) yield Promise.all(objects.map(fixAwaitingInType))
yield Y.utils.globalRoom.flushAll() yield Y.utils.globalRoom.flushAll()
yield Promise.all(objects.map(fixAwaitingInType)) yield Promise.all(objects.map(fixAwaitingInType))
@@ -200,7 +200,7 @@ g.applyRandomTransactionsAllRejoinNoGC = async(function * applyRandomTransaction
}) })
g.applyRandomTransactionsWithGC = async(function * applyRandomTransactions (users, objects, transactions, numberOfTransactions) { g.applyRandomTransactionsWithGC = async(function * applyRandomTransactions (users, objects, transactions, numberOfTransactions) {
yield* applyTransactions(1, numberOfTransactions, objects, users.slice(1), transactions) yield * applyTransactions(1, numberOfTransactions, objects, users.slice(1), transactions)
yield Y.utils.globalRoom.flushAll() yield Y.utils.globalRoom.flushAll()
yield Promise.all(objects.map(fixAwaitingInType)) yield Promise.all(objects.map(fixAwaitingInType))
for (var u in users) { for (var u in users) {
@@ -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] // t1 and t2 basically do the same. They define t[1,2], ds[1,2], and allDels[1,2]
function * t1 () { function * t1 () {
s1 = yield* this.getStateSet() s1 = yield * this.getStateSet()
ds1 = yield* this.getDeleteSet() ds1 = yield * this.getDeleteSet()
allDels1 = [] allDels1 = []
yield* this.ds.iterate(this, null, null, function * (d) { yield * this.ds.iterate(this, null, null, function * (d) {
allDels1.push(d) allDels1.push(d)
}) })
} }
function * t2 () { function * t2 () {
s2 = yield* this.getStateSet() s2 = yield * this.getStateSet()
ds2 = yield* this.getDeleteSet() ds2 = yield * this.getDeleteSet()
allDels2 = [] allDels2 = []
yield* this.ds.iterate(this, null, null, function * (d) { yield * this.ds.iterate(this, null, null, function * (d) {
allDels2.push(d) allDels2.push(d)
}) })
} }
@@ -269,25 +269,25 @@ g.compareAllUsers = async(function * compareAllUsers (users) {
for (var uid = 0; uid < users.length; uid++) { for (var uid = 0; uid < users.length; uid++) {
var u = users[uid] var u = users[uid]
u.db.requestTransaction(function * () { u.db.requestTransaction(function * () {
var sv = yield* this.getStateVector() var sv = yield * this.getStateVector()
for (var s of sv) { for (var s of sv) {
yield* this.updateState(s.user) yield * this.updateState(s.user)
} }
// compare deleted ops against deleteStore // compare deleted ops against deleteStore
yield* this.os.iterate(this, null, null, function * (o) { yield * this.os.iterate(this, null, null, function * (o) {
if (o.deleted === true) { if (o.deleted === true) {
expect(yield* this.isDeleted(o.id)).toBeTruthy() expect(yield * this.isDeleted(o.id)).toBeTruthy()
} }
}) })
// compare deleteStore against deleted ops // compare deleteStore against deleted ops
var ds = [] var ds = []
yield* this.ds.iterate(this, null, null, function * (d) { yield * this.ds.iterate(this, null, null, function * (d) {
ds.push(d) ds.push(d)
}) })
for (var j in ds) { for (var j in ds) {
var d = ds[j] var d = ds[j]
for (var i = 0; i < d.len; i++) { for (var i = 0; i < d.len; i++) {
var o = yield* this.getInsertion([d.id[0], d.id[1] + i]) var o = yield * this.getInsertion([d.id[0], d.id[1] + i])
// gc'd or deleted // gc'd or deleted
if (d.gc) { if (d.gc) {
expect(o).toBeFalsy() expect(o).toBeFalsy()
@@ -300,8 +300,8 @@ g.compareAllUsers = async(function * compareAllUsers (users) {
// compare allDels tree // compare allDels tree
if (s1 == null) { if (s1 == null) {
u.db.requestTransaction(function * () { u.db.requestTransaction(function * () {
yield* t1.call(this) yield * t1.call(this)
yield* this.os.iterate(this, null, null, function * (o) { yield * this.os.iterate(this, null, null, function * (o) {
o = Y.utils.copyObject(o) o = Y.utils.copyObject(o)
delete o.origin delete o.origin
delete o.originOf delete o.originOf
@@ -310,9 +310,9 @@ g.compareAllUsers = async(function * compareAllUsers (users) {
}) })
} else { } else {
u.db.requestTransaction(function * () { u.db.requestTransaction(function * () {
yield* t2.call(this) yield * t2.call(this)
var db2 = [] var db2 = []
yield* this.os.iterate(this, null, null, function * (o) { yield * this.os.iterate(this, null, null, function * (o) {
o = Y.utils.copyObject(o) o = Y.utils.copyObject(o)
delete o.origin delete o.origin
delete o.originOf delete o.originOf

View File

@@ -19,7 +19,7 @@
* requiredOps * requiredOps
- Operations that are required to execute this operation. - Operations that are required to execute this operation.
*/ */
module.exports = function (Y/* :any */) { export default function extendStruct (Y) {
var Struct = { var Struct = {
/* This is the only operation that is actually not a structure, because /* This is the only operation that is actually not a structure, because
it is not stored in the OS. This is why it _does not_ have an id it is not stored in the OS. This is why it _does not_ have an id
@@ -40,7 +40,7 @@ module.exports = function (Y/* :any */) {
return [] // [op.target] return [] // [op.target]
}, },
execute: function * (op) { execute: function * (op) {
return yield* this.deleteOperation(op.target, op.length || 1) return yield * this.deleteOperation(op.target, op.length || 1)
} }
}, },
Insert: { Insert: {
@@ -101,13 +101,13 @@ module.exports = function (Y/* :any */) {
return 0 return 0
} else { } else {
var d = 0 var d = 0
var o = yield* this.getInsertion(op.left) var o = yield * this.getInsertion(op.left)
while (!Y.utils.matchesId(o, op.origin)) { while (!Y.utils.matchesId(o, op.origin)) {
d++ d++
if (o.left == null) { if (o.left == null) {
break break
} else { } else {
o = yield* this.getInsertion(o.left) o = yield * this.getInsertion(o.left)
} }
} }
return d return d
@@ -138,17 +138,17 @@ module.exports = function (Y/* :any */) {
if (op.origin != null) { // TODO: !== instead of != if (op.origin != null) { // TODO: !== instead of !=
// we save in origin that op originates in it // we save in origin that op originates in it
// we need that later when we eventually garbage collect origin (see transaction) // we need that later when we eventually garbage collect origin (see transaction)
var origin = yield* this.getInsertionCleanEnd(op.origin) var origin = yield * this.getInsertionCleanEnd(op.origin)
if (origin.originOf == null) { if (origin.originOf == null) {
origin.originOf = [] origin.originOf = []
} }
origin.originOf.push(op.id) origin.originOf.push(op.id)
yield* this.setOperation(origin) yield * this.setOperation(origin)
if (origin.right != null) { if (origin.right != null) {
tryToRemergeLater.push(origin.right) tryToRemergeLater.push(origin.right)
} }
} }
var distanceToOrigin = i = yield* Struct.Insert.getDistanceToOrigin.call(this, op) // most cases: 0 (starts from 0) var distanceToOrigin = i = yield * Struct.Insert.getDistanceToOrigin.call(this, op) // most cases: 0 (starts from 0)
// now we begin to insert op in the list of insertions.. // now we begin to insert op in the list of insertions..
var o var o
@@ -157,29 +157,29 @@ module.exports = function (Y/* :any */) {
// find o. o is the first conflicting operation // find o. o is the first conflicting operation
if (op.left != null) { if (op.left != null) {
o = yield* this.getInsertionCleanEnd(op.left) o = yield * this.getInsertionCleanEnd(op.left)
if (!Y.utils.compareIds(op.left, op.origin) && o.right != null) { if (!Y.utils.compareIds(op.left, op.origin) && o.right != null) {
// only if not added previously // only if not added previously
tryToRemergeLater.push(o.right) tryToRemergeLater.push(o.right)
} }
o = (o.right == null) ? null : yield* this.getOperation(o.right) o = (o.right == null) ? null : yield * this.getOperation(o.right)
} else { // left == null } else { // left == null
parent = yield* this.getOperation(op.parent) parent = yield * this.getOperation(op.parent)
let startId = op.parentSub ? parent.map[op.parentSub] : parent.start let startId = op.parentSub ? parent.map[op.parentSub] : parent.start
start = startId == null ? null : yield* this.getOperation(startId) start = startId == null ? null : yield * this.getOperation(startId)
o = start o = start
} }
// make sure to split op.right if necessary (also add to tryCombineWithLeft) // make sure to split op.right if necessary (also add to tryCombineWithLeft)
if (op.right != null) { if (op.right != null) {
tryToRemergeLater.push(op.right) tryToRemergeLater.push(op.right)
yield* this.getInsertionCleanStart(op.right) yield * this.getInsertionCleanStart(op.right)
} }
// handle conflicts // handle conflicts
while (true) { while (true) {
if (o != null && !Y.utils.compareIds(o.id, op.right)) { if (o != null && !Y.utils.compareIds(o.id, op.right)) {
var oOriginDistance = yield* Struct.Insert.getDistanceToOrigin.call(this, o) var oOriginDistance = yield * Struct.Insert.getDistanceToOrigin.call(this, o)
if (oOriginDistance === i) { if (oOriginDistance === i) {
// case 1 // case 1
if (o.id[0] < op.id[0]) { if (o.id[0] < op.id[0]) {
@@ -197,7 +197,7 @@ module.exports = function (Y/* :any */) {
} }
i++ i++
if (o.right != null) { if (o.right != null) {
o = yield* this.getInsertion(o.right) o = yield * this.getInsertion(o.right)
} else { } else {
o = null o = null
} }
@@ -210,17 +210,17 @@ module.exports = function (Y/* :any */) {
var left = null var left = null
var right = null var right = null
if (parent == null) { if (parent == null) {
parent = yield* this.getOperation(op.parent) parent = yield * this.getOperation(op.parent)
} }
// reconnect left and set right of op // reconnect left and set right of op
if (op.left != null) { if (op.left != null) {
left = yield* this.getInsertion(op.left) left = yield * this.getInsertion(op.left)
// link left // link left
op.right = left.right op.right = left.right
left.right = op.id left.right = op.id
yield* this.setOperation(left) yield * this.setOperation(left)
} else { } else {
// set op.right from parent, if necessary // set op.right from parent, if necessary
op.right = op.parentSub ? parent.map[op.parentSub] || null : parent.start op.right = op.parentSub ? parent.map[op.parentSub] || null : parent.start
@@ -228,33 +228,33 @@ module.exports = function (Y/* :any */) {
// reconnect right // reconnect right
if (op.right != null) { if (op.right != null) {
// TODO: wanna connect right too? // TODO: wanna connect right too?
right = yield* this.getOperation(op.right) right = yield * this.getOperation(op.right)
right.left = Y.utils.getLastId(op) right.left = Y.utils.getLastId(op)
// if right exists, and it is supposed to be gc'd. Remove it from the gc // if right exists, and it is supposed to be gc'd. Remove it from the gc
if (right.gc != null) { if (right.gc != null) {
if (right.content != null && right.content.length > 1) { if (right.content != null && right.content.length > 1) {
right = yield* this.getInsertionCleanEnd(right.id) right = yield * this.getInsertionCleanEnd(right.id)
} }
this.store.removeFromGarbageCollector(right) this.store.removeFromGarbageCollector(right)
} }
yield* this.setOperation(right) yield * this.setOperation(right)
} }
// update parents .map/start/end properties // update parents .map/start/end properties
if (op.parentSub != null) { if (op.parentSub != null) {
if (left == null) { if (left == null) {
parent.map[op.parentSub] = op.id parent.map[op.parentSub] = op.id
yield* this.setOperation(parent) yield * this.setOperation(parent)
} }
// is a child of a map struct. // is a child of a map struct.
// Then also make sure that only the most left element is not deleted // Then also make sure that only the most left element is not deleted
// We do not call the type in this case (this is what the third parameter is for) // We do not call the type in this case (this is what the third parameter is for)
if (op.right != null) { if (op.right != null) {
yield* this.deleteOperation(op.right, 1, true) yield * this.deleteOperation(op.right, 1, true)
} }
if (op.left != null) { if (op.left != null) {
yield* this.deleteOperation(op.id, 1, true) yield * this.deleteOperation(op.id, 1, true)
} }
} else { } else {
if (right == null || left == null) { if (right == null || left == null) {
@@ -264,14 +264,14 @@ module.exports = function (Y/* :any */) {
if (left == null) { if (left == null) {
parent.start = op.id parent.start = op.id
} }
yield* this.setOperation(parent) yield * this.setOperation(parent)
} }
} }
// try to merge original op.left and op.origin // try to merge original op.left and op.origin
for (i = 0; i < tryToRemergeLater.length; i++) { for (i = 0; i < tryToRemergeLater.length; i++) {
var m = yield* this.getOperation(tryToRemergeLater[i]) var m = yield * this.getOperation(tryToRemergeLater[i])
yield* this.tryCombineWithLeft(m) yield * this.tryCombineWithLeft(m)
} }
} }
}, },
@@ -329,7 +329,7 @@ module.exports = function (Y/* :any */) {
return null return null
} }
var res = null var res = null
var o = yield* this.getOperation(op.start) var o = yield * this.getOperation(op.start)
while (true) { while (true) {
if (!o.deleted) { if (!o.deleted) {
@@ -337,7 +337,7 @@ module.exports = function (Y/* :any */) {
pos-- pos--
} }
if (pos >= 0 && o.right != null) { if (pos >= 0 && o.right != null) {
o = yield* this.getOperation(o.right) o = yield * this.getOperation(o.right)
} else { } else {
break break
} }
@@ -348,7 +348,7 @@ module.exports = function (Y/* :any */) {
o = o.start o = o.start
var res = [] var res = []
while (o != null) { // TODO: change to != (at least some convention) while (o != null) { // TODO: change to != (at least some convention)
var operation = yield* this.getOperation(o) var operation = yield * this.getOperation(o)
if (!operation.deleted) { if (!operation.deleted) {
res.push(f(operation)) res.push(f(operation))
} }
@@ -398,13 +398,13 @@ module.exports = function (Y/* :any */) {
get: function * (op, name) { get: function * (op, name) {
var oid = op.map[name] var oid = op.map[name]
if (oid != null) { if (oid != null) {
var res = yield* this.getOperation(oid) var res = yield * this.getOperation(oid)
if (res == null || res.deleted) { if (res == null || res.deleted) {
return void 0 return void 0
} else if (res.opContent == null) { } else if (res.opContent == null) {
return res.content[0] return res.content[0]
} else { } else {
return yield* this.getType(res.opContent) return yield * this.getType(res.opContent)
} }
} }
} }

View File

@@ -74,7 +74,7 @@
- this is called only by `getOperations(startSS)`. It makes an operation - this is called only by `getOperations(startSS)`. It makes an operation
applyable on a given SS. applyable on a given SS.
*/ */
module.exports = function (Y/* :any */) { export default function extendTransaction (Y) {
class TransactionInterface { class TransactionInterface {
/* :: /* ::
store: Y.AbstractDatabase; store: Y.AbstractDatabase;
@@ -91,7 +91,7 @@ module.exports = function (Y/* :any */) {
var send = [] var send = []
for (var i = 0; i < ops.length; i++) { for (var i = 0; i < ops.length; i++) {
var op = ops[i] var op = ops[i]
yield* this.store.tryExecute.call(this, op) yield * this.store.tryExecute.call(this, op)
if (op.id == null || typeof op.id[1] !== 'string') { if (op.id == null || typeof op.id[1] !== 'string') {
send.push(Y.Struct[op.struct].encode(op)) send.push(Y.Struct[op.struct].encode(op))
} }
@@ -104,15 +104,15 @@ module.exports = function (Y/* :any */) {
* deleteList (start) { * deleteList (start) {
while (start != null) { while (start != null) {
start = yield* this.getOperation(start) start = yield * this.getOperation(start)
if (!start.gc) { if (!start.gc) {
start.gc = true start.gc = true
start.deleted = true start.deleted = true
yield* this.setOperation(start) yield * this.setOperation(start)
var delLength = start.content != null ? start.content.length : 1 var delLength = start.content != null ? start.content.length : 1
yield* this.markDeleted(start.id, delLength) yield * this.markDeleted(start.id, delLength)
if (start.opContent != null) { if (start.opContent != null) {
yield* this.deleteOperation(start.opContent) yield * this.deleteOperation(start.opContent)
} }
this.store.queueGarbageCollector(start.id) this.store.queueGarbageCollector(start.id)
} }
@@ -127,10 +127,10 @@ module.exports = function (Y/* :any */) {
if (length == null) { if (length == null) {
length = 1 length = 1
} }
yield* this.markDeleted(targetId, length) yield * this.markDeleted(targetId, length)
while (length > 0) { while (length > 0) {
var callType = false var callType = false
var target = yield* this.os.findWithUpperBound([targetId[0], targetId[1] + length - 1]) var target = yield * this.os.findWithUpperBound([targetId[0], targetId[1] + length - 1])
var targetLength = target != null && target.content != null ? target.content.length : 1 var targetLength = target != null && target.content != null ? target.content.length : 1
if (target == null || target.id[0] !== targetId[0] || target.id[1] + targetLength <= targetId[1]) { if (target == null || target.id[0] !== targetId[0] || target.id[1] + targetLength <= targetId[1]) {
// does not exist or is not in the range of the deletion // does not exist or is not in the range of the deletion
@@ -141,12 +141,12 @@ module.exports = function (Y/* :any */) {
if (!target.deleted) { if (!target.deleted) {
if (target.id[1] < targetId[1]) { if (target.id[1] < targetId[1]) {
// starts to the left of the deletion range // starts to the left of the deletion range
target = yield* this.getInsertionCleanStart(targetId) target = yield * this.getInsertionCleanStart(targetId)
targetLength = target.content.length // must have content property! targetLength = target.content.length // must have content property!
} }
if (target.id[1] + targetLength > targetId[1] + length) { if (target.id[1] + targetLength > targetId[1] + length) {
// ends to the right of the deletion range // ends to the right of the deletion range
target = yield* this.getInsertionCleanEnd([targetId[0], targetId[1] + length - 1]) target = yield * this.getInsertionCleanEnd([targetId[0], targetId[1] + length - 1])
targetLength = target.content.length targetLength = target.content.length
} }
} }
@@ -161,35 +161,35 @@ module.exports = function (Y/* :any */) {
// delete containing lists // delete containing lists
if (target.start != null) { if (target.start != null) {
// TODO: don't do it like this .. -.- // TODO: don't do it like this .. -.-
yield* this.deleteList(target.start) yield * this.deleteList(target.start)
// yield* this.deleteList(target.id) -- do not gc itself because this may still get referenced // yield* this.deleteList(target.id) -- do not gc itself because this may still get referenced
} }
if (target.map != null) { if (target.map != null) {
for (var name in target.map) { for (var name in target.map) {
yield* this.deleteList(target.map[name]) yield * this.deleteList(target.map[name])
} }
// TODO: here to.. (see above) // TODO: here to.. (see above)
// yield* this.deleteList(target.id) -- see above // yield* this.deleteList(target.id) -- see above
} }
if (target.opContent != null) { if (target.opContent != null) {
yield* this.deleteOperation(target.opContent) yield * this.deleteOperation(target.opContent)
// target.opContent = null // target.opContent = null
} }
if (target.requires != null) { if (target.requires != null) {
for (var i = 0; i < target.requires.length; i++) { for (var i = 0; i < target.requires.length; i++) {
yield* this.deleteOperation(target.requires[i]) yield * this.deleteOperation(target.requires[i])
} }
} }
} }
var left var left
if (target.left != null) { if (target.left != null) {
left = yield* this.getInsertion(target.left) left = yield * this.getInsertion(target.left)
} else { } else {
left = null left = null
} }
// set here because it was deleted and/or gc'd // set here because it was deleted and/or gc'd
yield* this.setOperation(target) yield * this.setOperation(target)
/* /*
Check if it is possible to add right to the gc. Check if it is possible to add right to the gc.
@@ -198,12 +198,12 @@ module.exports = function (Y/* :any */) {
*/ */
var right var right
if (target.right != null) { if (target.right != null) {
right = yield* this.getOperation(target.right) right = yield * this.getOperation(target.right)
} else { } else {
right = null right = null
} }
if (callType && !preventCallType) { if (callType && !preventCallType) {
yield* this.store.operationAdded(this, { yield * this.store.operationAdded(this, {
struct: 'Delete', struct: 'Delete',
target: target.id, target: target.id,
length: targetLength, length: targetLength,
@@ -211,9 +211,9 @@ module.exports = function (Y/* :any */) {
}) })
} }
// need to gc in the end! // need to gc in the end!
yield* this.store.addToGarbageCollector.call(this, target, left) yield * this.store.addToGarbageCollector.call(this, target, left)
if (right != null) { if (right != null) {
yield* this.store.addToGarbageCollector.call(this, right, target) yield * this.store.addToGarbageCollector.call(this, right, target)
} }
} }
} }
@@ -224,22 +224,22 @@ module.exports = function (Y/* :any */) {
* markGarbageCollected (id, len) { * markGarbageCollected (id, len) {
// this.mem.push(["gc", id]); // this.mem.push(["gc", id]);
this.store.addToDebug('yield* this.markGarbageCollected(', id, ', ', len, ')') this.store.addToDebug('yield* this.markGarbageCollected(', id, ', ', len, ')')
var n = yield* this.markDeleted(id, len) var n = yield * this.markDeleted(id, len)
if (n.id[1] < id[1] && !n.gc) { if (n.id[1] < id[1] && !n.gc) {
// un-extend left // un-extend left
var newlen = n.len - (id[1] - n.id[1]) var newlen = n.len - (id[1] - n.id[1])
n.len -= newlen n.len -= newlen
yield* this.ds.put(n) yield * this.ds.put(n)
n = {id: id, len: newlen, gc: false} n = {id: id, len: newlen, gc: false}
yield* this.ds.put(n) yield * this.ds.put(n)
} }
// get prev&next before adding a new operation // get prev&next before adding a new operation
var prev = yield* this.ds.findPrev(id) var prev = yield * this.ds.findPrev(id)
var next = yield* this.ds.findNext(id) var next = yield * this.ds.findNext(id)
if (id[1] + len < n.id[1] + n.len && !n.gc) { if (id[1] + len < n.id[1] + n.len && !n.gc) {
// un-extend right // un-extend right
yield* this.ds.put({id: [id[0], id[1] + len], len: n.len - len, gc: false}) yield * this.ds.put({id: [id[0], id[1] + len], len: n.len - len, gc: false})
n.len = len n.len = len
} }
// set gc'd // set gc'd
@@ -251,7 +251,7 @@ module.exports = function (Y/* :any */) {
Y.utils.compareIds([prev.id[0], prev.id[1] + prev.len], n.id) Y.utils.compareIds([prev.id[0], prev.id[1] + prev.len], n.id)
) { ) {
prev.len += n.len prev.len += n.len
yield* this.ds.delete(n.id) yield * this.ds.delete(n.id)
n = prev n = prev
// ds.put n here? // ds.put n here?
} }
@@ -262,10 +262,10 @@ module.exports = function (Y/* :any */) {
Y.utils.compareIds([n.id[0], n.id[1] + n.len], next.id) Y.utils.compareIds([n.id[0], n.id[1] + n.len], next.id)
) { ) {
n.len += next.len n.len += next.len
yield* this.ds.delete(next.id) yield * this.ds.delete(next.id)
} }
yield* this.ds.put(n) yield * this.ds.put(n)
yield* this.updateState(n.id[0]) yield * this.updateState(n.id[0])
} }
/* /*
Mark an operation as deleted. Mark an operation as deleted.
@@ -277,7 +277,7 @@ module.exports = function (Y/* :any */) {
length = 1 length = 1
} }
// this.mem.push(["del", id]); // this.mem.push(["del", id]);
var n = yield* this.ds.findWithUpperBound(id) var n = yield * this.ds.findWithUpperBound(id)
if (n != null && n.id[0] === id[0]) { if (n != null && n.id[0] === id[0]) {
if (n.id[1] <= id[1] && id[1] <= n.id[1] + n.len) { if (n.id[1] <= id[1] && id[1] <= n.id[1] + n.len) {
// id is in n's range // id is in n's range
@@ -291,7 +291,7 @@ module.exports = function (Y/* :any */) {
if (diff < length) { if (diff < length) {
// a partial deletion // a partial deletion
n = {id: [id[0], id[1] + diff], len: length - diff, gc: false} n = {id: [id[0], id[1] + diff], len: length - diff, gc: false}
yield* this.ds.put(n) yield * this.ds.put(n)
} else { } else {
// already gc'd // already gc'd
throw new Error('Cannot happen! (it dit though.. :()') throw new Error('Cannot happen! (it dit though.. :()')
@@ -305,15 +305,15 @@ module.exports = function (Y/* :any */) {
} else { } else {
// cannot extend left (there is no left!) // cannot extend left (there is no left!)
n = {id: id, len: length, gc: false} n = {id: id, len: length, gc: false}
yield* this.ds.put(n) // TODO: you double-put !! yield * this.ds.put(n) // TODO: you double-put !!
} }
} else { } else {
// cannot extend left // cannot extend left
n = {id: id, len: length, gc: false} n = {id: id, len: length, gc: false}
yield* this.ds.put(n) yield * this.ds.put(n)
} }
// can extend right? // can extend right?
var next = yield* this.ds.findNext(n.id) var next = yield * this.ds.findNext(n.id)
if ( if (
next != null && next != null &&
n.id[0] === next.id[0] && n.id[0] === next.id[0] &&
@@ -329,8 +329,8 @@ module.exports = function (Y/* :any */) {
// delete the missing range after next // delete the missing range after next
diff = diff - next.len // missing range after next diff = diff - next.len // missing range after next
if (diff > 0) { if (diff > 0) {
yield* this.ds.put(n) // unneccessary? TODO! yield * this.ds.put(n) // unneccessary? TODO!
yield* this.markDeleted([next.id[0], next.id[1] + next.len], diff) yield * this.markDeleted([next.id[0], next.id[1] + next.len], diff)
} }
} }
break break
@@ -339,8 +339,8 @@ module.exports = function (Y/* :any */) {
if (diff > next.len) { if (diff > next.len) {
// n is even longer than next // n is even longer than next
// get next.next, and try to extend it // get next.next, and try to extend it
var _next = yield* this.ds.findNext(next.id) var _next = yield * this.ds.findNext(next.id)
yield* this.ds.delete(next.id) yield * this.ds.delete(next.id)
if (_next == null || n.id[0] !== _next.id[0]) { if (_next == null || n.id[0] !== _next.id[0]) {
break break
} else { } else {
@@ -351,13 +351,13 @@ module.exports = function (Y/* :any */) {
} else { } else {
// n just partially overlaps with next. extend n, delete next, and break this loop // n just partially overlaps with next. extend n, delete next, and break this loop
n.len += next.len - diff n.len += next.len - diff
yield* this.ds.delete(next.id) yield * this.ds.delete(next.id)
break break
} }
} }
} }
} }
yield* this.ds.put(n) yield * this.ds.put(n)
return n return n
} }
/* /*
@@ -372,28 +372,28 @@ module.exports = function (Y/* :any */) {
if (!this.store.gc) { if (!this.store.gc) {
return return
} }
yield* this.os.iterate(this, null, null, function * (op) { yield * this.os.iterate(this, null, null, function * (op) {
if (op.gc) { if (op.gc) {
delete op.gc delete op.gc
yield* this.setOperation(op) yield * this.setOperation(op)
} }
if (op.parent != null) { if (op.parent != null) {
var parentDeleted = yield* this.isDeleted(op.parent) var parentDeleted = yield * this.isDeleted(op.parent)
if (parentDeleted) { if (parentDeleted) {
op.gc = true op.gc = true
if (!op.deleted) { if (!op.deleted) {
yield* this.markDeleted(op.id, op.content != null ? op.content.length : 1) yield * this.markDeleted(op.id, op.content != null ? op.content.length : 1)
op.deleted = true op.deleted = true
if (op.opContent != null) { if (op.opContent != null) {
yield* this.deleteOperation(op.opContent) yield * this.deleteOperation(op.opContent)
} }
if (op.requires != null) { if (op.requires != null) {
for (var i = 0; i < op.requires.length; i++) { for (var i = 0; i < op.requires.length; i++) {
yield* this.deleteOperation(op.requires[i]) yield * this.deleteOperation(op.requires[i])
} }
} }
} }
yield* this.setOperation(op) yield * this.setOperation(op)
this.store.gc1.push(op.id) // this is ok becaues its shortly before sync (otherwise use queueGarbageCollector!) this.store.gc1.push(op.id) // this is ok becaues its shortly before sync (otherwise use queueGarbageCollector!)
return return
} }
@@ -401,9 +401,9 @@ module.exports = function (Y/* :any */) {
if (op.deleted) { if (op.deleted) {
var left = null var left = null
if (op.left != null) { if (op.left != null) {
left = yield* this.getInsertion(op.left) left = yield * this.getInsertion(op.left)
} }
yield* this.store.addToGarbageCollector.call(this, op, left) yield * this.store.addToGarbageCollector.call(this, op, left)
} }
}) })
} }
@@ -418,8 +418,8 @@ module.exports = function (Y/* :any */) {
*/ */
* garbageCollectOperation (id) { * garbageCollectOperation (id) {
this.store.addToDebug('yield* this.garbageCollectOperation(', id, ')') this.store.addToDebug('yield* this.garbageCollectOperation(', id, ')')
var o = yield* this.getOperation(id) var o = yield * this.getOperation(id)
yield* this.markGarbageCollected(id, (o != null && o.content != null) ? o.content.length : 1) // always mark gc'd yield * this.markGarbageCollected(id, (o != null && o.content != null) ? o.content.length : 1) // always mark gc'd
// if op exists, then clean that mess up.. // if op exists, then clean that mess up..
if (o != null) { if (o != null) {
var deps = [] var deps = []
@@ -430,32 +430,32 @@ module.exports = function (Y/* :any */) {
deps = deps.concat(o.requires) deps = deps.concat(o.requires)
} }
for (var i = 0; i < deps.length; i++) { for (var i = 0; i < deps.length; i++) {
var dep = yield* this.getOperation(deps[i]) var dep = yield * this.getOperation(deps[i])
if (dep != null) { if (dep != null) {
if (!dep.deleted) { if (!dep.deleted) {
yield* this.deleteOperation(dep.id) yield * this.deleteOperation(dep.id)
dep = yield* this.getOperation(dep.id) dep = yield * this.getOperation(dep.id)
} }
dep.gc = true dep.gc = true
yield* this.setOperation(dep) yield * this.setOperation(dep)
this.store.queueGarbageCollector(dep.id) this.store.queueGarbageCollector(dep.id)
} else { } else {
yield* this.markGarbageCollected(deps[i], 1) yield * this.markGarbageCollected(deps[i], 1)
} }
} }
// remove gc'd op from the left op, if it exists // remove gc'd op from the left op, if it exists
if (o.left != null) { if (o.left != null) {
var left = yield* this.getInsertion(o.left) var left = yield * this.getInsertion(o.left)
left.right = o.right left.right = o.right
yield* this.setOperation(left) yield * this.setOperation(left)
} }
// remove gc'd op from the right op, if it exists // remove gc'd op from the right op, if it exists
// also reset origins of right ops // also reset origins of right ops
if (o.right != null) { if (o.right != null) {
var right = yield* this.getOperation(o.right) var right = yield * this.getOperation(o.right)
right.left = o.left right.left = o.left
yield* this.setOperation(right) yield * this.setOperation(right)
if (o.originOf != null && o.originOf.length > 0) { if (o.originOf != null && o.originOf.length > 0) {
// find new origin of right ops // find new origin of right ops
@@ -463,7 +463,7 @@ module.exports = function (Y/* :any */) {
var neworigin = o.left var neworigin = o.left
var neworigin_ = null var neworigin_ = null
while (neworigin != null) { while (neworigin != null) {
neworigin_ = yield* this.getInsertion(neworigin) neworigin_ = yield * this.getInsertion(neworigin)
if (neworigin_.deleted) { if (neworigin_.deleted) {
break break
} }
@@ -506,10 +506,10 @@ module.exports = function (Y/* :any */) {
// ** Now the new implementation starts ** // ** Now the new implementation starts **
// reset neworigin of all originOf[*] // reset neworigin of all originOf[*]
for (var _i in o.originOf) { for (var _i in o.originOf) {
var originsIn = yield* this.getOperation(o.originOf[_i]) var originsIn = yield * this.getOperation(o.originOf[_i])
if (originsIn != null) { if (originsIn != null) {
originsIn.origin = neworigin originsIn.origin = neworigin
yield* this.setOperation(originsIn) yield * this.setOperation(originsIn)
} }
} }
if (neworigin != null) { if (neworigin != null) {
@@ -518,7 +518,7 @@ module.exports = function (Y/* :any */) {
} else { } else {
neworigin_.originOf = o.originOf.concat(neworigin_.originOf) neworigin_.originOf = o.originOf.concat(neworigin_.originOf)
} }
yield* this.setOperation(neworigin_) yield * this.setOperation(neworigin_)
} }
// we don't need to set right here, because // we don't need to set right here, because
// right should be in o.originOf => it is set it the previous for loop // right should be in o.originOf => it is set it the previous for loop
@@ -527,15 +527,15 @@ module.exports = function (Y/* :any */) {
// o may originate in another operation. // o may originate in another operation.
// Since o is deleted, we have to reset o.origin's `originOf` property // Since o is deleted, we have to reset o.origin's `originOf` property
if (o.origin != null) { if (o.origin != null) {
var origin = yield* this.getInsertion(o.origin) var origin = yield * this.getInsertion(o.origin)
origin.originOf = origin.originOf.filter(function (_id) { origin.originOf = origin.originOf.filter(function (_id) {
return !Y.utils.compareIds(id, _id) return !Y.utils.compareIds(id, _id)
}) })
yield* this.setOperation(origin) yield * this.setOperation(origin)
} }
var parent var parent
if (o.parent != null) { if (o.parent != null) {
parent = yield* this.getOperation(o.parent) parent = yield * this.getOperation(o.parent)
} }
// remove gc'd op from parent, if it exists // remove gc'd op from parent, if it exists
if (parent != null) { if (parent != null) {
@@ -562,32 +562,32 @@ module.exports = function (Y/* :any */) {
} }
} }
if (setParent) { if (setParent) {
yield* this.setOperation(parent) yield * this.setOperation(parent)
} }
} }
// finally remove it from the os // finally remove it from the os
yield* this.removeOperation(o.id) yield * this.removeOperation(o.id)
} }
} }
* checkDeleteStoreForState (state) { * checkDeleteStoreForState (state) {
var n = yield* this.ds.findWithUpperBound([state.user, state.clock]) var n = yield * this.ds.findWithUpperBound([state.user, state.clock])
if (n != null && n.id[0] === state.user && n.gc) { if (n != null && n.id[0] === state.user && n.gc) {
state.clock = Math.max(state.clock, n.id[1] + n.len) state.clock = Math.max(state.clock, n.id[1] + n.len)
} }
} }
* updateState (user) { * updateState (user) {
var state = yield* this.getState(user) var state = yield * this.getState(user)
yield* this.checkDeleteStoreForState(state) yield * this.checkDeleteStoreForState(state)
var o = yield* this.getInsertion([user, state.clock]) var o = yield * this.getInsertion([user, state.clock])
var oLength = (o != null && o.content != null) ? o.content.length : 1 var oLength = (o != null && o.content != null) ? o.content.length : 1
while (o != null && user === o.id[0] && o.id[1] <= state.clock && o.id[1] + oLength > state.clock) { while (o != null && user === o.id[0] && o.id[1] <= state.clock && o.id[1] + oLength > state.clock) {
// either its a new operation (1. case), or it is an operation that was deleted, but is not yet in the OS // either its a new operation (1. case), or it is an operation that was deleted, but is not yet in the OS
state.clock += oLength state.clock += oLength
yield* this.checkDeleteStoreForState(state) yield * this.checkDeleteStoreForState(state)
o = yield* this.os.findNext(o.id) o = yield * this.os.findNext(o.id)
oLength = (o != null && o.content != null) ? o.content.length : 1 oLength = (o != null && o.content != null) ? o.content.length : 1
} }
yield* this.setState(state) yield * this.setState(state)
} }
/* /*
apply a delete set in order to get apply a delete set in order to get
@@ -600,7 +600,7 @@ module.exports = function (Y/* :any */) {
var dv = ds[user] var dv = ds[user]
var pos = 0 var pos = 0
var d = dv[pos] var d = dv[pos]
yield* this.ds.iterate(this, [user, 0], [user, Number.MAX_VALUE], function * (n) { yield * this.ds.iterate(this, [user, 0], [user, Number.MAX_VALUE], function * (n) {
// cases: // cases:
// 1. d deletes something to the right of n // 1. d deletes something to the right of n
// => go to next n (break) // => go to next n (break)
@@ -649,14 +649,14 @@ module.exports = function (Y/* :any */) {
for (var i = 0; i < deletions.length; i++) { for (var i = 0; i < deletions.length; i++) {
var del = deletions[i] var del = deletions[i]
// always try to delete.. // always try to delete..
yield* this.deleteOperation([del[0], del[1]], del[2]) yield * this.deleteOperation([del[0], del[1]], del[2])
if (del[3]) { if (del[3]) {
// gc.. // gc..
yield* this.markGarbageCollected([del[0], del[1]], del[2]) // always mark gc'd yield * this.markGarbageCollected([del[0], del[1]], del[2]) // always mark gc'd
// remove operation.. // remove operation..
var counter = del[1] + del[2] var counter = del[1] + del[2]
while (counter >= del[1]) { while (counter >= del[1]) {
var o = yield* this.os.findWithUpperBound([del[0], counter - 1]) var o = yield * this.os.findWithUpperBound([del[0], counter - 1])
if (o == null) { if (o == null) {
break break
} }
@@ -667,14 +667,14 @@ module.exports = function (Y/* :any */) {
} }
if (o.id[1] + oLen > del[1] + del[2]) { if (o.id[1] + oLen > del[1] + del[2]) {
// overlaps right // overlaps right
o = yield* this.getInsertionCleanEnd([del[0], del[1] + del[2] - 1]) o = yield * this.getInsertionCleanEnd([del[0], del[1] + del[2] - 1])
} }
if (o.id[1] < del[1]) { if (o.id[1] < del[1]) {
// overlaps left // overlaps left
o = yield* this.getInsertionCleanStart([del[0], del[1]]) o = yield * this.getInsertionCleanStart([del[0], del[1]])
} }
counter = o.id[1] counter = o.id[1]
yield* this.garbageCollectOperation(o.id) yield * this.garbageCollectOperation(o.id)
} }
} }
if (this.store.forwardAppliedOperations) { if (this.store.forwardAppliedOperations) {
@@ -685,7 +685,7 @@ module.exports = function (Y/* :any */) {
} }
} }
* isGarbageCollected (id) { * isGarbageCollected (id) {
var n = yield* this.ds.findWithUpperBound(id) var n = yield * this.ds.findWithUpperBound(id)
return n != null && n.id[0] === id[0] && id[1] < n.id[1] + n.len && n.gc return n != null && n.id[0] === id[0] && id[1] < n.id[1] + n.len && n.gc
} }
/* /*
@@ -693,7 +693,7 @@ module.exports = function (Y/* :any */) {
*/ */
* getDeleteSet () { * getDeleteSet () {
var ds = {} var ds = {}
yield* this.ds.iterate(this, null, null, function * (n) { yield * this.ds.iterate(this, null, null, function * (n) {
var user = n.id[0] var user = n.id[0]
var counter = n.id[1] var counter = n.id[1]
var len = n.len var len = n.len
@@ -708,15 +708,15 @@ module.exports = function (Y/* :any */) {
return ds return ds
} }
* isDeleted (id) { * isDeleted (id) {
var n = yield* this.ds.findWithUpperBound(id) var n = yield * this.ds.findWithUpperBound(id)
return n != null && n.id[0] === id[0] && id[1] < n.id[1] + n.len return n != null && n.id[0] === id[0] && id[1] < n.id[1] + n.len
} }
* setOperation (op) { * setOperation (op) {
yield* this.os.put(op) yield * this.os.put(op)
return op return op
} }
* addOperation (op) { * addOperation (op) {
yield* this.os.put(op) yield * this.os.put(op)
if (this.store.y.connector.isSynced && this.store.forwardAppliedOperations && typeof op.id[1] !== 'string') { if (this.store.y.connector.isSynced && this.store.forwardAppliedOperations && typeof op.id[1] !== 'string') {
// is connected, and this is not going to be send in addOperation // is connected, and this is not going to be send in addOperation
this.store.y.connector.broadcastOps([op]) this.store.y.connector.broadcastOps([op])
@@ -731,7 +731,7 @@ module.exports = function (Y/* :any */) {
op.left[0] === op.id[0] && op.left[0] === op.id[0] &&
Y.utils.compareIds(op.left, op.origin) Y.utils.compareIds(op.left, op.origin)
) { ) {
var left = yield* this.getInsertion(op.left) var left = yield * this.getInsertion(op.left)
if (left.content != null && if (left.content != null &&
left.id[1] + left.content.length === op.id[1] && left.id[1] + left.content.length === op.id[1] &&
left.originOf.length === 1 && left.originOf.length === 1 &&
@@ -746,13 +746,13 @@ module.exports = function (Y/* :any */) {
} }
left.content = left.content.concat(op.content) left.content = left.content.concat(op.content)
left.right = op.right left.right = op.right
yield* this.os.delete(op.id) yield * this.os.delete(op.id)
yield* this.setOperation(left) yield * this.setOperation(left)
} }
} }
} }
* getInsertion (id) { * getInsertion (id) {
var ins = yield* this.os.findWithUpperBound(id) var ins = yield * this.os.findWithUpperBound(id)
if (ins == null) { if (ins == null) {
return null return null
} else { } else {
@@ -765,13 +765,13 @@ module.exports = function (Y/* :any */) {
} }
} }
* getInsertionCleanStartEnd (id) { * getInsertionCleanStartEnd (id) {
yield* this.getInsertionCleanStart(id) yield * this.getInsertionCleanStart(id)
return yield* this.getInsertionCleanEnd(id) return yield * this.getInsertionCleanEnd(id)
} }
// Return an insertion such that id is the first element of content // Return an insertion such that id is the first element of content
// This function manipulates an operation, if necessary // This function manipulates an operation, if necessary
* getInsertionCleanStart (id) { * getInsertionCleanStart (id) {
var ins = yield* this.getInsertion(id) var ins = yield * this.getInsertion(id)
if (ins != null) { if (ins != null) {
if (ins.id[1] === id[1]) { if (ins.id[1] === id[1]) {
return ins return ins
@@ -785,8 +785,8 @@ module.exports = function (Y/* :any */) {
left.right = ins.id left.right = ins.id
ins.left = leftLid ins.left = leftLid
// debugger // check // debugger // check
yield* this.setOperation(left) yield * this.setOperation(left)
yield* this.setOperation(ins) yield * this.setOperation(ins)
if (left.gc) { if (left.gc) {
this.store.queueGarbageCollector(ins.id) this.store.queueGarbageCollector(ins.id)
} }
@@ -799,7 +799,7 @@ module.exports = function (Y/* :any */) {
// Return an insertion such that id is the last element of content // Return an insertion such that id is the last element of content
// This function manipulates an operation, if necessary // This function manipulates an operation, if necessary
* getInsertionCleanEnd (id) { * getInsertionCleanEnd (id) {
var ins = yield* this.getInsertion(id) var ins = yield * this.getInsertion(id)
if (ins != null) { if (ins != null) {
if (ins.content == null || (ins.id[1] + ins.content.length - 1 === id[1])) { if (ins.content == null || (ins.id[1] + ins.content.length - 1 === id[1])) {
return ins return ins
@@ -813,8 +813,8 @@ module.exports = function (Y/* :any */) {
ins.right = right.id ins.right = right.id
right.left = insLid right.left = insLid
// debugger // check // debugger // check
yield* this.setOperation(right) yield * this.setOperation(right)
yield* this.setOperation(ins) yield * this.setOperation(ins)
if (ins.gc) { if (ins.gc) {
this.store.queueGarbageCollector(right.id) this.store.queueGarbageCollector(right.id)
} }
@@ -825,7 +825,7 @@ module.exports = function (Y/* :any */) {
} }
} }
* getOperation (id/* :any */)/* :Transaction<any> */ { * getOperation (id/* :any */)/* :Transaction<any> */ {
var o = yield* this.os.find(id) var o = yield * this.os.find(id)
if (id[0] !== '_' || o != null) { if (id[0] !== '_' || o != null) {
return o return o
} else { // type is string } else { // type is string
@@ -835,7 +835,7 @@ module.exports = function (Y/* :any */) {
var struct = comp[0] var struct = comp[0]
var op = Y.Struct[struct].create(id) var op = Y.Struct[struct].create(id)
op.type = comp[1] op.type = comp[1]
yield* this.setOperation(op) yield * this.setOperation(op)
return op return op
} else { } else {
// won't be called. but just in case.. // won't be called. but just in case..
@@ -846,17 +846,17 @@ module.exports = function (Y/* :any */) {
} }
} }
* removeOperation (id) { * removeOperation (id) {
yield* this.os.delete(id) yield * this.os.delete(id)
} }
* setState (state) { * setState (state) {
var val = { var val = {
id: [state.user], id: [state.user],
clock: state.clock clock: state.clock
} }
yield* this.ss.put(val) yield * this.ss.put(val)
} }
* getState (user) { * getState (user) {
var n = yield* this.ss.find([user]) var n = yield * this.ss.find([user])
var clock = n == null ? null : n.clock var clock = n == null ? null : n.clock
if (clock == null) { if (clock == null) {
clock = 0 clock = 0
@@ -868,7 +868,7 @@ module.exports = function (Y/* :any */) {
} }
* getStateVector () { * getStateVector () {
var stateVector = [] var stateVector = []
yield* this.ss.iterate(this, null, null, function * (n) { yield * this.ss.iterate(this, null, null, function * (n) {
stateVector.push({ stateVector.push({
user: n.id[0], user: n.id[0],
clock: n.clock clock: n.clock
@@ -878,7 +878,7 @@ module.exports = function (Y/* :any */) {
} }
* getStateSet () { * getStateSet () {
var ss = {} var ss = {}
yield* this.ss.iterate(this, null, null, function * (n) { yield * this.ss.iterate(this, null, null, function * (n) {
ss[n.id[0]] = n.clock ss[n.id[0]] = n.clock
}) })
return ss return ss
@@ -936,7 +936,7 @@ module.exports = function (Y/* :any */) {
} }
var send = [] var send = []
var endSV = yield* this.getStateVector() var endSV = yield * this.getStateVector()
for (var endState of endSV) { for (var endState of endSV) {
var user = endState.user var user = endState.user
if (user === '_') { if (user === '_') {
@@ -946,14 +946,14 @@ module.exports = function (Y/* :any */) {
if (startPos > 0) { if (startPos > 0) {
// There is a change that [user, startPos] is in a composed Insertion (with a smaller counter) // There is a change that [user, startPos] is in a composed Insertion (with a smaller counter)
// find out if that is the case // find out if that is the case
var firstMissing = yield* this.getInsertion([user, startPos]) var firstMissing = yield * this.getInsertion([user, startPos])
if (firstMissing != null) { if (firstMissing != null) {
// update startPos // update startPos
startPos = firstMissing.id[1] startPos = firstMissing.id[1]
startSS[user] = startPos startSS[user] = startPos
} }
} }
yield* this.os.iterate(this, [user, startPos], [user, Number.MAX_VALUE], function * (op) { yield * this.os.iterate(this, [user, startPos], [user, Number.MAX_VALUE], function * (op) {
op = Y.Struct[op.struct].encode(op) op = Y.Struct[op.struct].encode(op)
if (op.struct !== 'Insert') { if (op.struct !== 'Insert') {
send.push(op) send.push(op)
@@ -966,7 +966,7 @@ module.exports = function (Y/* :any */) {
// 2. or to the first operation that has an origin that is not to the // 2. or to the first operation that has an origin that is not to the
// right of op. // right of op.
// For this we maintain a list of ops which origins are not found yet. // For this we maintain a list of ops which origins are not found yet.
var missing_origins = [op] var missingOrigins = [op]
var newright = op.right var newright = op.right
while (true) { while (true) {
if (o.left == null) { if (o.left == null) {
@@ -974,15 +974,15 @@ module.exports = function (Y/* :any */) {
send.push(op) send.push(op)
if (!Y.utils.compareIds(o.id, op.id)) { if (!Y.utils.compareIds(o.id, op.id)) {
o = Y.Struct[op.struct].encode(o) o = Y.Struct[op.struct].encode(o)
o.right = missing_origins[missing_origins.length - 1].id o.right = missingOrigins[missingOrigins.length - 1].id
send.push(o) send.push(o)
} }
break break
} }
o = yield* this.getInsertion(o.left) o = yield * this.getInsertion(o.left)
// we set another o, check if we can reduce $missing_origins // we set another o, check if we can reduce $missingOrigins
while (missing_origins.length > 0 && Y.utils.matchesId(o, missing_origins[missing_origins.length - 1].origin)) { while (missingOrigins.length > 0 && Y.utils.matchesId(o, missingOrigins[missingOrigins.length - 1].origin)) {
missing_origins.pop() missingOrigins.pop()
} }
if (o.id[1] < (startSS[o.id[0]] || 0)) { if (o.id[1] < (startSS[o.id[0]] || 0)) {
// case 2. o is known // case 2. o is known
@@ -995,17 +995,18 @@ module.exports = function (Y/* :any */) {
send.push(op) send.push(op)
op = Y.Struct[op.struct].encode(o) op = Y.Struct[op.struct].encode(o)
op.right = newright op.right = newright
if (missing_origins.length > 0) { if (missingOrigins.length > 0) {
debugger
console.log('This should not happen .. :( please report this') console.log('This should not happen .. :( please report this')
} }
missing_origins = [op] missingOrigins = [op]
} else { } else {
// case 4. send o, continue to find op.origin // case 4. send o, continue to find op.origin
var s = Y.Struct[op.struct].encode(o) var s = Y.Struct[op.struct].encode(o)
s.right = missing_origins[missing_origins.length - 1].id s.right = missingOrigins[missingOrigins.length - 1].id
s.left = s.origin s.left = s.origin
send.push(s) send.push(s)
missing_origins.push(o) missingOrigins.push(o)
} }
} }
} }
@@ -1020,7 +1021,7 @@ module.exports = function (Y/* :any */) {
*/ */
* getOperationsUntransformed () { * getOperationsUntransformed () {
var ops = [] var ops = []
yield* this.os.iterate(this, null, null, function * (op) { yield * this.os.iterate(this, null, null, function * (op) {
if (op.id[0] !== '_') { if (op.id[0] !== '_') {
ops.push(op) ops.push(op)
} }
@@ -1039,25 +1040,25 @@ module.exports = function (Y/* :any */) {
// update parents .map/start/end properties // update parents .map/start/end properties
if (op.parentSub != null && op.left == null) { if (op.parentSub != null && op.left == null) {
// op is child of Map // 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 parent.map[op.parentSub] = op.id
yield* this.setOperation(parent) yield * this.setOperation(parent)
} else if (op.right == null || op.left == null) { } 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) { if (op.right == null) {
parent.end = Y.utils.getLastId(op) parent.end = Y.utils.getLastId(op)
} }
if (op.left == null) { if (op.left == null) {
parent.start = op.id 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) { for (var user in stateSet) {
yield* this.ss.put({ yield * this.ss.put({
id: [user], id: [user],
clock: stateSet[user] clock: stateSet[user]
}) })
@@ -1089,9 +1090,9 @@ module.exports = function (Y/* :any */) {
} }
*/ */
* flush () { * flush () {
yield* this.os.flush() yield * this.os.flush()
yield* this.ss.flush() yield * this.ss.flush()
yield* this.ds.flush() yield * this.ds.flush()
} }
} }
Y.Transaction = TransactionInterface Y.Transaction = TransactionInterface

View File

@@ -1,6 +1,3 @@
/* @flow */
'use strict'
/* /*
EventHandler is an helper class for constructing custom types. EventHandler is an helper class for constructing custom types.
@@ -23,7 +20,8 @@
database request to finish). EventHandler helps you to make your type database request to finish). EventHandler helps you to make your type
synchronous. synchronous.
*/ */
module.exports = function (Y /* : any*/) {
export default function Utils (Y) {
Y.utils = {} Y.utils = {}
Y.utils.bubbleEvent = function (type, event) { Y.utils.bubbleEvent = function (type, event) {
@@ -303,7 +301,7 @@ module.exports = function (Y /* : any*/) {
} }
var before = this.waiting.length var before = this.waiting.length
// somehow create new operations // somehow create new operations
yield* f.apply(transaction, args) yield * f.apply(transaction, args)
// remove all appended ops / awaited ops // remove all appended ops / awaited ops
this.waiting.splice(before) this.waiting.splice(before)
if (this.awaiting > 0) this.awaiting-- if (this.awaiting > 0) this.awaiting--
@@ -313,7 +311,7 @@ module.exports = function (Y /* : any*/) {
for (let i = 0; i < this.waiting.length; i++) { for (let i = 0; i < this.waiting.length; i++) {
var o = this.waiting[i] var o = this.waiting[i]
if (o.struct === 'Insert') { if (o.struct === 'Insert') {
var _o = yield* transaction.getInsertion(o.id) var _o = yield * transaction.getInsertion(o.id)
if (_o.parentSub != null && _o.left != null) { if (_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 // 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) this.waiting.splice(i, 1)
@@ -325,10 +323,10 @@ module.exports = function (Y /* : any*/) {
o.left = null o.left = null
} else { } else {
// find next undeleted op // find next undeleted op
var left = yield* transaction.getInsertion(_o.left) var left = yield * transaction.getInsertion(_o.left)
while (left.deleted != null) { while (left.deleted != null) {
if (left.left != null) { if (left.left != null) {
left = yield* transaction.getInsertion(left.left) left = yield * transaction.getInsertion(left.left)
} else { } else {
left = null left = null
break break
@@ -679,7 +677,7 @@ module.exports = function (Y /* : any*/) {
if (i < 0 && noSuperCall === undefined) { if (i < 0 && noSuperCall === undefined) {
// did not reach break in last loop // did not reach break in last loop
// read id and put it to the end of readBuffer // read id and put it to the end of readBuffer
o = yield* super.find(id) o = yield * super.find(id)
} }
if (o != null) { if (o != null) {
for (i = 0; i < this.readBuffer.length - 1; i++) { for (i = 0; i < this.readBuffer.length - 1; i++) {
@@ -709,7 +707,7 @@ module.exports = function (Y /* : any*/) {
// write writeBuffer[0] // write writeBuffer[0]
var write = this.writeBuffer[0] var write = this.writeBuffer[0]
if (write.id[0] !== null) { if (write.id[0] !== null) {
yield* super.put(write) yield * super.put(write)
} }
// put o to the end of writeBuffer // put o to the end of writeBuffer
for (i = 0; i < this.writeBuffer.length - 1; i++) { for (i = 0; i < this.writeBuffer.length - 1; i++) {
@@ -739,44 +737,44 @@ module.exports = function (Y /* : any*/) {
} }
} }
} }
yield* this.flush() yield * this.flush()
yield* super.delete(id) yield * super.delete(id)
} }
* findWithLowerBound (id) { * findWithLowerBound (id) {
var o = yield* this.find(id, true) var o = yield * this.find(id, true)
if (o != null) { if (o != null) {
return o return o
} else { } else {
yield* this.flush() yield * this.flush()
return yield* super.findWithLowerBound.apply(this, arguments) return yield * super.findWithLowerBound.apply(this, arguments)
} }
} }
* findWithUpperBound (id) { * findWithUpperBound (id) {
var o = yield* this.find(id, true) var o = yield * this.find(id, true)
if (o != null) { if (o != null) {
return o return o
} else { } else {
yield* this.flush() yield * this.flush()
return yield* super.findWithUpperBound.apply(this, arguments) return yield * super.findWithUpperBound.apply(this, arguments)
} }
} }
* findNext () { * findNext () {
yield* this.flush() yield * this.flush()
return yield* super.findNext.apply(this, arguments) return yield * super.findNext.apply(this, arguments)
} }
* findPrev () { * findPrev () {
yield* this.flush() yield * this.flush()
return yield* super.findPrev.apply(this, arguments) return yield * super.findPrev.apply(this, arguments)
} }
* iterate () { * iterate () {
yield* this.flush() yield * this.flush()
yield* super.iterate.apply(this, arguments) yield * super.iterate.apply(this, arguments)
} }
* flush () { * flush () {
for (var i = 0; i < this.writeBuffer.length; i++) { for (var i = 0; i < this.writeBuffer.length; i++) {
var write = this.writeBuffer[i] var write = this.writeBuffer[i]
if (write.id[0] !== null) { if (write.id[0] !== null) {
yield* super.put(write) yield * super.put(write)
this.writeBuffer[i] = { this.writeBuffer[i] = {
id: [null, null] id: [null, null]
} }

View File

@@ -1,18 +1,20 @@
/* @flow */ import debug from 'debug'
'use strict' 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'
require('./Connector.js')(Y) extendConnector(Y)
require('./Database.js')(Y) extendDatabase(Y)
require('./Transaction.js')(Y) extendTransaction(Y)
require('./Struct.js')(Y) extendStruct(Y)
require('./Utils.js')(Y) extendUtils(Y)
require('./Connectors/Test.js')(Y)
Y.debug = require('debug') Y.debug = debug
var requiringModules = {} var requiringModules = {}
module.exports = Y
Y.requiringModules = requiringModules Y.requiringModules = requiringModules
Y.extend = function (name, value) { Y.extend = function (name, value) {
@@ -110,7 +112,7 @@ type YOptions = {
} }
*/ */
function Y (opts/* :YOptions */) /* :Promise<YConfig> */ { export default function Y (opts/* :YOptions */) /* :Promise<YConfig> */ {
if (opts.hasOwnProperty('sourceDir')) { if (opts.hasOwnProperty('sourceDir')) {
Y.sourceDir = opts.sourceDir Y.sourceDir = opts.sourceDir
} }
@@ -120,26 +122,24 @@ function Y (opts/* :YOptions */) /* :Promise<YConfig> */ {
modules.push(opts.share[name]) modules.push(opts.share[name])
} }
return new Promise(function (resolve, reject) { return new Promise(function (resolve, reject) {
if (opts == null) reject('An options object is expected! ') if (opts == null) reject(new Error('An options object is expected!'))
else if (opts.connector == null) reject('You must specify a connector! (missing connector property)') else if (opts.connector == null) reject(new Error('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.connector.name == null) reject(new Error('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.db == null) reject(new Error('You must specify a database! (missing db property)'))
else if (opts.connector.name == null) reject('You must specify db name! (missing db.name property)') else if (opts.connector.name == null) reject(new Error('You must specify db name! (missing db.name property)'))
else { else {
opts = Y.utils.copyObject(opts) opts = Y.utils.copyObject(opts)
opts.connector = Y.utils.copyObject(opts.connector) opts.connector = Y.utils.copyObject(opts.connector)
opts.db = Y.utils.copyObject(opts.db) opts.db = Y.utils.copyObject(opts.db)
opts.share = Y.utils.copyObject(opts.share) opts.share = Y.utils.copyObject(opts.share)
setTimeout(function () { Y.requestModules(modules).then(function () {
Y.requestModules(modules).then(function () { var yconfig = new YConfig(opts)
var yconfig = new YConfig(opts) yconfig.db.whenUserIdSet(function () {
yconfig.db.whenUserIdSet(function () { yconfig.init(function () {
yconfig.init(function () { resolve(yconfig)
resolve(yconfig)
})
}) })
}).catch(reject) })
}, 0) }).catch(reject)
} }
}) })
} }
@@ -182,7 +182,7 @@ class YConfig {
args = typedef.parseArguments(args[0])[1] 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() this.store.whenTransactionsFinished()
.then(callback) .then(callback)
@@ -225,13 +225,13 @@ class YConfig {
} else { } else {
this.connector.disconnect() this.connector.disconnect()
} }
return this.db.whenTransactionsFinished(function () { return this.db.whenTransactionsFinished().then(function () {
this.db.destroyTypes() self.db.destroyTypes()
// make sure to wait for all transactions before destroying the db // make sure to wait for all transactions before destroying the db
this.db.requestTransaction(function * () { self.db.requestTransaction(function * () {
yield* self.db.destroy() yield * self.db.destroy()
}) })
return this.db.whenTransactionsFinished() return self.db.whenTransactionsFinished()
}) })
} }
} }

178
tests-lib/helper.js Normal file
View File

@@ -0,0 +1,178 @@
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)
})
}

151
tests-lib/test-connector.js Normal file
View File

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