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) {
|
module.exports = function (gulp, helperOptions) {
|
||||||
var runSequence = require('run-sequence').use(gulp)
|
var runSequence = require('run-sequence').use(gulp)
|
||||||
var options = minimist(process.argv.slice(2), {
|
var options = minimist(process.argv.slice(2), {
|
||||||
string: ['modulename', 'export', 'name', 'testport', 'testfiles'],
|
string: ['modulename', 'export', 'name', 'port', 'testfiles'],
|
||||||
default: {
|
default: {
|
||||||
modulename: helperOptions.moduleName,
|
modulename: helperOptions.moduleName,
|
||||||
targetName: helperOptions.targetName,
|
targetName: helperOptions.targetName,
|
||||||
export: 'ignore',
|
export: 'ignore',
|
||||||
testport: '8888',
|
port: '8888',
|
||||||
testfiles: '**/*.spec.js',
|
testfiles: '**/*.spec.js',
|
||||||
browserify: helperOptions.browserify != null ? helperOptions.browserify : false,
|
browserify: helperOptions.browserify != null ? helperOptions.browserify : false,
|
||||||
regenerator: true,
|
regenerator: false,
|
||||||
debug: false
|
debug: false
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
@ -80,7 +80,7 @@ module.exports = function (gulp, helperOptions) {
|
|||||||
.pipe(source('specs.js'))
|
.pipe(source('specs.js'))
|
||||||
.pipe(buffer())
|
.pipe(buffer())
|
||||||
.pipe($.sourcemaps.init({loadMaps: true}))
|
.pipe($.sourcemaps.init({loadMaps: true}))
|
||||||
.pipe($.sourcemaps.write())
|
.pipe($.sourcemaps.write('.'))
|
||||||
.pipe(gulp.dest('./build/'))
|
.pipe(gulp.dest('./build/'))
|
||||||
})
|
})
|
||||||
|
|
||||||
@ -88,7 +88,7 @@ module.exports = function (gulp, helperOptions) {
|
|||||||
gulp.watch(files.src, ['spec-build'])
|
gulp.watch(files.src, ['spec-build'])
|
||||||
return gulp.src('./build/specs.js')
|
return gulp.src('./build/specs.js')
|
||||||
.pipe($.jasmineBrowser.specRunner())
|
.pipe($.jasmineBrowser.specRunner())
|
||||||
.pipe($.jasmineBrowser.server({port: options.testport}))
|
.pipe($.jasmineBrowser.server({port: options.port}))
|
||||||
})
|
})
|
||||||
|
|
||||||
gulp.task('test', function () {
|
gulp.task('test', function () {
|
||||||
|
@ -53,8 +53,6 @@ require('./gulpfile.helper.js')(gulp, {
|
|||||||
targetName: 'y.js',
|
targetName: 'y.js',
|
||||||
moduleName: 'yjs',
|
moduleName: 'yjs',
|
||||||
specs: [
|
specs: [
|
||||||
'./src/Databases/RedBlackTree.spec.js',
|
|
||||||
'./src/Types/Array.spec.js',
|
|
||||||
'./src/Types/Map.spec.js',
|
'./src/Types/Map.spec.js',
|
||||||
'./src/Database.spec.js'
|
'./src/Database.spec.js'
|
||||||
]
|
]
|
||||||
|
@ -5,8 +5,7 @@
|
|||||||
"main": "y.js",
|
"main": "y.js",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"test": "node --harmony ./node_modules/.bin/gulp test",
|
"test": "node --harmony ./node_modules/.bin/gulp test",
|
||||||
"lint": "./node_modules/.bin/standard",
|
"lint": "./node_modules/.bin/standard"
|
||||||
"build": "./node_modules/.bin/gulp build"
|
|
||||||
},
|
},
|
||||||
"pre-commit": [
|
"pre-commit": [
|
||||||
"lint",
|
"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.wait = wait
|
||||||
|
|
||||||
g.databases = ['Memory']
|
g.databases = ['memory']
|
||||||
|
require('../../y-memory/src/Memory.js')(Y)
|
||||||
if (typeof window !== 'undefined') {
|
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.
|
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('./Transaction.js')(Y)
|
||||||
require('./Struct.js')(Y)
|
require('./Struct.js')(Y)
|
||||||
require('./Utils.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)
|
require('./Connectors/Test.js')(Y)
|
||||||
|
|
||||||
var requiringModules = {}
|
var requiringModules = {}
|
||||||
@ -17,47 +14,55 @@ module.exports = Y
|
|||||||
|
|
||||||
Y.extend = function (name, value) {
|
Y.extend = function (name, value) {
|
||||||
Y[name] = value
|
Y[name] = value
|
||||||
var resolves = requiringModules[name]
|
|
||||||
if (requiringModules[name] != null) {
|
if (requiringModules[name] != null) {
|
||||||
for (var i = 0; i < resolves.length; i++) {
|
requiringModules[name].resolve()
|
||||||
resolves[i]()
|
|
||||||
}
|
|
||||||
delete requiringModules[name]
|
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/Map.js')(Y)
|
||||||
require('./Types/TextBind.js')(Y)
|
|
||||||
|
|
||||||
function Y (opts) {
|
function Y (opts) {
|
||||||
opts.types = opts.types != null ? opts.types : []
|
opts.types = opts.types != null ? opts.types : []
|
||||||
var modules = [opts.db.name, opts.connector.name].concat(opts.types)
|
var modules = [opts.db.name, opts.connector.name].concat(opts.types)
|
||||||
var promises = []
|
Y.sourceDir = opts.sourceDir
|
||||||
for (var i = 0; i < modules.length; i++) {
|
return Y.requestModules(modules).then(function () {
|
||||||
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 () {
|
|
||||||
return new Promise(function (resolve) {
|
return new Promise(function (resolve) {
|
||||||
var yconfig = new YConfig(opts, function () {
|
var yconfig = new YConfig(opts, function () {
|
||||||
yconfig.db.whenUserIdSet(function () {
|
yconfig.db.whenUserIdSet(function () {
|
||||||
|
Loading…
x
Reference in New Issue
Block a user