switched to *standard* coding style

This commit is contained in:
Kevin Jahns 2015-07-21 17:14:03 +02:00
parent ee116b8ca4
commit ee983ceff6
26 changed files with 2067 additions and 2204 deletions

View File

@ -1,4 +0,0 @@
build/
y.js
y.js.map
interfaces/

View File

@ -1,43 +0,0 @@
{
"env": {
"es6": true
},
"rules": {
"strict": 0,
"camelcase": [1, {"properties": "never"}],
"no-underscore-dangle": 0,
"no-constant-condition": 0,
"no-empty": 0,
"new-cap": [2, { "capIsNewExceptions": ["List", "Y"] }],
},
"parser": "babel-eslint",
"globals": {
"copyObject": true,
"Struct": true,
"OperationStore": true,
"AbstractOperationStore": true,
"AbstractTransaction": true,
"AbstractConnector": true,
"Transaction": true,
"IndexedDB": true,
"IDBRequest": true,
"GeneratorFunction": true,
"Y": true,
"setTimeout": true,
"setInterval": true,
"Operation": true,
"getRandom": true,
"RBTree": true,
"compareIds": true,
"EventHandler": true,
"compareAllUsers": true,
"createUsers": true,
"getRandomNumber": true,
"applyRandomTransactions": true,
"CustomType": true,
"window": true,
"document": true,
"smaller": true,
"wait": true
}
}

View File

@ -1,13 +0,0 @@
[ignore]
.*/node_modules/.*
.*/build/.*
./y.js
./y.js.map
[include]
[libs]
./interfaces
./src
[options]

View File

@ -1,29 +1,30 @@
/* global Y */
Y({
db: {
name: "Memory"
name: 'Memory'
},
connector: {
name: "WebRTC",
room: "mineeeeeee",
name: 'WebRTC',
room: 'mineeeeeee',
debug: true
}
}).then(function(yconfig){
window.y = yconfig.root;
window.yconfig = yconfig;
var textarea = document.getElementById("textfield");
var contenteditable = document.getElementById("contenteditable");
yconfig.root.observe(function(events){
}).then(function (yconfig) {
window.y = yconfig.root
window.yconfig = yconfig
var textarea = document.getElementById('textfield')
var contenteditable = document.getElementById('contenteditable')
yconfig.root.observe(function (events) {
for (var e in events) {
var event = events[e];
if (event.name === "text" && (event.type === "add" || event.type === "update")) {
event.object.get(event.name).then(function(text){ //eslint-disable-line
text.bind(textarea);
text.bind(contenteditable);
window.ytext = text;
});
var event = events[e]
if (event.name === 'text' && (event.type === 'add' || event.type === 'update')) {
event.object.get(event.name).then(function (text) { // eslint-disable-line
text.bind(textarea)
text.bind(contenteditable)
window.ytext = text
})
}
}
});
yconfig.root.set("text", Y.TextBind);
});
})
yconfig.root.set('text', Y.TextBind)
})

View File

@ -1,4 +1,4 @@
/*eslint-env node */
/* eslint-env node */
/** Gulp Commands
@ -38,47 +38,42 @@
Builds the test suite
- test:
Test this library
- lint:
Lint this library. A successful lint is required for committing to this repository!
*/
var gulp = require("gulp");
var sourcemaps = require("gulp-sourcemaps");
var babel = require("gulp-babel");
var uglify = require("gulp-uglify");
var minimist = require("minimist");
var eslint = require("gulp-eslint");
var jasmine = require("gulp-jasmine");
var jasmineBrowser = require("gulp-jasmine-browser");
var concat = require("gulp-concat");
var watch = require("gulp-watch");
var gulp = require('gulp')
var sourcemaps = require('gulp-sourcemaps')
var babel = require('gulp-babel')
var uglify = require('gulp-uglify')
var minimist = require('minimist')
var jasmine = require('gulp-jasmine')
var jasmineBrowser = require('gulp-jasmine-browser')
var concat = require('gulp-concat')
var watch = require('gulp-watch')
var polyfills = [
"./node_modules/gulp-babel/node_modules/babel-core/node_modules/regenerator/runtime.js"
];
'./node_modules/gulp-babel/node_modules/babel-core/node_modules/regenerator/runtime.js'
]
var options = minimist(process.argv.slice(2), {
string: ["export", "name", "testport", "testfiles"],
string: ['export', 'name', 'testport', 'testfiles'],
default: {
export: "ignore",
name: "y.js",
testport: "8888",
testfiles: "src/**/*.js"
export: 'ignore',
name: 'y.js',
testport: '8888',
testfiles: 'src/**/*.js'
}
});
})
var files = {
y: polyfills.concat(["src/y.js", "src/Connector.js", "src/OperationStore.js", "src/Struct.js", "src/Utils.js",
"src/OperationStores/RedBlackTree.js", "src/**/*.js", "!src/**/*.spec.js"]),
lint: ["src/**/*.js", "gulpfile.js"],
y: polyfills.concat(['src/y.js', 'src/Connector.js', 'src/OperationStore.js', 'src/Struct.js', 'src/Utils.js',
'src/OperationStores/RedBlackTree.js', 'src/**/*.js', '!src/**/*.spec.js']),
test: polyfills.concat([options.testfiles]),
build_test: ["build_test/y.js"]
};
build_test: ['build_test/y.js']
}
gulp.task("build", function () {
/*return gulp.src(files.y)
gulp.task('build', function () {
/*
return gulp.src(files.y)
.pipe(sourcemaps.init())
.pipe(concat(options.name))
.pipe(babel({
@ -90,71 +85,62 @@ gulp.task("build", function () {
.pipe(sourcemaps.write("."))
.pipe(gulp.dest("."));*/
return gulp.src(files.y)
.pipe(sourcemaps.init())
.pipe(concat(options.name))
.pipe(babel({
loose: "all",
modules: "ignore",
optional: ["es7.asyncFunctions"],
blacklist: ["regenerator"],
experimental: true
}))
.pipe(sourcemaps.write())
.pipe(gulp.dest("."));
});
.pipe(sourcemaps.init())
.pipe(concat(options.name))
.pipe(babel({
loose: 'all',
modules: 'ignore',
optional: ['es7.asyncFunctions'],
blacklist: ['regenerator'],
experimental: true
}))
.pipe(sourcemaps.write())
.pipe(gulp.dest('.'))
})
gulp.task("lint", function(){
return gulp.src(files.lint)
.pipe(eslint())
.pipe(eslint.format())
.pipe(eslint.failOnError());
});
gulp.task("test", function () {
gulp.task('test', function () {
return gulp.src(files.test)
.pipe(sourcemaps.init())
.pipe(concat("jasmine"))
.pipe(concat('jasmine'))
.pipe(babel({
loose: "all",
optional: ["es7.asyncFunctions"],
modules: "ignore",
loose: 'all',
optional: ['es7.asyncFunctions'],
modules: 'ignore',
experimental: true
}))
.pipe(uglify())
.pipe(sourcemaps.write())
.pipe(gulp.dest("build"))
.pipe(gulp.dest('build'))
.pipe(jasmine({
verbose: true,
includeStuckTrace: true
}));
});
}))
})
gulp.task("build_jasmine_browser", function(){
gulp.task('build_jasmine_browser', function () {
gulp.src(files.test)
.pipe(sourcemaps.init())
.pipe(concat("jasmine_browser.js"))
.pipe(babel({
loose: "all",
modules: "ignore",
optional: ["es7.asyncFunctions"],
// blacklist: "regenerator",
experimental: true
}))
.pipe(sourcemaps.write())
.pipe(gulp.dest("build"));
});
.pipe(sourcemaps.init())
.pipe(concat('jasmine_browser.js'))
.pipe(babel({
loose: 'all',
modules: 'ignore',
optional: ['es7.asyncFunctions'],
// blacklist: "regenerator",
experimental: true
}))
.pipe(sourcemaps.write())
.pipe(gulp.dest('build'))
})
gulp.task('develop', ['build_jasmine_browser', 'build'], function () {
gulp.watch(files.test, ['build_jasmine_browser'])
// gulp.watch(files.test, ["test"])
gulp.watch(files.test, ['build'])
gulp.task("develop", ["build_jasmine_browser", "build"], function(){
gulp.watch(files.test, ["build_jasmine_browser"]);
//gulp.watch(files.test, ["test"]);
gulp.watch(files.test, ["build"]);
return gulp.src("build/jasmine_browser.js")
.pipe(watch("build/jasmine_browser.js"))
return gulp.src('build/jasmine_browser.js')
.pipe(watch('build/jasmine_browser.js'))
.pipe(jasmineBrowser.specRunner())
.pipe(jasmineBrowser.server({port: options.testport}));
});
.pipe(jasmineBrowser.server({port: options.testport}))
})
gulp.task("default", ["build", "test"]);
gulp.task('default', ['build', 'test'])

View File

@ -1,5 +0,0 @@
/* @flow */
declare var describe : Function;
declare var it : Function;
declare var expect : Function;

View File

@ -5,13 +5,21 @@
"main": "y.js",
"scripts": {
"test": "gulp test",
"lint": "gulp lint",
"lint": "standard",
"build": "gulp build"
},
"pre-commit": [
"lint",
"build"
],
"standard": {
"parser": "babel-eslint",
"ignore": [
"build/**",
"./y.js",
"./y.js.map"
]
},
"repository": {
"type": "git",
"url": "https://github.com/y-js/yjs.git"
@ -33,12 +41,10 @@
},
"homepage": "http://y-js.org",
"devDependencies": {
"babel-eslint": "^3.1.15",
"eslint": "^0.22.1",
"babel-eslint": "^3.1.23",
"gulp": "^3.9.0",
"gulp-babel": "^5.1.0",
"gulp-concat": "^2.5.2",
"gulp-eslint": "^0.13.2",
"gulp-jasmine": "^2.0.1",
"gulp-jasmine-browser": "^0.1.3",
"gulp-sourcemaps": "^1.5.2",
@ -46,6 +52,7 @@
"gulp-util": "^3.0.5",
"gulp-watch": "^4.2.4",
"minimist": "^1.1.1",
"pre-commit": "^1.0.10"
"pre-commit": "^1.0.10",
"standard": "^5.0.0-2"
}
}

View File

@ -1,192 +1,191 @@
class AbstractConnector { //eslint-disable-line no-unused-vars
class AbstractConnector { // eslint-disable-line no-unused-vars
/*
opts
.role : String Role of this client ("master" or "slave")
.userId : String that uniquely defines the user.
*/
constructor (y, opts) {
this.y = y;
if (opts == null){
opts = {};
this.y = y
if (opts == null) {
opts = {}
}
if (opts.role == null || opts.role === "master") {
this.role = "master";
} else if (opts.role === "slave") {
this.role = "slave";
if (opts.role == null || opts.role === 'master') {
this.role = 'master'
} else if (opts.role === 'slave') {
this.role = 'slave'
} else {
throw new Error("Role must be either 'master' or 'slave'!");
throw new Error("Role must be either 'master' or 'slave'!")
}
this.role = opts.role;
this.connections = {};
this.userEventListeners = [];
this.whenSyncedListeners = [];
this.currentSyncTarget = null;
this.syncingClients = [];
this.forwardToSyncingClients = (opts.forwardToSyncingClients === false) ? false : true;
this.debug = opts.debug ? true : false;
this.broadcastedHB = false;
this.role = opts.role
this.connections = {}
this.userEventListeners = []
this.whenSyncedListeners = []
this.currentSyncTarget = null
this.syncingClients = []
this.forwardToSyncingClients = opts.forwardToSyncingClients !== false
this.debug = opts.debug === true
this.broadcastedHB = false
}
setUserId (userId) {
this.userId = userId;
this.y.db.setUserId(userId);
this.userId = userId
this.y.db.setUserId(userId)
}
onUserEvent (f) {
this.userEventListeners.push(f);
this.userEventListeners.push(f)
}
userLeft (user : string) {
delete this.connections[user];
if (user === this.currentSyncTarget){
this.currentSyncTarget = null;
this.findNextSyncTarget();
userLeft (user) {
delete this.connections[user]
if (user === this.currentSyncTarget) {
this.currentSyncTarget = null
this.findNextSyncTarget()
}
for (var f of this.userEventListeners){
for (var f of this.userEventListeners) {
f({
action: "userLeft",
action: 'userLeft',
user: user
});
})
}
}
userJoined (user, role) {
if(role == null){
throw new Error("You must specify the role of the joined user!");
if (role == null) {
throw new Error('You must specify the role of the joined user!')
}
if (this.connections[user] != null) {
throw new Error("This user already joined!");
throw new Error('This user already joined!')
}
this.connections[user] = {
isSynced: false,
role: role
};
}
for (var f of this.userEventListeners) {
f({
action: "userJoined",
action: 'userJoined',
user: user,
role: role
});
})
}
if (this.currentSyncTarget == null) {
this.findNextSyncTarget();
this.findNextSyncTarget()
}
}
// Execute a function _when_ we are connected.
// If not connected, wait until connected
whenSynced (f) {
if (this.isSynced === true) {
f();
f()
} else {
this.whenSyncedListeners.push(f);
this.whenSyncedListeners.push(f)
}
}
// returns false, if there is no sync target
// true otherwise
findNextSyncTarget () {
if (this.currentSyncTarget != null) {
return; // "The current sync has not finished!"
return // "The current sync has not finished!"
}
var syncUser = null;
var syncUser = null
for (var uid in this.connections) {
if (!this.connections[uid].isSynced) {
syncUser = uid;
break;
syncUser = uid
break
}
}
if (syncUser != null){
var conn = this;
this.currentSyncTarget = syncUser;
this.y.db.requestTransaction(function*(){
if (syncUser != null) {
var conn = this
this.currentSyncTarget = syncUser
this.y.db.requestTransaction(function *() {
conn.send(syncUser, {
type: "sync step 1",
type: 'sync step 1',
stateVector: yield* this.getStateVector()
});
});
})
})
}
// set the state to synced!
if (!this.isSynced) {
this.isSynced = true;
this.isSynced = true
for (var f of this.whenSyncedListeners) {
f();
f()
}
this.whenSyncedListeners = null;
this.whenSyncedListeners = null
}
}
send (uid, message) {
if (this.debug) {
console.log(`me -> ${uid}: ${message.type}`);//eslint-disable-line
console.dir(m); //eslint-disable-line
console.log(`me -> ${uid}: ${message.type}`);// eslint-disable-line
console.dir(m); // eslint-disable-line
}
super(uid, message);
super(uid, message)
}
// You received a raw message, and you know that it is intended for to Yjs. Then call this function.
receiveMessage (sender, m){
receiveMessage (sender, m) {
if (sender === this.userId) {
return;
return
}
if (this.debug) {
console.log(`${sender} -> me: ${m.type}`);//eslint-disable-line
console.dir(m); //eslint-disable-line
console.log(`${sender} -> me: ${m.type}`);// eslint-disable-line
console.dir(m); // eslint-disable-line
}
if (m.type === "sync step 1") {
if (m.type === 'sync step 1') {
// TODO: make transaction, stream the ops
let conn = this;
this.y.db.requestTransaction(function*(){
var ops = yield* this.getOperations(m.stateVector);
var sv = yield* this.getStateVector();
let conn = this
this.y.db.requestTransaction(function *() {
var ops = yield* this.getOperations(m.stateVector)
var sv = yield* this.getStateVector()
conn.send(sender, {
type: "sync step 2",
type: 'sync step 2',
os: ops,
stateVector: sv
});
})
if (this.forwardToSyncingClients) {
conn.syncingClients.push(sender);
setTimeout(function(){
conn.syncingClients = conn.syncingClients.filter(function(cli){
return cli !== sender;
});
conn.syncingClients.push(sender)
setTimeout(function () {
conn.syncingClients = conn.syncingClients.filter(function (cli) {
return cli !== sender
})
conn.send(sender, {
type: "sync done"
});
}, conn.syncingClientDuration);
type: 'sync done'
})
}, conn.syncingClientDuration)
} else {
conn.send(sender, {
type: "sync done"
});
type: 'sync done'
})
}
});
} else if (m.type === "sync step 2") {
this.y.db.apply(m.os);
let conn = this;
var broadcastHB = !this.broadcastedHB;
this.broadcastedHB = true;
this.y.db.requestTransaction(function*(){
var ops = yield* this.getOperations(m.stateVector);
})
} else if (m.type === 'sync step 2') {
this.y.db.apply(m.os)
let conn = this
var broadcastHB = !this.broadcastedHB
this.broadcastedHB = true
this.y.db.requestTransaction(function *() {
var ops = yield* this.getOperations(m.stateVector)
if (ops.length > 0) {
m = {
type: "update",
type: 'update',
ops: ops
};
}
if (!broadcastHB) {
conn.send(sender, m);
conn.send(sender, m)
} else {
// broadcast only once!
conn.broadcast(m);
conn.broadcast(m)
}
}
});
} else if (m.type === "sync done") {
this.connections[sender].isSynced = true;
})
} else if (m.type === 'sync done') {
this.connections[sender].isSynced = true
if (sender === this.currentSyncTarget) {
this.currentSyncTarget = null;
this.findNextSyncTarget();
this.currentSyncTarget = null
this.findNextSyncTarget()
}
} else if (m.type === "update") {
} else if (m.type === 'update') {
if (this.forwardToSyncingClients) {
for (var client of this.syncingClients) {
this.send(client, m);
this.send(client, m)
}
}
this.y.db.apply(m.ops);
this.y.db.apply(m.ops)
}
}
// Currently, the HB encodes operations as JSON. For the moment I want to keep it
@ -202,36 +201,36 @@ class AbstractConnector { //eslint-disable-line no-unused-vars
// expects an ltx (less than xml) object
parseMessageFromXml (m) {
function parseArray (node) {
for (var n of node.children){
if (n.getAttribute("isArray") === "true") {
return parseArray(n);
for (var n of node.children) {
if (n.getAttribute('isArray') === 'true') {
return parseArray(n)
} else {
return parseObject(n);
return parseObject(n)
}
}
}
function parseObject (node) {
var json = {};
var json = {}
for (var attrName in node.attrs) {
var value = node.attrs[attrName];
var int = parseInt(value);
if (isNaN(int) || ("" + int) !== value){
json[attrName] = value;
var value = node.attrs[attrName]
var int = parseInt(value, 10)
if (isNaN(int) || ('' + int) !== value) {
json[attrName] = value
} else {
json[attrName] = int;
json[attrName] = int
}
}
for (var n in node.children){
var name = n.name;
if (n.getAttribute("isArray") === "true") {
json[name] = parseArray(n);
for (var n in node.children) {
var name = n.name
if (n.getAttribute('isArray') === 'true') {
json[name] = parseArray(n)
} else {
json[name] = parseObject(n);
json[name] = parseObject(n)
}
}
return json;
return json
}
parseObject(m);
parseObject(m)
}
// encode message in xml
// we use string because Strophe only accepts an "xml-string"..
@ -245,34 +244,34 @@ class AbstractConnector { //eslint-disable-line no-unused-vars
// attributes is optional
function encodeObject (m, json) {
for (var name in json) {
var value = json[name];
var value = json[name]
if (name == null) {
// nop
} else if (value.constructor === Object) {
encodeObject(m.c(name), value);
encodeObject(m.c(name), value)
} else if (value.constructor === Array) {
encodeArray(m.c(name), value);
encodeArray(m.c(name), value)
} else {
m.setAttribute(name, value);
m.setAttribute(name, value)
}
}
}
function encodeArray (m, array) {
m.setAttribute("isArray", "true");
m.setAttribute('isArray', 'true')
for (var e of array) {
if (e.constructor === Object) {
encodeObject(m.c("array-element"), e);
encodeObject(m.c('array-element'), e)
} else {
encodeArray(m.c("array-element"), e);
encodeArray(m.c('array-element'), e)
}
}
}
if (obj.constructor === Object) {
encodeObject(msg.c("y", { xmlns: "http://y.ninja/connector-stanza" }), obj);
encodeObject(msg.c('y', { xmlns: 'http://y.ninja/connector-stanza' }), obj)
} else if (obj.constructor === Array) {
encodeArray(msg.c("y", { xmlns: "http://y.ninja/connector-stanza" }), obj);
encodeArray(msg.c('y', { xmlns: 'http://y.ninja/connector-stanza' }), obj)
} else {
throw new Error("I can't encode this json!");
throw new Error("I can't encode this json!")
}
}
}

View File

@ -1,102 +1,104 @@
/* global getRandom, AbstractConnector, Y, wait */
var globalRoom = {
users: {},
buffers: {},
removeUser: function(user : AbstractConnector){
removeUser: function (user) {
for (var i in this.users) {
this.users[i].userLeft(user);
this.users[i].userLeft(user)
}
delete this.users[user];
delete this.buffers[user];
delete this.users[user]
delete this.buffers[user]
},
addUser: function(connector){
this.users[connector.userId] = connector;
this.buffers[connector.userId] = [];
addUser: function (connector) {
this.users[connector.userId] = connector
this.buffers[connector.userId] = []
for (var uname in this.users) {
if (uname !== connector.userId) {
var u = this.users[uname];
u.userJoined(connector.userId, "master");
connector.userJoined(u.userId, "master");
var u = this.users[uname]
u.userJoined(connector.userId, 'master')
connector.userJoined(u.userId, 'master')
}
}
}
};
function flushOne(){
var bufs = [];
}
function flushOne () {
var bufs = []
for (var i in globalRoom.buffers) {
if (globalRoom.buffers[i].length > 0) {
bufs.push(i);
bufs.push(i)
}
}
if (bufs.length > 0) {
var userId = getRandom(bufs);
var m = globalRoom.buffers[userId].shift();
var user = globalRoom.users[userId];
user.receiveMessage(m[0], m[1]);
return true;
var userId = getRandom(bufs)
var m = globalRoom.buffers[userId].shift()
var user = globalRoom.users[userId]
user.receiveMessage(m[0], m[1])
return true
} else {
return false;
return false
}
}
// setInterval(flushOne, 10);
// setInterval(flushOne, 10)
var userIdCounter = 0;
var userIdCounter = 0
class Test extends AbstractConnector {
constructor (y, options) {
if(options === undefined){
throw new Error("Options must not be undefined!");
if (options === undefined) {
throw new Error('Options must not be undefined!')
}
options.role = "master";
options.forwardToSyncingClients = false;
super(y, options);
this.setUserId((userIdCounter++) + "");
globalRoom.addUser(this);
this.globalRoom = globalRoom;
options.role = 'master'
options.forwardToSyncingClients = false
super(y, options)
this.setUserId((userIdCounter++) + '')
globalRoom.addUser(this)
this.globalRoom = globalRoom
}
send (userId, message) {
globalRoom.buffers[userId].push(JSON.parse(JSON.stringify([this.userId, message])));
globalRoom.buffers[userId].push(JSON.parse(JSON.stringify([this.userId, message])))
}
broadcast (message) {
for (var key in globalRoom.buffers) {
globalRoom.buffers[key].push(JSON.parse(JSON.stringify([this.userId, message])));
globalRoom.buffers[key].push(JSON.parse(JSON.stringify([this.userId, message])))
}
}
disconnect () {
globalRoom.removeUser(this.userId);
globalRoom.removeUser(this.userId)
}
flush() {
var buff = globalRoom.buffers[this.userId];
flush () {
var buff = globalRoom.buffers[this.userId]
while (buff.length > 0) {
var m = buff.shift();
this.receiveMessage(m[0], m[1]);
var m = buff.shift()
this.receiveMessage(m[0], m[1])
}
}
flushAll () {
var def = Promise.defer();
var def = Promise.defer()
// flushes may result in more created operations,
// flush until there is nothing more to flush
function nextFlush() {
var c = flushOne();
function nextFlush () {
var c = flushOne()
if (c) {
while(flushOne()) {
//nop
while (flushOne()) {
// nop
}
wait().then(nextFlush);
wait().then(nextFlush)
} else {
wait().then(function(){
def.resolve();
});
wait().then(function () {
def.resolve()
})
}
}
// in the case that there are
// still actions that want to be performed
wait(0).then(nextFlush);
return def.promise;
wait(0).then(nextFlush)
return def.promise
}
flushOne() {
flushOne();
flushOne () {
flushOne()
}
}
Y.Test = Test;
Y.Test = Test

View File

@ -1,86 +1,87 @@
/* global AbstractConnector, Y */
class WebRTC extends AbstractConnector {
constructor (y, options) {
if(options === undefined){
throw new Error("Options must not be undefined!");
if (options === undefined) {
throw new Error('Options must not be undefined!')
}
if(options.room == null) {
throw new Error("You must define a room name!");
if (options.room == null) {
throw new Error('You must define a room name!')
}
options.role = "slave";
super(y, options);
options.role = 'slave'
super(y, options)
var room = options.room;
var room = options.room
var webrtcOptions = {
url: options.url || "https://yatta.ninja:8888",
url: options.url || 'https://yatta.ninja:8888',
room: options.room
};
}
var swr = new SimpleWebRTC(webrtcOptions); //eslint-disable-line no-undef
this.swr = swr;
var self = this;
var swr = new SimpleWebRTC(webrtcOptions) // eslint-disable-line no-undef
this.swr = swr
var self = this
swr.once("connectionReady", function(userId){
swr.once('connectionReady', function (userId) {
// SimpleWebRTC (swr) is initialized
swr.joinRoom(room);
swr.joinRoom(room)
swr.once("joinedRoom", function(){
self.setUserId(userId);
swr.once('joinedRoom', function () {
self.setUserId(userId)
/*
var i;
var i
// notify the connector class about all the users that already
// joined the session
for(i in self.swr.webrtc.peers){
self.userJoined(self.swr.webrtc.peers[i].id, "master");
self.userJoined(self.swr.webrtc.peers[i].id, "master")
}*/
swr.on("channelMessage", function(peer, room_, message){
swr.on('channelMessage', function (peer, room_, message) {
// The client received a message
// Check if the connector is already initialized,
// only then forward the message to the connector class
if(message.type != null ){
self.receiveMessage(peer.id, message.payload);
if (message.type != null) {
self.receiveMessage(peer.id, message.payload)
}
});
});
})
})
swr.on("createdPeer", function(peer){
swr.on('createdPeer', function (peer) {
// a new peer/client joined the session.
// Notify the connector class, if the connector
// is already initialized
self.userJoined(peer.id, "master");
});
self.userJoined(peer.id, 'master')
})
swr.on("peerStreamRemoved", function(peer){
swr.on('peerStreamRemoved', function (peer) {
// a client left the session.
// Notify the connector class, if the connector
// is already initialized
self.userLeft(peer.id);
});
});
self.userLeft(peer.id)
})
})
}
send (uid, message) {
var self = this;
var self = this
// we have to make sure that the message is sent under all circumstances
var send = function(){
var send = function () {
// check if the clients still exists
var peer = self.swr.webrtc.getPeers(uid)[0];
var success;
if(peer){
var peer = self.swr.webrtc.getPeers(uid)[0]
var success
if (peer) {
// success is true, if the message is successfully sent
success = peer.sendDirectly("simplewebrtc", "yjs", message);
success = peer.sendDirectly('simplewebrtc', 'yjs', message)
}
if(!success){
if (!success) {
// resend the message if it didn't work
setTimeout(send, 500);
setTimeout(send, 500)
}
};
}
// try to send the message
send();
send()
}
broadcast (message) {
this.swr.sendDirectlyToAll("simplewebrtc", "yjs", message);
this.swr.sendDirectlyToAll('simplewebrtc', 'yjs', message)
}
}
Y.WebRTC = WebRTC;
Y.WebRTC = WebRTC

View File

@ -1,107 +1,107 @@
/* @flow */
/*eslint-env browser,jasmine */
/* global Y */
/* eslint-env browser,jasmine */
/***
This is "just" a compilation of functions that help to test this library!
/*
This is just a compilation of functions that help to test this library!
***/
function wait(t = 0) {//eslint-disable-line
var def = Promise.defer();
setTimeout(function(){
def.resolve();
}, t);
return def.promise;
var def = Promise.defer()
setTimeout(function () {
def.resolve()
}, t)
return def.promise
}
// returns a random element of o
// works on Object, and Array
function getRandom (o) {
if (o instanceof Array) {
return o[Math.floor(Math.random() * o.length)];
return o[Math.floor(Math.random() * o.length)]
} else if (o.constructor === Object) {
var ks = [];
var ks = []
for (var key in o) {
ks.push(key);
ks.push(key)
}
return o[getRandom(ks)];
return o[getRandom(ks)]
}
}
function getRandomNumber(n) {//eslint-disable-line
if (n == null) {
n = 9999;
n = 9999
}
return Math.floor(Math.random() * n);
return Math.floor(Math.random() * n)
}
async function applyRandomTransactions (users, objects, transactions, numberOfTransactions) {//eslint-disable-line
function randomTransaction (root) {
var f = getRandom(transactions);
f(root);
var f = getRandom(transactions)
f(root)
}
for(var i = 0; i < numberOfTransactions; i++) {
var r = Math.random();
for (var i = 0; i < numberOfTransactions; i++) {
var r = Math.random()
if (r >= 0.9) {
// 10% chance to flush
users[0].connector.flushOne();
users[0].connector.flushOne()
} else {
randomTransaction(getRandom(objects));
randomTransaction(getRandom(objects))
}
wait();
wait()
}
}
async function compareAllUsers(users){//eslint-disable-line
var s1, s2;
var db1 = [];
function* t1(){
s1 = yield* this.getStateSet();
var s1, s2
var db1 = []
function * t1 () {
s1 = yield* this.getStateSet()
}
function* t2(){
s2 = yield* this.getStateSet();
function * t2 () {
s2 = yield* this.getStateSet()
}
await users[0].connector.flushAll();
await users[0].connector.flushAll()
for (var uid = 0; uid < users.length; uid++) {
if (s1 == null) {
var u = users[uid];
u.db.requestTransaction(t1);
await wait();
var u = users[uid]
u.db.requestTransaction(t1)
await wait()
u.db.os.iterate(null, null, function(o){//eslint-disable-line
db1.push(o);
});
db1.push(o)
})
} else {
var u2 = users[uid];
u2.db.requestTransaction(t2);
await wait();
expect(s1).toEqual(s2);
var count = 0;
var u2 = users[uid]
u2.db.requestTransaction(t2)
await wait()
expect(s1).toEqual(s2)
var count = 0
u2.db.os.iterate(null, null, function(o){//eslint-disable-line
expect(db1[count++]).toEqual(o);
});
expect(db1[count++]).toEqual(o)
})
}
}
}
async function createUsers(self, numberOfUsers) {//eslint-disable-line
if (globalRoom.users[0] != null) {//eslint-disable-line
await globalRoom.users[0].flushAll();//eslint-disable-line
await globalRoom.users[0].flushAll()//eslint-disable-line
}
//destroy old users
// destroy old users
for (var u in globalRoom.users) {//eslint-disable-line
globalRoom.users[u].y.destroy()//eslint-disable-line
}
self.users = [];
self.users = []
var promises = [];
var promises = []
for (var i = 0; i < numberOfUsers; i++) {
promises.push(Y({
db: {
name: "Memory"
name: 'Memory'
},
connector: {
name: "Test",
name: 'Test',
debug: false
}
}));
}))
}
self.users = await Promise.all(promises);
self.users = await Promise.all(promises)
}

View File

@ -1,58 +1,52 @@
/* @flow */
class AbstractTransaction { //eslint-disable-line no-unused-vars
constructor (store : OperationStore) {
this.store = store;
/* global Y, copyObject, Struct, RBTree */
class AbstractTransaction { // eslint-disable-line no-unused-vars
constructor (store) {
this.store = store
}
*getType (id) {
var sid = JSON.stringify(id);
var t = this.store.initializedTypes[sid];
* getType (id) {
var sid = JSON.stringify(id)
var t = this.store.initializedTypes[sid]
if (t == null) {
var op = yield* this.getOperation(id);
var op = yield* this.getOperation(id)
if (op != null) {
t = yield* Y[op.type].initType.call(this, this.store, op);
this.store.initializedTypes[sid] = t;
t = yield* Y[op.type].initType.call(this, this.store, op)
this.store.initializedTypes[sid] = t
}
}
return t;
return t
}
*createType (model) {
var sid = JSON.stringify(model.id);
var t = yield* Y[model.type].initType.call(this, this.store, model);
this.store.initializedTypes[sid] = t;
return t;
* createType (model) {
var sid = JSON.stringify(model.id)
var t = yield* Y[model.type].initType.call(this, this.store, model)
this.store.initializedTypes[sid] = t
return t
}
*applyCreatedOperations (ops) {
var send = [];
* applyCreatedOperations (ops) {
var send = []
for (var i = 0; i < ops.length; i++) {
var op = ops[i];
yield* this.store.tryExecute.call(this, op);
send.push(copyObject(Struct[op.struct].encode(op)));
var op = ops[i]
yield* this.store.tryExecute.call(this, op)
send.push(copyObject(Struct[op.struct].encode(op)))
}
if (this.store.y.connector.broadcastedHB){
if (this.store.y.connector.broadcastedHB) {
this.store.y.connector.broadcast({
type: "update",
type: 'update',
ops: send
});
})
}
}
}
type Listener = {
f : GeneratorFunction, // is called when all operations are available
missing : number // number of operations that are missing
}
type Id = [string, number];
class AbstractOperationStore { //eslint-disable-line no-unused-vars
class AbstractOperationStore { // eslint-disable-line no-unused-vars
constructor (y) {
this.y = y;
this.y = y
// E.g. this.listenersById[id] : Array<Listener>
this.listenersById = {};
this.listenersById = {}
// Execute the next time a transaction is requested
this.listenersByIdExecuteNow = [];
this.listenersByIdExecuteNow = []
// A transaction is requested
this.listenersByIdRequestPending = false;
this.listenersByIdRequestPending = false
/* To make things more clear, the following naming conventions:
* ls : we put this.listenersById on ls
* l : Array<Listener>
@ -65,163 +59,163 @@ class AbstractOperationStore { //eslint-disable-line no-unused-vars
*/
// TODO: Use ES7 Weak Maps. This way types that are no longer user,
// wont be kept in memory.
this.initializedTypes = {};
this.whenUserIdSetListener = null;
this.waitingOperations = new RBTree();
this.initializedTypes = {}
this.whenUserIdSetListener = null
this.waitingOperations = new RBTree()
}
setUserId (userId) {
this.userId = userId;
this.opClock = 0;
this.userId = userId
this.opClock = 0
if (this.whenUserIdSetListener != null) {
this.whenUserIdSetListener();
this.whenUserIdSetListener = null;
this.whenUserIdSetListener()
this.whenUserIdSetListener = null
}
}
whenUserIdSet (f) {
if (this.userId != null) {
f();
f()
} else {
this.whenUserIdSetListener = f;
this.whenUserIdSetListener = f
}
}
getNextOpId () {
if (this.userId == null) {
throw new Error("OperationStore not yet initialized!");
throw new Error('OperationStore not yet initialized!')
}
return [this.userId, this.opClock++];
return [this.userId, this.opClock++]
}
apply (ops) {
for (var key in ops) {
var o = ops[key];
var required = Struct[o.struct].requiredOps(o);
this.whenOperationsExist(required, o);
var o = ops[key]
var required = Struct[o.struct].requiredOps(o)
this.whenOperationsExist(required, o)
}
}
// op is executed as soon as every operation requested is available.
// Note that Transaction can (and should) buffer requests.
whenOperationsExist (ids : Array<Id>, op : Operation) {
whenOperationsExist (ids, op) {
if (ids.length > 0) {
let listener : Listener = {
let listener = {
op: op,
missing: ids.length
};
}
for (let key in ids) {
let id = ids[key];
let sid = JSON.stringify(id);
let l = this.listenersById[sid];
if (l == null){
l = [];
this.listenersById[sid] = l;
let id = ids[key]
let sid = JSON.stringify(id)
let l = this.listenersById[sid]
if (l == null) {
l = []
this.listenersById[sid] = l
}
l.push(listener);
l.push(listener)
}
} else {
this.listenersByIdExecuteNow.push({
op: op
});
})
}
if (this.listenersByIdRequestPending){
return;
if (this.listenersByIdRequestPending) {
return
}
this.listenersByIdRequestPending = true;
var store = this;
this.listenersByIdRequestPending = true
var store = this
this.requestTransaction(function*(){
var exeNow = store.listenersByIdExecuteNow;
store.listenersByIdExecuteNow = [];
this.requestTransaction(function *() {
var exeNow = store.listenersByIdExecuteNow
store.listenersByIdExecuteNow = []
var ls = store.listenersById;
store.listenersById = {};
var ls = store.listenersById
store.listenersById = {}
store.listenersByIdRequestPending = false;
store.listenersByIdRequestPending = false
for (let key in exeNow) {
let o = exeNow[key].op;
yield* store.tryExecute.call(this, o);
let o = exeNow[key].op
yield* store.tryExecute.call(this, o)
}
for (var sid in ls){
var l = ls[sid];
var id = JSON.parse(sid);
if ((yield* this.getOperation(id)) == null){
store.listenersById[sid] = l;
for (var sid in ls) {
var l = ls[sid]
var id = JSON.parse(sid)
if ((yield* this.getOperation(id)) == null) {
store.listenersById[sid] = l
} else {
for (let key in l) {
let listener = l[key];
let o = listener.op;
if (--listener.missing === 0){
yield* store.tryExecute.call(this, o);
let listener = l[key]
let o = listener.op
if (--listener.missing === 0) {
yield* store.tryExecute.call(this, o)
}
}
}
}
});
})
}
*tryExecute (op) {
if (op.struct === "Delete") {
yield* Struct.Delete.execute.call(this, op);
* tryExecute (op) {
if (op.struct === 'Delete') {
yield* Struct.Delete.execute.call(this, op)
} else {
while (op != null) {
var state = yield* this.getState(op.id[0]);
if (op.id[1] === state.clock){
state.clock++;
yield* this.setState.call(this, state);
yield* Struct[op.struct].execute.call(this, op);
yield* this.addOperation(op);
yield* this.store.operationAdded(this, op);
var state = yield* this.getState(op.id[0])
if (op.id[1] === state.clock) {
state.clock++
yield* this.setState(state)
yield* Struct[op.struct].execute.call(this, op)
yield* this.addOperation(op)
yield* this.store.operationAdded(this, op)
// find next operation to execute
op = this.store.waitingOperations.find([op.id[0], state.clock]);
op = this.store.waitingOperations.find([op.id[0], state.clock])
if (op != null) {
this.store.waitingOperations.delete([op.id[0], state.clock]);
this.store.waitingOperations.delete([op.id[0], state.clock])
}
} else {
if (op.id[1] > state.clock) {
// has to be executed at some point later
this.store.waitingOperations.add(op);
this.store.waitingOperations.add(op)
}
op = null;
op = null
}
}
}
}
// called by a transaction when an operation is added
*operationAdded (transaction, op) {
var sid = JSON.stringify(op.id);
var l = this.listenersById[sid];
delete this.listenersById[sid];
* operationAdded (transaction, op) {
var sid = JSON.stringify(op.id)
var l = this.listenersById[sid]
delete this.listenersById[sid]
// notify whenOperation listeners (by id)
if (l != null) {
for (var key in l){
var listener = l[key];
if (--listener.missing === 0){
this.whenOperationsExist([], listener.op);
for (var key in l) {
var listener = l[key]
if (--listener.missing === 0) {
this.whenOperationsExist([], listener.op)
}
}
}
// notify parent, if it has been initialized as a custom type
var t = this.initializedTypes[JSON.stringify(op.parent)];
var t = this.initializedTypes[JSON.stringify(op.parent)]
if (t != null) {
yield* t._changed(transaction, copyObject(op));
yield* t._changed(transaction, copyObject(op))
}
}
removeParentListener (id, f) {
var ls = this.parentListeners[id];
var ls = this.parentListeners[id]
if (ls != null) {
this.parentListeners[id] = ls.filter(function(g){
return (f !== g);
});
this.parentListeners[id] = ls.filter(function (g) {
return (f !== g)
})
}
}
addParentListener (id, f) {
var ls = this.parentListeners[JSON.stringify(id)];
var ls = this.parentListeners[JSON.stringify(id)]
if (ls == null) {
ls = [];
this.parentListeners[JSON.stringify(id)] = ls;
ls = []
this.parentListeners[JSON.stringify(id)] = ls
}
ls.push(f);
ls.push(f)
}
}

View File

@ -1,5 +0,0 @@
/* @flow */
/*eslint-env browser,jasmine,console */
describe("OperationStore", function() {
});

View File

@ -1,208 +1,179 @@
type State = {
user: string,
clock: number
};
type StateVector = Array<State>;
type StateSet = Object;
type IDBTransaction = Function;
type IDBObjectStore = Function;
type IDBRequest = Function;
type IDBCursor = Function;
type IDBKeyRange = Function;
type IDBOpenDBRequest = Function;
declare var indexedDB : Object;
Y.IndexedDB = (function(){ //eslint-disable-line no-unused-vars
class Transaction extends AbstractTransaction { //eslint-disable-line
transaction: IDBTransaction;
sv: IDBObjectStore;
os: IDBObjectStore;
store: OperationStore;
constructor (store : OperationStore) {
super(store);
this.transaction = store.db.transaction(["OperationStore", "StateVector"], "readwrite");
this.sv = this.transaction.objectStore("StateVector");
this.os = this.transaction.objectStore("OperationStore");
this.buffer = {};
Y.IndexedDB = (function () { // eslint-disable-line
class Transaction extends AbstractTransaction { // eslint-disable-line
constructor (store) {
super(store)
this.transaction = store.db.transaction(['OperationStore', 'StateVector'], 'readwrite')
this.sv = this.transaction.objectStore('StateVector')
this.os = this.transaction.objectStore('OperationStore')
this.buffer = {}
}
*setOperation (op) {
yield this.os.put(op);
this.buffer[JSON.stringify(op.id)] = op;
return op;
* setOperation (op) {
yield this.os.put(op)
this.buffer[JSON.stringify(op.id)] = op
return op
}
*getOperation (id) {
var op = this.buffer[JSON.stringify(id)];
* getOperation (id) {
var op = this.buffer[JSON.stringify(id)]
if (op == null) {
op = yield this.os.get(id);
this.buffer[JSON.stringify(id)] = op;
op = yield this.os.get(id)
this.buffer[JSON.stringify(id)] = op
}
return op;
return op
}
*removeOperation (id) {
this.buffer[JSON.stringify(id)] = null;
return yield this.os.delete(id);
* removeOperation (id) {
this.buffer[JSON.stringify(id)] = null
return yield this.os.delete(id)
}
*setState (state : State) : State {
return yield this.sv.put(state);
* setState (state) {
return yield this.sv.put(state)
}
*getState (user : string) : State {
var state;
if ((state = yield this.sv.get(user)) != null){
return state;
* getState (user) {
var state
if ((state = yield this.sv.get(user)) != null) {
return state
} else {
return {
user: user,
clock: 0
};
}
}
*getStateVector () : StateVector {
var stateVector = [];
var cursorResult = this.sv.openCursor();
var cursor;
while ((cursor = yield cursorResult) != null) {
stateVector.push(cursor.value);
cursor.continue();
}
return stateVector;
}
*getStateSet () : StateSet {
var sv : StateVector = yield* this.getStateVector();
var ss : StateSet = {};
for (var state of sv){
ss[state.user] = state.clock;
}
return ss;
}
*getOperations (startSS : StateSet) {
if (startSS == null){
startSS = {};
}
var ops = [];
var endSV : StateVector = yield* this.getStateVector();
for (var endState of endSV) {
var user = endState.user;
var startPos = startSS[user] || 0;
var endPos = endState.clock;
var range = IDBKeyRange.bound([user, startPos], [user, endPos]);
var cursorResult = this.os.openCursor(range);
var cursor;
while ((cursor = yield cursorResult) != null) {
ops.push(cursor.value);
cursor.continue();
}
}
return ops;
}
* getStateVector () {
var stateVector = []
var cursorResult = this.sv.openCursor()
var cursor
while ((cursor = yield cursorResult) != null) {
stateVector.push(cursor.value)
cursor.continue()
}
return stateVector
}
* getStateSet () {
var sv = yield* this.getStateVector()
var ss = {}
for (var state of sv) {
ss[state.user] = state.clock
}
return ss
}
* getOperations (startSS) {
if (startSS == null) {
startSS = {}
}
var ops = []
var endSV = yield* this.getStateVector()
for (var endState of endSV) {
var user = endState.user
var startPos = startSS[user] || 0
var endPos = endState.clock
var range = window.IDBKeyRange.bound([user, startPos], [user, endPos])
var cursorResult = this.os.openCursor(range)
var cursor
while ((cursor = yield cursorResult) != null) {
ops.push(cursor.value)
cursor.continue()
}
}
return ops
}
}
class OperationStore extends AbstractOperationStore { //eslint-disable-line no-undef
namespace: string;
ready: Promise;
whenReadyListeners: Array<Function>;
class OperationStore extends AbstractOperationStore { // eslint-disable-line no-undef
constructor (y, opts) {
super(y);
super(y)
if (opts == null) {
opts = {};
opts = {}
}
if (opts.namespace == null || typeof opts.namespace !== "string") {
throw new Error("IndexedDB: expect a string (opts.namespace)!");
if (opts.namespace == null || typeof opts.namespace !== 'string') {
throw new Error('IndexedDB: expect a string (opts.namespace)!')
} else {
this.namespace = opts.namespace;
this.namespace = opts.namespace
}
if (opts.idbVersion != null) {
this.idbVersion = opts.idbVersion;
this.idbVersion = opts.idbVersion
} else {
this.idbVersion = 5;
this.idbVersion = 5
}
this.transactionQueue = {
queue: [],
onRequest: null
};
}
var store = this;
var store = this
var tGen = (function *transactionGen(){
store.db = yield indexedDB.open(opts.namespace, store.idbVersion);
var transactionQueue = store.transactionQueue;
var tGen = (function * transactionGen () {
store.db = yield window.indexedDB.open(opts.namespace, store.idbVersion)
var transactionQueue = store.transactionQueue
var transaction = null;
var cont = true;
var transaction = null
var cont = true
while (cont) {
var request = yield transactionQueue;
transaction = new Transaction(store);
var request = yield transactionQueue
transaction = new Transaction(store)
yield* request.call(transaction, request);/*
yield* request.call(transaction, request) /*
while (transactionQueue.queue.length > 0) {
yield* transactionQueue.queue.shift().call(transaction);
yield* transactionQueue.queue.shift().call(transaction)
}*/
}
})();
})()
function handleTransactions(t){ //eslint-disable-line no-unused-vars
var request = t.value;
if (t.done){
return;
} else if (request.constructor === IDBRequest
|| request.constructor === IDBCursor ) {
request.onsuccess = function(){
handleTransactions(tGen.next(request.result));
};
request.onerror = function(err){
tGen.throw(err);
};
} else if (request === store.transactionQueue) {
if (request.queue.length > 0){
handleTransactions(tGen.next(request.queue.shift()));
} else {
request.onRequest = function(){
request.onRequest = null;
handleTransactions(tGen.next(request.queue.shift()));
};
function handleTransactions (t) { // eslint-disable-line no-unused-vars
var request = t.value
if (t.done) {
return
} else if (request.constructor === window.IDBRequest || request.constructor === window.IDBCursor) {
request.onsuccess = function () {
handleTransactions(tGen.next(request.result))
}
} else if ( request.constructor === IDBOpenDBRequest ) {
request.onsuccess = function(event){
var db = event.target.result;
handleTransactions(tGen.next(db));
};
request.onerror = function(){
tGen.throw("Couldn't open IndexedDB database!");
};
request.onupgradeneeded = function(event){
var db = event.target.result;
try {
db.createObjectStore("OperationStore", {keyPath: "id"});
db.createObjectStore("StateVector", {keyPath: "user"});
} catch (e) {
// console.log("Store already exists!");
request.onerror = function (err) {
tGen.throw(err)
}
} else if (request === store.transactionQueue) {
if (request.queue.length > 0) {
handleTransactions(tGen.next(request.queue.shift()))
} else {
request.onRequest = function () {
request.onRequest = null
handleTransactions(tGen.next(request.queue.shift()))
}
};
}
} else if (request.constructor === window.IDBOpenDBRequest) {
request.onsuccess = function (event) {
var db = event.target.result
handleTransactions(tGen.next(db))
}
request.onerror = function () {
tGen.throw("Couldn't open IndexedDB database!")
}
request.onupgradeneeded = function (event) {
var db = event.target.result
try {
db.createObjectStore('OperationStore', {keyPath: 'id'})
db.createObjectStore('StateVector', {keyPath: 'user'})
} catch (e) {
// console.log("Store already exists!")
}
}
} else {
tGen.throw("You can not yield this type!");
tGen.throw('You can not yield this type!')
}
}
handleTransactions(tGen.next());
handleTransactions(tGen.next())
}
requestTransaction (makeGen : Function) {
this.transactionQueue.queue.push(makeGen);
requestTransaction (makeGen) {
this.transactionQueue.queue.push(makeGen)
if (this.transactionQueue.onRequest != null) {
this.transactionQueue.onRequest();
this.transactionQueue.onRequest()
}
}
*removeDatabase () {
this.db.close();
yield indexedDB.deleteDatabase(this.namespace);
* removeDatabase () {
this.db.close()
yield window.indexedDB.deleteDatabase(this.namespace)
}
}
return OperationStore;
})();
return OperationStore
})()

View File

@ -1,117 +1,117 @@
/* @flow */
/*eslint-env browser,jasmine */
/* global Y */
/* eslint-env browser,jasmine */
if(typeof window !== "undefined"){
jasmine.DEFAULT_TIMEOUT_INTERVAL = 1000;
describe("IndexedDB", function() {
var ob;
beforeAll(function(){
ob = new Y.IndexedDB(null, {namespace: "Test"});
});
if (typeof window !== 'undefined') {
jasmine.DEFAULT_TIMEOUT_INTERVAL = 1000
describe('IndexedDB', function () {
var ob
beforeAll(function () {
ob = new Y.IndexedDB(null, {namespace: 'Test'})
})
it("can add and get operation", function(done) {
ob.requestTransaction(function*(){
it('can add and get operation', function (done) {
ob.requestTransaction(function *() {
var op = yield* this.setOperation({
"id": ["1", 0],
"stuff": true
});
expect(yield* this.getOperation(["1", 0]))
.toEqual(op);
done();
});
});
'id': ['1', 0],
'stuff': true
})
expect(yield* this.getOperation(['1', 0]))
.toEqual(op)
done()
})
})
it("can remove operation", function(done) {
ob.requestTransaction(function*(){
it('can remove operation', function (done) {
ob.requestTransaction(function *() {
var op = yield* this.setOperation({
"id": ["1", 0],
"stuff": true
});
expect(yield* this.getOperation(["1", 0]))
.toEqual(op);
yield* this.removeOperation(["1", 0]);
expect(yield* this.getOperation(["1", 0]))
.toBeUndefined();
done();
});
});
'id': ['1', 0],
'stuff': true
})
expect(yield* this.getOperation(['1', 0]))
.toEqual(op)
yield* this.removeOperation(['1', 0])
expect(yield* this.getOperation(['1', 0]))
.toBeUndefined()
done()
})
})
it("getOperation(op) returns undefined if op does not exist", function(done){
ob.requestTransaction(function*(){
var op = yield* this.getOperation("plzDon'tBeThere");
expect(op).toBeUndefined();
done();
});
});
it('getOperation(op) returns undefined if op does not exist', function (done) {
ob.requestTransaction(function *() {
var op = yield* this.getOperation("plzDon'tBeThere")
expect(op).toBeUndefined()
done()
})
})
it("yield throws if request is unknown", function(done){
ob.requestTransaction(function*(){
it('yield throws if request is unknown', function (done) {
ob.requestTransaction(function *() {
try {
yield* this.setOperation();
yield* this.setOperation()
} catch (e) {
expect(true).toEqual(true);
done();
return;
expect(true).toEqual(true)
done()
return
}
expect("Expected an Error!").toEqual(true);
done();
});
});
expect('Expected an Error!').toEqual(true)
done()
})
})
it("sets and gets stateVector", function(done){
ob.requestTransaction(function*(){
var s1 = {user: "1", clock: 1};
var s2 = {user: "2", clock: 3};
yield* this.setState(s1);
yield* this.setState(s2);
var sv = yield* this.getStateVector();
expect(sv).not.toBeUndefined();
expect(sv).toEqual([s1, s2]);
done();
});
});
it('sets and gets stateVector', function (done) {
ob.requestTransaction(function *() {
var s1 = {user: '1', clock: 1}
var s2 = {user: '2', clock: 3}
yield* this.setState(s1)
yield* this.setState(s2)
var sv = yield* this.getStateVector()
expect(sv).not.toBeUndefined()
expect(sv).toEqual([s1, s2])
done()
})
})
it("gets stateSet", function(done){
ob.requestTransaction(function*(){
var s1 = {user: "1", clock: 1};
var s2 = {user: "2", clock: 3};
yield* this.setState(s1);
yield* this.setState(s2);
var sv = yield* this.getStateSet();
expect(sv).not.toBeUndefined();
it('gets stateSet', function (done) {
ob.requestTransaction(function *() {
var s1 = {user: '1', clock: 1}
var s2 = {user: '2', clock: 3}
yield* this.setState(s1)
yield* this.setState(s2)
var sv = yield* this.getStateSet()
expect(sv).not.toBeUndefined()
expect(sv).toEqual({
"1": 1,
"2": 3
});
done();
});
});
'1': 1,
'2': 3
})
done()
})
})
it("getOperations returns operations (no parameters)", function(done){
ob.requestTransaction(function*(){
var s1 = {user: "1", clock: 55};
yield* this.setState(s1);
it('getOperations returns operations (no parameters)', function (done) {
ob.requestTransaction(function *() {
var s1 = {user: '1', clock: 55}
yield* this.setState(s1)
var op1 = yield* this.setOperation({
"id": ["1", 0],
"stuff": true
});
'id': ['1', 0],
'stuff': true
})
var op2 = yield* this.setOperation({
"id": ["1", 3],
"stuff": true
});
var ops = yield* this.getOperations();
expect(ops.length).toBeGreaterThan(1);
expect(ops[0]).toEqual(op1);
expect(ops[1]).toEqual(op2);
done();
});
});
afterAll(function(done){
ob.requestTransaction(function*(){
yield* ob.removeDatabase();
ob = null;
done();
});
});
});
'id': ['1', 3],
'stuff': true
})
var ops = yield* this.getOperations()
expect(ops.length).toBeGreaterThan(1)
expect(ops[0]).toEqual(op1)
expect(ops[1]).toEqual(op2)
done()
})
})
afterAll(function (done) {
ob.requestTransaction(function *() {
yield* ob.removeDatabase()
ob = null
done()
})
})
})
}

View File

@ -1,155 +1,144 @@
type State = {
user: string,
clock: number
};
/* global Struct, RBTree, Y */
function copyObject (o) {
var c = {};
var c = {}
for (var key in o) {
c[key] = o[key];
c[key] = o[key]
}
return c;
return c
}
type StateVector = Array<State>;
type StateSet = Object;
Y.Memory = (function () { // eslint-disable-line no-unused-vars
class Transaction extends AbstractTransaction { // eslint-disable-line
Y.Memory = (function(){ //eslint-disable-line no-unused-vars
class Transaction extends AbstractTransaction { //eslint-disable-line
ss: StateSet;
os: RBTree;
store: OperationStore;
constructor (store : OperationStore) {
super(store);
this.ss = store.ss;
this.os = store.os;
constructor (store) {
super(store)
this.ss = store.ss
this.os = store.os
}
*setOperation (op) {
* setOperation (op) { // eslint-disable-line
// TODO: you can remove this step! probs..
var n = this.os.findNode(op.id);
n.val = op;
return op;
var n = this.os.findNode(op.id)
n.val = op
return op
}
*addOperation (op) {
this.os.add(op);
* addOperation (op) { // eslint-disable-line
this.os.add(op)
}
*getOperation (id) {
* getOperation (id) { // eslint-disable-line
if (id == null) {
throw new Error("You must define id!");
throw new Error('You must define id!')
}
return this.os.find(id);
return this.os.find(id)
}
*removeOperation (id) {
this.os.delete(id);
* removeOperation (id) { // eslint-disable-line
this.os.delete(id)
}
*setState (state : State) : State {
this.ss[state.user] = state.clock;
* setState (state) { // eslint-disable-line
this.ss[state.user] = state.clock
}
*getState (user : string) : State {
var clock = this.ss[user];
if (clock == null){
clock = 0;
* getState (user) { // eslint-disable-line
var clock = this.ss[user]
if (clock == null) {
clock = 0
}
return {
user: user,
clock: clock
};
}
}
*getStateVector () : StateVector {
var stateVector = [];
* getStateVector () { // eslint-disable-line
var stateVector = []
for (var user in this.ss) {
var clock = this.ss[user];
var clock = this.ss[user]
stateVector.push({
user: user,
clock: clock
});
})
}
return stateVector;
return stateVector
}
*getStateSet () : StateSet {
return this.ss;
* getStateSet () { // eslint-disable-line
return this.ss
}
*getOperations (startSS : StateSet) {
* getOperations (startSS) {
// TODO: use bounds here!
if (startSS == null){
startSS = {};
if (startSS == null) {
startSS = {}
}
var ops = [];
var ops = []
var endSV : StateVector = yield* this.getStateVector();
var endSV = yield* this.getStateVector()
for (var endState of endSV) {
var user = endState.user;
if (user === "_") {
continue;
var user = endState.user
if (user === '_') {
continue
}
var startPos = startSS[user] || 0;
var endPos = endState.clock;
var startPos = startSS[user] || 0
var endPos = endState.clock
this.os.iterate([user, startPos], [user, endPos], function(op){//eslint-disable-line
ops.push(Struct[op.struct].encode(op));
});
this.os.iterate([user, startPos], [user, endPos], function (op) {// eslint-disable-line
ops.push(Struct[op.struct].encode(op))
})
}
var res = [];
var res = []
for (var op of ops) {
res.push(yield* this.makeOperationReady.call(this, startSS, op));
res.push(yield* this.makeOperationReady(startSS, op))
}
return res;
return res
}
*makeOperationReady (ss, op) {
* makeOperationReady (ss, op) {
// instead of ss, you could use currSS (a ss that increments when you add an operation)
var clock;
var o = op;
while (o.right != null){
var clock
var o = op
while (o.right != null) {
// while unknown, go to the right
clock = ss[o.right[0]];
clock = ss[o.right[0]]
if (clock != null && o.right[1] < clock) {
break;
break
}
o = yield* this.getOperation(o.right);
o = yield* this.getOperation(o.right)
}
op = copyObject(op);
op.right = o.right;
return op;
op = copyObject(op)
op.right = o.right
return op
}
}
class OperationStore extends AbstractOperationStore { //eslint-disable-line no-undef
class OperationStore extends AbstractOperationStore { // eslint-disable-line no-undef
constructor (y) {
super(y);
this.os = new RBTree();
this.ss = {};
this.waitingTransactions = [];
this.transactionInProgress = false;
super(y)
this.os = new RBTree()
this.ss = {}
this.waitingTransactions = []
this.transactionInProgress = false
}
requestTransaction (_makeGen : Function) {
requestTransaction (_makeGen) {
if (!this.transactionInProgress) {
this.transactionInProgress = true;
this.transactionInProgress = true
setTimeout(() => {
var makeGen = _makeGen;
var makeGen = _makeGen
while (makeGen != null) {
var t = new Transaction(this);
var gen = makeGen.call(t);
var res = gen.next();
while(!res.done){
if (res.value === "transaction") {
res = gen.next(t);
var t = new Transaction(this)
var gen = makeGen.call(t)
var res = gen.next()
while (!res.done) {
if (res.value === 'transaction') {
res = gen.next(t)
} else {
throw new Error("You must not yield this type. (Maybe you meant to use 'yield*'?)");
throw new Error("You must not yield this type. (Maybe you meant to use 'yield*'?)")
}
}
makeGen = this.waitingTransactions.shift();
makeGen = this.waitingTransactions.shift()
}
this.transactionInProgress = false;
}, 0);
this.transactionInProgress = false
}, 0)
} else {
this.waitingTransactions.push(_makeGen);
this.waitingTransactions.push(_makeGen)
}
}
*removeDatabase () {
delete this.os;
* removeDatabase () { // eslint-disable-line
delete this.os
}
}
return OperationStore;
})();
return OperationStore
})()

View File

@ -1,367 +1,367 @@
/* global compareIds */
function smaller (a, b) {
return a[0] < b[0] || (a[0] === b[0] && a[1] < b[1]);
return a[0] < b[0] || (a[0] === b[0] && a[1] < b[1])
}
class N {
// A created node is always red!
constructor (val) {
this.val = val;
this.color = true;
this._left = null;
this._right = null;
this._parent = null;
this.val = val
this.color = true
this._left = null
this._right = null
this._parent = null
if (val.id === null) {
throw new Error("You must define id!");
throw new Error('You must define id!')
}
}
isRed () { return this.color; }
isBlack () { return !this.color; }
redden () { this.color = true; return this; }
blacken () { this.color = false; return this; }
isRed () { return this.color }
isBlack () { return !this.color }
redden () { this.color = true; return this }
blacken () { this.color = false; return this }
get grandparent () {
return this.parent.parent;
return this.parent.parent
}
get parent () {
return this._parent;
return this._parent
}
get sibling () {
return (this === this.parent.left) ?
this.parent.right : this.parent.left;
this.parent.right : this.parent.left
}
get left () {
return this._left;
return this._left
}
get right () {
return this._right;
return this._right
}
set left (n) {
if (n !== null) {
n._parent = this;
n._parent = this
}
this._left = n;
this._left = n
}
set right (n) {
if (n !== null) {
n._parent = this;
n._parent = this
}
this._right = n;
this._right = n
}
rotateLeft (tree) {
var parent = this.parent;
var newParent = this.right;
var newRight = this.right.left;
newParent.left = this;
this.right = newRight;
var parent = this.parent
var newParent = this.right
var newRight = this.right.left
newParent.left = this
this.right = newRight
if (parent === null) {
tree.root = newParent;
newParent._parent = null;
tree.root = newParent
newParent._parent = null
} else if (parent.left === this) {
parent.left = newParent;
parent.left = newParent
} else if (parent.right === this) {
parent.right = newParent;
parent.right = newParent
} else {
throw new Error("The elements are wrongly connected!");
throw new Error('The elements are wrongly connected!')
}
}
next () {
if ( this.right !== null ) {
if (this.right !== null) {
// search the most left node in the right tree
var o = this.right;
var o = this.right
while (o.left !== null) {
o = o.left;
o = o.left
}
return o;
return o
} else {
var p = this;
var p = this
while (p.parent !== null && p !== p.parent.left) {
p = p.parent;
p = p.parent
}
return p.parent;
return p.parent
}
}
rotateRight (tree) {
var parent = this.parent;
var newParent = this.left;
var newLeft = this.left.right;
newParent.right = this;
this.left = newLeft;
var parent = this.parent
var newParent = this.left
var newLeft = this.left.right
newParent.right = this
this.left = newLeft
if (parent === null) {
tree.root = newParent;
newParent._parent = null;
tree.root = newParent
newParent._parent = null
} else if (parent.left === this) {
parent.left = newParent;
parent.left = newParent
} else if (parent.right === this) {
parent.right = newParent;
parent.right = newParent
} else {
throw new Error("The elements are wrongly connected!");
throw new Error('The elements are wrongly connected!')
}
}
getUncle () {
// we can assume that grandparent exists when this is called!
if (this.parent === this.parent.parent.left) {
return this.parent.parent.right;
return this.parent.parent.right
} else {
return this.parent.parent.left;
return this.parent.parent.left
}
}
}
class RBTree { //eslint-disable-line no-unused-vars
class RBTree { // eslint-disable-line no-unused-vars
constructor () {
this.root = null;
this.length = 0;
this.root = null
this.length = 0
}
findNodeWithLowerBound (from) {
if (from === void 0) {
throw new Error("You must define from!");
throw new Error('You must define from!')
}
var o = this.root;
var o = this.root
if (o === null) {
return false;
return false
} else {
while (true) {
if ((from === null || smaller(from, o.val.id)) && o.left !== null) {
// o is included in the bound
// try to find an element that is closer to the bound
o = o.left;
o = o.left
} else if (from !== null && smaller(o.val.id, from)) {
// o is not within the bound, maybe one of the right elements is..
if (o.right !== null) {
o = o.right;
o = o.right
} else {
// there is no right element. Search for the next bigger element,
// this should be within the bounds
return o.next();
return o.next()
}
} else {
return o;
return o
}
}
}
}
iterate (from, to, f) {
var o = this.findNodeWithLowerBound(from);
while ( o !== null && (to === null || smaller(o.val.id, to) || compareIds(o.val.id, to)) ) {
f(o.val);
o = o.next();
var o = this.findNodeWithLowerBound(from)
while (o !== null && (to === null || smaller(o.val.id, to) || compareIds(o.val.id, to))) {
f(o.val)
o = o.next()
}
return true;
return true
}
find (id) {
return this.findNode(id).val;
return this.findNode(id).val
}
findNode (id) {
if (id == null || id.constructor !== Array) {
throw new Error("Expect id to be an array!");
throw new Error('Expect id to be an array!')
}
var o = this.root;
var o = this.root
if (o === null) {
return false;
return false
} else {
while (true) {
if (o === null) {
return false;
return false
}
if (smaller(id, o.val.id)) {
o = o.left;
o = o.left
} else if (smaller(o.val.id, id)) {
o = o.right;
o = o.right
} else {
return o;
return o
}
}
}
}
delete (id) {
if (id == null || id.constructor !== Array) {
throw new Error("id is expected to be an Array!");
throw new Error('id is expected to be an Array!')
}
var d = this.findNode(id);
var d = this.findNode(id)
if (d == null) {
throw new Error("Element does not exist!");
throw new Error('Element does not exist!')
}
this.length--;
this.length--
if (d.left !== null && d.right !== null) {
// switch d with the greates element in the left subtree.
// o should have at most one child.
var o = d.left;
var o = d.left
// find
while (o.right !== null) {
o = o.right;
o = o.right
}
// switch
d.val = o.val;
d = o;
d.val = o.val
d = o
}
// d has at most one child
// let n be the node that replaces d
var isFakeChild;
var child = d.left || d.right;
if ( child === null) {
isFakeChild = true;
child = new N({id: 0});
child.blacken();
d.right = child;
var isFakeChild
var child = d.left || d.right
if (child === null) {
isFakeChild = true
child = new N({id: 0})
child.blacken()
d.right = child
} else {
isFakeChild = false;
isFakeChild = false
}
if (d.parent === null) {
if (!isFakeChild) {
this.root = child;
child.blacken();
child._parent = null;
this.root = child
child.blacken()
child._parent = null
} else {
this.root = null;
this.root = null
}
return;
return
} else if (d.parent.left === d) {
d.parent.left = child;
d.parent.left = child
} else if (d.parent.right === d) {
d.parent.right = child;
d.parent.right = child
} else {
throw new Error("Impossible!");
throw new Error('Impossible!')
}
if ( d.isBlack() ) {
if ( child.isRed() ) {
child.blacken();
if (d.isBlack()) {
if (child.isRed()) {
child.blacken()
} else {
this._fixDelete(child);
this._fixDelete(child)
}
}
this.root.blacken();
this.root.blacken()
if (isFakeChild) {
if (child.parent.left === child) {
child.parent.left = null;
child.parent.left = null
} else if (child.parent.right === child) {
child.parent.right = null;
child.parent.right = null
} else {
throw new Error("Impossible #3");
throw new Error('Impossible #3')
}
}
}
_fixDelete (n) {
function isBlack (node) {
return node !== null ? node.isBlack() : true;
return node !== null ? node.isBlack() : true
}
function isRed(node) {
return node !== null ? node.isRed() : false;
function isRed (node) {
return node !== null ? node.isRed() : false
}
if (n.parent === null) {
// this can only be called after the first iteration of fixDelete.
return;
return
}
// d was already replaced by the child
// d is not the root
// d and child are black
var sibling = n.sibling;
var sibling = n.sibling
if (isRed(sibling)) {
// make sibling the grandfather
n.parent.redden();
sibling.blacken();
n.parent.redden()
sibling.blacken()
if (n === n.parent.left) {
n.parent.rotateLeft(this);
n.parent.rotateLeft(this)
} else if (n === n.parent.right) {
n.parent.rotateRight(this);
n.parent.rotateRight(this)
} else {
throw new Error("Impossible #2");
throw new Error('Impossible #2')
}
sibling = n.sibling;
sibling = n.sibling
}
// parent, sibling, and children of n are black
if ( n.parent.isBlack() &&
sibling.isBlack() &&
isBlack(sibling.left) &&
isBlack(sibling.right)
if (n.parent.isBlack() &&
sibling.isBlack() &&
isBlack(sibling.left) &&
isBlack(sibling.right)
) {
sibling.redden();
this._fixDelete(n.parent);
} else if ( n.parent.isRed() &&
sibling.isBlack() &&
isBlack(sibling.left) &&
isBlack(sibling.right)
sibling.redden()
this._fixDelete(n.parent)
} else if (n.parent.isRed() &&
sibling.isBlack() &&
isBlack(sibling.left) &&
isBlack(sibling.right)
) {
sibling.redden();
n.parent.blacken();
sibling.redden()
n.parent.blacken()
} else {
if ( n === n.parent.left &&
sibling.isBlack() &&
isRed(sibling.left) &&
isBlack(sibling.right)
if (n === n.parent.left &&
sibling.isBlack() &&
isRed(sibling.left) &&
isBlack(sibling.right)
) {
sibling.redden();
sibling.left.blacken();
sibling.rotateRight(this);
sibling = n.sibling;
} else if ( n === n.parent.right &&
sibling.isBlack() &&
isRed(sibling.right) &&
isBlack(sibling.left)
sibling.redden()
sibling.left.blacken()
sibling.rotateRight(this)
sibling = n.sibling
} else if (n === n.parent.right &&
sibling.isBlack() &&
isRed(sibling.right) &&
isBlack(sibling.left)
) {
sibling.redden();
sibling.right.blacken();
sibling.rotateLeft(this);
sibling = n.sibling;
sibling.redden()
sibling.right.blacken()
sibling.rotateLeft(this)
sibling = n.sibling
}
sibling.color = n.parent.color;
n.parent.blacken();
sibling.color = n.parent.color
n.parent.blacken()
if (n === n.parent.left) {
sibling.right.blacken();
n.parent.rotateLeft(this);
sibling.right.blacken()
n.parent.rotateLeft(this)
} else {
sibling.left.blacken();
n.parent.rotateRight(this);
sibling.left.blacken()
n.parent.rotateRight(this)
}
}
}
add (v) {
if (v == null || v.id == null || v.id.constructor !== Array) {
throw new Error("v is expected to have an id property which is an Array!");
throw new Error('v is expected to have an id property which is an Array!')
}
var node = new N(v);
var node = new N(v)
if (this.root !== null) {
var p = this.root; // p abbrev. parent
var p = this.root // p abbrev. parent
while (true) {
if (smaller(node.val.id, p.val.id)) {
if (p.left === null) {
p.left = node;
break;
p.left = node
break
} else {
p = p.left;
p = p.left
}
} else if (smaller(p.val.id, node.val.id)) {
if (p.right === null) {
p.right = node;
break;
p.right = node
break
} else {
p = p.right;
p = p.right
}
} else {
return false;
return false
}
}
this._fixInsert(node);
this._fixInsert(node)
} else {
this.root = node;
this.root = node
}
this.length++;
this.root.blacken();
this.length++
this.root.blacken()
}
_fixInsert (n) {
if (n.parent === null) {
n.blacken();
return;
n.blacken()
return
} else if (n.parent.isBlack()) {
return;
return
}
var uncle = n.getUncle();
var uncle = n.getUncle()
if (uncle !== null && uncle.isRed()) {
// Note: parent: red, uncle: red
n.parent.blacken();
uncle.blacken();
n.grandparent.redden();
this._fixInsert(n.grandparent);
n.parent.blacken()
uncle.blacken()
n.grandparent.redden()
this._fixInsert(n.grandparent)
} else {
// Note: parent: red, uncle: black or null
// Now we transform the tree in such a way that
@ -370,30 +370,28 @@ class RBTree { //eslint-disable-line no-unused-vars
// and grandparent.left.left.isRed
// 2) grandparent.right.isRed
// and grandparent.right.right.isRed
if (n === n.parent.right
&& n.parent === n.grandparent.left) {
n.parent.rotateLeft(this);
// Since we rotated and want to use the previous
// cases, we need to set n in such a way that
// n.parent.isRed again
n = n.left;
} else if (n === n.parent.left
&& n.parent === n.grandparent.right) {
n.parent.rotateRight(this);
// see above
n = n.right;
if (n === n.parent.right && n.parent === n.grandparent.left) {
n.parent.rotateLeft(this)
// Since we rotated and want to use the previous
// cases, we need to set n in such a way that
// n.parent.isRed again
n = n.left
} else if (n === n.parent.left && n.parent === n.grandparent.right) {
n.parent.rotateRight(this)
// see above
n = n.right
}
// Case 1) or 2) hold from here on.
// Now traverse grandparent, make parent a black node
// on the highest level which holds two red nodes.
n.parent.blacken();
n.grandparent.redden();
n.parent.blacken()
n.grandparent.redden()
if (n === n.parent.left) {
// Case 1
n.grandparent.rotateRight(this);
n.grandparent.rotateRight(this)
} else {
// Case 2
n.grandparent.rotateLeft(this);
n.grandparent.rotateLeft(this)
}
}
}

View File

@ -1,209 +1,209 @@
/* @flow */
/*eslint-env browser,jasmine,console */
/* global RBTree, smaller, compareIds */
/* eslint-env browser,jasmine,console */
var numberOfRBTreeTests = 1000;
var numberOfRBTreeTests = 1000
function itRedNodesDoNotHaveBlackChildren (tree) {
it("Red nodes do not have black children", function(){
it('Red nodes do not have black children', function () {
function traverse (n) {
if (n == null) {
return;
return
}
if (n.isRed()) {
if (n.left != null) {
expect(n.left.isRed()).not.toBeTruthy();
expect(n.left.isRed()).not.toBeTruthy()
}
if (n.right != null) {
expect(n.right.isRed()).not.toBeTruthy();
expect(n.right.isRed()).not.toBeTruthy()
}
}
traverse(n.left);
traverse(n.right);
traverse(n.left)
traverse(n.right)
}
traverse(tree.root);
});
traverse(tree.root)
})
}
function itBlackHeightOfSubTreesAreEqual (tree){
it("Black-height of sub-trees are equal", function(){
function itBlackHeightOfSubTreesAreEqual (tree) {
it('Black-height of sub-trees are equal', function () {
function traverse (n) {
if (n == null) {
return 0;
return 0
}
var sub1 = traverse(n.left);
var sub2 = traverse(n.right);
expect(sub1).toEqual(sub2);
if(n.isRed()) {
return sub1;
var sub1 = traverse(n.left)
var sub2 = traverse(n.right)
expect(sub1).toEqual(sub2)
if (n.isRed()) {
return sub1
} else {
return sub1 + 1;
return sub1 + 1
}
}
traverse(tree.root);
});
traverse(tree.root)
})
}
function itRootNodeIsBlack(tree) {
it("root node is black", function(){
expect(tree.root == null || tree.root.isBlack()).toBeTruthy();
});
function itRootNodeIsBlack (tree) {
it('root node is black', function () {
expect(tree.root == null || tree.root.isBlack()).toBeTruthy()
})
}
describe("RedBlack Tree", function(){
beforeEach(function(){
this.tree = new RBTree();
});
it("can add&retrieve 5 elements", function(){
this.tree.add({val: "four", id: [4]});
this.tree.add({val: "one", id: [1]});
this.tree.add({val: "three", id: [3]});
this.tree.add({val: "two", id: [2]});
this.tree.add({val: "five", id: [5]});
expect(this.tree.find([1]).val).toEqual("one");
expect(this.tree.find([2]).val).toEqual("two");
expect(this.tree.find([3]).val).toEqual("three");
expect(this.tree.find([4]).val).toEqual("four");
expect(this.tree.find([5]).val).toEqual("five");
});
describe('RedBlack Tree', function () {
beforeEach(function () {
this.tree = new RBTree()
})
it('can add&retrieve 5 elements', function () {
this.tree.add({val: 'four', id: [4]})
this.tree.add({val: 'one', id: [1]})
this.tree.add({val: 'three', id: [3]})
this.tree.add({val: 'two', id: [2]})
this.tree.add({val: 'five', id: [5]})
expect(this.tree.find([1]).val).toEqual('one')
expect(this.tree.find([2]).val).toEqual('two')
expect(this.tree.find([3]).val).toEqual('three')
expect(this.tree.find([4]).val).toEqual('four')
expect(this.tree.find([5]).val).toEqual('five')
})
it("5 elements do not exist anymore after deleting them", function(){
this.tree.add({val: "four", id: [4]});
this.tree.add({val: "one", id: [1]});
this.tree.add({val: "three", id: [3]});
this.tree.add({val: "two", id: [2]});
this.tree.add({val: "five", id: [5]});
this.tree.delete([4]);
expect(this.tree.find([4])).not.toBeTruthy();
this.tree.delete([3]);
expect(this.tree.find([3])).not.toBeTruthy();
this.tree.delete([2]);
expect(this.tree.find([2])).not.toBeTruthy();
this.tree.delete([1]);
expect(this.tree.find([1])).not.toBeTruthy();
this.tree.delete([5]);
expect(this.tree.find([5])).not.toBeTruthy();
});
it('5 elements do not exist anymore after deleting them', function () {
this.tree.add({val: 'four', id: [4]})
this.tree.add({val: 'one', id: [1]})
this.tree.add({val: 'three', id: [3]})
this.tree.add({val: 'two', id: [2]})
this.tree.add({val: 'five', id: [5]})
this.tree.delete([4])
expect(this.tree.find([4])).not.toBeTruthy()
this.tree.delete([3])
expect(this.tree.find([3])).not.toBeTruthy()
this.tree.delete([2])
expect(this.tree.find([2])).not.toBeTruthy()
this.tree.delete([1])
expect(this.tree.find([1])).not.toBeTruthy()
this.tree.delete([5])
expect(this.tree.find([5])).not.toBeTruthy()
})
it("debug #1", function(){
this.tree.add({id: [2]});
this.tree.add({id: [0]});
this.tree.delete([2]);
this.tree.add({id: [1]});
expect(this.tree.find([0])).not.toBeUndefined();
expect(this.tree.find([1])).not.toBeUndefined();
expect(this.tree.find([2])).toBeUndefined();
});
describe("debug #2", function(){
var tree = new RBTree();
tree.add({id: [8433]});
tree.add({id: [12844]});
tree.add({id: [1795]});
tree.add({id: [30302]});
tree.add({id: [64287]});
tree.delete([8433]);
tree.add({id: [28996]});
tree.delete([64287]);
tree.add({id: [22721]});
it('debug #1', function () {
this.tree.add({id: [2]})
this.tree.add({id: [0]})
this.tree.delete([2])
this.tree.add({id: [1]})
expect(this.tree.find([0])).not.toBeUndefined()
expect(this.tree.find([1])).not.toBeUndefined()
expect(this.tree.find([2])).toBeUndefined()
})
describe('debug #2', function () {
var tree = new RBTree()
tree.add({id: [8433]})
tree.add({id: [12844]})
tree.add({id: [1795]})
tree.add({id: [30302]})
tree.add({id: [64287]})
tree.delete([8433])
tree.add({id: [28996]})
tree.delete([64287])
tree.add({id: [22721]})
itRootNodeIsBlack(tree, []);
itBlackHeightOfSubTreesAreEqual(tree, []);
});
itRootNodeIsBlack(tree, [])
itBlackHeightOfSubTreesAreEqual(tree, [])
})
describe(`After adding&deleting (0.8/0.2) ${numberOfRBTreeTests} times`, function () {
var elements = [];
var tree = new RBTree();
for(var i = 0; i < numberOfRBTreeTests; i++) {
var r = Math.random();
var elements = []
var tree = new RBTree()
for (var i = 0; i < numberOfRBTreeTests; i++) {
var r = Math.random()
if (r < 0.8) {
var obj = [Math.floor(Math.random() * numberOfRBTreeTests * 10000)];
elements.push(obj);
tree.add({id: obj});
var obj = [Math.floor(Math.random() * numberOfRBTreeTests * 10000)]
elements.push(obj)
tree.add({id: obj})
} else if (elements.length > 0) {
var elemid = Math.floor(Math.random() * elements.length);
var elem = elements[elemid];
elements = elements.filter(function(e){return !compareIds(e,elem); }); //eslint-disable-line
tree.delete(elem);
var elemid = Math.floor(Math.random() * elements.length)
var elem = elements[elemid]
elements = elements.filter(function (e) {return !compareIds(e, elem); }); // eslint-disable-line
tree.delete(elem)
}
}
itRootNodeIsBlack(tree);
itRootNodeIsBlack(tree)
it("can find every object", function(){
for(var id of elements) {
expect(tree.find(id).id).toEqual(id);
it('can find every object', function () {
for (var id of elements) {
expect(tree.find(id).id).toEqual(id)
}
});
})
it("can find every object with lower bound search", function(){
for(var id of elements) {
expect(tree.findNodeWithLowerBound(id).val.id).toEqual(id);
it('can find every object with lower bound search', function () {
for (var id of elements) {
expect(tree.findNodeWithLowerBound(id).val.id).toEqual(id)
}
});
itRedNodesDoNotHaveBlackChildren(tree);
})
itRedNodesDoNotHaveBlackChildren(tree)
itBlackHeightOfSubTreesAreEqual(tree);
itBlackHeightOfSubTreesAreEqual(tree)
it("iterating over a tree with lower bound yields the right amount of results", function(){
var lowerBound = elements[Math.floor(Math.random() * elements.length)];
var expectedResults = elements.filter(function(e, pos){
return (smaller(lowerBound, e) || compareIds(e, lowerBound)) && elements.indexOf(e) === pos;
}).length;
it('iterating over a tree with lower bound yields the right amount of results', function () {
var lowerBound = elements[Math.floor(Math.random() * elements.length)]
var expectedResults = elements.filter(function (e, pos) {
return (smaller(lowerBound, e) || compareIds(e, lowerBound)) && elements.indexOf(e) === pos
}).length
var actualResults = 0;
tree.iterate(lowerBound, null, function(val){
expect(val).not.toBeUndefined();
actualResults++;
});
expect(expectedResults).toEqual(actualResults);
});
var actualResults = 0
tree.iterate(lowerBound, null, function (val) {
expect(val).not.toBeUndefined()
actualResults++
})
expect(expectedResults).toEqual(actualResults)
})
it("iterating over a tree without bounds yield the right amount of results", function(){
var lowerBound = null;
var expectedResults = elements.filter(function(e, pos){
return elements.indexOf(e) === pos;
}).length;
var actualResults = 0;
tree.iterate(lowerBound, null, function(val){
expect(val).not.toBeUndefined();
actualResults++;
});
expect(expectedResults).toEqual(actualResults);
});
it('iterating over a tree without bounds yield the right amount of results', function () {
var lowerBound = null
var expectedResults = elements.filter(function (e, pos) {
return elements.indexOf(e) === pos
}).length
var actualResults = 0
tree.iterate(lowerBound, null, function (val) {
expect(val).not.toBeUndefined()
actualResults++
})
expect(expectedResults).toEqual(actualResults)
})
it("iterating over a tree with upper bound yields the right amount of results", function(){
var upperBound = elements[Math.floor(Math.random() * elements.length)];
var expectedResults = elements.filter(function(e, pos){
return (smaller(e, upperBound) || compareIds(e, upperBound)) && elements.indexOf(e) === pos;
}).length;
it('iterating over a tree with upper bound yields the right amount of results', function () {
var upperBound = elements[Math.floor(Math.random() * elements.length)]
var expectedResults = elements.filter(function (e, pos) {
return (smaller(e, upperBound) || compareIds(e, upperBound)) && elements.indexOf(e) === pos
}).length
var actualResults = 0;
tree.iterate(null, upperBound, function(val){
expect(val).not.toBeUndefined();
actualResults++;
});
expect(expectedResults).toEqual(actualResults);
});
var actualResults = 0
tree.iterate(null, upperBound, function (val) {
expect(val).not.toBeUndefined()
actualResults++
})
expect(expectedResults).toEqual(actualResults)
})
it("iterating over a tree with upper and lower bounds yield the right amount of results", function(){
var b1 = elements[Math.floor(Math.random() * elements.length)];
var b2 = elements[Math.floor(Math.random() * elements.length)];
var upperBound, lowerBound;
it('iterating over a tree with upper and lower bounds yield the right amount of results', function () {
var b1 = elements[Math.floor(Math.random() * elements.length)]
var b2 = elements[Math.floor(Math.random() * elements.length)]
var upperBound, lowerBound
if (smaller(b1, b2)) {
lowerBound = b1;
upperBound = b2;
lowerBound = b1
upperBound = b2
} else {
lowerBound = b2;
upperBound = b1;
lowerBound = b2
upperBound = b1
}
var expectedResults = elements.filter(function(e, pos){
return (smaller(lowerBound, e) || compareIds(e, lowerBound))
&& (smaller(e, upperBound) || compareIds(e, upperBound)) && elements.indexOf(e) === pos;
}).length;
var actualResults = 0;
tree.iterate(lowerBound, upperBound, function(val){
expect(val).not.toBeUndefined();
actualResults++;
});
expect(expectedResults).toEqual(actualResults);
});
});
});
var expectedResults = elements.filter(function (e, pos) {
return (smaller(lowerBound, e) || compareIds(e, lowerBound)) &&
(smaller(e, upperBound) || compareIds(e, upperBound)) && elements.indexOf(e) === pos
}).length
var actualResults = 0
tree.iterate(lowerBound, upperBound, function (val) {
expect(val).not.toBeUndefined()
actualResults++
})
expect(expectedResults).toEqual(actualResults)
})
})
})

View File

@ -1,36 +1,16 @@
/* @flow */
// Op is anything that we could get from the OperationStore.
type Op = Object;
type Id = [string, number];
type List = {
id: Id,
start: Insert,
end: Insert
};
type Insert = {
id: Id,
left: Insert,
right: Insert,
origin: Insert,
parent: List,
content: any
};
function compareIds(id1, id2) {
/* global copyObject, Y*/
function compareIds (id1, id2) {
if (id1 == null || id2 == null) {
if (id1 == null && id2 == null) {
return true;
return true
}
return false;
return false
}
if (id1[0] === id2[0] && id1[1] === id2[1]) {
return true;
return true
} else {
return false;
return false
}
}
@ -42,26 +22,26 @@ var Struct = {
*/
Delete: {
encode: function (op) {
return op;
return op
},
requiredOps: function (op) {
return [op.target];
return [op.target]
},
execute: function* (op) {
var target = yield* this.getOperation(op.target);
execute: function * (op) {
var target = yield* this.getOperation(op.target)
if (!target.deleted) {
target.deleted = true;
yield* this.setOperation(target);
var t = this.store.initializedTypes[JSON.stringify(target.parent)];
target.deleted = true
yield* this.setOperation(target)
var t = this.store.initializedTypes[JSON.stringify(target.parent)]
if (t != null) {
yield* t._changed(this, copyObject(op));
yield* t._changed(this, copyObject(op))
}
}
}
},
Insert: {
/*{
/* {
content: any,
left: Id,
right: Id,
@ -71,8 +51,9 @@ var Struct = {
id: this.os.getNextOpId()
}
*/
encode: function(op){
/*var e = {
encode: function (op) {
/*
var e = {
id: op.id,
left: op.left,
right: op.right,
@ -80,44 +61,44 @@ var Struct = {
parent: op.parent,
content: op.content,
struct: "Insert"
};
}
if (op.parentSub != null){
e.parentSub = op.parentSub;
e.parentSub = op.parentSub
}
return e;*/
return op;
return op
},
requiredOps: function(op){
var ids = [];
if(op.left != null){
ids.push(op.left);
requiredOps: function (op) {
var ids = []
if (op.left != null) {
ids.push(op.left)
}
if(op.right != null){
ids.push(op.right);
if (op.right != null) {
ids.push(op.right)
}
//if(op.right == null && op.left == null) {}
ids.push(op.parent);
// if(op.right == null && op.left == null) {}
ids.push(op.parent)
if (op.opContent != null) {
ids.push(op.opContent);
ids.push(op.opContent)
}
return ids;
return ids
},
getDistanceToOrigin: function *(op){
getDistanceToOrigin: function *(op) {
if (op.left == null) {
return 0;
return 0
} else {
var d = 0;
var o = yield* this.getOperation(op.left);
var d = 0
var o = yield* this.getOperation(op.left)
while (!compareIds(op.origin, (o ? o.id : null))) {
d++;
d++
if (o.left == null) {
break;
break
} else {
o = yield* this.getOperation(o.left);
o = yield* this.getOperation(o.left)
}
}
return d;
return d
}
},
/*
@ -135,86 +116,86 @@ var Struct = {
# case 3: $origin > $o.origin
# $this insert_position is to the left of $o (forever!)
*/
execute: function*(op){
var i; // loop counter
var distanceToOrigin = i = yield* Struct.Insert.getDistanceToOrigin.call(this, op); // most cases: 0 (starts from 0)
var o;
var parent;
var start;
execute: function *(op) {
var i // loop counter
var distanceToOrigin = i = yield* Struct.Insert.getDistanceToOrigin.call(this, op) // most cases: 0 (starts from 0)
var o
var parent
var start
// find o. o is the first conflicting operation
if (op.left != null) {
o = yield* this.getOperation(op.left);
o = (o.right == null) ? null : yield* this.getOperation(o.right);
o = yield* this.getOperation(op.left)
o = (o.right == null) ? null : yield* this.getOperation(o.right)
} else { // left == null
parent = yield* this.getOperation(op.parent);
let startId = op.parentSub ? parent.map[op.parentSub] : parent.start;
start = startId == null ? null : yield* this.getOperation(startId);
o = start;
parent = yield* this.getOperation(op.parent)
let startId = op.parentSub ? parent.map[op.parentSub] : parent.start
start = startId == null ? null : yield* this.getOperation(startId)
o = start
}
// handle conflicts
while (true) {
if (o != null && !compareIds(o.id, op.right)){
var oOriginDistance = yield* Struct.Insert.getDistanceToOrigin.call(this, o);
if (o != null && !compareIds(o.id, op.right)) {
var oOriginDistance = yield* Struct.Insert.getDistanceToOrigin.call(this, o)
if (oOriginDistance === i) {
// case 1
if (o.id[0] < op.id[0]) {
op.left = o.id;
distanceToOrigin = i + 1;
op.left = o.id
distanceToOrigin = i + 1
}
} else if (oOriginDistance < i) {
// case 2
if (i - distanceToOrigin <= oOriginDistance) {
op.left = o.id;
distanceToOrigin = i + 1;
op.left = o.id
distanceToOrigin = i + 1
}
} else {
break;
break
}
i++;
o = o.right ? yield* this.getOperation(o.right) : null;
i++
o = o.right ? yield* this.getOperation(o.right) : null
} else {
break;
break
}
}
// reconnect..
var left = null;
var right = null;
parent = parent || (yield* this.getOperation(op.parent));
var left = null
var right = null
parent = parent || (yield* this.getOperation(op.parent))
// reconnect left and set right of op
if (op.left != null) {
left = yield* this.getOperation(op.left);
op.right = left.right;
left.right = op.id;
yield* this.setOperation(left);
left = yield* this.getOperation(op.left)
op.right = left.right
left.right = op.id
yield* this.setOperation(left)
} else {
op.right = op.parentSub ? (parent.map[op.parentSub] || null) : parent.start;
op.right = op.parentSub ? (parent.map[op.parentSub] || null) : parent.start
}
// reconnect right
if (op.right != null) {
right = yield* this.getOperation(op.right);
right.left = op.id;
yield* this.setOperation(right);
right = yield* this.getOperation(op.right)
right.left = op.id
yield* this.setOperation(right)
}
// notify parent
if (op.parentSub != null) {
if (left == null) {
parent.map[op.parentSub] = op.id;
yield* this.setOperation(parent);
parent.map[op.parentSub] = op.id
yield* this.setOperation(parent)
}
} else {
if (right == null || left == null) {
if (right == null) {
parent.end = op.id;
parent.end = op.id
}
if (left == null) {
parent.start = op.id;
parent.start = op.id
}
yield* this.setOperation(parent);
yield* this.setOperation(parent)
}
}
}
@ -229,61 +210,61 @@ var Struct = {
id: this.os.getNextOpId()
}
*/
encode: function(op){
encode: function (op) {
return {
struct: "List",
struct: 'List',
id: op.id,
type: op.type
};
}
},
requiredOps: function(){
requiredOps: function () {
/*
var ids = [];
var ids = []
if (op.start != null) {
ids.push(op.start);
ids.push(op.start)
}
if (op.end != null){
ids.push(op.end);
ids.push(op.end)
}
return ids;
return ids
*/
return [];
return []
},
execute: function* (op) {
op.start = null;
op.end = null;
execute: function * (op) { // eslint-disable-line
op.start = null
op.end = null
},
ref: function* (op : Op, pos : number) : Insert {
ref: function * (op, pos) {
if (op.start == null) {
return null;
return null
}
var res = null;
var o = yield* this.getOperation(op.start);
var res = null
var o = yield* this.getOperation(op.start)
while ( true ) {
while (true) {
if (!o.deleted) {
res = o;
pos--;
res = o
pos--
}
if (pos >= 0 && o.right != null) {
o = (yield* this.getOperation(o.right));
o = (yield* this.getOperation(o.right))
} else {
break;
break
}
}
return res;
return res
},
map: function* (o : Op, f : Function) : Array<any> {
o = o.start;
var res = [];
while ( o !== null) {
var operation = yield* this.getOperation(o);
map: function * (o, f) {
o = o.start
var res = []
while (o !== null) {
var operation = yield* this.getOperation(o)
if (!operation.deleted) {
res.push(f(operation));
res.push(f(operation))
}
o = operation.right;
o = operation.right
}
return res;
return res
}
},
Map: {
@ -295,42 +276,41 @@ var Struct = {
id: this.os.getNextOpId()
}
*/
encode: function(op){
encode: function (op) {
return {
struct: "Map",
struct: 'Map',
type: op.type,
id: op.id,
map: {} // overwrite map!!
};
}
},
requiredOps: function(){
requiredOps: function () {
/*
var ids = [];
var ids = []
for (var end in op.map) {
ids.push(op.map[end]);
ids.push(op.map[end])
}
return ids;
return ids
*/
return [];
return []
},
execute: function* () {
},
get: function* (op, name) {
var oid = op.map[name];
execute: function * () {},
get: function * (op, name) {
var oid = op.map[name]
if (oid != null) {
var res = yield* this.getOperation(oid);
var res = yield* this.getOperation(oid)
return (res == null || res.deleted) ? void 0 : (res.opContent == null
? res.content : yield* this.getType(res.opContent));
? res.content : yield* this.getType(res.opContent))
}
},
delete: function* (op, name) {
var v = op.map[name] || null;
delete: function * (op, name) {
var v = op.map[name] || null
if (v != null) {
yield* Struct.Delete.create.call(this, {
target: v
});
})
}
}
}
};
Y.Struct = Struct;
}
Y.Struct = Struct

View File

View File

@ -1,86 +1,85 @@
/* global EventHandler, Y, CustomType, Struct */
(function(){
;(function () {
class YArray {
constructor (os, _model, idArray, valArray) {
this.os = os;
this._model = _model;
this.os = os
this._model = _model
// Array of all the operation id's
this.idArray = idArray;
this.idArray = idArray
// Array of all the values
this.valArray = valArray;
this.eventHandler = new EventHandler( ops =>{
var userEvents = [];
this.valArray = valArray
this.eventHandler = new EventHandler(ops => {
var userEvents = []
for (var i in ops) {
var op = ops[i];
if (op.struct === "Insert") {
let pos;
var op = ops[i]
if (op.struct === 'Insert') {
let pos
// we check op.left only!,
// because op.right might not be defined when this is called
if (op.left === null) {
pos = 0;
pos = 0
} else {
var sid = JSON.stringify(op.left);
pos = this.idArray.indexOf(sid) + 1;
var sid = JSON.stringify(op.left)
pos = this.idArray.indexOf(sid) + 1
if (pos <= 0) {
throw new Error("Unexpected operation!");
throw new Error('Unexpected operation!')
}
}
this.idArray.splice(pos, 0, JSON.stringify(op.id));
this.valArray.splice(pos, 0, op.content);
this.idArray.splice(pos, 0, JSON.stringify(op.id))
this.valArray.splice(pos, 0, op.content)
userEvents.push({
type: "insert",
type: 'insert',
object: this,
index: pos,
length: 1
});
} else if (op.struct === "Delete") {
let pos = this.idArray.indexOf(JSON.stringify(op.target));
this.idArray.splice(pos, 1);
this.valArray.splice(pos, 1);
})
} else if (op.struct === 'Delete') {
let pos = this.idArray.indexOf(JSON.stringify(op.target))
this.idArray.splice(pos, 1)
this.valArray.splice(pos, 1)
userEvents.push({
type: "delete",
type: 'delete',
object: this,
index: pos,
length: 1
});
})
} else {
throw new Error("Unexpected struct!");
throw new Error('Unexpected struct!')
}
}
this.eventHandler.callUserEventListeners(userEvents);
});
this.eventHandler.callUserEventListeners(userEvents)
})
}
get length () {
return this.idArray.length;
return this.idArray.length
}
get (pos) {
if (pos == null || typeof pos !== "number") {
throw new Error("pos must be a number!");
if (pos == null || typeof pos !== 'number') {
throw new Error('pos must be a number!')
}
return this.valArray[pos];
return this.valArray[pos]
}
toArray() {
return this.valArray.slice();
toArray () {
return this.valArray.slice()
}
insert (pos, contents) {
if (typeof pos !== "number") {
throw new Error("pos must be a number!");
if (typeof pos !== 'number') {
throw new Error('pos must be a number!')
}
if (!(contents instanceof Array)) {
throw new Error("contents must be an Array of objects!");
throw new Error('contents must be an Array of objects!')
}
if (contents.length === 0) {
return;
return
}
if (pos > this.idArray.length || pos < 0) {
throw new Error("This position exceeds the range of the array!");
throw new Error('This position exceeds the range of the array!')
}
var mostLeft = pos === 0 ? null : JSON.parse(this.idArray[pos - 1]);
var mostLeft = pos === 0 ? null : JSON.parse(this.idArray[pos - 1])
var ops = [];
var prevId = mostLeft;
var ops = []
var prevId = mostLeft
for (var i = 0; i < contents.length; i++) {
var op = {
left: prevId,
@ -90,97 +89,97 @@
// at the time of creating this operation, and is therefore not defined in idArray
parent: this._model,
content: contents[i],
struct: "Insert",
struct: 'Insert',
id: this.os.getNextOpId()
};
ops.push(op);
prevId = op.id;
}
ops.push(op)
prevId = op.id
}
var eventHandler = this.eventHandler;
eventHandler.awaitAndPrematurelyCall(ops);
this.os.requestTransaction(function*(){
var eventHandler = this.eventHandler
eventHandler.awaitAndPrematurelyCall(ops)
this.os.requestTransaction(function *() {
// now we can set the right reference.
var mostRight;
var mostRight
if (mostLeft != null) {
mostRight = (yield* this.getOperation(mostLeft)).right;
mostRight = (yield* this.getOperation(mostLeft)).right
} else {
mostRight = (yield* this.getOperation(ops[0].parent)).start;
mostRight = (yield* this.getOperation(ops[0].parent)).start
}
for (var j in ops) {
ops[j].right = mostRight;
ops[j].right = mostRight
}
yield* this.applyCreatedOperations(ops);
eventHandler.awaitedLastInserts(ops.length);
});
yield* this.applyCreatedOperations(ops)
eventHandler.awaitedLastInserts(ops.length)
})
}
delete (pos, length = 1) {
if (typeof length !== "number") {
throw new Error("pos must be a number!");
if (typeof length !== 'number') {
throw new Error('pos must be a number!')
}
if (typeof pos !== "number") {
throw new Error("pos must be a number!");
if (typeof pos !== 'number') {
throw new Error('pos must be a number!')
}
if (pos + length > this.idArray.length || pos < 0 || length < 0) {
throw new Error("The deletion range exceeds the range of the array!");
throw new Error('The deletion range exceeds the range of the array!')
}
if (length === 0) {
return;
return
}
var eventHandler = this.eventHandler;
var newLeft = pos > 0 ? JSON.parse(this.idArray[pos - 1]) : null;
var dels = [];
var eventHandler = this.eventHandler
var newLeft = pos > 0 ? JSON.parse(this.idArray[pos - 1]) : null
var dels = []
for (var i = 0; i < length; i++) {
dels.push({
target: JSON.parse(this.idArray[pos + i]),
struct: "Delete"
});
struct: 'Delete'
})
}
eventHandler.awaitAndPrematurelyCall(dels);
this.os.requestTransaction(function*(){
yield* this.applyCreatedOperations(dels);
eventHandler.awaitedLastDeletes(dels.length, newLeft);
});
eventHandler.awaitAndPrematurelyCall(dels)
this.os.requestTransaction(function *() {
yield* this.applyCreatedOperations(dels)
eventHandler.awaitedLastDeletes(dels.length, newLeft)
})
}
observe (f) {
this.eventHandler.addUserEventListener(f);
this.eventHandler.addUserEventListener(f)
}
*_changed (transaction, op) {
if (op.struct === "Insert") {
var l = op.left;
var left;
* _changed (transaction, op) {
if (op.struct === 'Insert') {
var l = op.left
var left
while (l != null) {
left = yield* transaction.getOperation(l);
left = yield* transaction.getOperation(l)
if (!left.deleted) {
break;
break
}
l = left.left;
l = left.left
}
op.left = l;
op.left = l
}
this.eventHandler.receivedOp(op);
this.eventHandler.receivedOp(op)
}
}
Y.Array = new CustomType({
class: YArray,
createType: function* YArrayCreator () {
createType: function * YArrayCreator () {
var model = {
start: null,
end: null,
struct: "List",
type: "Array",
struct: 'List',
type: 'Array',
id: this.store.getNextOpId()
};
yield* this.applyCreatedOperations([model]);
return yield* this.createType(model);
}
yield* this.applyCreatedOperations([model])
return yield* this.createType(model)
},
initType: function* YArrayInitializer(os, model){
var valArray = [];
var idArray = yield* Y.Struct.List.map.call(this, model, function(c){
valArray.push(c.content);
return JSON.stringify(c.id);
});
return new YArray(os, model.id, idArray, valArray);
initType: function * YArrayInitializer (os, model) {
var valArray = []
var idArray = yield* Struct.List.map.call(this, model, function (c) {
valArray.push(c.content)
return JSON.stringify(c.id)
})
return new YArray(os, model.id, idArray, valArray)
}
});
})();
})
})()

View File

@ -1,143 +1,145 @@
/* @flow */
/*eslint-env browser,jasmine */
/* global createUsers, wait, Y, compareAllUsers, getRandomNumber, applyRandomTransactions */
/* eslint-env browser,jasmine */
var numberOfYArrayTests = 80;
var numberOfYArrayTests = 80
describe("Array Type", function(){
var y1, y2, y3, flushAll;
describe('Array Type', function () {
var y1, y2, y3, flushAll
jasmine.DEFAULT_TIMEOUT_INTERVAL = 50000;
beforeEach(async function(done){
await createUsers(this, 5);
y1 = this.users[0].root;
y2 = this.users[1].root;
y3 = this.users[2].root;
flushAll = this.users[0].connector.flushAll;
done();
});
jasmine.DEFAULT_TIMEOUT_INTERVAL = 50000
beforeEach(async function (done) {
await createUsers(this, 5)
y1 = this.users[0].root
y2 = this.users[1].root
y3 = this.users[2].root
flushAll = this.users[0].connector.flushAll
done()
})
afterEach(async function(done) {
await compareAllUsers(this.users);
done();
});
await compareAllUsers(this.users)
done()
})
describe("Basic tests", function(){
it("insert three elements, try re-get property", async function(done){
var array = await y1.set("Array", Y.Array);
array.insert(0, [1, 2, 3]);
array = await y1.get("Array"); // re-get property
expect(array.toArray()).toEqual([1, 2, 3]);
done();
});
it("Basic insert in array (handle three conflicts)", async function(done){
var l1, l2, l3;
await y1.set("Array", Y.Array);
await flushAll();
(l1 = await y1.get("Array")).insert(0, [0]);
(l2 = await y2.get("Array")).insert(0, [1]);
(l3 = await y3.get("Array")).insert(0, [2]);
await flushAll();
expect(l1.toArray()).toEqual(l2.toArray());
expect(l2.toArray()).toEqual(l3.toArray());
done();
});
it("Basic insert&delete in array (handle three conflicts)", async function(done){
var l1, l2, l3;
l1 = await y1.set("Array", Y.Array);
l1.insert(0, ["x", "y", "z"]);
await flushAll();
l1.insert(1, [0]);
l2 = await y2.get("Array");
l2.delete(0);
l2.delete(1);
l3 = await y3.get("Array");
l3.insert(1, [2]);
await flushAll();
expect(l1.toArray()).toEqual(l2.toArray());
expect(l2.toArray()).toEqual(l3.toArray());
expect(l2.toArray()).toEqual([0, 2, "y"]);
done();
});
it("Basic insert. Then delete the whole array", async function(done){
var l1, l2, l3;
l1 = await y1.set("Array", Y.Array);
l1.insert(0, ["x", "y", "z"]);
await flushAll();
l1.delete(0, 3);
l2 = await y2.get("Array");
l3 = await y3.get("Array");
await flushAll();
expect(l1.toArray()).toEqual(l2.toArray());
expect(l2.toArray()).toEqual(l3.toArray());
expect(l2.toArray()).toEqual([]);
done();
});
it("throw insert & delete events", async function(done){
var array = await this.users[0].root.set("array", Y.Array);
var event;
array.observe(function(e){
event = e;
});
array.insert(0, [0]);
describe('Basic tests', function () {
it('insert three elements, try re-get property', async function (done) {
var array = await y1.set('Array', Y.Array)
array.insert(0, [1, 2, 3])
array = await y1.get('Array') // re-get property
expect(array.toArray()).toEqual([1, 2, 3])
done()
})
it('Basic insert in array (handle three conflicts)', async function (done) {
await y1.set('Array', Y.Array)
await flushAll()
var l1 = await y1.get('Array')
l1.insert(0, [0])
var l2 = await y2.get('Array')
l2.insert(0, [1])
var l3 = await y3.get('Array')
l3.insert(0, [2])
await flushAll()
expect(l1.toArray()).toEqual(l2.toArray())
expect(l2.toArray()).toEqual(l3.toArray())
done()
})
it('Basic insert&delete in array (handle three conflicts)', async function (done) {
var l1, l2, l3
l1 = await y1.set('Array', Y.Array)
l1.insert(0, ['x', 'y', 'z'])
await flushAll()
l1.insert(1, [0])
l2 = await y2.get('Array')
l2.delete(0)
l2.delete(1)
l3 = await y3.get('Array')
l3.insert(1, [2])
await flushAll()
expect(l1.toArray()).toEqual(l2.toArray())
expect(l2.toArray()).toEqual(l3.toArray())
expect(l2.toArray()).toEqual([0, 2, 'y'])
done()
})
it('Basic insert. Then delete the whole array', async function (done) {
var l1, l2, l3
l1 = await y1.set('Array', Y.Array)
l1.insert(0, ['x', 'y', 'z'])
await flushAll()
l1.delete(0, 3)
l2 = await y2.get('Array')
l3 = await y3.get('Array')
await flushAll()
expect(l1.toArray()).toEqual(l2.toArray())
expect(l2.toArray()).toEqual(l3.toArray())
expect(l2.toArray()).toEqual([])
done()
})
it('throw insert & delete events', async function (done) {
var array = await this.users[0].root.set('array', Y.Array)
var event
array.observe(function (e) {
event = e
})
array.insert(0, [0])
expect(event).toEqual([{
type: "insert",
type: 'insert',
object: array,
index: 0,
length: 1
}]);
array.delete(0);
}])
array.delete(0)
expect(event).toEqual([{
type: "delete",
type: 'delete',
object: array,
index: 0,
length: 1
}]);
await wait(50);
done();
});
});
describe(`${numberOfYArrayTests} Random tests`, function(){
}])
await wait(50)
done()
})
})
describe(`${numberOfYArrayTests} Random tests`, function () {
var randomArrayTransactions = [
function insert (array) {
array.insert(getRandomNumber(array.toArray().length), [getRandomNumber()]);
array.insert(getRandomNumber(array.toArray().length), [getRandomNumber()])
},
function _delete (array) {
var length = array.toArray().length;
var length = array.toArray().length
if (length > 0) {
array.delete(getRandomNumber(length - 1));
array.delete(getRandomNumber(length - 1))
}
}
];
function compareArrayValues(arrays){
var firstArray;
]
function compareArrayValues (arrays) {
var firstArray
for (var l of arrays) {
var val = l.toArray();
var val = l.toArray()
if (firstArray == null) {
firstArray = val;
firstArray = val
} else {
expect(val).toEqual(firstArray);
expect(val).toEqual(firstArray)
}
}
}
beforeEach(async function(done){
await this.users[0].root.set("Array", Y.Array);
await flushAll();
beforeEach(async function (done) {
await this.users[0].root.set('Array', Y.Array)
await flushAll()
var promises = [];
var promises = []
for (var u = 0; u < this.users.length; u++) {
promises.push(this.users[u].root.get("Array"));
promises.push(this.users[u].root.get('Array'))
}
this.arrays = await Promise.all(promises);
done();
});
it("arrays.length equals users.length", async function(done){
expect(this.arrays.length).toEqual(this.users.length);
done();
});
it(`succeed after ${numberOfYArrayTests} actions`, async function(done){
await applyRandomTransactions(this.users, this.arrays, randomArrayTransactions, numberOfYArrayTests);
await flushAll();
await compareArrayValues(this.arrays);
done();
});
});
});
this.arrays = await Promise.all(promises)
done()
})
it('arrays.length equals users.length', async function (done) { // eslint-disable-line
expect(this.arrays.length).toEqual(this.users.length)
done()
})
it(`succeed after ${numberOfYArrayTests} actions`, async function (done) {
await applyRandomTransactions(this.users, this.arrays, randomArrayTransactions, numberOfYArrayTests)
await flushAll()
await compareArrayValues(this.arrays)
done()
})
})
})

View File

@ -1,76 +1,78 @@
(function(){
/* global EventHandler, Y, CustomType, copyObject, compareIds */
;(function () {
class YMap {
constructor (os, model) {
this._model = model.id;
this.os = os;
this.map = copyObject(model.map);
this.contents = {};
this.opContents = {};
this.eventHandler = new EventHandler( ops =>{
var userEvents = [];
this._model = model.id
this.os = os
this.map = copyObject(model.map)
this.contents = {}
this.opContents = {}
this.eventHandler = new EventHandler(ops => {
var userEvents = []
for (var i in ops) {
var op = ops[i];
var oldValue;
var op = ops[i]
var oldValue
// key is the name to use to access (op)content
var key = op.struct === "Delete" ? op.key : op.parentSub;
var key = op.struct === 'Delete' ? op.key : op.parentSub
// compute oldValue
if (this.opContents[key] != null) {
let prevType = this.opContents[key];
oldValue = () => { //eslint-disable-line
let def = Promise.defer();
this.os.requestTransaction(function*(){//eslint-disable-line
def.resolve(yield* this.getType(prevType));
});
return def.promise;
};
let prevType = this.opContents[key]
oldValue = () => {// eslint-disable-line
let def = Promise.defer()
this.os.requestTransaction(function *() {// eslint-disable-line
def.resolve(yield* this.getType(prevType))
})
return def.promise
}
} else {
oldValue = this.contents[key];
oldValue = this.contents[key]
}
// compute op event
if (op.struct === "Insert"){
if (op.struct === 'Insert') {
if (op.left === null) {
if (op.opContent != null) {
delete this.contents[key];
this.opContents[key] = op.opContent;
delete this.contents[key]
this.opContents[key] = op.opContent
} else {
delete this.opContents[key];
this.contents[key] = op.content;
delete this.opContents[key]
this.contents[key] = op.content
}
this.map[key] = op.id;
this.map[key] = op.id
var insertEvent = {
name: key,
object: this
};
if (oldValue === undefined) {
insertEvent.type = "add";
} else {
insertEvent.type = "update";
insertEvent.oldValue = oldValue;
}
userEvents.push(insertEvent);
if (oldValue === undefined) {
insertEvent.type = 'add'
} else {
insertEvent.type = 'update'
insertEvent.oldValue = oldValue
}
userEvents.push(insertEvent)
}
} else if (op.struct === "Delete") {
} else if (op.struct === 'Delete') {
if (compareIds(this.map[key], op.target)) {
if (this.opContents[key] != null) {
delete this.opContents[key];
delete this.opContents[key]
} else {
delete this.contents[key];
delete this.contents[key]
}
var deleteEvent = {
name: key,
object: this,
oldValue: oldValue,
type: "delete"
};
userEvents.push(deleteEvent);
type: 'delete'
}
userEvents.push(deleteEvent)
}
} else {
throw new Error("Unexpected Operation!");
throw new Error('Unexpected Operation!')
}
}
this.eventHandler.callUserEventListeners(userEvents);
});
this.eventHandler.callUserEventListeners(userEvents)
})
}
get (key) {
// return property.
@ -78,34 +80,34 @@
// if property is a type, return a promise
if (this.opContents[key] == null) {
if (key == null) {
return copyObject(this.contents);
return copyObject(this.contents)
} else {
return this.contents[key];
return this.contents[key]
}
} else {
let def = Promise.defer();
var oid = this.opContents[key];
this.os.requestTransaction(function*(){
def.resolve(yield* this.getType(oid));
});
return def.promise;
let def = Promise.defer()
var oid = this.opContents[key]
this.os.requestTransaction(function *() {
def.resolve(yield* this.getType(oid))
})
return def.promise
}
}
delete (key) {
var right = this.map[key];
var right = this.map[key]
if (right != null) {
var del = {
target: right,
struct: "Delete"
};
var eventHandler = this.eventHandler;
var modDel = copyObject(del);
modDel.key = key;
eventHandler.awaitAndPrematurelyCall([modDel]);
this.os.requestTransaction(function*(){
yield* this.applyCreatedOperations([del]);
eventHandler.awaitedLastDeletes(1);
});
struct: 'Delete'
}
var eventHandler = this.eventHandler
var modDel = copyObject(del)
modDel.key = key
eventHandler.awaitAndPrematurelyCall([modDel])
this.os.requestTransaction(function *() {
yield* this.applyCreatedOperations([del])
eventHandler.awaitedLastDeletes(1)
})
}
}
set (key, value) {
@ -113,108 +115,108 @@
// if property is a type, return a promise
// if not, apply immediately on this type an call event
var right = this.map[key] || null;
var right = this.map[key] || null
var insert = {
left: null,
right: right,
origin: null,
parent: this._model,
parentSub: key,
struct: "Insert"
};
var def = Promise.defer();
if ( value instanceof CustomType) {
// construct a new type
this.os.requestTransaction(function*(){
var type = yield* value.createType.call(this);
insert.opContent = type._model;
insert.id = this.store.getNextOpId();
yield* this.applyCreatedOperations([insert]);
def.resolve(type);
});
} else {
insert.content = value;
insert.id = this.os.getNextOpId();
var eventHandler = this.eventHandler;
eventHandler.awaitAndPrematurelyCall([insert]);
this.os.requestTransaction(function*(){
yield* this.applyCreatedOperations([insert]);
eventHandler.awaitedLastInserts(1);
});
def.resolve(value);
struct: 'Insert'
}
return def.promise;
var def = Promise.defer()
if (value instanceof CustomType) {
// construct a new type
this.os.requestTransaction(function *() {
var type = yield* value.createType.call(this)
insert.opContent = type._model
insert.id = this.store.getNextOpId()
yield* this.applyCreatedOperations([insert])
def.resolve(type)
})
} else {
insert.content = value
insert.id = this.os.getNextOpId()
var eventHandler = this.eventHandler
eventHandler.awaitAndPrematurelyCall([insert])
this.os.requestTransaction(function *() {
yield* this.applyCreatedOperations([insert])
eventHandler.awaitedLastInserts(1)
})
def.resolve(value)
}
return def.promise
}
observe (f) {
this.eventHandler.addUserEventListener(f);
this.eventHandler.addUserEventListener(f)
}
unobserve (f) {
this.eventHandler.removeUserEventListener(f);
this.eventHandler.removeUserEventListener(f)
}
observePath (path, f) {
var self = this;
var self = this
if (path.length === 0) {
this.observe(f);
return Promise.resolve(function(){
self.unobserve(f);
});
this.observe(f)
return Promise.resolve(function () {
self.unobserve(f)
})
} else {
var deleteChildObservers;
var resetObserverPath = function(){
var promise = self.get(path[0]);
var deleteChildObservers
var resetObserverPath = function () {
var promise = self.get(path[0])
if (!promise instanceof Promise) {
// its either not defined or a premitive value
promise = self.set(path[0], Y.Map);
promise = self.set(path[0], Y.Map)
}
return promise.then(function(map){
return map.observePath(path.slice(1), f);
}).then(function(_deleteChildObservers){
deleteChildObservers = _deleteChildObservers;
return Promise.resolve();
});
};
var observer = function(events){
return promise.then(function (map) {
return map.observePath(path.slice(1), f)
}).then(function (_deleteChildObservers) {
deleteChildObservers = _deleteChildObservers
return Promise.resolve()
})
}
var observer = function (events) {
for (var e in events) {
var event = events[e];
var event = events[e]
if (event.name === path[0]) {
deleteChildObservers();
if (event.type === "add" || event.type === "update") {
resetObserverPath();
deleteChildObservers()
if (event.type === 'add' || event.type === 'update') {
resetObserverPath()
}
}
}
};
self.observe(observer);
}
self.observe(observer)
return resetObserverPath().then(
Promise.resolve(function(){
deleteChildObservers();
self.unobserve(observer);
Promise.resolve(function () {
deleteChildObservers()
self.unobserve(observer)
})
);
)
}
}
*_changed (transaction, op) {
if (op.struct === "Delete") {
op.key = (yield* transaction.getOperation(op.target)).parentSub;
* _changed (transaction, op) {
if (op.struct === 'Delete') {
op.key = (yield* transaction.getOperation(op.target)).parentSub
}
this.eventHandler.receivedOp(op);
this.eventHandler.receivedOp(op)
}
}
Y.Map = new CustomType({
class: YMap,
createType: function* YMapCreator(){
createType: function * YMapCreator () {
var model = {
map: {},
struct: "Map",
type: "Map",
struct: 'Map',
type: 'Map',
id: this.store.getNextOpId()
};
yield* this.applyCreatedOperations([model]);
return yield* this.createType(model);
}
yield* this.applyCreatedOperations([model])
return yield* this.createType(model)
},
initType: function* YMapInitializer(os, model){
return new YMap(os, model);
initType: function * YMapInitializer (os, model) { // eslint-disable-line
return new YMap(os, model)
}
});
})();
})
})()

View File

@ -1,207 +1,207 @@
/* @flow */
/*eslint-env browser,jasmine */
/* global createUsers, Y, compareAllUsers, getRandomNumber, applyRandomTransactions */
/* eslint-env browser,jasmine */
var numberOfYMapTests = 100;
var numberOfYMapTests = 100
describe("Map Type", function(){
var y1, y2, y3, y4, flushAll;
describe('Map Type', function () {
var y1, y2, y3, y4, flushAll
jasmine.DEFAULT_TIMEOUT_INTERVAL = 50000;
beforeEach(async function(done){
await createUsers(this, 5);
y1 = this.users[0].root;
y2 = this.users[1].root;
y3 = this.users[2].root;
y4 = this.users[3].root;
flushAll = this.users[0].connector.flushAll;
done();
});
jasmine.DEFAULT_TIMEOUT_INTERVAL = 50000
beforeEach(async function (done) {
await createUsers(this, 5)
y1 = this.users[0].root
y2 = this.users[1].root
y3 = this.users[2].root
y4 = this.users[3].root
flushAll = this.users[0].connector.flushAll
done()
})
afterEach(async function(done) {
await compareAllUsers(this.users);
done();
}, 5000);
await compareAllUsers(this.users)
done()
}, 5000)
describe("Basic tests", function(){
it("Basic get&set of Map property (converge via sync)", async function(done){
y1.set("stuff", "stuffy");
expect(y1.get("stuff")).toEqual("stuffy");
await flushAll();
describe('Basic tests', function () {
it('Basic get&set of Map property (converge via sync)', async function (done) {
y1.set('stuff', 'stuffy')
expect(y1.get('stuff')).toEqual('stuffy')
await flushAll()
for (var key in this.users) {
var u = this.users[key].root;
expect(u.get("stuff")).toEqual("stuffy");
var u = this.users[key].root
expect(u.get('stuff')).toEqual('stuffy')
}
await compareAllUsers(this.users);
done();
});
it("Map can set custom types (Map)", async function(done){
var map = await y1.set("Map", Y.Map);
map.set("one", 1);
map = await y1.get("Map");
expect(map.get("one")).toEqual(1);
await compareAllUsers(this.users);
done();
});
it("Map can set custom types (Array)", async function(done){
var array = await y1.set("Array", Y.Array);
array.insert(0, [1, 2, 3]);
array = await y1.get("Array");
expect(array.toArray()).toEqual([1, 2, 3]);
await compareAllUsers(this.users);
done();
});
it("Basic get&set of Map property (converge via update)", async function(done){
await flushAll();
y1.set("stuff", "stuffy");
expect(y1.get("stuff")).toEqual("stuffy");
await compareAllUsers(this.users)
done()
})
it('Map can set custom types (Map)', async function (done) {
var map = await y1.set('Map', Y.Map)
map.set('one', 1)
map = await y1.get('Map')
expect(map.get('one')).toEqual(1)
await compareAllUsers(this.users)
done()
})
it('Map can set custom types (Array)', async function (done) {
var array = await y1.set('Array', Y.Array)
array.insert(0, [1, 2, 3])
array = await y1.get('Array')
expect(array.toArray()).toEqual([1, 2, 3])
await compareAllUsers(this.users)
done()
})
it('Basic get&set of Map property (converge via update)', async function (done) {
await flushAll()
y1.set('stuff', 'stuffy')
expect(y1.get('stuff')).toEqual('stuffy')
await flushAll();
await flushAll()
for (var key in this.users) {
var r = this.users[key].root;
expect(r.get("stuff")).toEqual("stuffy");
var r = this.users[key].root
expect(r.get('stuff')).toEqual('stuffy')
}
done();
});
it("Basic get&set of Map property (handle conflict)", async function(done){
await flushAll();
y1.set("stuff", "c0");
done()
})
it('Basic get&set of Map property (handle conflict)', async function (done) {
await flushAll()
y1.set('stuff', 'c0')
y2.set("stuff", "c1");
y2.set('stuff', 'c1')
await flushAll();
await flushAll()
for (var key in this.users) {
var u = this.users[key];
expect(u.root.get("stuff")).toEqual("c0");
var u = this.users[key]
expect(u.root.get('stuff')).toEqual('c0')
}
await compareAllUsers(this.users);
done();
});
it("Basic get&set&delete of Map property (handle conflict)", async function(done){
await flushAll();
y1.set("stuff", "c0");
y1.delete("stuff");
y2.set("stuff", "c1");
await flushAll();
await compareAllUsers(this.users)
done()
})
it('Basic get&set&delete of Map property (handle conflict)', async function (done) {
await flushAll()
y1.set('stuff', 'c0')
y1.delete('stuff')
y2.set('stuff', 'c1')
await flushAll()
for (var key in this.users) {
var u = this.users[key];
expect(u.root.get("stuff")).toBeUndefined();
var u = this.users[key]
expect(u.root.get('stuff')).toBeUndefined()
}
await compareAllUsers(this.users);
done();
});
it("Basic get&set of Map property (handle three conflicts)", async function(done){
await flushAll();
y1.set("stuff", "c0");
y2.set("stuff", "c1");
y2.set("stuff", "c2");
y3.set("stuff", "c3");
await flushAll();
await compareAllUsers(this.users)
done()
})
it('Basic get&set of Map property (handle three conflicts)', async function (done) {
await flushAll()
y1.set('stuff', 'c0')
y2.set('stuff', 'c1')
y2.set('stuff', 'c2')
y3.set('stuff', 'c3')
await flushAll()
for (var key in this.users) {
var u = this.users[key];
expect(u.root.get("stuff")).toEqual("c0");
var u = this.users[key]
expect(u.root.get('stuff')).toEqual('c0')
}
await compareAllUsers(this.users);
done();
});
it("Basic get&set&delete of Map property (handle three conflicts)", async function(done){
await flushAll();
y1.set("stuff", "c0");
y2.set("stuff", "c1");
y2.set("stuff", "c2");
y3.set("stuff", "c3");
await flushAll();
y1.set("stuff", "deleteme");
y1.delete("stuff");
y2.set("stuff", "c1");
y3.set("stuff", "c2");
y4.set("stuff", "c3");
await flushAll();
await compareAllUsers(this.users)
done()
})
it('Basic get&set&delete of Map property (handle three conflicts)', async function (done) {
await flushAll()
y1.set('stuff', 'c0')
y2.set('stuff', 'c1')
y2.set('stuff', 'c2')
y3.set('stuff', 'c3')
await flushAll()
y1.set('stuff', 'deleteme')
y1.delete('stuff')
y2.set('stuff', 'c1')
y3.set('stuff', 'c2')
y4.set('stuff', 'c3')
await flushAll()
for (var key in this.users) {
var u = this.users[key];
expect(u.root.get("stuff")).toBeUndefined();
var u = this.users[key]
expect(u.root.get('stuff')).toBeUndefined()
}
await compareAllUsers(this.users);
done();
});
it("throws add & update & delete events (with type and primitive content)", async function(done){
var event;
await flushAll();
y1.observe(function(e){
event = e; // just put it on event, should be thrown synchronously anyway
});
y1.set("stuff", 4);
await compareAllUsers(this.users)
done()
})
it('throws add & update & delete events (with type and primitive content)', async function (done) {
var event
await flushAll()
y1.observe(function (e) {
event = e // just put it on event, should be thrown synchronously anyway
})
y1.set('stuff', 4)
expect(event).toEqual([{
type: "add",
type: 'add',
object: y1,
name: "stuff"
}]);
name: 'stuff'
}])
// update, oldValue is in contents
await y1.set("stuff", Y.Array);
await y1.set('stuff', Y.Array)
expect(event).toEqual([{
type: "update",
type: 'update',
object: y1,
name: "stuff",
name: 'stuff',
oldValue: 4
}]);
y1.get("stuff").then(function(replacedArray){
}])
y1.get('stuff').then(function (replacedArray) {
// update, oldValue is in opContents
y1.set("stuff", 5);
var getYArray = event[0].oldValue;
expect(typeof getYArray.constructor === "function").toBeTruthy();
getYArray().then(function(array){
expect(array).toEqual(replacedArray);
y1.set('stuff', 5)
var getYArray = event[0].oldValue
expect(typeof getYArray.constructor === 'function').toBeTruthy()
getYArray().then(function (array) {
expect(array).toEqual(replacedArray)
// delete
y1.delete("stuff");
y1.delete('stuff')
expect(event).toEqual([{
type: "delete",
name: "stuff",
type: 'delete',
name: 'stuff',
object: y1,
oldValue: 5
}]);
done();
});
});
});
});
describe(`${numberOfYMapTests} Random tests`, function(){
}])
done()
})
})
})
})
describe(`${numberOfYMapTests} Random tests`, function () {
var randomMapTransactions = [
function set (map) {
map.set("somekey", getRandomNumber());
map.set('somekey', getRandomNumber())
},
function delete_ (map) {
map.delete("somekey");
map.delete('somekey')
}
];
function compareMapValues(maps){
var firstMap;
]
function compareMapValues (maps) {
var firstMap
for (var map of maps) {
var val = map.get();
var val = map.get()
if (firstMap == null) {
firstMap = val;
firstMap = val
} else {
expect(val).toEqual(firstMap);
expect(val).toEqual(firstMap)
}
}
}
beforeEach(async function(done){
await y1.set("Map", Y.Map);
await flushAll();
beforeEach(async function (done) {
await y1.set('Map', Y.Map)
await flushAll()
var promises = [];
var promises = []
for (var u = 0; u < this.users.length; u++) {
promises.push(this.users[u].root.get("Map"));
promises.push(this.users[u].root.get('Map'))
}
this.maps = await Promise.all(promises);
done();
});
it(`succeed after ${numberOfYMapTests} actions`, async function(done){
await applyRandomTransactions(this.users, this.maps, randomMapTransactions, numberOfYMapTests);
await flushAll();
await compareMapValues(this.maps);
done();
});
});
});
this.maps = await Promise.all(promises)
done()
})
it(`succeed after ${numberOfYMapTests} actions`, async function (done) {
await applyRandomTransactions(this.users, this.maps, randomMapTransactions, numberOfYMapTests)
await flushAll()
await compareMapValues(this.maps)
done()
})
})
})

View File

@ -1,287 +1,288 @@
/* global Y, CustomType */
(function(){
class YTextBind extends Y.Array.class {
;(function () {
class YTextBind extends Y . Array . class {
constructor (os, _model, idArray, valArray) {
super(os, _model, idArray, valArray);
this.textfields = [];
super(os, _model, idArray, valArray)
this.textfields = []
}
toString () {
return this.valArray.join("");
return this.valArray.join('')
}
insert (pos, content) {
super(pos, content.split(""));
super(pos, content.split(''))
}
bind (textfield, domRoot) {
domRoot = domRoot || window; //eslint-disable-line
if (domRoot.getSelection == null) {
domRoot = window;//eslint-disable-line
}
domRoot = domRoot || window; // eslint-disable-line
if (domRoot.getSelection == null) {
domRoot = window;// eslint-disable-line
}
// don't duplicate!
for (var t in this.textfields) {
if (this.textfields[t] === textfield) {
return;
// don't duplicate!
for (var t in this.textfields) {
if (this.textfields[t] === textfield) {
return
}
}
var creatorToken = false
var word = this
textfield.value = this.toString()
this.textfields.push(textfield)
var createRange, writeRange, writeContent
if (textfield.selectionStart != null && textfield.setSelectionRange != null) {
createRange = function (fix) {
var left = textfield.selectionStart
var right = textfield.selectionEnd
if (fix != null) {
left = fix(left)
right = fix(right)
}
return {
left: left,
right: right
}
}
var creatorToken = false;
writeRange = function (range) {
writeContent(word.toString())
textfield.setSelectionRange(range.left, range.right)
}
writeContent = function (content) {
textfield.value = content
}
} else {
createRange = function (fix) {
var range = {}
var s = domRoot.getSelection()
var clength = textfield.textContent.length
range.left = Math.min(s.anchorOffset, clength)
range.right = Math.min(s.focusOffset, clength)
if (fix != null) {
range.left = fix(range.left)
range.right = fix(range.right)
}
var editedElement = s.focusNode
if (editedElement === textfield || editedElement === textfield.childNodes[0]) {
range.isReal = true
} else {
range.isReal = false
}
return range
}
var word = this;
textfield.value = this.toString();
this.textfields.push(textfield);
var createRange, writeRange, writeContent;
if(textfield.selectionStart != null && textfield.setSelectionRange != null) {
createRange = function (fix) {
var left = textfield.selectionStart;
var right = textfield.selectionEnd;
if (fix != null) {
left = fix(left);
right = fix(right);
writeRange = function (range) {
writeContent(word.toString())
var textnode = textfield.childNodes[0]
if (range.isReal && textnode != null) {
if (range.left < 0) {
range.left = 0
}
return {
left: left,
right: right
};
};
writeRange = function (range) {
writeContent(word.toString());
textfield.setSelectionRange(range.left, range.right);
};
writeContent = function (content){
textfield.value = content;
};
range.right = Math.max(range.left, range.right)
if (range.right > textnode.length) {
range.right = textnode.length
}
range.left = Math.min(range.left, range.right)
var r = document.createRange(); // eslint-disable-line
r.setStart(textnode, range.left)
r.setEnd(textnode, range.right)
var s = window.getSelection(); // eslint-disable-line
s.removeAllRanges()
s.addRange(r)
}
}
writeContent = function (content) {
var contentArray = content.replace(new RegExp('\n', 'g'), ' ').split(' ');// eslint-disable-line
textfield.innerText = ''
for (var i in contentArray) {
var c = contentArray[i]
textfield.innerText += c
if (i !== contentArray.length - 1) {
textfield.innerHTML += '&nbsp;'
}
}
}
}
writeContent(this.toString())
this.observe(function (events) {
for (var e in events) {
var event = events[e]
if (!creatorToken) {
var oPos, fix
if (event.type === 'insert') {
oPos = event.index
fix = function (cursor) {// eslint-disable-line
if (cursor <= oPos) {
return cursor
} else {
cursor += 1
return cursor
}
}
var r = createRange(fix)
writeRange(r)
} else if (event.type === 'delete') {
oPos = event.index
fix = function (cursor) {// eslint-disable-line
if (cursor < oPos) {
return cursor
} else {
cursor -= 1
return cursor
}
}
r = createRange(fix)
writeRange(r)
}
}
}
})
// consume all text-insert changes.
textfield.onkeypress = function (event) {
if (word.is_deleted) {
// if word is deleted, do not do anything ever again
textfield.onkeypress = null
return true
}
creatorToken = true
var char
if (event.keyCode === 13) {
char = '\n'
} else if (event.key != null) {
if (event.charCode === 32) {
char = ' '
} else {
char = event.key
}
} else {
createRange = function (fix) {
var range = {};
var s = domRoot.getSelection();
var clength = textfield.textContent.length;
range.left = Math.min(s.anchorOffset, clength);
range.right = Math.min(s.focusOffset, clength);
if(fix != null){
range.left = fix(range.left);
range.right = fix(range.right);
}
var editedElement = s.focusNode;
if(editedElement === textfield || editedElement === textfield.childNodes[0]){
range.isReal = true;
} else {
range.isReal = false;
}
return range;
};
writeRange = function (range) {
writeContent(word.toString());
var textnode = textfield.childNodes[0];
if(range.isReal && textnode != null) {
if(range.left < 0){
range.left = 0;
}
range.right = Math.max(range.left, range.right);
if (range.right > textnode.length) {
range.right = textnode.length;
}
range.left = Math.min(range.left, range.right);
var r = document.createRange(); //eslint-disable-line
r.setStart(textnode, range.left);
r.setEnd(textnode, range.right);
var s = window.getSelection(); //eslint-disable-line
s.removeAllRanges();
s.addRange(r);
}
};
writeContent = function (content) {
var contentArray = content.replace(new RegExp("\n", 'g')," ").split(" ");//eslint-disable-line
textfield.innerText = "";
for(var i in contentArray){
var c = contentArray[i];
textfield.innerText += c;
if(i !== contentArray.length - 1){
textfield.innerHTML += "&nbsp;";
}
}
};
char = window.String.fromCharCode(event.keyCode); // eslint-disable-line
}
writeContent(this.toString());
this.observe(function (events) {
for(var e in events) {
var event = events[e];
if (!creatorToken) {
var oPos, fix;
if( event.type === "insert") {
oPos = event.index;
fix = function (cursor) {//eslint-disable-line
if (cursor <= oPos) {
return cursor;
} else {
cursor += 1;
return cursor;
}
};
var r = createRange(fix);
writeRange(r);
} else if (event.type === "delete") {
oPos = event.index;
fix = function (cursor){//eslint-disable-line
if (cursor < oPos) {
return cursor;
} else {
cursor -= 1;
return cursor;
}
};
r = createRange(fix);
writeRange(r);
if (char.length > 1) {
return true
} else if (char.length > 0) {
var r = createRange()
var pos = Math.min(r.left, r.right, word.length)
var diff = Math.abs(r.right - r.left)
word.delete(pos, diff)
word.insert(pos, char)
r.left = pos + char.length
r.right = r.left
writeRange(r)
}
event.preventDefault()
creatorToken = false
return false
}
textfield.onpaste = function (event) {
if (word.is_deleted) {
// if word is deleted, do not do anything ever again
textfield.onpaste = null
return true
}
event.preventDefault()
}
textfield.oncut = function (event) {
if (word.is_deleted) {
// if word is deleted, do not do anything ever again
textfield.oncut = null
return true
}
event.preventDefault()
}
//
// consume deletes. Note that
// chrome: won't consume deletions on keypress event.
// keyCode is deprecated. BUT: I don't see another way.
// since event.key is not implemented in the current version of chrome.
// Every browser supports keyCode. Let's stick with it for now..
//
textfield.onkeydown = function (event) {
creatorToken = true
if (word.is_deleted) {
// if word is deleted, do not do anything ever again
textfield.onkeydown = null
return true
}
var r = createRange()
var pos = Math.min(r.left, r.right, word.toString().length)
var diff = Math.abs(r.left - r.right)
if (event.keyCode != null && event.keyCode === 8) { // Backspace
if (diff > 0) {
word.delete(pos, diff)
r.left = pos
r.right = pos
writeRange(r)
} else {
if (event.ctrlKey != null && event.ctrlKey) {
var val = word.toString()
var newPos = pos
var delLength = 0
if (pos > 0) {
newPos--
delLength++
}
while (newPos > 0 && val[newPos] !== ' ' && val[newPos] !== '\n') {
newPos--
delLength++
}
word.delete(newPos, pos - newPos)
r.left = newPos
r.right = newPos
writeRange(r)
} else {
if (pos > 0) {
word.delete(pos - 1, 1)
r.left = pos - 1
r.right = pos - 1
writeRange(r)
}
}
}
});
// consume all text-insert changes.
textfield.onkeypress = function (event) {
if (word.is_deleted) {
// if word is deleted, do not do anything ever again
textfield.onkeypress = null;
return true;
}
creatorToken = true;
var char;
if (event.keyCode === 13) {
char = "\n";
} else if (event.key != null) {
if (event.charCode === 32) {
char = " ";
} else {
char = event.key;
}
event.preventDefault()
creatorToken = false
return false
} else if (event.keyCode != null && event.keyCode === 46) { // Delete
if (diff > 0) {
word.delete(pos, diff)
r.left = pos
r.right = pos
writeRange(r)
} else {
char = window.String.fromCharCode(event.keyCode); //eslint-disable-line
word.delete(pos, 1)
r.left = pos
r.right = pos
writeRange(r)
}
if (char.length > 1) {
return true;
} else if (char.length > 0) {
var r = createRange();
var pos = Math.min(r.left, r.right, word.length);
var diff = Math.abs(r.right - r.left);
word.delete(pos, diff);
word.insert(pos, char);
r.left = pos + char.length;
r.right = r.left;
writeRange(r);
}
event.preventDefault();
creatorToken = false;
return false;
};
textfield.onpaste = function (event) {
if (word.is_deleted) {
// if word is deleted, do not do anything ever again
textfield.onpaste = null;
return true;
}
event.preventDefault();
};
textfield.oncut = function (event) {
if (word.is_deleted) {
// if word is deleted, do not do anything ever again
textfield.oncut = null;
return true;
}
event.preventDefault();
};
//
// consume deletes. Note that
// chrome: won't consume deletions on keypress event.
// keyCode is deprecated. BUT: I don't see another way.
// since event.key is not implemented in the current version of chrome.
// Every browser supports keyCode. Let's stick with it for now..
//
textfield.onkeydown = function (event) {
creatorToken = true;
if (word.is_deleted) {
// if word is deleted, do not do anything ever again
textfield.onkeydown = null;
return true;
}
var r = createRange();
var pos = Math.min(r.left, r.right, word.toString().length);
var diff = Math.abs(r.left - r.right);
if (event.keyCode != null && event.keyCode === 8) { // Backspace
if (diff > 0) {
word.delete(pos, diff);
r.left = pos;
r.right = pos;
writeRange(r);
} else {
if (event.ctrlKey != null && event.ctrlKey) {
var val = word.toString();
var newPos = pos;
var delLength = 0;
if (pos > 0) {
newPos--;
delLength++;
}
while (newPos > 0 && val[newPos] !== " " && val[newPos] !== "\n") {
newPos--;
delLength++;
}
word.delete(newPos, pos - newPos);
r.left = newPos;
r.right = newPos;
writeRange(r);
} else {
if (pos > 0) {
word.delete(pos - 1, 1);
r.left = pos - 1;
r.right = pos - 1;
writeRange(r);
}
}
}
event.preventDefault();
creatorToken = false;
return false;
} else if (event.keyCode != null && event.keyCode === 46) { // Delete
if (diff > 0) {
word.delete(pos, diff);
r.left = pos;
r.right = pos;
writeRange(r);
} else {
word.delete(pos, 1);
r.left = pos;
r.right = pos;
writeRange(r);
}
event.preventDefault();
creatorToken = false;
return false;
} else {
creatorToken = false;
return true;
}
};
event.preventDefault()
creatorToken = false
return false
} else {
creatorToken = false
return true
}
}
}
}
Y.TextBind = new CustomType({
class: YTextBind,
createType: function* YTextBindCreator () {
createType: function * YTextBindCreator () {
var model = {
start: null,
end: null,
struct: "List",
type: "TextBind",
struct: 'List',
type: 'TextBind',
id: this.store.getNextOpId()
};
yield* this.applyCreatedOperations([model]);
return yield* this.createType(model);
}
yield* this.applyCreatedOperations([model])
return yield* this.createType(model)
},
initType: function* YTextBindInitializer(os, model){
var valArray = [];
var idArray = yield* Y.Struct.List.map.call(this, model, function(c){
valArray.push(c.content);
return JSON.stringify(c.id);
});
return new YTextBind(os, model.id, idArray, valArray);
initType: function * YTextBindInitializer (os, model) {
var valArray = []
var idArray = yield* Y.Struct.List.map.call(this, model, function (c) {
valArray.push(c.content)
return JSON.stringify(c.id)
})
return new YTextBind(os, model.id, idArray, valArray)
}
});
})();
})
})()

View File

@ -1,100 +1,101 @@
/* global copyObject, compareIds */
var GeneratorFunction = (function*(){}).constructor;//eslint-disable-line
var GeneratorFunction = (function *() {}).constructor;// eslint-disable-line
class EventHandler { //eslint-disable-line
class EventHandler { // eslint-disable-line
constructor (onevent) {
this.waiting = [];
this.awaiting = 0;
this.onevent = onevent;
this.userEventListeners = [];
this.waiting = []
this.awaiting = 0
this.onevent = onevent
this.userEventListeners = []
}
receivedOp (op) {
if (this.awaiting <= 0) {
this.onevent([op]);
this.onevent([op])
} else {
this.waiting.push(copyObject(op));
this.waiting.push(copyObject(op))
}
}
awaitAndPrematurelyCall (ops) {
this.awaiting++;
this.onevent(ops);
this.awaiting++
this.onevent(ops)
}
addUserEventListener (f) {
this.userEventListeners.push(f);
this.userEventListeners.push(f)
}
removeUserEventListener (f) {
this.userEventListeners = this.userEventListeners.filter(function(g){
return f !== g;
});
this.userEventListeners = this.userEventListeners.filter(function (g) {
return f !== g
})
}
removeAllUserEventListeners () {
this.userEventListeners = [];
this.userEventListeners = []
}
callUserEventListeners (event) {
for (var i in this.userEventListeners) {
try {
this.userEventListeners[i](event);
this.userEventListeners[i](event)
} catch (e) {
console.log("User events must not throw Errors!");//eslint-disable-line
console.log('User events must not throw Errors!');// eslint-disable-line
}
}
}
awaitedLastInserts (n) {
var ops = this.waiting.splice(this.waiting.length - n);
var ops = this.waiting.splice(this.waiting.length - n)
for (var oid = 0; oid < ops.length; oid++) {
var op = ops[oid];
var op = ops[oid]
for (var i = this.waiting.length - 1; i >= 0; i--) {
let w = this.waiting[i];
let w = this.waiting[i]
if (compareIds(op.left, w.id)) {
// include the effect of op in w
w.right = op.id;
w.right = op.id
// exclude the effect of w in op
op.left = w.left;
op.left = w.left
} else if (compareIds(op.right, w.id)) {
// similar..
w.left = op.id;
op.right = w.right;
w.left = op.id
op.right = w.right
}
}
}
this.tryCallEvents();
this.tryCallEvents()
}
awaitedLastDeletes (n, newLeft) {
var ops = this.waiting.splice(this.waiting.length - n);
var ops = this.waiting.splice(this.waiting.length - n)
for (var j in ops) {
var del = ops[j];
var del = ops[j]
if (newLeft != null) {
for (var i in this.waiting) {
let w = this.waiting[i];
let w = this.waiting[i]
// We will just care about w.left
if (compareIds(del.target, w.left)) {
del.left = newLeft;
del.left = newLeft
}
}
}
}
this.tryCallEvents();
this.tryCallEvents()
}
tryCallEvents () {
this.awaiting--;
this.awaiting--
if (this.awaiting <= 0 && this.waiting.length > 0) {
var events = this.waiting;
this.waiting = [];
this.onevent(events);
var events = this.waiting
this.waiting = []
this.onevent(events)
}
}
}
class CustomType { //eslint-disable-line
class CustomType { // eslint-disable-line
constructor (def) {
if (def.createType == null
|| def.initType == null
|| def.class == null
if (def.createType == null ||
def.initType == null ||
def.class == null
) {
throw new Error("Custom type was not initialized correctly!");
throw new Error('Custom type was not initialized correctly!')
}
this.createType = def.createType;
this.initType = def.initType;
this.class = def.class;
this.createType = def.createType
this.initType = def.initType
this.class = def.class
}
}