outsourced Textbind, improved automatic module loader
This commit is contained in:
parent
08d07796ee
commit
2d20fd59d0
@ -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 () {
|
||||
|
@ -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'
|
||||
]
|
||||
|
@ -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",
|
||||
|
@ -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
|
||||
}
|
@ -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
|
||||
}
|
@ -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
|
||||
}
|
@ -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()
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
@ -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.
|
||||
|
@ -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
|
||||
}
|
@ -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()
|
||||
}))
|
||||
})
|
||||
})
|
||||
}
|
@ -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)
|
||||
}
|
||||
})
|
||||
}
|
73
src/y.js
73
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 () {
|
||||
|
Loading…
x
Reference in New Issue
Block a user