From ee983ceff6457050627dd35af1ebf7cc3f979772 Mon Sep 17 00:00:00 2001 From: Kevin Jahns <kevin.jahns@rwth-aachen.de> Date: Tue, 21 Jul 2015 17:14:03 +0200 Subject: [PATCH] switched to *standard* coding style --- .eslintignore | 4 - .eslintrc | 43 -- .flowconfig | 13 - Examples/TextBind/index.js | 39 +- gulpfile.js | 154 ++++--- interfaces/jasmine.js | 5 - package.json | 17 +- src/Connector.js | 247 ++++++----- src/Connectors/Test.js | 104 ++--- src/Connectors/WebRTC.js | 85 ++-- src/Helper.spec.js | 98 ++--- src/OperationStore.js | 224 +++++----- src/OperationStore.spec.js | 5 - src/OperationStores/IndexedDB.js | 261 ++++++------ src/OperationStores/IndexedDB.spec.js | 200 ++++----- src/OperationStores/Memory.js | 181 ++++---- src/OperationStores/RedBlackTree.js | 374 +++++++++-------- src/OperationStores/RedBlackTree.spec.js | 342 ++++++++-------- src/Struct.js | 270 ++++++------ src/Struct.spec.js | 0 src/Types/Array.js | 195 +++++---- src/Types/Array.spec.js | 230 +++++------ src/Types/Map.js | 250 ++++++------ src/Types/Map.spec.js | 316 +++++++------- src/Types/TextBind.js | 499 ++++++++++++----------- src/Utils.js | 85 ++-- 26 files changed, 2052 insertions(+), 2189 deletions(-) delete mode 100644 .eslintignore delete mode 100644 .eslintrc delete mode 100644 .flowconfig delete mode 100644 interfaces/jasmine.js delete mode 100644 src/OperationStore.spec.js delete mode 100644 src/Struct.spec.js diff --git a/.eslintignore b/.eslintignore deleted file mode 100644 index 2778cf50..00000000 --- a/.eslintignore +++ /dev/null @@ -1,4 +0,0 @@ -build/ -y.js -y.js.map -interfaces/ diff --git a/.eslintrc b/.eslintrc deleted file mode 100644 index d1b1705f..00000000 --- a/.eslintrc +++ /dev/null @@ -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 - } -} diff --git a/.flowconfig b/.flowconfig deleted file mode 100644 index 4e39e3aa..00000000 --- a/.flowconfig +++ /dev/null @@ -1,13 +0,0 @@ -[ignore] -.*/node_modules/.* -.*/build/.* -./y.js -./y.js.map - -[include] - -[libs] -./interfaces -./src - -[options] diff --git a/Examples/TextBind/index.js b/Examples/TextBind/index.js index 6d5da99a..32d25c4c 100644 --- a/Examples/TextBind/index.js +++ b/Examples/TextBind/index.js @@ -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) +}) diff --git a/gulpfile.js b/gulpfile.js index c3f54f44..e2a17a5c 100644 --- a/gulpfile.js +++ b/gulpfile.js @@ -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(".")); -}); - -gulp.task("lint", function(){ - return gulp.src(files.lint) - .pipe(eslint()) - .pipe(eslint.format()) - .pipe(eslint.failOnError()); -}); - -gulp.task("test", function () { + .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('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")); -}); - - -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")) + .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']) + + 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']) diff --git a/interfaces/jasmine.js b/interfaces/jasmine.js deleted file mode 100644 index a7cdfe6e..00000000 --- a/interfaces/jasmine.js +++ /dev/null @@ -1,5 +0,0 @@ -/* @flow */ - -declare var describe : Function; -declare var it : Function; -declare var expect : Function; diff --git a/package.json b/package.json index 086b8519..18425ac4 100644 --- a/package.json +++ b/package.json @@ -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" } } diff --git a/src/Connector.js b/src/Connector.js index fffe84f7..96be5dfb 100644 --- a/src/Connector.js +++ b/src/Connector.js @@ -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!") } } } diff --git a/src/Connectors/Test.js b/src/Connectors/Test.js index 9152e8ae..68d47c7c 100644 --- a/src/Connectors/Test.js +++ b/src/Connectors/Test.js @@ -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 diff --git a/src/Connectors/WebRTC.js b/src/Connectors/WebRTC.js index 4a3550d9..11e750d0 100644 --- a/src/Connectors/WebRTC.js +++ b/src/Connectors/WebRTC.js @@ -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 diff --git a/src/Helper.spec.js b/src/Helper.spec.js index 560ec628..3045459d 100644 --- a/src/Helper.spec.js +++ b/src/Helper.spec.js @@ -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) } diff --git a/src/OperationStore.js b/src/OperationStore.js index 421d94ed..2f462eb1 100644 --- a/src/OperationStore.js +++ b/src/OperationStore.js @@ -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) } } diff --git a/src/OperationStore.spec.js b/src/OperationStore.spec.js deleted file mode 100644 index 48739ae3..00000000 --- a/src/OperationStore.spec.js +++ /dev/null @@ -1,5 +0,0 @@ -/* @flow */ -/*eslint-env browser,jasmine,console */ - -describe("OperationStore", function() { -}); diff --git a/src/OperationStores/IndexedDB.js b/src/OperationStores/IndexedDB.js index 6f3184d5..b8e24cbc 100644 --- a/src/OperationStores/IndexedDB.js +++ b/src/OperationStores/IndexedDB.js @@ -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; + * getStateVector () { + var stateVector = [] + var cursorResult = this.sv.openCursor() + var cursor while ((cursor = yield cursorResult) != null) { - stateVector.push(cursor.value); - cursor.continue(); + stateVector.push(cursor.value) + cursor.continue() } - return stateVector; + return stateVector } - *getStateSet () : StateSet { - var sv : StateVector = yield* this.getStateVector(); - var ss : StateSet = {}; - for (var state of sv){ - ss[state.user] = state.clock; + * getStateSet () { + var sv = yield* this.getStateVector() + var ss = {} + for (var state of sv) { + ss[state.user] = state.clock } - return ss; + return ss } - *getOperations (startSS : StateSet) { - if (startSS == null){ - startSS = {}; + * getOperations (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; - 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; + 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(); + ops.push(cursor.value) + cursor.continue() } } - return ops; + 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); - }; + })() + + 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)) + } + request.onerror = function (err) { + tGen.throw(err) + } } else if (request === store.transactionQueue) { - if (request.queue.length > 0){ - handleTransactions(tGen.next(request.queue.shift())); + if (request.queue.length > 0) { + handleTransactions(tGen.next(request.queue.shift())) } else { - request.onRequest = function(){ - request.onRequest = null; - handleTransactions(tGen.next(request.queue.shift())); - }; + 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)) } - } 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; + 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"}); + db.createObjectStore('OperationStore', {keyPath: 'id'}) + db.createObjectStore('StateVector', {keyPath: 'user'}) } catch (e) { - // console.log("Store already exists!"); + // 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 +})() diff --git a/src/OperationStores/IndexedDB.spec.js b/src/OperationStores/IndexedDB.spec.js index 7d542d5a..159d4ede 100644 --- a/src/OperationStores/IndexedDB.spec.js +++ b/src/OperationStores/IndexedDB.spec.js @@ -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() + }) + }) + }) } diff --git a/src/OperationStores/Memory.js b/src/OperationStores/Memory.js index 93c6546b..fed52b39 100644 --- a/src/OperationStores/Memory.js +++ b/src/OperationStores/Memory.js @@ -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 +})() diff --git a/src/OperationStores/RedBlackTree.js b/src/OperationStores/RedBlackTree.js index 5f60db17..9a627ef8 100644 --- a/src/OperationStores/RedBlackTree.js +++ b/src/OperationStores/RedBlackTree.js @@ -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) } } } diff --git a/src/OperationStores/RedBlackTree.spec.js b/src/OperationStores/RedBlackTree.spec.js index 6183b3af..1eff8e76 100644 --- a/src/OperationStores/RedBlackTree.spec.js +++ b/src/OperationStores/RedBlackTree.spec.js @@ -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"); - }); - - 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]}); - - itRootNodeIsBlack(tree, []); - itBlackHeightOfSubTreesAreEqual(tree, []); - }); +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('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, []) + }) 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); - - 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; - - 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; - - 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; + }) + itRedNodesDoNotHaveBlackChildren(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 + + 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 + + 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 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) + }) + }) +}) diff --git a/src/Struct.js b/src/Struct.js index 37884537..e0e1caa5 100644 --- a/src/Struct.js +++ b/src/Struct.js @@ -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 diff --git a/src/Struct.spec.js b/src/Struct.spec.js deleted file mode 100644 index e69de29b..00000000 diff --git a/src/Types/Array.js b/src/Types/Array.js index 3c58e73a..189d79bf 100644 --- a/src/Types/Array.js +++ b/src/Types/Array.js @@ -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) } - }); -})(); + }) +})() diff --git a/src/Types/Array.spec.js b/src/Types/Array.spec.js index b7cf1334..b9b9c983 100644 --- a/src/Types/Array.spec.js +++ b/src/Types/Array.spec.js @@ -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() + }) + }) +}) diff --git a/src/Types/Map.js b/src/Types/Map.js index bfb28a85..e7f0debf 100644 --- a/src/Types/Map.js +++ b/src/Types/Map.js @@ -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"; + insertEvent.type = 'add' } else { - insertEvent.type = "update"; - insertEvent.oldValue = oldValue; + insertEvent.type = 'update' + insertEvent.oldValue = oldValue } - userEvents.push(insertEvent); + 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) { + 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); - }); + 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]); + 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); + this.os.requestTransaction(function *() { + yield* this.applyCreatedOperations([insert]) + eventHandler.awaitedLastInserts(1) + }) + def.resolve(value) } - return def.promise; + 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) } - }); -})(); + }) +})() diff --git a/src/Types/Map.spec.js b/src/Types/Map.spec.js index b27a1110..4f448814 100644 --- a/src/Types/Map.spec.js +++ b/src/Types/Map.spec.js @@ -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() + }) + }) +}) diff --git a/src/Types/TextBind.js b/src/Types/TextBind.js index 4f88991b..9498399c 100644 --- a/src/Types/TextBind.js +++ b/src/Types/TextBind.js @@ -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 } + } + var creatorToken = false - // don't duplicate! - for (var t in this.textfields) { - if (this.textfields[t] === textfield) { - return; + 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 + } + } + 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 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 - }; - }; - 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; + writeRange = function (range) { + writeContent(word.toString()) + var textnode = textfield.childNodes[0] + if (range.isReal && textnode != null) { + if (range.left < 0) { + range.left = 0 } - 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); + range.right = Math.max(range.left, range.right) + if (range.right > textnode.length) { + range.right = textnode.length } - }; - 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 += " "; - } + 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 += ' ' } - }; + } } - writeContent(this.toString()); + } + 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); + 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; - } + } + }) + // 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 = window.String.fromCharCode(event.keyCode); //eslint-disable-line - } - 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; + char = event.key } - 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); - } + } else { + char = window.String.fromCharCode(event.keyCode); // eslint-disable-line + } + 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++ } - } - 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); + while (newPos > 0 && val[newPos] !== ' ' && val[newPos] !== '\n') { + newPos-- + delLength++ + } + word.delete(newPos, pos - newPos) + r.left = newPos + r.right = newPos + writeRange(r) } else { - word.delete(pos, 1); - r.left = pos; - r.right = pos; - writeRange(r); + if (pos > 0) { + word.delete(pos - 1, 1) + r.left = pos - 1 + r.right = pos - 1 + writeRange(r) + } } - event.preventDefault(); - creatorToken = false; - return false; + } + 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 { - creatorToken = false; - return true; + word.delete(pos, 1) + r.left = pos + r.right = pos + writeRange(r) } - }; + 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) } - }); -})(); + }) +})() diff --git a/src/Utils.js b/src/Utils.js index c24c59fc..645597f7 100644 --- a/src/Utils.js +++ b/src/Utils.js @@ -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 } } -- GitLab