added comments to most of the classes.
This commit is contained in:
parent
6f3a291ef5
commit
b1d70ef25e
5
.gitignore
vendored
5
.gitignore
vendored
@ -3,11 +3,10 @@ bower_components
|
||||
build
|
||||
build_test
|
||||
.directory
|
||||
.c9
|
||||
.codio
|
||||
.settings
|
||||
.jshintignore
|
||||
.jshintrc
|
||||
.validate.json
|
||||
y.js
|
||||
y.js.map
|
||||
./y.js
|
||||
./y.js.map
|
||||
|
18
package.json
18
package.json
@ -41,18 +41,18 @@
|
||||
},
|
||||
"homepage": "http://y-js.org",
|
||||
"devDependencies": {
|
||||
"babel-eslint": "^4.1.1",
|
||||
"babel-eslint": "^4.1.2",
|
||||
"gulp": "^3.9.0",
|
||||
"gulp-babel": "^5.1.0",
|
||||
"gulp-concat": "^2.5.2",
|
||||
"gulp-babel": "^5.2.1",
|
||||
"gulp-concat": "^2.6.0",
|
||||
"gulp-jasmine": "^2.0.1",
|
||||
"gulp-jasmine-browser": "^0.1.3",
|
||||
"gulp-jasmine-browser": "^0.2.3",
|
||||
"gulp-sourcemaps": "^1.5.2",
|
||||
"gulp-uglify": "^1.2.0",
|
||||
"gulp-util": "^3.0.5",
|
||||
"gulp-watch": "^4.2.4",
|
||||
"minimist": "^1.1.1",
|
||||
"pre-commit": "^1.0.10",
|
||||
"gulp-uglify": "^1.4.1",
|
||||
"gulp-util": "^3.0.6",
|
||||
"gulp-watch": "^4.3.5",
|
||||
"minimist": "^1.2.0",
|
||||
"pre-commit": "^1.1.1",
|
||||
"standard": "^5.2.2"
|
||||
}
|
||||
}
|
||||
|
@ -1,22 +0,0 @@
|
||||
{
|
||||
"spec_dir": "build",
|
||||
"spec_files": [
|
||||
"**/**.spec.js"
|
||||
],
|
||||
"helpers": [
|
||||
"Helper.spec.js",
|
||||
"y.js",
|
||||
"Connector.js",
|
||||
"OperationStore.js",
|
||||
"Struct.js",
|
||||
"Utils.js",
|
||||
"OperationStores/RedBlackTree.js",
|
||||
"OperationStores/Memory.js",
|
||||
"OperationStores/IndexedDB.js",
|
||||
"Connectors/Test.js",
|
||||
"Connectors/WebRTC.js",
|
||||
"Types/Array.js",
|
||||
"Types/Map.js",
|
||||
"Types/TextBind.js"
|
||||
]
|
||||
}
|
@ -1,11 +1,12 @@
|
||||
/* globals Y */
|
||||
'use strict'
|
||||
|
||||
class AbstractConnector { // eslint-disable-line no-unused-vars
|
||||
class AbstractConnector {
|
||||
/*
|
||||
opts
|
||||
.role : String Role of this client ("master" or "slave")
|
||||
.userId : String that uniquely defines the user.
|
||||
opts contains the following information:
|
||||
role : String Role of this client ("master" or "slave")
|
||||
userId : String Uniquely defines the user.
|
||||
debug: Boolean Whether to print debug messages (optional)
|
||||
*/
|
||||
constructor (y, opts) {
|
||||
this.y = y
|
||||
@ -90,8 +91,11 @@ class AbstractConnector { // eslint-disable-line no-unused-vars
|
||||
this.whenSyncedListeners.push(f)
|
||||
}
|
||||
}
|
||||
// returns false, if there is no sync target
|
||||
// true otherwise
|
||||
/*
|
||||
|
||||
returns false, if there is no sync target
|
||||
true otherwise
|
||||
*/
|
||||
findNextSyncTarget () {
|
||||
if (this.currentSyncTarget != null) {
|
||||
return // "The current sync has not finished!"
|
||||
@ -115,7 +119,7 @@ class AbstractConnector { // eslint-disable-line no-unused-vars
|
||||
})
|
||||
})
|
||||
}
|
||||
// set the state to synced!
|
||||
// This user synced with at least one user, set the state to synced (TODO: does this suffice?)
|
||||
if (!this.isSynced) {
|
||||
this.isSynced = true
|
||||
for (var f of this.whenSyncedListeners) {
|
||||
@ -129,7 +133,9 @@ class AbstractConnector { // eslint-disable-line no-unused-vars
|
||||
console.log(`me -> ${uid}: ${message.type}`, m);// eslint-disable-line
|
||||
}
|
||||
}
|
||||
// You received a raw message, and you know that it is intended for to Yjs. Then call this function.
|
||||
/*
|
||||
You received a raw message, and you know that it is intended for Yjs. Then call this function.
|
||||
*/
|
||||
receiveMessage (sender, m) {
|
||||
if (sender === this.userId) {
|
||||
return
|
||||
|
@ -83,26 +83,26 @@ class Test extends Y.AbstractConnector {
|
||||
}
|
||||
}
|
||||
flushAll () {
|
||||
var def = Promise.defer()
|
||||
// flushes may result in more created operations,
|
||||
// flush until there is nothing more to flush
|
||||
function nextFlush () {
|
||||
var c = flushOne()
|
||||
if (c) {
|
||||
while (flushOne()) {
|
||||
// nop
|
||||
return new Promise(function (resolve) {
|
||||
// flushes may result in more created operations,
|
||||
// flush until there is nothing more to flush
|
||||
function nextFlush () {
|
||||
var c = flushOne()
|
||||
if (c) {
|
||||
while (flushOne()) {
|
||||
// nop
|
||||
}
|
||||
wait().then(nextFlush)
|
||||
} else {
|
||||
wait().then(function () {
|
||||
resolve()
|
||||
})
|
||||
}
|
||||
wait().then(nextFlush)
|
||||
} else {
|
||||
wait().then(function () {
|
||||
def.resolve()
|
||||
})
|
||||
}
|
||||
}
|
||||
// in the case that there are
|
||||
// still actions that want to be performed
|
||||
wait(0).then(nextFlush)
|
||||
return def.promise
|
||||
// in the case that there are
|
||||
// still actions that want to be performed
|
||||
wait(0).then(nextFlush)
|
||||
})
|
||||
}
|
||||
flushOne () {
|
||||
flushOne()
|
||||
|
@ -3,8 +3,9 @@
|
||||
|
||||
/*
|
||||
This is just a compilation of functions that help to test this library!
|
||||
***/
|
||||
*/
|
||||
|
||||
// When testing, you store everything on the global object. We call it g
|
||||
var g
|
||||
if (typeof global !== 'undefined') {
|
||||
g = global
|
||||
@ -15,20 +16,29 @@ if (typeof global !== 'undefined') {
|
||||
}
|
||||
g.g = g
|
||||
|
||||
g.YConcurrency_TestingMode = true
|
||||
|
||||
jasmine.DEFAULT_TIMEOUT_INTERVAL = 5000
|
||||
|
||||
/*
|
||||
Wait for a specified amount of time (in ms). defaults to 5ms
|
||||
*/
|
||||
function wait (t) {
|
||||
if (t == null) {
|
||||
t = 5
|
||||
}
|
||||
var def = Promise.defer()
|
||||
setTimeout(function () {
|
||||
def.resolve()
|
||||
}, t)
|
||||
return def.promise
|
||||
return new Promise(function (resolve) {
|
||||
setTimeout(function () {
|
||||
resolve()
|
||||
}, t)
|
||||
})
|
||||
}
|
||||
g.wait = wait
|
||||
|
||||
// returns a random element of o
|
||||
// works on Object, and Array
|
||||
/*
|
||||
returns a random element of o.
|
||||
works on Object, and Array
|
||||
*/
|
||||
function getRandom (o) {
|
||||
if (o instanceof Array) {
|
||||
return o[Math.floor(Math.random() * o.length)]
|
||||
@ -42,7 +52,7 @@ function getRandom (o) {
|
||||
}
|
||||
g.getRandom = getRandom
|
||||
|
||||
function getRandomNumber(n) {//eslint-disable-line
|
||||
function getRandomNumber (n) {
|
||||
if (n == null) {
|
||||
n = 9999
|
||||
}
|
||||
@ -50,7 +60,7 @@ function getRandomNumber(n) {//eslint-disable-line
|
||||
}
|
||||
g.getRandomNumber = getRandomNumber
|
||||
|
||||
g.applyRandomTransactions = async(function * applyRandomTransactions (users, objects, transactions, numberOfTransactions) { //eslint-disable-line
|
||||
g.applyRandomTransactions = async(function * applyRandomTransactions (users, objects, transactions, numberOfTransactions) {
|
||||
function randomTransaction (root) {
|
||||
var f = getRandom(transactions)
|
||||
f(root)
|
||||
@ -86,8 +96,12 @@ g.garbageCollectAllUsers = async(function * garbageCollectAllUsers (users) {
|
||||
})
|
||||
|
||||
g.compareAllUsers = async(function * compareAllUsers (users) { //eslint-disable-line
|
||||
var s1, s2, ds1, ds2, allDels1, allDels2
|
||||
var db1 = []
|
||||
var s1, s2 // state sets
|
||||
var ds1, ds2 // delete sets
|
||||
var allDels1, allDels2 // all deletions
|
||||
var db1 = [] // operation store of user1
|
||||
|
||||
// t1 and t2 basically do the same. They define t[1,2], ds[1,2], and allDels[1,2]
|
||||
function * t1 () {
|
||||
s1 = yield* this.getStateSet()
|
||||
ds1 = yield* this.getDeleteSet()
|
||||
@ -105,6 +119,7 @@ g.compareAllUsers = async(function * compareAllUsers (users) { //eslint-disable-
|
||||
})
|
||||
}
|
||||
yield users[0].connector.flushAll()
|
||||
// gc two times because of the two gc phases (really collect everything)
|
||||
yield g.garbageCollectAllUsers(users)
|
||||
yield wait(50)
|
||||
yield g.garbageCollectAllUsers(users)
|
||||
@ -137,7 +152,7 @@ g.compareAllUsers = async(function * compareAllUsers (users) { //eslint-disable-
|
||||
if (s1 == null) {
|
||||
u.db.requestTransaction(t1)
|
||||
yield wait()
|
||||
u.db.os.iterate(null, null, function(o){//eslint-disable-line
|
||||
u.db.os.iterate(null, null, function (o) {
|
||||
db1.push(o)
|
||||
})
|
||||
} else {
|
||||
@ -147,22 +162,22 @@ g.compareAllUsers = async(function * compareAllUsers (users) { //eslint-disable-
|
||||
expect(allDels1).toEqual(allDels2) // inner structure
|
||||
expect(ds1).toEqual(ds2) // exported structure
|
||||
var count = 0
|
||||
u.db.os.iterate(null, null, function(o){//eslint-disable-line
|
||||
u.db.os.iterate(null, null, function (o) {
|
||||
expect(db1[count++]).toEqual(o)
|
||||
})
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
g.createUsers = async(function * createUsers (self, numberOfUsers) { //eslint-disable-line
|
||||
if (Y.utils.globalRoom.users[0] != null) {//eslint-disable-line
|
||||
yield Y.utils.globalRoom.users[0].flushAll()//eslint-disable-line
|
||||
g.createUsers = async(function * createUsers (self, numberOfUsers) {
|
||||
if (Y.utils.globalRoom.users[0] != null) {
|
||||
yield Y.utils.globalRoom.users[0].flushAll()
|
||||
}
|
||||
// destroy old users
|
||||
for (var u in Y.utils.globalRoom.users) {//eslint-disable-line
|
||||
Y.utils.globalRoom.users[u].y.destroy()//eslint-disable-line
|
||||
for (var u in Y.utils.globalRoom.users) {
|
||||
Y.utils.globalRoom.users[u].y.destroy()
|
||||
}
|
||||
self.users = []
|
||||
self.users = null
|
||||
|
||||
var promises = []
|
||||
for (var i = 0; i < numberOfUsers; i++) {
|
||||
@ -178,14 +193,18 @@ g.createUsers = async(function * createUsers (self, numberOfUsers) { //eslint-di
|
||||
}))
|
||||
}
|
||||
self.users = yield Promise.all(promises)
|
||||
return self.users
|
||||
})
|
||||
|
||||
/*
|
||||
Until async/await arrives in js, we use this function to wait for promises
|
||||
by yielding them.
|
||||
*/
|
||||
function async (makeGenerator) {
|
||||
return function (arg) {
|
||||
var generator = makeGenerator.apply(this, arguments)
|
||||
|
||||
function handle (result) {
|
||||
// result => { done: [Boolean], value: [Object] }
|
||||
if (result.done) return Promise.resolve(result.value)
|
||||
|
||||
return Promise.resolve(result.value).then(function (res) {
|
||||
@ -194,12 +213,14 @@ function async (makeGenerator) {
|
||||
return handle(generator.throw(err))
|
||||
})
|
||||
}
|
||||
|
||||
try {
|
||||
// this may throw errors here, but its ok since this is used only for debugging
|
||||
return handle(generator.next())
|
||||
/* try {
|
||||
return handle(generator.next())
|
||||
} catch (ex) {
|
||||
return Promise.reject(ex)
|
||||
}
|
||||
generator.throw(ex) // TODO: check this out
|
||||
// return Promise.reject(ex)
|
||||
}*/
|
||||
}
|
||||
}
|
||||
g.async = async
|
||||
|
@ -1,7 +1,76 @@
|
||||
/* global Y */
|
||||
|
||||
'use strict'
|
||||
|
||||
/*
|
||||
Partial definition of a transaction
|
||||
By convention, a transaction has the following properties:
|
||||
* ss for StateSet
|
||||
* os for OperationStore
|
||||
* ds for DeleteStore
|
||||
|
||||
A transaction must also define the following methods:
|
||||
* checkDeleteStoreForState(state)
|
||||
- When increasing the state of a user, an operation with an higher id
|
||||
may already be garbage collected, and therefore it will never be received.
|
||||
update the state to reflect this knowledge. This won't call a method to save the state!
|
||||
* getDeleteSet(id)
|
||||
- Get the delete set in a readable format:
|
||||
{
|
||||
"userX": [
|
||||
[5,1], // starting from position 5, one operations is deleted
|
||||
[9,4] // starting from position 9, four operations are deleted
|
||||
],
|
||||
"userY": ...
|
||||
}
|
||||
* isDeleted(id)
|
||||
* getOpsFromDeleteSet(ds) -- TODO: just call Struct.Delete.delete(id) here
|
||||
- get a set of deletions that need to be applied in order to get to
|
||||
achieve the state of the supplied ds
|
||||
* setOperation(op)
|
||||
- write `op` to the database.
|
||||
Note: this is allowed to return an in-memory object.
|
||||
E.g. the Memory adapter returns the object that it has in-memory.
|
||||
Changing values on this object will be stored directly in the database
|
||||
without calling this function. Therefore,
|
||||
setOperation may have no functionality in some adapters. This also has
|
||||
implications on the way we use operations that were served from the database.
|
||||
We try not to call copyObject, if not necessary.
|
||||
* addOperation(op)
|
||||
- add an operation to the database.
|
||||
This may only be called once for every op.id
|
||||
* getOperation(id)
|
||||
* removeOperation(id)
|
||||
- remove an operation from the database. This is called when an operation
|
||||
is garbage collected.
|
||||
* setState(state)
|
||||
- `state` is of the form
|
||||
{
|
||||
user: "1",
|
||||
clock: 4
|
||||
} <- meaning that we have four operations from user "1"
|
||||
(with these id's respectively: 0, 1, 2, and 3)
|
||||
* getState(user)
|
||||
* getStateVector()
|
||||
- Get the state of the OS in the form
|
||||
[{
|
||||
user: "userX",
|
||||
clock: 11
|
||||
},
|
||||
..
|
||||
]
|
||||
* getStateSet()
|
||||
- Get the state of the OS in the form
|
||||
{
|
||||
"userX": 11,
|
||||
"userY": 22
|
||||
}
|
||||
* getOperations(startSS)
|
||||
- Get the all the operations that are necessary in order to achive the
|
||||
stateSet of this user, starting from a stateSet supplied by another user
|
||||
* makeOperationReady(ss, op)
|
||||
- this is called only by `getOperations(startSS)`. It makes an operation
|
||||
applyable on a given SS.
|
||||
*/
|
||||
class AbstractTransaction {
|
||||
constructor (store) {
|
||||
this.store = store
|
||||
@ -41,6 +110,18 @@ class AbstractTransaction {
|
||||
}
|
||||
Y.AbstractTransaction = AbstractTransaction
|
||||
|
||||
/*
|
||||
Partial definition of an OperationStore.
|
||||
TODO: name it Database, operation store only holds operations.
|
||||
|
||||
A database definition must alse define the following methods:
|
||||
* logTable() (optional)
|
||||
- show relevant information information in a table
|
||||
* requestTransaction(makeGen)
|
||||
- request a transaction
|
||||
* destroy()
|
||||
- destroy the database
|
||||
*/
|
||||
class AbstractOperationStore { // eslint-disable-line no-unused-vars
|
||||
constructor (y, opts) {
|
||||
this.y = y
|
||||
@ -71,44 +152,44 @@ class AbstractOperationStore { // eslint-disable-line no-unused-vars
|
||||
this.gcTimeout = opts.gcTimeout || 5000
|
||||
var os = this
|
||||
function garbageCollect () {
|
||||
var def = Promise.defer()
|
||||
os.requestTransaction(function * () {
|
||||
for (var i in os.gc2) {
|
||||
var oid = os.gc2[i]
|
||||
var o = yield* this.getOperation(oid)
|
||||
if (o.left != null) {
|
||||
var left = yield* this.getOperation(o.left)
|
||||
left.right = o.right
|
||||
yield* this.setOperation(left)
|
||||
return new Promise((resolve) => {
|
||||
os.requestTransaction(function * () {
|
||||
for (var i in os.gc2) {
|
||||
var oid = os.gc2[i]
|
||||
var o = yield* this.getOperation(oid)
|
||||
if (o.left != null) {
|
||||
var left = yield* this.getOperation(o.left)
|
||||
left.right = o.right
|
||||
yield* this.setOperation(left)
|
||||
}
|
||||
if (o.right != null) {
|
||||
var right = yield* this.getOperation(o.right)
|
||||
right.left = o.left
|
||||
yield* this.setOperation(right)
|
||||
}
|
||||
var parent = yield* this.getOperation(o.parent)
|
||||
var setParent = false
|
||||
if (Y.utils.compareIds(parent.start, o.id)) {
|
||||
setParent = true
|
||||
parent.start = o.right
|
||||
}
|
||||
if (Y.utils.compareIds(parent.end, o.id)) {
|
||||
setParent = true
|
||||
parent.end = o.left
|
||||
}
|
||||
if (setParent) {
|
||||
yield* this.setOperation(parent)
|
||||
}
|
||||
yield* this.removeOperation(o.id)
|
||||
}
|
||||
if (o.right != null) {
|
||||
var right = yield* this.getOperation(o.right)
|
||||
right.left = o.left
|
||||
yield* this.setOperation(right)
|
||||
os.gc2 = os.gc1
|
||||
os.gc1 = []
|
||||
if (os.gcTimeout > 0) {
|
||||
os.gcInterval = setTimeout(garbageCollect, os.gcTimeout)
|
||||
}
|
||||
var parent = yield* this.getOperation(o.parent)
|
||||
var setParent = false
|
||||
if (Y.utils.compareIds(parent.start, o.id)) {
|
||||
setParent = true
|
||||
parent.start = o.right
|
||||
}
|
||||
if (Y.utils.compareIds(parent.end, o.id)) {
|
||||
setParent = true
|
||||
parent.end = o.left
|
||||
}
|
||||
if (setParent) {
|
||||
yield* this.setOperation(parent)
|
||||
}
|
||||
yield* this.removeOperation(o.id)
|
||||
}
|
||||
os.gc2 = os.gc1
|
||||
os.gc1 = []
|
||||
if (os.gcTimeout > 0) {
|
||||
os.gcInterval = setTimeout(garbageCollect, os.gcTimeout)
|
||||
}
|
||||
def.resolve()
|
||||
resolve()
|
||||
})
|
||||
})
|
||||
return def.promise
|
||||
}
|
||||
this.garbageCollect = garbageCollect
|
||||
if (this.gcTimeout > 0) {
|
||||
|
@ -1,15 +1,6 @@
|
||||
/* global Y */
|
||||
'use strict'
|
||||
|
||||
function copyObject (o) {
|
||||
var c = {}
|
||||
for (var key in o) {
|
||||
c[key] = o[key]
|
||||
}
|
||||
return c
|
||||
}
|
||||
Y.utils.copyObject = copyObject
|
||||
|
||||
class DeleteStore extends Y.utils.RBTree {
|
||||
constructor () {
|
||||
super()
|
||||
@ -120,8 +111,8 @@ class DeleteStore extends Y.utils.RBTree {
|
||||
|
||||
Y.utils.DeleteStore = DeleteStore
|
||||
|
||||
Y.Memory = (function () { // eslint-disable-line no-unused-vars
|
||||
class Transaction extends Y.AbstractTransaction { // eslint-disable-line
|
||||
Y.Memory = (function () {
|
||||
class Transaction extends Y.AbstractTransaction {
|
||||
|
||||
constructor (store) {
|
||||
super(store)
|
||||
@ -144,28 +135,25 @@ Y.Memory = (function () { // eslint-disable-line no-unused-vars
|
||||
* getOpsFromDeleteSet (ds) {
|
||||
return this.ds.getDeletions(ds)
|
||||
}
|
||||
* setOperation (op) { // eslint-disable-line
|
||||
* setOperation (op) {
|
||||
// TODO: you can remove this step! probs..
|
||||
var n = this.os.findNode(op.id)
|
||||
n.val = op
|
||||
return op
|
||||
}
|
||||
* addOperation (op) { // eslint-disable-line
|
||||
* addOperation (op) {
|
||||
this.os.add(op)
|
||||
}
|
||||
* getOperation (id) { // eslint-disable-line
|
||||
if (id == null) {
|
||||
throw new Error('You must define id!')
|
||||
}
|
||||
* getOperation (id) {
|
||||
return this.os.find(id)
|
||||
}
|
||||
* removeOperation (id) { // eslint-disable-line
|
||||
* removeOperation (id) {
|
||||
this.os.delete(id)
|
||||
}
|
||||
* setState (state) { // eslint-disable-line
|
||||
* setState (state) {
|
||||
this.ss[state.user] = state.clock
|
||||
}
|
||||
* getState (user) { // eslint-disable-line
|
||||
* getState (user) {
|
||||
var clock = this.ss[user]
|
||||
if (clock == null) {
|
||||
clock = 0
|
||||
@ -175,7 +163,7 @@ Y.Memory = (function () { // eslint-disable-line no-unused-vars
|
||||
clock: clock
|
||||
}
|
||||
}
|
||||
* getStateVector () { // eslint-disable-line
|
||||
* getStateVector () {
|
||||
var stateVector = []
|
||||
for (var user in this.ss) {
|
||||
var clock = this.ss[user]
|
||||
@ -186,7 +174,7 @@ Y.Memory = (function () { // eslint-disable-line no-unused-vars
|
||||
}
|
||||
return stateVector
|
||||
}
|
||||
* getStateSet () { // eslint-disable-line
|
||||
* getStateSet () {
|
||||
return this.ss
|
||||
}
|
||||
* getOperations (startSS) {
|
||||
@ -225,7 +213,7 @@ Y.Memory = (function () { // eslint-disable-line no-unused-vars
|
||||
}
|
||||
* makeOperationReady (ss, op) {
|
||||
// instead of ss, you could use currSS (a ss that increments when you add an operation)
|
||||
op = copyObject(op)
|
||||
op = Y.utils.copyObject(op)
|
||||
var o = op
|
||||
var clock
|
||||
while (o.right != null) {
|
||||
|
@ -1,10 +1,9 @@
|
||||
/* global Y */
|
||||
'use strict'
|
||||
|
||||
function smaller (a, b) {
|
||||
return a[0] < b[0] || (a[0] === b[0] && a[1] < b[1])
|
||||
}
|
||||
Y.utils.smaller = smaller
|
||||
/*
|
||||
This file contains a not so fancy implemantion of a Red Black Tree.
|
||||
*/
|
||||
|
||||
class N {
|
||||
// A created node is always red!
|
||||
@ -126,7 +125,7 @@ class N {
|
||||
}
|
||||
}
|
||||
|
||||
class RBTree { // eslint-disable-line no-unused-vars
|
||||
class RBTree {
|
||||
constructor () {
|
||||
this.root = null
|
||||
this.length = 0
|
||||
@ -140,11 +139,11 @@ class RBTree { // eslint-disable-line no-unused-vars
|
||||
return null
|
||||
} else {
|
||||
while (true) {
|
||||
if ((from === null || smaller(from, o.val.id)) && o.left !== null) {
|
||||
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 && smaller(o.val.id, from)) {
|
||||
} 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
|
||||
@ -168,11 +167,11 @@ class RBTree { // eslint-disable-line no-unused-vars
|
||||
return null
|
||||
} else {
|
||||
while (true) {
|
||||
if ((to === null || smaller(o.val.id, to)) && o.right !== null) {
|
||||
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 && smaller(to, o.val.id)) {
|
||||
} 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
|
||||
@ -189,7 +188,7 @@ class RBTree { // eslint-disable-line no-unused-vars
|
||||
}
|
||||
iterate (from, to, f) {
|
||||
var o = this.findNodeWithLowerBound(from)
|
||||
while (o !== null && (to === null || smaller(o.val.id, to) || Y.utils.compareIds(o.val.id, to))) {
|
||||
while (o !== null && (to === null || Y.utils.smaller(o.val.id, to) || Y.utils.compareIds(o.val.id, to))) {
|
||||
f(o.val)
|
||||
o = o.next()
|
||||
}
|
||||
@ -226,9 +225,9 @@ class RBTree { // eslint-disable-line no-unused-vars
|
||||
if (o === null) {
|
||||
return false
|
||||
}
|
||||
if (smaller(id, o.val.id)) {
|
||||
if (Y.utils.smaller(id, o.val.id)) {
|
||||
o = o.left
|
||||
} else if (smaller(o.val.id, id)) {
|
||||
} else if (Y.utils.smaller(o.val.id, id)) {
|
||||
o = o.right
|
||||
} else {
|
||||
return o
|
||||
@ -386,14 +385,14 @@ class RBTree { // eslint-disable-line no-unused-vars
|
||||
if (this.root !== null) {
|
||||
var p = this.root // p abbrev. parent
|
||||
while (true) {
|
||||
if (smaller(node.val.id, p.val.id)) {
|
||||
if (Y.utils.smaller(node.val.id, p.val.id)) {
|
||||
if (p.left === null) {
|
||||
p.left = node
|
||||
break
|
||||
} else {
|
||||
p = p.left
|
||||
}
|
||||
} else if (smaller(p.val.id, node.val.id)) {
|
||||
} else if (Y.utils.smaller(p.val.id, node.val.id)) {
|
||||
if (p.right === null) {
|
||||
p.right = node
|
||||
break
|
||||
|
113
src/Struct.js
113
src/Struct.js
@ -1,25 +1,31 @@
|
||||
/* global Y */
|
||||
'use strict'
|
||||
|
||||
function compareIds (id1, id2) {
|
||||
if (id1 == null || id2 == null) {
|
||||
if (id1 == null && id2 == null) {
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
if (id1[0] === id2[0] && id1[1] === id2[1]) {
|
||||
return true
|
||||
} else {
|
||||
return false
|
||||
}
|
||||
}
|
||||
Y.utils.compareIds = compareIds
|
||||
/*
|
||||
An operation also defines the structure of a type. This is why operation and
|
||||
structure are used interchangeably here.
|
||||
|
||||
It must be of the type Object. I hope to achieve some performance
|
||||
improvements when working on databases that support the json format.
|
||||
|
||||
An operation must have the following properties:
|
||||
|
||||
* encode
|
||||
- Encode the structure in a readable format (preferably string- todo)
|
||||
* decode (todo)
|
||||
- decode structure to json
|
||||
* execute
|
||||
- Execute the semantics of an operation.
|
||||
* requiredOps
|
||||
- Operations that are required to execute this operation.
|
||||
*/
|
||||
|
||||
var Struct = {
|
||||
/* This Operations does _not_ have an id!
|
||||
{
|
||||
target: Id
|
||||
/* This is the only operation that is actually not a structure, because
|
||||
it is not stored in the OS. This is why it _does not_ have an id
|
||||
|
||||
op = {
|
||||
target: Id
|
||||
}
|
||||
*/
|
||||
Delete: {
|
||||
@ -29,16 +35,18 @@ var Struct = {
|
||||
requiredOps: function (op) {
|
||||
return [op.target]
|
||||
},
|
||||
execute: function * (op) {
|
||||
// console.log('Delete', op, console.trace())
|
||||
var target = yield* this.getOperation(op.target)
|
||||
/*
|
||||
Delete an operation from the OS, and add it to the GC, if necessary.
|
||||
*/
|
||||
delete: function * (targetId) {
|
||||
var target = yield* this.getOperation(targetId)
|
||||
if (target != null && !target.deleted) {
|
||||
target.deleted = true
|
||||
if (target.left !== null && (yield* this.getOperation(target.left)).deleted) {
|
||||
if (target.left != null && (yield* this.getOperation(target.left)).deleted) {
|
||||
this.store.addToGarbageCollector(target.id)
|
||||
target.gc = true
|
||||
}
|
||||
if (target.right !== null) {
|
||||
if (target.right != null) {
|
||||
var right = yield* this.getOperation(target.right)
|
||||
if (right.deleted && right.gc == null) {
|
||||
this.store.addToGarbageCollector(right.id)
|
||||
@ -49,15 +57,21 @@ var Struct = {
|
||||
yield* this.setOperation(target)
|
||||
var t = this.store.initializedTypes[JSON.stringify(target.parent)]
|
||||
if (t != null) {
|
||||
yield* t._changed(this, Y.utils.copyObject(op))
|
||||
yield* t._changed(this, {
|
||||
struct: 'Delete',
|
||||
target: targetId
|
||||
})
|
||||
}
|
||||
}
|
||||
this.ds.delete(op.target)
|
||||
var state = yield* this.getState(op.target[0])
|
||||
if (state.clock === op.target[1]) {
|
||||
this.ds.delete(targetId)
|
||||
var state = yield* this.getState(targetId[0])
|
||||
if (state.clock === targetId[1]) {
|
||||
yield* this.checkDeleteStoreForState(state)
|
||||
yield* this.setState(state)
|
||||
}
|
||||
},
|
||||
execute: function * (op) {
|
||||
yield* Struct.Delete.delete.call(this, op.target)
|
||||
}
|
||||
},
|
||||
Insert: {
|
||||
@ -65,27 +79,15 @@ var Struct = {
|
||||
content: any,
|
||||
left: Id,
|
||||
right: Id,
|
||||
origin: id,
|
||||
origin: Id,
|
||||
parent: Id,
|
||||
parentSub: string (optional),
|
||||
id: this.os.getNextOpId()
|
||||
parentSub: string (optional), // child of Map type
|
||||
id: Id
|
||||
}
|
||||
*/
|
||||
encode: function (op) {
|
||||
/* bad idea, right?
|
||||
var e = {
|
||||
id: op.id,
|
||||
left: op.left,
|
||||
right: op.right,
|
||||
origin: op.origin,
|
||||
parent: op.parent,
|
||||
content: op.content,
|
||||
struct: "Insert"
|
||||
}
|
||||
if (op.parentSub != null){
|
||||
e.parentSub = op.parentSub
|
||||
}
|
||||
return e;*/
|
||||
// TODO: you could not send the "left" property, then you also have to
|
||||
// "op.left = null" in $execute or $decode
|
||||
return op
|
||||
},
|
||||
requiredOps: function (op) {
|
||||
@ -96,7 +98,7 @@ var Struct = {
|
||||
if (op.right != null) {
|
||||
ids.push(op.right)
|
||||
}
|
||||
// if(op.right == null && op.left == null) {}
|
||||
// if (op.right == null && op.left == null) {
|
||||
ids.push(op.parent)
|
||||
|
||||
if (op.opContent != null) {
|
||||
@ -104,13 +106,13 @@ var Struct = {
|
||||
}
|
||||
return ids
|
||||
},
|
||||
getDistanceToOrigin: function *(op) {
|
||||
getDistanceToOrigin: function * (op) {
|
||||
if (op.left == null) {
|
||||
return 0
|
||||
} else {
|
||||
var d = 0
|
||||
var o = yield* this.getOperation(op.left)
|
||||
while (!compareIds(op.origin, (o ? o.id : null))) {
|
||||
while (!Y.utils.compareIds(op.origin, (o ? o.id : null))) {
|
||||
d++
|
||||
if (o.left == null) {
|
||||
break
|
||||
@ -156,7 +158,7 @@ var Struct = {
|
||||
|
||||
// handle conflicts
|
||||
while (true) {
|
||||
if (o != null && !compareIds(o.id, op.right)) {
|
||||
if (o != null && !Y.utils.compareIds(o.id, op.right)) {
|
||||
var oOriginDistance = yield* Struct.Insert.getDistanceToOrigin.call(this, o)
|
||||
if (oOriginDistance === i) {
|
||||
// case 1
|
||||
@ -250,7 +252,7 @@ var Struct = {
|
||||
*/
|
||||
return []
|
||||
},
|
||||
execute: function * (op) { // eslint-disable-line
|
||||
execute: function * (op) {
|
||||
op.start = null
|
||||
op.end = null
|
||||
},
|
||||
@ -277,7 +279,7 @@ var Struct = {
|
||||
map: function * (o, f) {
|
||||
o = o.start
|
||||
var res = []
|
||||
while (o !== null) {
|
||||
while (o !== null) { // TODO: change to != (at least some convention)
|
||||
var operation = yield* this.getOperation(o)
|
||||
if (!operation.deleted) {
|
||||
res.push(f(operation))
|
||||
@ -305,16 +307,12 @@ var Struct = {
|
||||
}
|
||||
},
|
||||
requiredOps: function () {
|
||||
/*
|
||||
var ids = []
|
||||
for (var end in op.map) {
|
||||
ids.push(op.map[end])
|
||||
}
|
||||
return ids
|
||||
*/
|
||||
return []
|
||||
},
|
||||
execute: function * () {},
|
||||
/*
|
||||
Get a property by name
|
||||
*/
|
||||
get: function * (op, name) {
|
||||
var oid = op.map[name]
|
||||
if (oid != null) {
|
||||
@ -323,6 +321,9 @@ var Struct = {
|
||||
? res.content : yield* this.getType(res.opContent))
|
||||
}
|
||||
},
|
||||
/*
|
||||
Delete a property by name
|
||||
*/
|
||||
delete: function * (op, name) {
|
||||
var v = op.map[name] || null
|
||||
if (v != null) {
|
||||
|
@ -49,7 +49,7 @@
|
||||
throw new Error('Unexpected struct!')
|
||||
}
|
||||
}
|
||||
this.eventHandler.callUserEventListeners(userEvents)
|
||||
this.eventHandler.callEventListeners(userEvents)
|
||||
})
|
||||
}
|
||||
get length () {
|
||||
@ -110,7 +110,7 @@
|
||||
ops[j].right = mostRight
|
||||
}
|
||||
yield* this.applyCreatedOperations(ops)
|
||||
eventHandler.awaitedLastInserts(ops.length)
|
||||
eventHandler.awaitedInserts(ops.length)
|
||||
})
|
||||
}
|
||||
delete (pos, length) {
|
||||
@ -139,11 +139,11 @@
|
||||
eventHandler.awaitAndPrematurelyCall(dels)
|
||||
this.os.requestTransaction(function *() {
|
||||
yield* this.applyCreatedOperations(dels)
|
||||
eventHandler.awaitedLastDeletes(dels.length, newLeft)
|
||||
eventHandler.awaitedDeletes(dels.length, newLeft)
|
||||
})
|
||||
}
|
||||
observe (f) {
|
||||
this.eventHandler.addUserEventListener(f)
|
||||
this.eventHandler.addEventListener(f)
|
||||
}
|
||||
* _changed (transaction, op) {
|
||||
if (!op.deleted) {
|
||||
|
@ -6,7 +6,6 @@ var numberOfYArrayTests = 10
|
||||
describe('Array Type', function () {
|
||||
var y1, y2, y3, yconfig1, yconfig2, yconfig3, flushAll
|
||||
|
||||
jasmine.DEFAULT_TIMEOUT_INTERVAL = 100
|
||||
beforeEach(async(function * (done) {
|
||||
yield createUsers(this, 3)
|
||||
y1 = (yconfig1 = this.users[0]).root
|
||||
@ -59,7 +58,7 @@ describe('Array Type', function () {
|
||||
expect(l2.toArray()).toEqual(l3.toArray())
|
||||
expect(l2.toArray()).toEqual([0, 2, 'y'])
|
||||
done()
|
||||
}))
|
||||
}), 100)
|
||||
it('Handles getOperations ascending ids bug in late sync', async(function * (done) {
|
||||
var l1, l2
|
||||
l1 = yield y1.set('Array', Y.Array)
|
||||
|
@ -21,11 +21,11 @@
|
||||
if (this.opContents[key] != null) {
|
||||
let prevType = this.opContents[key]
|
||||
oldValue = () => {// eslint-disable-line
|
||||
let def = Promise.defer()
|
||||
this.os.requestTransaction(function *() {// eslint-disable-line
|
||||
def.resolve(yield* this.getType(prevType))
|
||||
return new Promise((resolve) => {
|
||||
this.os.requestTransaction(function *() {// eslint-disable-line
|
||||
resolve(yield* this.getType(prevType))
|
||||
})
|
||||
})
|
||||
return def.promise
|
||||
}
|
||||
} else {
|
||||
oldValue = this.contents[key]
|
||||
@ -77,7 +77,7 @@
|
||||
throw new Error('Unexpected Operation!')
|
||||
}
|
||||
}
|
||||
this.eventHandler.callUserEventListeners(userEvents)
|
||||
this.eventHandler.callEventListeners(userEvents)
|
||||
})
|
||||
}
|
||||
get (key) {
|
||||
@ -91,12 +91,12 @@
|
||||
return this.contents[key]
|
||||
}
|
||||
} else {
|
||||
let def = Promise.defer()
|
||||
var oid = this.opContents[key]
|
||||
this.os.requestTransaction(function *() {
|
||||
def.resolve(yield* this.getType(oid))
|
||||
return new Promise((resolve) => {
|
||||
var oid = this.opContents[key]
|
||||
this.os.requestTransaction(function *() {
|
||||
resolve(yield* this.getType(oid))
|
||||
})
|
||||
})
|
||||
return def.promise
|
||||
}
|
||||
}
|
||||
delete (key) {
|
||||
@ -112,7 +112,7 @@
|
||||
eventHandler.awaitAndPrematurelyCall([modDel])
|
||||
this.os.requestTransaction(function *() {
|
||||
yield* this.applyCreatedOperations([del])
|
||||
eventHandler.awaitedLastDeletes(1)
|
||||
eventHandler.awaitedDeletes(1)
|
||||
})
|
||||
}
|
||||
}
|
||||
@ -130,35 +130,35 @@
|
||||
parentSub: key,
|
||||
struct: 'Insert'
|
||||
}
|
||||
var def = Promise.defer()
|
||||
if (value instanceof Y.utils.CustomType) {
|
||||
// construct a new type
|
||||
this.os.requestTransaction(function *() {
|
||||
var type = yield* value.createType.call(this)
|
||||
insert.opContent = type._model
|
||||
insert.id = this.store.getNextOpId()
|
||||
yield* this.applyCreatedOperations([insert])
|
||||
def.resolve(type)
|
||||
})
|
||||
} else {
|
||||
insert.content = value
|
||||
insert.id = this.os.getNextOpId()
|
||||
var eventHandler = this.eventHandler
|
||||
eventHandler.awaitAndPrematurelyCall([insert])
|
||||
return new Promise((resolve) => {
|
||||
if (value instanceof Y.utils.CustomType) {
|
||||
// construct a new type
|
||||
this.os.requestTransaction(function *() {
|
||||
var type = yield* value.createType.call(this)
|
||||
insert.opContent = type._model
|
||||
insert.id = this.store.getNextOpId()
|
||||
yield* this.applyCreatedOperations([insert])
|
||||
resolve(type)
|
||||
})
|
||||
} else {
|
||||
insert.content = value
|
||||
insert.id = this.os.getNextOpId()
|
||||
var eventHandler = this.eventHandler
|
||||
eventHandler.awaitAndPrematurelyCall([insert])
|
||||
|
||||
this.os.requestTransaction(function *() {
|
||||
yield* this.applyCreatedOperations([insert])
|
||||
eventHandler.awaitedLastInserts(1)
|
||||
})
|
||||
def.resolve(value)
|
||||
}
|
||||
return def.promise
|
||||
this.os.requestTransaction(function *() {
|
||||
yield* this.applyCreatedOperations([insert])
|
||||
eventHandler.awaitedInserts(1)
|
||||
})
|
||||
resolve(value)
|
||||
}
|
||||
})
|
||||
}
|
||||
observe (f) {
|
||||
this.eventHandler.addUserEventListener(f)
|
||||
this.eventHandler.addEventListener(f)
|
||||
}
|
||||
unobserve (f) {
|
||||
this.eventHandler.removeUserEventListener(f)
|
||||
this.eventHandler.removeEventListener(f)
|
||||
}
|
||||
observePath (path, f) {
|
||||
var self = this
|
||||
|
@ -6,7 +6,6 @@ var numberOfYMapTests = 5
|
||||
describe('Map Type', function () {
|
||||
var y1, y2, y3, y4, flushAll
|
||||
|
||||
jasmine.DEFAULT_TIMEOUT_INTERVAL = 50000
|
||||
beforeEach(async(function * (done) {
|
||||
yield createUsers(this, 5)
|
||||
y1 = this.users[0].root
|
||||
|
130
src/Utils.js
130
src/Utils.js
@ -1,13 +1,44 @@
|
||||
/* global Y */
|
||||
'use strict'
|
||||
|
||||
class EventHandler { // eslint-disable-line
|
||||
/*
|
||||
EventHandler is an helper class for constructing custom types.
|
||||
|
||||
Why: When constructing custom types, you sometimes want your types to work
|
||||
synchronous: E.g.
|
||||
``` Synchronous
|
||||
mytype.setSomething("yay")
|
||||
mytype.getSomething() === "yay"
|
||||
```
|
||||
``` Asynchronous
|
||||
mytype.setSomething("yay")
|
||||
mytype.getSomething() === undefined
|
||||
mytype.waitForSomething().then(function(){
|
||||
mytype.getSomething() === "yay"
|
||||
})
|
||||
|
||||
The structures usually work asynchronously (you have to wait for the
|
||||
database request to finish). EventHandler will help you to make your type
|
||||
synchronously.
|
||||
*/
|
||||
class EventHandler {
|
||||
/*
|
||||
onevent: is called when the structure changes.
|
||||
|
||||
Note: "awaiting opertations" is used to denote operations that were
|
||||
prematurely called. Events for received operations can not be executed until
|
||||
all prematurely called operations were executed ("waiting operations")
|
||||
*/
|
||||
constructor (onevent) {
|
||||
this.waiting = []
|
||||
this.awaiting = 0
|
||||
this.onevent = onevent
|
||||
this.userEventListeners = []
|
||||
this.eventListeners = []
|
||||
}
|
||||
/*
|
||||
Call this when a new operation arrives. It will be executed right away if
|
||||
there are no waiting operations, that you prematurely executed
|
||||
*/
|
||||
receivedOp (op) {
|
||||
if (this.awaiting <= 0) {
|
||||
this.onevent([op])
|
||||
@ -15,31 +46,43 @@ class EventHandler { // eslint-disable-line
|
||||
this.waiting.push(Y.utils.copyObject(op))
|
||||
}
|
||||
}
|
||||
/*
|
||||
You created some operations, and you want the `onevent` function to be
|
||||
called right away. Received operations will not be executed untill all
|
||||
prematurely called operations are executed
|
||||
*/
|
||||
awaitAndPrematurelyCall (ops) {
|
||||
this.awaiting++
|
||||
this.onevent(ops)
|
||||
}
|
||||
addUserEventListener (f) {
|
||||
this.userEventListeners.push(f)
|
||||
/*
|
||||
Basic event listener boilerplate...
|
||||
TODO: maybe put this in a different type..
|
||||
*/
|
||||
addEventListener (f) {
|
||||
this.eventListeners.push(f)
|
||||
}
|
||||
removeUserEventListener (f) {
|
||||
this.userEventListeners = this.userEventListeners.filter(function (g) {
|
||||
removeEventListener (f) {
|
||||
this.eventListeners = this.eventListeners.filter(function (g) {
|
||||
return f !== g
|
||||
})
|
||||
}
|
||||
removeAllUserEventListeners () {
|
||||
this.userEventListeners = []
|
||||
removeAllEventListeners () {
|
||||
this.eventListeners = []
|
||||
}
|
||||
callUserEventListeners (event) {
|
||||
for (var i in this.userEventListeners) {
|
||||
callEventListeners (event) {
|
||||
for (var i in this.eventListeners) {
|
||||
try {
|
||||
this.userEventListeners[i](event)
|
||||
this.eventListeners[i](event)
|
||||
} catch (e) {
|
||||
console.log('User events must not throw Errors!');// eslint-disable-line
|
||||
console.log('User events must not throw Errors!') // eslint-disable-line
|
||||
}
|
||||
}
|
||||
}
|
||||
awaitedLastInserts (n) {
|
||||
/*
|
||||
Call this when you successfully awaited the execution of n Insert operations
|
||||
*/
|
||||
awaitedInserts (n) {
|
||||
var ops = this.waiting.splice(this.waiting.length - n)
|
||||
for (var oid = 0; oid < ops.length; oid++) {
|
||||
var op = ops[oid]
|
||||
@ -57,9 +100,12 @@ class EventHandler { // eslint-disable-line
|
||||
}
|
||||
}
|
||||
}
|
||||
this.tryCallEvents()
|
||||
this._tryCallEvents()
|
||||
}
|
||||
awaitedLastDeletes (n, newLeft) {
|
||||
/*
|
||||
Call this when you successfully awaited the execution of n Delete operations
|
||||
*/
|
||||
awaitedDeletes (n, newLeft) {
|
||||
var ops = this.waiting.splice(this.waiting.length - n)
|
||||
for (var j in ops) {
|
||||
var del = ops[j]
|
||||
@ -73,9 +119,12 @@ class EventHandler { // eslint-disable-line
|
||||
}
|
||||
}
|
||||
}
|
||||
this.tryCallEvents()
|
||||
this._tryCallEvents()
|
||||
}
|
||||
tryCallEvents () {
|
||||
/* (private)
|
||||
Try to execute the events for the waiting operations
|
||||
*/
|
||||
_tryCallEvents () {
|
||||
this.awaiting--
|
||||
if (this.awaiting <= 0 && this.waiting.length > 0) {
|
||||
var events = this.waiting
|
||||
@ -86,6 +135,17 @@ class EventHandler { // eslint-disable-line
|
||||
}
|
||||
Y.utils.EventHandler = EventHandler
|
||||
|
||||
/*
|
||||
A wrapper for the definition of a custom type.
|
||||
Every custom type must have three properties:
|
||||
|
||||
* createType
|
||||
- Defines the model of a newly created custom type and returns the type
|
||||
* initType
|
||||
- Given a model, creates a custom type
|
||||
* class
|
||||
- the constructor of the custom type (e.g. in order to inherit from a type)
|
||||
*/
|
||||
class CustomType { // eslint-disable-line
|
||||
constructor (def) {
|
||||
if (def.createType == null ||
|
||||
@ -100,3 +160,39 @@ class CustomType { // eslint-disable-line
|
||||
}
|
||||
}
|
||||
Y.utils.CustomType = CustomType
|
||||
|
||||
/*
|
||||
Make a flat copy of an object
|
||||
(just copy properties)
|
||||
*/
|
||||
function copyObject (o) {
|
||||
var c = {}
|
||||
for (var key in o) {
|
||||
c[key] = o[key]
|
||||
}
|
||||
return c
|
||||
}
|
||||
Y.utils.copyObject = copyObject
|
||||
|
||||
/*
|
||||
Defines a smaller relation on Id's
|
||||
*/
|
||||
function smaller (a, b) {
|
||||
return a[0] < b[0] || (a[0] === b[0] && a[1] < b[1])
|
||||
}
|
||||
Y.utils.smaller = smaller
|
||||
|
||||
function compareIds (id1, id2) {
|
||||
if (id1 == null || id2 == null) {
|
||||
if (id1 == null && id2 == null) {
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
if (id1[0] === id2[0] && id1[1] === id2[1]) {
|
||||
return true
|
||||
} else {
|
||||
return false
|
||||
}
|
||||
}
|
||||
Y.utils.compareIds = compareIds
|
||||
|
17
src/y.js
17
src/y.js
@ -2,20 +2,19 @@
|
||||
'use strict'
|
||||
|
||||
function Y (opts) {
|
||||
var def = Promise.defer()
|
||||
new YConfig(opts, function (yconfig) { // eslint-disable-line
|
||||
yconfig.db.whenUserIdSet(function () {
|
||||
def.resolve(yconfig)
|
||||
return new Promise(function (resolve) {
|
||||
var yconfig = new YConfig(opts, function () {
|
||||
yconfig.db.whenUserIdSet(function () {
|
||||
resolve(yconfig)
|
||||
})
|
||||
})
|
||||
})
|
||||
return def.promise
|
||||
}
|
||||
|
||||
class YConfig { // eslint-disable-line no-unused-vars
|
||||
class YConfig {
|
||||
constructor (opts, callback) {
|
||||
this.db = new Y[opts.db.name](this, opts.db)
|
||||
this.connector = new Y[opts.connector.name](this, opts.connector)
|
||||
var yconfig = this
|
||||
this.db.requestTransaction(function * requestTransaction () {
|
||||
// create initial Map type
|
||||
var model = {
|
||||
@ -27,7 +26,7 @@ class YConfig { // eslint-disable-line no-unused-vars
|
||||
yield* this.addOperation(model)
|
||||
var root = yield* this.createType(model)
|
||||
this.store.y.root = root
|
||||
callback(yconfig)
|
||||
callback()
|
||||
})
|
||||
}
|
||||
isConnected () {
|
||||
@ -55,7 +54,7 @@ class YConfig { // eslint-disable-line no-unused-vars
|
||||
}
|
||||
}
|
||||
|
||||
if (g) { // eslint-disable-line
|
||||
if (typeof YConcurrency_TestingMode !== 'undefined') {
|
||||
g.Y = Y //eslint-disable-line
|
||||
// debugger //eslint-disable-line
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user