From 2d20fd59d0b7154b58c85defe5f99a46f41a35c4 Mon Sep 17 00:00:00 2001 From: Kevin Jahns Date: Mon, 9 Nov 2015 03:03:37 +0100 Subject: [PATCH] outsourced Textbind, improved automatic module loader --- gulpfile.helper.js | 10 +- gulpfile.js | 2 - package.json | 3 +- src/Databases/IndexedDB.js | 179 ----------- src/Databases/Memory.js | 62 ---- src/Databases/RedBlackTree.js | 490 ----------------------------- src/Databases/RedBlackTree.spec.js | 213 ------------- src/SpecHelper.js | 7 +- src/Types/Array.js | 198 ------------ src/Types/Array.spec.js | 312 ------------------ src/Types/TextBind.js | 289 ----------------- src/y.js | 73 +++-- 12 files changed, 50 insertions(+), 1788 deletions(-) delete mode 100644 src/Databases/IndexedDB.js delete mode 100644 src/Databases/Memory.js delete mode 100644 src/Databases/RedBlackTree.js delete mode 100644 src/Databases/RedBlackTree.spec.js delete mode 100644 src/Types/Array.js delete mode 100644 src/Types/Array.spec.js delete mode 100644 src/Types/TextBind.js diff --git a/gulpfile.helper.js b/gulpfile.helper.js index e9b975fd..7925246a 100644 --- a/gulpfile.helper.js +++ b/gulpfile.helper.js @@ -5,15 +5,15 @@ var minimist = require('minimist') module.exports = function (gulp, helperOptions) { var runSequence = require('run-sequence').use(gulp) var options = minimist(process.argv.slice(2), { - string: ['modulename', 'export', 'name', 'testport', 'testfiles'], + string: ['modulename', 'export', 'name', 'port', 'testfiles'], default: { modulename: helperOptions.moduleName, targetName: helperOptions.targetName, export: 'ignore', - testport: '8888', + port: '8888', testfiles: '**/*.spec.js', browserify: helperOptions.browserify != null ? helperOptions.browserify : false, - regenerator: true, + regenerator: false, debug: false } }) @@ -80,7 +80,7 @@ module.exports = function (gulp, helperOptions) { .pipe(source('specs.js')) .pipe(buffer()) .pipe($.sourcemaps.init({loadMaps: true})) - .pipe($.sourcemaps.write()) + .pipe($.sourcemaps.write('.')) .pipe(gulp.dest('./build/')) }) @@ -88,7 +88,7 @@ module.exports = function (gulp, helperOptions) { gulp.watch(files.src, ['spec-build']) return gulp.src('./build/specs.js') .pipe($.jasmineBrowser.specRunner()) - .pipe($.jasmineBrowser.server({port: options.testport})) + .pipe($.jasmineBrowser.server({port: options.port})) }) gulp.task('test', function () { diff --git a/gulpfile.js b/gulpfile.js index e2416b32..8fcf38bb 100644 --- a/gulpfile.js +++ b/gulpfile.js @@ -53,8 +53,6 @@ require('./gulpfile.helper.js')(gulp, { targetName: 'y.js', moduleName: 'yjs', specs: [ - './src/Databases/RedBlackTree.spec.js', - './src/Types/Array.spec.js', './src/Types/Map.spec.js', './src/Database.spec.js' ] diff --git a/package.json b/package.json index 89a6a40c..36c86870 100644 --- a/package.json +++ b/package.json @@ -5,8 +5,7 @@ "main": "y.js", "scripts": { "test": "node --harmony ./node_modules/.bin/gulp test", - "lint": "./node_modules/.bin/standard", - "build": "./node_modules/.bin/gulp build" + "lint": "./node_modules/.bin/standard" }, "pre-commit": [ "lint", diff --git a/src/Databases/IndexedDB.js b/src/Databases/IndexedDB.js deleted file mode 100644 index 060cebec..00000000 --- a/src/Databases/IndexedDB.js +++ /dev/null @@ -1,179 +0,0 @@ -'use strict' - -module.exports = function (Y) { - class Store { - constructor (transaction, name) { - this.store = transaction.objectStore(name) - } - * find (id) { - return yield this.store.get(id) - } - * put (v) { - yield this.store.put(v) - } - * delete (id) { - yield this.store.delete(id) - } - * findWithLowerBound (start) { - return yield this.store.openCursor(window.IDBKeyRange.lowerBound(start)) - } - * findWithUpperBound (end) { - return yield this.store.openCursor(window.IDBKeyRange.upperBound(end), 'prev') - } - * findNext (id) { - return yield* this.findWithLowerBound([id[0], id[1] + 1]) - } - * findPrev (id) { - return yield* this.findWithUpperBound([id[0], id[1] - 1]) - } - * iterate (t, start, end, gen) { - var range = null - if (start != null && end != null) { - range = window.IDBKeyRange.bound(start, end) - } else if (start != null) { - range = window.IDBKeyRange.lowerBound(start) - } else if (end != null) { - range = window.IDBKeyRange.upperBound(end) - } - var cursorResult = this.store.openCursor(range) - while ((yield cursorResult) != null) { - yield* gen.call(t, cursorResult.result.value) - cursorResult.result.continue() - } - } - - } - class Transaction extends Y.Transaction { - constructor (store) { - super(store) - var transaction = store.db.transaction(['OperationStore', 'StateStore', 'DeleteStore'], 'readwrite') - this.store = store - this.ss = new Store(transaction, 'StateStore') - this.os = new Store(transaction, 'OperationStore') - this.ds = new Store(transaction, 'DeleteStore') - } - } - class OperationStore extends Y.AbstractDatabase { - constructor (y, opts) { - super(y, opts) - if (opts == null) { - opts = {} - } - if (opts.namespace == null || typeof opts.namespace !== 'string') { - throw new Error('IndexedDB: expect a string (opts.namespace)!') - } else { - this.namespace = opts.namespace - } - if (opts.idbVersion != null) { - this.idbVersion = opts.idbVersion - } else { - this.idbVersion = 5 - } - var store = this - // initialize database! - this.requestTransaction(function * () { - store.db = yield window.indexedDB.open(opts.namespace, store.idbVersion) - }) - if (opts.cleanStart) { - this.requestTransaction(function * () { - yield this.os.store.clear() - yield this.ds.store.clear() - yield this.ss.store.clear() - }) - } - var operationsToAdd = [] - window.addEventListener('storage', function (event) { - if (event.key === '__YJS__' + store.namespace) { - operationsToAdd.push(event.newValue) - if (operationsToAdd.length === 1) { - store.requestTransaction(function * () { - var add = operationsToAdd - operationsToAdd = [] - for (var i in add) { - // don't call the localStorage event twice.. - var op = JSON.parse(add[i]) - if (op.struct !== 'Delete') { - op = yield* this.getOperation(op.id) - } - yield* this.store.operationAdded(this, op, true) - } - }) - } - } - }, false) - } - * operationAdded (transaction, op, noAdd) { - yield* super.operationAdded(transaction, op) - if (!noAdd) { - window.localStorage['__YJS__' + this.namespace] = JSON.stringify(op) - } - } - transact (makeGen) { - var transaction = this.db != null ? new Transaction(this) : null - var store = this - - var gen = makeGen.call(transaction) - handleTransactions(gen.next()) - - function handleTransactions (result) { - var request = result.value - if (result.done) { - makeGen = store.getNextRequest() - if (makeGen != null) { - if (transaction == null && store.db != null) { - transaction = new Transaction(store) - } - gen = makeGen.call(transaction) - handleTransactions(gen.next()) - } // else no transaction in progress! - return - } - if (request.constructor === window.IDBRequest) { - request.onsuccess = function () { - var res = request.result - if (res != null && res.constructor === window.IDBCursorWithValue) { - res = res.value - } - handleTransactions(gen.next(res)) - } - request.onerror = function (err) { - gen.throw(err) - } - } else if (request.constructor === window.IDBCursor) { - request.onsuccess = function () { - handleTransactions(gen.next(request.result != null ? request.result.value : null)) - } - request.onerror = function (err) { - gen.throw(err) - } - } else if (request.constructor === window.IDBOpenDBRequest) { - request.onsuccess = function (event) { - var db = event.target.result - handleTransactions(gen.next(db)) - } - request.onerror = function () { - gen.throw("Couldn't open IndexedDB database!") - } - request.onupgradeneeded = function (event) { - var db = event.target.result - try { - db.createObjectStore('OperationStore', {keyPath: 'id'}) - db.createObjectStore('DeleteStore', {keyPath: 'id'}) - db.createObjectStore('StateStore', {keyPath: 'id'}) - } catch (e) { - console.log('Store already exists!') - } - } - } else { - gen.throw('You must not yield this type!') - } - } - } - // TODO: implement "free".. - * destroy () { - this.db.close() - yield window.indexedDB.deleteDatabase(this.namespace) - } - } - Y.IndexedDB = OperationStore -} diff --git a/src/Databases/Memory.js b/src/Databases/Memory.js deleted file mode 100644 index 69b2e830..00000000 --- a/src/Databases/Memory.js +++ /dev/null @@ -1,62 +0,0 @@ -'use strict' - -module.exports = function (Y) { - class Transaction extends Y.Transaction { - constructor (store) { - super(store) - this.store = store - this.ss = store.ss - this.os = store.os - this.ds = store.ds - } - } - class Database extends Y.AbstractDatabase { - constructor (y, opts) { - super(y, opts) - this.os = new Y.utils.RBTree() - this.ds = new Y.utils.RBTree() - this.ss = new Y.utils.RBTree() - } - logTable () { - var self = this - self.requestTransaction(function * () { - console.log('User: ', this.store.y.connector.userId, "==============================") // eslint-disable-line - console.log("State Set (SS):", yield* this.getStateSet()) // eslint-disable-line - console.log("Operation Store (OS):") // eslint-disable-line - yield* this.os.logTable() // eslint-disable-line - console.log("Deletion Store (DS):") //eslint-disable-line - yield* this.ds.logTable() // eslint-disable-line - if (this.store.gc1.length > 0 || this.store.gc2.length > 0) { - console.warn('GC1|2 not empty!', this.store.gc1, this.store.gc2) - } - if (JSON.stringify(this.store.listenersById) !== '{}') { - console.warn('listenersById not empty!') - } - if (JSON.stringify(this.store.listenersByIdExecuteNow) !== '[]') { - console.warn('listenersByIdExecuteNow not empty!') - } - if (this.store.transactionInProgress) { - console.warn('Transaction still in progress!') - } - }, true) - } - transact (makeGen) { - var t = new Transaction(this) - while (makeGen !== null) { - var gen = makeGen.call(t) - var res = gen.next() - while (!res.done) { - res = gen.next(res.value) - } - makeGen = this.getNextRequest() - } - } - * destroy () { - super.destroy() - delete this.os - delete this.ss - delete this.ds - } - } - Y.Memory = Database -} diff --git a/src/Databases/RedBlackTree.js b/src/Databases/RedBlackTree.js deleted file mode 100644 index 5c06cc17..00000000 --- a/src/Databases/RedBlackTree.js +++ /dev/null @@ -1,490 +0,0 @@ -'use strict' - -/* - This file contains a not so fancy implemantion of a Red Black Tree. -*/ - -module.exports = function (Y) { - 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 - if (val.id === null) { - 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 } - get grandparent () { - return this.parent.parent - } - get parent () { - return this._parent - } - get sibling () { - return (this === this.parent.left) - ? this.parent.right : this.parent.left - } - get left () { - return this._left - } - get right () { - return this._right - } - set left (n) { - if (n !== null) { - n._parent = this - } - this._left = n - } - set right (n) { - if (n !== null) { - n._parent = this - } - this._right = n - } - rotateLeft (tree) { - 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 - } else if (parent.left === this) { - parent.left = newParent - } else if (parent.right === this) { - parent.right = newParent - } else { - throw new Error('The elements are wrongly connected!') - } - } - next () { - if (this.right !== null) { - // search the most left node in the right tree - var o = this.right - while (o.left !== null) { - o = o.left - } - return o - } else { - var p = this - while (p.parent !== null && p !== p.parent.left) { - p = p.parent - } - return p.parent - } - } - prev () { - if (this.left !== null) { - // search the most right node in the left tree - var o = this.left - while (o.right !== null) { - o = o.right - } - return o - } else { - var p = this - while (p.parent !== null && p !== p.parent.right) { - p = 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 - if (parent === null) { - tree.root = newParent - newParent._parent = null - } else if (parent.left === this) { - parent.left = newParent - } else if (parent.right === this) { - parent.right = newParent - } else { - 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 - } else { - return this.parent.parent.left - } - } - } - - class RBTree { - constructor () { - this.root = null - this.length = 0 - } - * findNext (id) { - return yield* this.findWithLowerBound([id[0], id[1] + 1]) - } - * findPrev (id) { - return yield* this.findWithUpperBound([id[0], id[1] - 1]) - } - findNodeWithLowerBound (from) { - if (from === void 0) { - throw new Error('You must define from!') - } - var o = this.root - if (o === null) { - return null - } else { - while (true) { - if ((from === null || Y.utils.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 - } else if (from !== null && Y.utils.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 - } else { - // there is no right element. Search for the next bigger element, - // this should be within the bounds - return o.next() - } - } else { - return o - } - } - } - } - findNodeWithUpperBound (to) { - if (to === void 0) { - throw new Error('You must define from!') - } - var o = this.root - if (o === null) { - return null - } else { - while (true) { - if ((to === null || Y.utils.smaller(o.val.id, to)) && o.right !== null) { - // o is included in the bound - // try to find an element that is closer to the bound - o = o.right - } else if (to !== null && Y.utils.smaller(to, o.val.id)) { - // o is not within the bound, maybe one of the left elements is.. - if (o.left !== null) { - o = o.left - } else { - // there is no left element. Search for the prev smaller element, - // this should be within the bounds - return o.prev() - } - } else { - return o - } - } - } - } - * findWithLowerBound (from) { - var n = this.findNodeWithLowerBound(from) - return n == null ? null : n.val - } - * findWithUpperBound (to) { - var n = this.findNodeWithUpperBound(to) - return n == null ? null : n.val - } - * iterate (t, from, to, f) { - var o = this.findNodeWithLowerBound(from) - while (o !== null && (to === null || Y.utils.smaller(o.val.id, to) || Y.utils.compareIds(o.val.id, to))) { - yield* f.call(t, o.val) - o = o.next() - } - return true - } - * logTable (from, to, filter) { - if (filter == null) { - filter = function () { - return true - } - } - if (from == null) { from = null } - if (to == null) { to = null } - var os = [] - yield* this.iterate(this, from, to, function * (o) { - if (filter(o)) { - var o_ = {} - for (var key in o) { - if (typeof o[key] === 'object') { - o_[key] = JSON.stringify(o[key]) - } else { - o_[key] = o[key] - } - } - os.push(o_) - } - }) - if (console.table != null) { - console.table(os) - } - } - * find (id) { - var n - return (n = this.findNode(id)) ? n.val : null - } - findNode (id) { - if (id == null || id.constructor !== Array) { - throw new Error('Expect id to be an array!') - } - var o = this.root - if (o === null) { - return false - } else { - while (true) { - if (o === null) { - return false - } - if (Y.utils.smaller(id, o.val.id)) { - o = o.left - } else if (Y.utils.smaller(o.val.id, id)) { - o = o.right - } else { - return o - } - } - } - } - * delete (id) { - if (id == null || id.constructor !== Array) { - throw new Error('id is expected to be an Array!') - } - var d = this.findNode(id) - if (d == null) { - throw new Error('Element does not exist!') - } - 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 - // find - while (o.right !== null) { - o = o.right - } - // switch - 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 - } else { - isFakeChild = false - } - - if (d.parent === null) { - if (!isFakeChild) { - this.root = child - child.blacken() - child._parent = null - } else { - this.root = null - } - return - } else if (d.parent.left === d) { - d.parent.left = child - } else if (d.parent.right === d) { - d.parent.right = child - } else { - throw new Error('Impossible!') - } - if (d.isBlack()) { - if (child.isRed()) { - child.blacken() - } else { - this._fixDelete(child) - } - } - this.root.blacken() - if (isFakeChild) { - if (child.parent.left === child) { - child.parent.left = null - } else if (child.parent.right === child) { - child.parent.right = null - } else { - throw new Error('Impossible #3') - } - } - } - _fixDelete (n) { - function isBlack (node) { - return node !== null ? node.isBlack() : true - } - 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 - } - // d was already replaced by the child - // d is not the root - // d and child are black - var sibling = n.sibling - if (isRed(sibling)) { - // make sibling the grandfather - n.parent.redden() - sibling.blacken() - if (n === n.parent.left) { - n.parent.rotateLeft(this) - } else if (n === n.parent.right) { - n.parent.rotateRight(this) - } else { - throw new Error('Impossible #2') - } - sibling = n.sibling - } - // parent, sibling, and children of n are black - 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() - n.parent.blacken() - } else { - 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.right.blacken() - sibling.rotateLeft(this) - sibling = n.sibling - } - sibling.color = n.parent.color - n.parent.blacken() - if (n === n.parent.left) { - sibling.right.blacken() - n.parent.rotateLeft(this) - } else { - sibling.left.blacken() - n.parent.rotateRight(this) - } - } - } - * put (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!') - } - var node = new N(v) - if (this.root !== null) { - var p = this.root // p abbrev. parent - while (true) { - if (Y.utils.smaller(node.val.id, p.val.id)) { - if (p.left === null) { - p.left = node - break - } else { - p = p.left - } - } else if (Y.utils.smaller(p.val.id, node.val.id)) { - if (p.right === null) { - p.right = node - break - } else { - p = p.right - } - } else { - p.val = node.val - return p - } - } - this._fixInsert(node) - } else { - this.root = node - } - this.length++ - this.root.blacken() - return node - } - _fixInsert (n) { - if (n.parent === null) { - n.blacken() - return - } else if (n.parent.isBlack()) { - return - } - 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) - } else { - // Note: parent: red, uncle: black or null - // Now we transform the tree in such a way that - // either of these holds: - // 1) grandparent.left.isRed - // 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 - } - // 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() - if (n === n.parent.left) { - // Case 1 - n.grandparent.rotateRight(this) - } else { - // Case 2 - n.grandparent.rotateLeft(this) - } - } - } - } - - Y.utils.RBTree = RBTree -} diff --git a/src/Databases/RedBlackTree.spec.js b/src/Databases/RedBlackTree.spec.js deleted file mode 100644 index 62a9d9b8..00000000 --- a/src/Databases/RedBlackTree.spec.js +++ /dev/null @@ -1,213 +0,0 @@ -/* eslint-env browser,jasmine,console */ -'use strict' - -var Y = require('../SpecHelper.js') -var numberOfRBTreeTests = 1000 - -function itRedNodesDoNotHaveBlackChildren () { - it('Red nodes do not have black children', function () { - function traverse (n) { - if (n == null) { - return - } - if (n.isRed()) { - if (n.left != null) { - expect(n.left.isRed()).not.toBeTruthy() - } - if (n.right != null) { - expect(n.right.isRed()).not.toBeTruthy() - } - } - traverse(n.left) - traverse(n.right) - } - traverse(this.tree.root) - }) -} - -function itBlackHeightOfSubTreesAreEqual () { - it('Black-height of sub-trees are equal', function () { - function traverse (n) { - if (n == null) { - return 0 - } - var sub1 = traverse(n.left) - var sub2 = traverse(n.right) - expect(sub1).toEqual(sub2) - if (n.isRed()) { - return sub1 - } else { - return sub1 + 1 - } - } - traverse(this.tree.root) - }) -} - -function itRootNodeIsBlack () { - it('root node is black', function () { - expect(this.tree.root == null || this.tree.root.isBlack()).toBeTruthy() - }) -} - -describe('RedBlack Tree', function () { - var tree, memory - describe('debug #2', function () { - beforeAll(function (done) { - this.memory = new Y.Memory(null, { - name: 'Memory', - gcTimeout: -1 - }) - this.tree = this.memory.os - tree = this.tree - memory = this.memory - memory.requestTransaction(function * () { - yield* tree.put({id: [8433]}) - yield* tree.put({id: [12844]}) - yield* tree.put({id: [1795]}) - yield* tree.put({id: [30302]}) - yield* tree.put({id: [64287]}) - yield* tree.delete([8433]) - yield* tree.put({id: [28996]}) - yield* tree.delete([64287]) - yield* tree.put({id: [22721]}) - done() - }) - }) - - itRootNodeIsBlack() - itBlackHeightOfSubTreesAreEqual([]) - }) - - describe(`After adding&deleting (0.8/0.2) ${numberOfRBTreeTests} times`, function () { - var elements = [] - beforeAll(function (done) { - this.memory = new Y.Memory(null, { - name: 'Memory', - gcTimeout: -1 - }) - this.tree = this.memory.os - tree = this.tree - memory = this.memory - memory.requestTransaction(function * () { - for (var i = 0; i < numberOfRBTreeTests; i++) { - var r = Math.random() - if (r < 0.8) { - var obj = [Math.floor(Math.random() * numberOfRBTreeTests * 10000)] - if (!tree.findNode(obj)) { - elements.push(obj) - yield* tree.put({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 !Y.utils.compareIds(e, elem) - }) - yield* tree.delete(elem) - } - } - done() - }) - }) - - itRootNodeIsBlack() - - it('can find every object', function (done) { - memory.requestTransaction(function * () { - for (var id of elements) { - expect((yield* tree.find(id)).id).toEqual(id) - } - done() - }) - }) - - it('can find every object with lower bound search', function (done) { - this.memory.requestTransaction(function * () { - for (var id of elements) { - expect((yield* tree.findWithLowerBound(id)).id).toEqual(id) - } - done() - }) - }) - itRedNodesDoNotHaveBlackChildren() - - itBlackHeightOfSubTreesAreEqual() - - it('iterating over a tree with lower bound yields the right amount of results', function (done) { - var lowerBound = elements[Math.floor(Math.random() * elements.length)] - var expectedResults = elements.filter(function (e, pos) { - return (Y.utils.smaller(lowerBound, e) || Y.utils.compareIds(e, lowerBound)) && elements.indexOf(e) === pos - }).length - - var actualResults = 0 - this.memory.requestTransaction(function * () { - yield* tree.iterate(this, lowerBound, null, function * (val) { - expect(val).toBeDefined() - actualResults++ - }) - expect(expectedResults).toEqual(actualResults) - done() - }) - }) - - it('iterating over a tree without bounds yield the right amount of results', function (done) { - var lowerBound = null - var expectedResults = elements.filter(function (e, pos) { - return elements.indexOf(e) === pos - }).length - var actualResults = 0 - this.memory.requestTransaction(function * () { - yield* tree.iterate(this, lowerBound, null, function * (val) { - expect(val).toBeDefined() - actualResults++ - }) - expect(expectedResults).toEqual(actualResults) - done() - }) - }) - - it('iterating over a tree with upper bound yields the right amount of results', function (done) { - var upperBound = elements[Math.floor(Math.random() * elements.length)] - var expectedResults = elements.filter(function (e, pos) { - return (Y.utils.smaller(e, upperBound) || Y.utils.compareIds(e, upperBound)) && elements.indexOf(e) === pos - }).length - - var actualResults = 0 - this.memory.requestTransaction(function * () { - yield* tree.iterate(this, null, upperBound, function * (val) { - expect(val).toBeDefined() - actualResults++ - }) - expect(expectedResults).toEqual(actualResults) - done() - }) - }) - - it('iterating over a tree with upper and lower bounds yield the right amount of results', function (done) { - var b1 = elements[Math.floor(Math.random() * elements.length)] - var b2 = elements[Math.floor(Math.random() * elements.length)] - var upperBound, lowerBound - if (Y.utils.smaller(b1, b2)) { - lowerBound = b1 - upperBound = b2 - } else { - lowerBound = b2 - upperBound = b1 - } - var expectedResults = elements.filter(function (e, pos) { - return (Y.utils.smaller(lowerBound, e) || Y.utils.compareIds(e, lowerBound)) && - (Y.utils.smaller(e, upperBound) || Y.utils.compareIds(e, upperBound)) && elements.indexOf(e) === pos - }).length - var actualResults = 0 - this.memory.requestTransaction(function * () { - yield* tree.iterate(this, lowerBound, upperBound, function * (val) { - expect(val).toBeDefined() - actualResults++ - }) - expect(expectedResults).toEqual(actualResults) - done() - }) - }) - }) -}) diff --git a/src/SpecHelper.js b/src/SpecHelper.js index a63bf433..6cf30d8d 100644 --- a/src/SpecHelper.js +++ b/src/SpecHelper.js @@ -44,10 +44,13 @@ function wait (t) { } g.wait = wait -g.databases = ['Memory'] +g.databases = ['memory'] +require('../../y-memory/src/Memory.js')(Y) if (typeof window !== 'undefined') { - g.databases.push('IndexedDB') + g.databases.push('indexeddb') + require('../../y-indexeddb/src/IndexedDB.js')(Y) } +require('../../y-array/src/Array.js') /* returns a random element of o. diff --git a/src/Types/Array.js b/src/Types/Array.js deleted file mode 100644 index 2ae81cb9..00000000 --- a/src/Types/Array.js +++ /dev/null @@ -1,198 +0,0 @@ -/* global Y */ -'use strict' - -function extend (Y) { - class YArray { - constructor (os, _model, idArray, valArray) { - this.os = os - this._model = _model - // Array of all the operation id's - this.idArray = idArray - // Array of all the values - this.valArray = valArray - this.eventHandler = new Y.utils.EventHandler(ops => { - var userEvents = [] - for (var i in ops) { - 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 - } else { - var sid = JSON.stringify(op.left) - pos = this.idArray.indexOf(sid) + 1 - if (pos <= 0) { - throw new Error('Unexpected operation!') - } - } - this.idArray.splice(pos, 0, JSON.stringify(op.id)) - this.valArray.splice(pos, 0, op.content) - userEvents.push({ - type: 'insert', - object: this, - index: pos, - length: 1 - }) - } else if (op.struct === 'Delete') { - let pos = this.idArray.indexOf(JSON.stringify(op.target)) - if (pos >= 0) { - this.idArray.splice(pos, 1) - this.valArray.splice(pos, 1) - userEvents.push({ - type: 'delete', - object: this, - index: pos, - length: 1 - }) - } - } else { - throw new Error('Unexpected struct!') - } - } - this.eventHandler.callEventListeners(userEvents) - }) - } - get length () { - return this.idArray.length - } - get (pos) { - if (pos == null || typeof pos !== 'number') { - throw new Error('pos must be a number!') - } - return this.valArray[pos] - } - toArray () { - return this.valArray.slice() - } - insert (pos, contents) { - 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!') - } - if (contents.length === 0) { - return - } - if (pos > this.idArray.length || pos < 0) { - throw new Error('This position exceeds the range of the array!') - } - var mostLeft = pos === 0 ? null : JSON.parse(this.idArray[pos - 1]) - - var ops = [] - var prevId = mostLeft - for (var i = 0; i < contents.length; i++) { - var op = { - left: prevId, - origin: prevId, - // right: mostRight, - // NOTE: I intentionally do not define right here, because it could be deleted - // at the time of creating this operation, and is therefore not defined in idArray - parent: this._model, - content: contents[i], - struct: 'Insert', - id: this.os.getNextOpId() - } - ops.push(op) - prevId = op.id - } - var eventHandler = this.eventHandler - eventHandler.awaitAndPrematurelyCall(ops) - this.os.requestTransaction(function *() { - // now we can set the right reference. - var mostRight - if (mostLeft != null) { - mostRight = (yield* this.getOperation(mostLeft)).right - } else { - mostRight = (yield* this.getOperation(ops[0].parent)).start - } - for (var j in ops) { - ops[j].right = mostRight - } - yield* this.applyCreatedOperations(ops) - eventHandler.awaitedInserts(ops.length) - }) - } - delete (pos, length) { - if (length == null) { length = 1 } - 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 (pos + length > this.idArray.length || pos < 0 || length < 0) { - throw new Error('The deletion range exceeds the range of the array!') - } - if (length === 0) { - return - } - 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' - }) - } - eventHandler.awaitAndPrematurelyCall(dels) - this.os.requestTransaction(function *() { - yield* this.applyCreatedOperations(dels) - eventHandler.awaitedDeletes(dels.length, newLeft) - }) - } - observe (f) { - this.eventHandler.addEventListener(f) - } - * _changed (transaction, op) { - if (!op.deleted) { - if (op.struct === 'Insert') { - var l = op.left - var left - while (l != null) { - left = yield* transaction.getOperation(l) - if (!left.deleted) { - break - } - l = left.left - } - op.left = l - } - this.eventHandler.receivedOp(op) - } - } - } - - Y.extend('Array', new Y.utils.CustomType({ - class: YArray, - createType: function * YArrayCreator () { - var modelid = this.store.getNextOpId() - var model = { - struct: 'List', - type: 'Array', - start: null, - end: null, - id: modelid - } - yield* this.applyCreatedOperations([model]) - return modelid - }, - 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) - } - })) -} - -if (typeof Y !== 'undefined') { - extend(Y) -} else { - module.exports = extend -} diff --git a/src/Types/Array.spec.js b/src/Types/Array.spec.js deleted file mode 100644 index f385e52c..00000000 --- a/src/Types/Array.spec.js +++ /dev/null @@ -1,312 +0,0 @@ -/* global createUsers, databases, wait, compareAllUsers, getRandomNumber, applyRandomTransactionsAllRejoinNoGC, applyRandomTransactionsWithGC, async, garbageCollectAllUsers, describeManyTimes */ -/* eslint-env browser,jasmine */ -'use strict' - -var Y = require('../SpecHelper.js') -var numberOfYArrayTests = 10 -var repeatArrayTests = 2 - -for (let database of databases) { - describe(`Array Type (DB: ${database})`, function () { - var y1, y2, y3, yconfig1, yconfig2, yconfig3, flushAll - - beforeEach(async(function * (done) { - yield createUsers(this, 3, database) - y1 = (yconfig1 = this.users[0]).root - y2 = (yconfig2 = this.users[1]).root - y3 = (yconfig3 = this.users[2]).root - flushAll = this.users[0].connector.flushAll - yield wait(10) - done() - })) - afterEach(async(function * (done) { - yield compareAllUsers(this.users) - done() - })) - - describe('Basic tests', function () { - it('insert three elements, try re-get property', async(function * (done) { - var array = yield y1.set('Array', Y.Array) - array.insert(0, [1, 2, 3]) - array = yield 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) { - yield y1.set('Array', Y.Array) - yield flushAll() - var l1 = yield y1.get('Array') - l1.insert(0, [0]) - var l2 = yield y2.get('Array') - l2.insert(0, [1]) - var l3 = yield y3.get('Array') - l3.insert(0, [2]) - yield 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 = yield y1.set('Array', Y.Array) - l1.insert(0, ['x', 'y', 'z']) - yield flushAll() - l1.insert(1, [0]) - l2 = yield y2.get('Array') - l2.delete(0) - l2.delete(1) - l3 = yield y3.get('Array') - l3.insert(1, [2]) - yield flushAll() - expect(l1.toArray()).toEqual(l2.toArray()) - expect(l2.toArray()).toEqual(l3.toArray()) - expect(l2.toArray()).toEqual([0, 2, 'y']) - done() - })) - it('Handles getOperations ascending ids bug in late sync', async(function * (done) { - var l1, l2 - l1 = yield y1.set('Array', Y.Array) - l1.insert(0, ['x', 'y']) - yield flushAll() - yconfig3.disconnect() - yconfig2.disconnect() - yield wait() - l2 = yield y2.get('Array') - l2.insert(1, [2]) - l2.insert(1, [3]) - yield yconfig2.reconnect() - yield yconfig3.reconnect() - expect(l1.toArray()).toEqual(l2.toArray()) - done() - })) - it('Handles deletions in late sync', async(function * (done) { - var l1, l2 - l1 = yield y1.set('Array', Y.Array) - l1.insert(0, ['x', 'y']) - yield flushAll() - yield yconfig2.disconnect() - yield wait() - l2 = yield y2.get('Array') - l2.delete(1, 1) - l1.delete(0, 2) - yield yconfig2.reconnect() - expect(l1.toArray()).toEqual(l2.toArray()) - done() - })) - it('Handles deletions in late sync (2)', async(function * (done) { - var l1, l2 - l1 = yield y1.set('Array', Y.Array) - yield flushAll() - l2 = yield y2.get('Array') - l1.insert(0, ['x', 'y']) - l1.delete(0, 2) - yield flushAll() - expect(l1.toArray()).toEqual(l2.toArray()) - done() - })) - it('Basic insert. Then delete the whole array', async(function * (done) { - var l1, l2, l3 - l1 = yield y1.set('Array', Y.Array) - l1.insert(0, ['x', 'y', 'z']) - yield flushAll() - l1.delete(0, 3) - l2 = yield y2.get('Array') - l3 = yield y3.get('Array') - yield flushAll() - expect(l1.toArray()).toEqual(l2.toArray()) - expect(l2.toArray()).toEqual(l3.toArray()) - expect(l2.toArray()).toEqual([]) - done() - })) - it('Basic insert. Then delete the whole array (merge listeners on late sync)', async(function * (done) { - var l1, l2, l3 - l1 = yield y1.set('Array', Y.Array) - l1.insert(0, ['x', 'y', 'z']) - yield flushAll() - yconfig2.disconnect() - l1.delete(0, 3) - l2 = yield y2.get('Array') - yield wait() - yield yconfig2.reconnect() - yield wait() - l3 = yield y3.get('Array') - yield flushAll() - expect(l1.toArray()).toEqual(l2.toArray()) - expect(l2.toArray()).toEqual(l3.toArray()) - expect(l2.toArray()).toEqual([]) - done() - })) - // TODO? - /* it('Basic insert. Then delete the whole array (merge deleter on late sync)', async(function * (done) { - var l1, l2, l3 - l1 = yield y1.set('Array', Y.Array) - l1.insert(0, ['x', 'y', 'z']) - yield flushAll() - yconfig1.disconnect() - l1.delete(0, 3) - l2 = yield y2.get('Array') - yield yconfig1.reconnect() - l3 = yield y3.get('Array') - yield 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 = yield 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', - object: array, - index: 0, - length: 1 - }]) - array.delete(0) - expect(event).toEqual([{ - type: 'delete', - object: array, - index: 0, - length: 1 - }]) - yield wait(50) - done() - })) - it('garbage collects', async(function * (done) { - var l1, l2, l3 - l1 = yield y1.set('Array', Y.Array) - l1.insert(0, ['x', 'y', 'z']) - yield flushAll() - yconfig1.disconnect() - l1.delete(0, 3) - l2 = yield y2.get('Array') - yield wait() - yield yconfig1.reconnect() - yield wait() - l3 = yield y3.get('Array') - yield flushAll() - yield garbageCollectAllUsers(this.users) - expect(l1.toArray()).toEqual(l2.toArray()) - expect(l2.toArray()).toEqual(l3.toArray()) - expect(l2.toArray()).toEqual([]) - done() - })) - it('debug right not existend in Insert.execute', async(function * (done) { - yconfig1.db.requestTransaction(function * () { - var ops = [{'struct':'Map','type':'Map','id':['130',0],'map':{}},{'id':['130',1],'left':null,'right':null,'origin':null,'parent':['_',0],'struct':'Insert','parentSub':'Map','opContent':['130',0]},{'struct':'Map','type':'Map','id':['130',0],'map':{}},{'id':['130',1],'left':null,'right':null,'origin':null,'parent':['_',0],'struct':'Insert','parentSub':'Map','opContent':['130',0]},{'struct':'Map','type':'Map','id':['130',0],'map':{}},{'id':['130',1],'left':null,'right':null,'origin':null,'parent':['_',0],'struct':'Insert','parentSub':'Map','opContent':['130',0]},{'left':null,'right':null,'origin':null,'parent':['130',0],'parentSub':'somekey','struct':'Insert','content':512,'id':['133',0]},{'id':['130',2],'left':null,'right':null,'origin':null,'parent':['130',0],'struct':'Insert','parentSub':'somekey','content':1131},{'id':['130',3],'left':null,'right':['130',2],'origin':null,'parent':['130',0],'struct':'Insert','parentSub':'somekey','content':4196},{'id':['131',3],'left':null,'right':null,'origin':null,'parent':['130',0],'struct':'Insert','parentSub':'somekey','content':5022}]//eslint-disable-line - - for (var o of ops) { - yield* this.store.tryExecute.call(this, o) - } - }) - yield wait() - yield yconfig3.disconnect() - yield yconfig2.disconnect() - yield flushAll() - wait() - yield yconfig3.reconnect() - yield yconfig2.reconnect() - yield wait() - yield flushAll() - done() - })) - it('debug right not existend in Insert.execute (2)', async(function * (done) { - yconfig1.db.requestTransaction(function * () { - yield* this.store.tryExecute.call(this, {'struct': 'Map', 'type': 'Map', 'id': ['153', 0], 'map': {}}) - yield* this.store.tryExecute.call(this, {'id': ['153', 1], 'left': null, 'right': null, 'origin': null, 'parent': ['_', 0], 'struct': 'Insert', 'parentSub': 'Map', 'opContent': ['153', 0]}) - yield* this.store.tryExecute.call(this, {'struct': 'Map', 'type': 'Map', 'id': ['153', 0], 'map': {}}) - yield* this.store.tryExecute.call(this, {'id': ['153', 1], 'left': null, 'right': null, 'origin': null, 'parent': ['_', 0], 'struct': 'Insert', 'parentSub': 'Map', 'opContent': ['153', 0]}) - yield* this.store.tryExecute.call(this, {'struct': 'Map', 'type': 'Map', 'id': ['153', 0], 'map': {}}) - yield* this.store.tryExecute.call(this, {'id': ['153', 1], 'left': null, 'right': null, 'origin': null, 'parent': ['_', 0], 'struct': 'Insert', 'parentSub': 'Map', 'opContent': ['153', 0]}) - yield* this.store.tryExecute.call(this, {'left': null, 'right': null, 'origin': null, 'parent': ['153', 0], 'parentSub': 'somekey', 'struct': 'Insert', 'content': 3784, 'id': ['154', 0]}) - yield* this.store.tryExecute.call(this, {'left': null, 'right': ['154', 0], 'origin': null, 'parent': ['153', 0], 'parentSub': 'somekey', 'struct': 'Insert', 'content': 8217, 'id': ['154', 1]}) - yield* this.store.tryExecute.call(this, {'left': null, 'right': ['154', 1], 'origin': null, 'parent': ['153', 0], 'parentSub': 'somekey', 'struct': 'Insert', 'content': 5036, 'id': ['154', 2]}) - yield* this.store.tryExecute.call(this, {'id': ['153', 2], 'left': null, 'right': null, 'origin': null, 'parent': ['153', 0], 'struct': 'Insert', 'parentSub': 'somekey', 'content': 417}) - yield* this.store.tryExecute.call(this, {'id': ['155', 0], 'left': null, 'right': null, 'origin': null, 'parent': ['153', 0], 'struct': 'Insert', 'parentSub': 'somekey', 'content': 2202}) - yield* this.garbageCollectOperation(['153', 2]) - yield* this.garbageCollectOperation(['154', 0]) - yield* this.garbageCollectOperation(['154', 1]) - yield* this.garbageCollectOperation(['154', 2]) - yield* this.garbageCollectOperation(['155', 0]) - yield* this.garbageCollectOperation(['156', 0]) - yield* this.garbageCollectOperation(['157', 0]) - yield* this.garbageCollectOperation(['157', 1]) - yield* this.store.tryExecute.call(this, {'id': ['153', 3], 'left': null, 'right': null, 'origin': null, 'parent': ['153', 0], 'struct': 'Insert', 'parentSub': 'somekey', 'content': 4372}) - }) - yield wait() - yield yconfig3.disconnect() - yield yconfig2.disconnect() - yield flushAll() - wait() - yield yconfig3.reconnect() - yield yconfig2.reconnect() - yield wait() - yield flushAll() - done() - })) - }) - describeManyTimes(repeatArrayTests, `Random tests`, function () { - var randomArrayTransactions = [ - function insert (array) { - array.insert(getRandomNumber(array.toArray().length), [getRandomNumber()]) - }, - function _delete (array) { - var length = array.toArray().length - if (length > 0) { - array.delete(getRandomNumber(length - 1)) - } - } - ] - function compareArrayValues (arrays) { - var firstArray - for (var l of arrays) { - var val = l.toArray() - if (firstArray == null) { - firstArray = val - } else { - expect(val).toEqual(firstArray) - } - } - } - beforeEach(async(function * (done) { - yield this.users[0].root.set('Array', Y.Array) - yield flushAll() - - var promises = [] - for (var u = 0; u < this.users.length; u++) { - promises.push(this.users[u].root.get('Array')) - } - this.arrays = yield 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, no GC, all users disconnecting/reconnecting`, async(function * (done) { - for (var u of this.users) { - u.connector.debug = true - } - yield applyRandomTransactionsAllRejoinNoGC(this.users, this.arrays, randomArrayTransactions, numberOfYArrayTests) - yield flushAll() - yield compareArrayValues(this.arrays) - yield compareAllUsers(this.users) - done() - })) - it(`succeed after ${numberOfYArrayTests} actions, GC, user[0] is not disconnecting`, async(function * (done) { - for (var u of this.users) { - u.connector.debug = true - } - yield applyRandomTransactionsWithGC(this.users, this.arrays, randomArrayTransactions, numberOfYArrayTests) - yield flushAll() - yield compareArrayValues(this.arrays) - yield compareAllUsers(this.users) - done() - })) - }) - }) -} diff --git a/src/Types/TextBind.js b/src/Types/TextBind.js deleted file mode 100644 index 86e7a587..00000000 --- a/src/Types/TextBind.js +++ /dev/null @@ -1,289 +0,0 @@ -'use strict' - -module.exports = function (Y) { - class YTextBind extends Y.Array['class'] { - constructor (os, _model, idArray, valArray) { - super(os, _model, idArray, valArray) - this.textfields = [] - } - toString () { - return this.valArray.join('') - } - insert (pos, content) { - super.insert(pos, content.split('')) - } - bind (textfield, domRoot) { - 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 - - 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 - } - - writeRange = function (range) { - writeContent(word.toString()) - var textnode = textfield.childNodes[0] - if (range.isReal && textnode != null) { - if (range.left < 0) { - range.left = 0 - } - range.right = Math.max(range.left, range.right) - if (range.right > textnode.length) { - range.right = textnode.length - } - range.left = Math.min(range.left, range.right) - var r = document.createRange(); // eslint-disable-line - r.setStart(textnode, range.left) - r.setEnd(textnode, range.right) - var s = window.getSelection(); // eslint-disable-line - s.removeAllRanges() - s.addRange(r) - } - } - writeContent = function (content) { - var contentArray = content.replace(new RegExp('\n', 'g'), ' ').split(' ');// eslint-disable-line - textfield.innerText = '' - for (var i in contentArray) { - var c = contentArray[i] - textfield.innerText += c - if (i !== contentArray.length - 1) { - textfield.innerHTML += ' ' - } - } - } - } - writeContent(this.toString()) - - this.observe(function (events) { - for (var e in events) { - var event = events[e] - if (!creatorToken) { - var oPos, fix - if (event.type === 'insert') { - oPos = event.index - fix = function (cursor) {// eslint-disable-line - if (cursor <= oPos) { - return cursor - } else { - cursor += 1 - return cursor - } - } - var r = createRange(fix) - writeRange(r) - } else if (event.type === 'delete') { - oPos = event.index - fix = function (cursor) {// eslint-disable-line - if (cursor < oPos) { - return cursor - } else { - cursor -= 1 - return cursor - } - } - r = createRange(fix) - writeRange(r) - } - } - } - }) - // consume all text-insert changes. - textfield.onkeypress = function (event) { - if (word.is_deleted) { - // if word is deleted, do not do anything ever again - textfield.onkeypress = null - return true - } - creatorToken = true - var char - if (event.keyCode === 13) { - char = '\n' - } else if (event.key != null) { - if (event.charCode === 32) { - char = ' ' - } else { - char = event.key - } - } else { - 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++ - } - while (newPos > 0 && val[newPos] !== ' ' && val[newPos] !== '\n') { - newPos-- - delLength++ - } - word.delete(newPos, pos - newPos) - r.left = newPos - r.right = newPos - writeRange(r) - } else { - if (pos > 0) { - word.delete(pos - 1, 1) - r.left = pos - 1 - r.right = pos - 1 - writeRange(r) - } - } - } - event.preventDefault() - creatorToken = false - return false - } else if (event.keyCode != null && event.keyCode === 46) { // Delete - if (diff > 0) { - word.delete(pos, diff) - r.left = pos - r.right = pos - writeRange(r) - } else { - word.delete(pos, 1) - r.left = pos - r.right = pos - writeRange(r) - } - event.preventDefault() - creatorToken = false - return false - } else { - creatorToken = false - return true - } - } - } - } - Y.TextBind = new Y.utils.CustomType({ - class: YTextBind, - createType: function * YTextBindCreator () { - var modelid = this.store.getNextOpId() - var model = { - start: null, - end: null, - struct: 'List', - type: 'TextBind', - id: modelid - } - yield* this.applyCreatedOperations([model]) - return modelid - }, - 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/y.js b/src/y.js index 421fbcfa..91780c2f 100644 --- a/src/y.js +++ b/src/y.js @@ -6,9 +6,6 @@ require('./Database.js')(Y) require('./Transaction.js')(Y) require('./Struct.js')(Y) require('./Utils.js')(Y) -require('./Databases/RedBlackTree.js')(Y) -require('./Databases/Memory.js')(Y) -require('./Databases/IndexedDB.js')(Y) require('./Connectors/Test.js')(Y) var requiringModules = {} @@ -17,47 +14,55 @@ module.exports = Y Y.extend = function (name, value) { Y[name] = value - var resolves = requiringModules[name] if (requiringModules[name] != null) { - for (var i = 0; i < resolves.length; i++) { - resolves[i]() - } + requiringModules[name].resolve() delete requiringModules[name] } } -require('./Types/Array.js')(Y) +Y.requestModules = function (modules) { + var promises = [] + for (var i = 0; i < modules.length; i++) { + var modulename = 'y-' + modules[i].toLowerCase() + if (Y[modules[i]] == null) { + if (requiringModules[modules[i]] == null) { + try { + require(modulename)(Y) + } catch (e) { + // module does not exist + if (typeof window !== 'undefined') { + var imported = document.createElement('script') + imported.src = Y.sourceDir + '/' + modulename + '/' + modulename + '.js' + document.head.appendChild(imported) + ;(function () { + var modname = modules[i] + var promise = new Promise(function (resolve) { + requiringModules[modname] = { + resolve: resolve, + promise: promise + } + }) + promises.push(promise) + })() + } else { + throw e + } + } + } else { + promises.push(requiringModules[modules[i]].promise) + } + } + } + return Promise.all(promises) +} + require('./Types/Map.js')(Y) -require('./Types/TextBind.js')(Y) function Y (opts) { opts.types = opts.types != null ? opts.types : [] var modules = [opts.db.name, opts.connector.name].concat(opts.types) - var promises = [] - for (var i = 0; i < modules.length; i++) { - if (Y[modules[i]] == null) { - try { - require(modules[i])(Y) - } catch (e) { - // module does not exist - if (window != null) { - if (requiringModules[modules[i]] == null) { - var imported = document.createElement('script') - var name = modules[i].toLowerCase() - imported.src = opts.sourceDir + '/y-' + name + '/y-' + name + '.js' - document.head.appendChild(imported) - requiringModules[modules[i]] = [] - } - promises.push(new Promise(function (resolve) { - requiringModules[modules[i]].push(resolve) - })) - } else { - throw e - } - } - } - } - return Promise.all(promises).then(function () { + Y.sourceDir = opts.sourceDir + return Y.requestModules(modules).then(function () { return new Promise(function (resolve) { var yconfig = new YConfig(opts, function () { yconfig.db.whenUserIdSet(function () {