Compare commits
5 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
658081566b | ||
|
|
ffe0ec5a38 | ||
|
|
5cfe209688 | ||
|
|
51c8d3bdc6 | ||
|
|
0f86b71f78 |
@@ -8,8 +8,7 @@ Y({
|
|||||||
},
|
},
|
||||||
connector: {
|
connector: {
|
||||||
name: 'websockets-client',
|
name: 'websockets-client',
|
||||||
room: 'richtext-example',
|
room: 'richtext-example'
|
||||||
debug: true
|
|
||||||
// url: 'http://127.0.0.1:1234'
|
// url: 'http://127.0.0.1:1234'
|
||||||
},
|
},
|
||||||
sourceDir: '/bower_components',
|
sourceDir: '/bower_components',
|
||||||
|
|||||||
8
Examples/Xml/index.html
Normal file
8
Examples/Xml/index.html
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html>
|
||||||
|
<body>
|
||||||
|
<script src="../bower_components/yjs/y.es6"></script>
|
||||||
|
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.0.0-beta1/jquery.min.js"></script>
|
||||||
|
<script src="./index.js"></script>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
24
Examples/Xml/index.js
Normal file
24
Examples/Xml/index.js
Normal file
@@ -0,0 +1,24 @@
|
|||||||
|
/* global Y */
|
||||||
|
|
||||||
|
// initialize a shared object. This function call returns a promise!
|
||||||
|
Y({
|
||||||
|
db: {
|
||||||
|
name: 'memory'
|
||||||
|
},
|
||||||
|
connector: {
|
||||||
|
name: 'websockets-client',
|
||||||
|
// url: 'http://127.0.0.1:1234',
|
||||||
|
room: 'Xml-example'
|
||||||
|
},
|
||||||
|
sourceDir: '/bower_components',
|
||||||
|
share: {
|
||||||
|
xml: 'Xml("p")' // y.share.xml is of type Y.Xml with tagname "p"
|
||||||
|
}
|
||||||
|
}).then(function (y) {
|
||||||
|
window.yXml = y
|
||||||
|
// bind xml type to a dom, and put it in body
|
||||||
|
y.share.xml.getDom().then(function (dom) {
|
||||||
|
window.sharedDom = dom
|
||||||
|
document.body.appendChild(dom)
|
||||||
|
})
|
||||||
|
})
|
||||||
22
README.md
22
README.md
@@ -168,18 +168,28 @@ The promise returns an instance of Y. We denote it with a lower case `y`.
|
|||||||
* y.db.userId :: String
|
* y.db.userId :: String
|
||||||
* The used user id for this client. **Never overwrite this**
|
* The used user id for this client. **Never overwrite this**
|
||||||
|
|
||||||
|
|
||||||
# Status
|
|
||||||
Yjs is a work in progress. Different versions of the *y-* repositories may not work together. Just drop me a line if you run into troubles.
|
|
||||||
|
|
||||||
## Get help
|
## Get help
|
||||||
There are some friendly people on [](https://gitter.im/y-js/yjs?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge) who may help you with your problem, and answer your questions.
|
There are some friendly people on [](https://gitter.im/y-js/yjs?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge) who may help you with your problem, and answer your questions.
|
||||||
|
|
||||||
Please report _any_ issues to the [Github issue page](https://github.com/y-js/yjs/issues)! I try to fix them very soon, if possible.
|
Please report _any_ issues to the [Github issue page](https://github.com/y-js/yjs/issues)! I try to fix them very soon, if possible.
|
||||||
|
If you want to see an issue fixed, please subscribe to the thread (or remind me via gitter).
|
||||||
|
|
||||||
|
|
||||||
## Changelog
|
## Changelog
|
||||||
### 1.0.0
|
|
||||||
This is a complete rewrite of the 0.5 version of Yjs. Since Yjs 1.0 it is possible to work asynchronously on a persistent database, which enables offline support.
|
### 10.0.0
|
||||||
|
|
||||||
|
* Support for more complex types (a type can be a composition of several types)
|
||||||
|
* Fixes several memory leaks
|
||||||
|
|
||||||
|
### 9.0.0
|
||||||
|
There were several rolling updates from 0.6 to 0.8. We consider Yjs stable since a long time,
|
||||||
|
and intend to continue stable releases. From this release forward y-* modules will implement peer-dependencies for npm, and dependencies for bower.
|
||||||
|
Furthermore, incompatible yjs instances will now throw errors when syncing - this feature was influenced by #48. The versioning jump was influenced by react (see [here](https://facebook.github.io/react/blog/2016/02/19/new-versioning-scheme.html))
|
||||||
|
|
||||||
|
|
||||||
|
### 0.6.0
|
||||||
|
This is a complete rewrite of the 0.5 version of Yjs. Since Yjs 0.6.0 it is possible to work asynchronously on a persistent database, which enables offline support.
|
||||||
* Switched to semver versioning
|
* Switched to semver versioning
|
||||||
* Requires a promise implementation in environment (es6 promises suffice, included in all the major browsers). Otherwise you have to include a polyfill
|
* Requires a promise implementation in environment (es6 promises suffice, included in all the major browsers). Otherwise you have to include a polyfill
|
||||||
* Y.Object has been renamed to Y.Map
|
* Y.Object has been renamed to Y.Map
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "yjs",
|
"name": "yjs",
|
||||||
"version": "9.0.3",
|
"version": "10.0.2",
|
||||||
"homepage": "y-js.org",
|
"homepage": "y-js.org",
|
||||||
"authors": [
|
"authors": [
|
||||||
"Kevin Jahns <kevin.jahns@rwth-aachen.de>"
|
"Kevin Jahns <kevin.jahns@rwth-aachen.de>"
|
||||||
|
|||||||
404
y.es6
404
y.es6
@@ -20,6 +20,8 @@ module.exports = function (Y/* :any */) {
|
|||||||
userId: UserId;
|
userId: UserId;
|
||||||
send: Function;
|
send: Function;
|
||||||
broadcast: Function;
|
broadcast: Function;
|
||||||
|
broadcastOpBuffer: Array<Operation>;
|
||||||
|
protocolVersion: number;
|
||||||
*/
|
*/
|
||||||
/*
|
/*
|
||||||
opts contains the following information:
|
opts contains the following information:
|
||||||
@@ -52,7 +54,7 @@ module.exports = function (Y/* :any */) {
|
|||||||
this.broadcastedHB = false
|
this.broadcastedHB = false
|
||||||
this.syncStep2 = Promise.resolve()
|
this.syncStep2 = Promise.resolve()
|
||||||
this.broadcastOpBuffer = []
|
this.broadcastOpBuffer = []
|
||||||
this.protocolVersion = 8
|
this.protocolVersion = 10
|
||||||
}
|
}
|
||||||
reconnect () {
|
reconnect () {
|
||||||
}
|
}
|
||||||
@@ -151,7 +153,8 @@ module.exports = function (Y/* :any */) {
|
|||||||
conn.send(syncUser, {
|
conn.send(syncUser, {
|
||||||
type: 'sync step 1',
|
type: 'sync step 1',
|
||||||
stateSet: stateSet,
|
stateSet: stateSet,
|
||||||
deleteSet: deleteSet
|
deleteSet: deleteSet,
|
||||||
|
protocolVersion: conn.protocolVersion
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
} else {
|
} else {
|
||||||
@@ -235,7 +238,8 @@ module.exports = function (Y/* :any */) {
|
|||||||
type: 'sync step 2',
|
type: 'sync step 2',
|
||||||
os: ops,
|
os: ops,
|
||||||
stateSet: currentStateSet,
|
stateSet: currentStateSet,
|
||||||
deleteSet: ds
|
deleteSet: ds,
|
||||||
|
protocolVersion: this.protocolVersion
|
||||||
})
|
})
|
||||||
if (this.forwardToSyncingClients) {
|
if (this.forwardToSyncingClients) {
|
||||||
conn.syncingClients.push(sender)
|
conn.syncingClients.push(sender)
|
||||||
@@ -665,6 +669,18 @@ module.exports = function (Y /* :any */) {
|
|||||||
garbageCollect()
|
garbageCollect()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
emptyGarbageCollector () {
|
||||||
|
return new Promise(resolve => {
|
||||||
|
var check = () => {
|
||||||
|
if (this.gc1.length > 0 || this.gc2.length > 0) {
|
||||||
|
this.garbageCollect().then(check)
|
||||||
|
} else {
|
||||||
|
resolve()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
setTimeout(check, 0)
|
||||||
|
})
|
||||||
|
}
|
||||||
addToDebug () {
|
addToDebug () {
|
||||||
if (typeof YConcurrency_TestingMode !== 'undefined') {
|
if (typeof YConcurrency_TestingMode !== 'undefined') {
|
||||||
var command /* :string */ = Array.prototype.map.call(arguments, function (s) {
|
var command /* :string */ = Array.prototype.map.call(arguments, function (s) {
|
||||||
@@ -760,9 +776,7 @@ module.exports = function (Y /* :any */) {
|
|||||||
this.userIdPromise.then(f)
|
this.userIdPromise.then(f)
|
||||||
}
|
}
|
||||||
getNextOpId () {
|
getNextOpId () {
|
||||||
if (this._nextUserId != null) {
|
if (this.userId == null) {
|
||||||
return this._nextUserId
|
|
||||||
} else if (this.userId == null) {
|
|
||||||
throw new Error('OperationStore not yet initialized!')
|
throw new Error('OperationStore not yet initialized!')
|
||||||
} else {
|
} else {
|
||||||
return [this.userId, this.opClock++]
|
return [this.userId, this.opClock++]
|
||||||
@@ -781,6 +795,9 @@ module.exports = function (Y /* :any */) {
|
|||||||
var o = ops[i]
|
var o = ops[i]
|
||||||
if (o.id == null || o.id[0] !== this.y.connector.userId) {
|
if (o.id == null || o.id[0] !== this.y.connector.userId) {
|
||||||
var required = Y.Struct[o.struct].requiredOps(o)
|
var required = Y.Struct[o.struct].requiredOps(o)
|
||||||
|
if (o.requires != null) {
|
||||||
|
required = required.concat(o.requires)
|
||||||
|
}
|
||||||
this.whenOperationsExist(required, o)
|
this.whenOperationsExist(required, o)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -893,15 +910,7 @@ module.exports = function (Y /* :any */) {
|
|||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// increase SS
|
// increase SS
|
||||||
var o = op
|
yield* transaction.updateState(op.id[0])
|
||||||
var state = yield* transaction.getState(op.id[0])
|
|
||||||
while (o != null && o.id[1] === state.clock && op.id[0] === o.id[0]) {
|
|
||||||
// either its a new operation (1. case), or it is an operation that was deleted, but is not yet in the OS
|
|
||||||
state.clock++
|
|
||||||
yield* transaction.checkDeleteStoreForState(state)
|
|
||||||
o = yield* transaction.os.findNext(o.id)
|
|
||||||
}
|
|
||||||
yield* transaction.setState(state)
|
|
||||||
|
|
||||||
// notify whenOperation listeners (by id)
|
// notify whenOperation listeners (by id)
|
||||||
var sid = JSON.stringify(op.id)
|
var sid = JSON.stringify(op.id)
|
||||||
@@ -918,6 +927,15 @@ module.exports = function (Y /* :any */) {
|
|||||||
}
|
}
|
||||||
var t = this.initializedTypes[JSON.stringify(op.parent)]
|
var t = this.initializedTypes[JSON.stringify(op.parent)]
|
||||||
|
|
||||||
|
// if parent is deleted, mark as gc'd and return
|
||||||
|
if (op.parent != null) {
|
||||||
|
var parentIsDeleted = yield* transaction.isDeleted(op.parent)
|
||||||
|
if (parentIsDeleted) {
|
||||||
|
yield* transaction.deleteList(op.id)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Delete if DS says this is actually deleted
|
// Delete if DS says this is actually deleted
|
||||||
var opIsDeleted = yield* transaction.isDeleted(op.id)
|
var opIsDeleted = yield* transaction.isDeleted(op.id)
|
||||||
if (!op.deleted && opIsDeleted) {
|
if (!op.deleted && opIsDeleted) {
|
||||||
@@ -930,7 +948,12 @@ module.exports = function (Y /* :any */) {
|
|||||||
|
|
||||||
// notify parent, if it was instanciated as a custom type
|
// notify parent, if it was instanciated as a custom type
|
||||||
if (t != null) {
|
if (t != null) {
|
||||||
yield* t._changed(transaction, Y.utils.copyObject(op))
|
let o = Y.utils.copyObject(op)
|
||||||
|
if (opIsDeleted && !o.deleted) {
|
||||||
|
// op did not reflect the created delete op (happens when not using y-memory)
|
||||||
|
o.deleted = true
|
||||||
|
}
|
||||||
|
yield* t._changed(transaction, o)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1061,7 +1084,7 @@ module.exports = function (Y/* :any */) {
|
|||||||
if (op.parentSub != null) {
|
if (op.parentSub != null) {
|
||||||
e.parentSub = op.parentSub
|
e.parentSub = op.parentSub
|
||||||
}
|
}
|
||||||
if (op.opContent != null) {
|
if (op.hasOwnProperty('opContent')) {
|
||||||
e.opContent = op.opContent
|
e.opContent = op.opContent
|
||||||
} else {
|
} else {
|
||||||
e.content = op.content
|
e.content = op.content
|
||||||
@@ -1256,11 +1279,18 @@ module.exports = function (Y/* :any */) {
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
encode: function (op) {
|
encode: function (op) {
|
||||||
return {
|
var e = {
|
||||||
struct: 'List',
|
struct: 'List',
|
||||||
id: op.id,
|
id: op.id,
|
||||||
type: op.type
|
type: op.type
|
||||||
}
|
}
|
||||||
|
if (op.requires != null) {
|
||||||
|
e.requires = op.requires
|
||||||
|
}
|
||||||
|
if (op.info != null) {
|
||||||
|
e.info = op.info
|
||||||
|
}
|
||||||
|
return e
|
||||||
},
|
},
|
||||||
requiredOps: function () {
|
requiredOps: function () {
|
||||||
/*
|
/*
|
||||||
@@ -1329,12 +1359,19 @@ module.exports = function (Y/* :any */) {
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
encode: function (op) {
|
encode: function (op) {
|
||||||
return {
|
var e = {
|
||||||
struct: 'Map',
|
struct: 'Map',
|
||||||
type: op.type,
|
type: op.type,
|
||||||
id: op.id,
|
id: op.id,
|
||||||
map: {} // overwrite map!!
|
map: {} // overwrite map!!
|
||||||
}
|
}
|
||||||
|
if (op.requires != null) {
|
||||||
|
e.requires = op.requires
|
||||||
|
}
|
||||||
|
if (op.info != null) {
|
||||||
|
e.info = op.info
|
||||||
|
}
|
||||||
|
return e
|
||||||
},
|
},
|
||||||
requiredOps: function () {
|
requiredOps: function () {
|
||||||
return []
|
return []
|
||||||
@@ -1451,26 +1488,52 @@ module.exports = function (Y/* :any */) {
|
|||||||
If it does not exist yes, create it.
|
If it does not exist yes, create it.
|
||||||
TODO: delete type from store.initializedTypes[id] when corresponding id was deleted!
|
TODO: delete type from store.initializedTypes[id] when corresponding id was deleted!
|
||||||
*/
|
*/
|
||||||
* getType (id) {
|
* getType (id, args) {
|
||||||
var sid = JSON.stringify(id)
|
var sid = JSON.stringify(id)
|
||||||
var t = this.store.initializedTypes[sid]
|
var t = this.store.initializedTypes[sid]
|
||||||
if (t == null) {
|
if (t == null) {
|
||||||
var op/* :MapStruct | ListStruct */ = yield* this.getOperation(id)
|
var op/* :MapStruct | ListStruct */ = yield* this.getOperation(id)
|
||||||
if (op != null) {
|
if (op != null) {
|
||||||
t = yield* Y[op.type].initType.call(this, this.store, op)
|
t = yield* Y[op.type].typeDefinition.initType.call(this, this.store, op, args)
|
||||||
this.store.initializedTypes[sid] = t
|
this.store.initializedTypes[sid] = t
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return t
|
return t
|
||||||
}
|
}
|
||||||
* createType (typedefinition) {
|
* createType (typedefinition, id) {
|
||||||
var structname = typedefinition.struct
|
var structname = typedefinition[0].struct
|
||||||
var id = this.store.getNextOpId()
|
id = id || this.store.getNextOpId()
|
||||||
var op = Y.Struct[structname].create(id)
|
var op
|
||||||
op.type = typedefinition.name
|
if (id[0] === '_') {
|
||||||
yield* this.applyCreatedOperations([op])
|
op = yield* this.getOperation(id)
|
||||||
return yield* this.getType(id)
|
} else {
|
||||||
|
op = Y.Struct[structname].create(id)
|
||||||
|
op.type = typedefinition[0].name
|
||||||
|
}
|
||||||
|
if (typedefinition[0].appendAdditionalInfo != null) {
|
||||||
|
yield* typedefinition[0].appendAdditionalInfo.call(this, op, typedefinition[1])
|
||||||
|
}
|
||||||
|
if (op[0] === '_') {
|
||||||
|
yield* this.setOperation(op)
|
||||||
|
} else {
|
||||||
|
yield* this.applyCreatedOperations([op])
|
||||||
|
}
|
||||||
|
return yield* this.getType(id, typedefinition[1])
|
||||||
}
|
}
|
||||||
|
/* createType (typedefinition, id) {
|
||||||
|
var structname = typedefinition[0].struct
|
||||||
|
id = id || this.store.getNextOpId()
|
||||||
|
var op = Y.Struct[structname].create(id)
|
||||||
|
op.type = typedefinition[0].name
|
||||||
|
if (typedefinition[0].appendAdditionalInfo != null) {
|
||||||
|
yield* typedefinition[0].appendAdditionalInfo.call(this, op, typedefinition[1])
|
||||||
|
}
|
||||||
|
// yield* this.applyCreatedOperations([op])
|
||||||
|
yield* Y.Struct[op.struct].execute.call(this, op)
|
||||||
|
yield* this.addOperation(op)
|
||||||
|
yield* this.store.operationAdded(this, op)
|
||||||
|
return yield* this.getType(id, typedefinition[1])
|
||||||
|
}*/
|
||||||
/*
|
/*
|
||||||
Apply operations that this user created (no remote ones!)
|
Apply operations that this user created (no remote ones!)
|
||||||
* does not check for Struct.*.requiredOps()
|
* does not check for Struct.*.requiredOps()
|
||||||
@@ -1481,7 +1544,7 @@ module.exports = function (Y/* :any */) {
|
|||||||
for (var i = 0; i < ops.length; i++) {
|
for (var i = 0; i < ops.length; i++) {
|
||||||
var op = ops[i]
|
var op = ops[i]
|
||||||
yield* this.store.tryExecute.call(this, op)
|
yield* this.store.tryExecute.call(this, op)
|
||||||
if (op.id == null || op.id[0] !== '_') {
|
if (op.id == null || typeof op.id[1] !== 'string') {
|
||||||
send.push(Y.Struct[op.struct].encode(op))
|
send.push(Y.Struct[op.struct].encode(op))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1492,17 +1555,21 @@ module.exports = function (Y/* :any */) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
* deleteList (start) {
|
* deleteList (start) {
|
||||||
if (this.store.y.connector.isSynced) {
|
while (start != null) {
|
||||||
while (start != null && this.store.y.connector.isSynced) {
|
start = yield* this.getOperation(start)
|
||||||
start = yield* this.getOperation(start)
|
if (!start.gc) {
|
||||||
start.gc = true
|
start.gc = true
|
||||||
|
start.deleted = true
|
||||||
yield* this.setOperation(start)
|
yield* this.setOperation(start)
|
||||||
// TODO: will always reset the parent..
|
yield* this.markDeleted(start.id, 1)
|
||||||
this.store.gc1.push(start.id)
|
if (start.opContent != null) {
|
||||||
start = start.right
|
yield* this.deleteOperation(start.opContent)
|
||||||
|
}
|
||||||
|
if (this.store.y.connector.isSynced) {
|
||||||
|
this.store.gc1.push(start.id)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
} else {
|
start = start.right
|
||||||
// TODO: when not possible??? do later in (gcWhenSynced)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1517,7 +1584,7 @@ module.exports = function (Y/* :any */) {
|
|||||||
yield* this.markDeleted(targetId, 1)
|
yield* this.markDeleted(targetId, 1)
|
||||||
}
|
}
|
||||||
|
|
||||||
if (target != null && target.gc == null) {
|
if (target != null) {
|
||||||
if (!target.deleted) {
|
if (!target.deleted) {
|
||||||
callType = true
|
callType = true
|
||||||
// set deleted & notify type
|
// set deleted & notify type
|
||||||
@@ -1537,18 +1604,23 @@ module.exports = function (Y/* :any */) {
|
|||||||
if (target.start != null) {
|
if (target.start != null) {
|
||||||
// TODO: don't do it like this .. -.-
|
// TODO: don't do it like this .. -.-
|
||||||
yield* this.deleteList(target.start)
|
yield* this.deleteList(target.start)
|
||||||
yield* this.deleteList(target.id)
|
// yield* this.deleteList(target.id) -- do not gc itself because this may still get referenced
|
||||||
}
|
}
|
||||||
if (target.map != null) {
|
if (target.map != null) {
|
||||||
for (var name in target.map) {
|
for (var name in target.map) {
|
||||||
yield* this.deleteList(target.map[name])
|
yield* this.deleteList(target.map[name])
|
||||||
}
|
}
|
||||||
// TODO: here to.. (see above)
|
// TODO: here to.. (see above)
|
||||||
yield* this.deleteList(target.id)
|
// yield* this.deleteList(target.id) -- see above
|
||||||
}
|
}
|
||||||
if (target.opContent != null) {
|
if (target.opContent != null) {
|
||||||
yield* this.deleteOperation(target.opContent)
|
yield* this.deleteOperation(target.opContent)
|
||||||
target.opContent = null
|
// target.opContent = null
|
||||||
|
}
|
||||||
|
if (target.requires != null) {
|
||||||
|
for (var i = 0; i < target.requires.length; i++) {
|
||||||
|
yield* this.deleteOperation(target.requires[i])
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
var left
|
var left
|
||||||
@@ -1715,9 +1787,40 @@ module.exports = function (Y/* :any */) {
|
|||||||
*/
|
*/
|
||||||
* garbageCollectAfterSync () {
|
* garbageCollectAfterSync () {
|
||||||
yield* this.os.iterate(this, null, null, function * (op) {
|
yield* this.os.iterate(this, null, null, function * (op) {
|
||||||
if (op.deleted && op.left != null) {
|
if (op.gc) {
|
||||||
var left = yield* this.getOperation(op.left)
|
this.store.gc1.push(op.id)
|
||||||
this.store.addToGarbageCollector(op, left)
|
} else {
|
||||||
|
if (op.parent != null) {
|
||||||
|
var parentDeleted = yield* this.isDeleted(op.parent)
|
||||||
|
if (parentDeleted) {
|
||||||
|
op.gc = true
|
||||||
|
if (!op.deleted) {
|
||||||
|
yield* this.markDeleted(op.id, 1)
|
||||||
|
op.deleted = true
|
||||||
|
if (op.opContent != null) {
|
||||||
|
yield* this.deleteOperation(op.opContent)
|
||||||
|
/*
|
||||||
|
var opContent = yield* this.getOperation(op.opContent)
|
||||||
|
opContent.gc = true
|
||||||
|
yield* this.setOperation(opContent)
|
||||||
|
this.store.gc1.push(opContent.id)
|
||||||
|
*/
|
||||||
|
}
|
||||||
|
if (op.requires != null) {
|
||||||
|
for (var i = 0; i < op.requires.length; i++) {
|
||||||
|
yield* this.deleteOperation(op.requires[i])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
yield* this.setOperation(op)
|
||||||
|
this.store.gc1.push(op.id)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (op.deleted && op.left != null) {
|
||||||
|
var left = yield* this.getOperation(op.left)
|
||||||
|
this.store.addToGarbageCollector(op, left)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
@@ -1743,6 +1846,29 @@ module.exports = function (Y/* :any */) {
|
|||||||
}
|
}
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
var deps = []
|
||||||
|
if (o.opContent != null) {
|
||||||
|
deps.push(o.opContent)
|
||||||
|
}
|
||||||
|
if (o.requires != null) {
|
||||||
|
deps = deps.concat(o.requires)
|
||||||
|
}
|
||||||
|
for (var i = 0; i < deps.length; i++) {
|
||||||
|
var dep = yield* this.getOperation(deps[i])
|
||||||
|
if (dep != null) {
|
||||||
|
if (!dep.deleted) {
|
||||||
|
yield* this.deleteOperation(dep.id)
|
||||||
|
dep = yield* this.getOperation(dep.id)
|
||||||
|
}
|
||||||
|
dep.gc = true
|
||||||
|
yield* this.setOperation(dep)
|
||||||
|
this.store.gc1.push(dep.id)
|
||||||
|
} else {
|
||||||
|
yield* this.markGarbageCollected(deps[i], 1)
|
||||||
|
yield* this.updateState(deps[i][0]) // TODO: unneccessary?
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// remove gc'd op from the left op, if it exists
|
// remove gc'd op from the left op, if it exists
|
||||||
if (o.left != null) {
|
if (o.left != null) {
|
||||||
var left = yield* this.getOperation(o.left)
|
var left = yield* this.getOperation(o.left)
|
||||||
@@ -1825,20 +1951,22 @@ module.exports = function (Y/* :any */) {
|
|||||||
// so we have to set right here
|
// so we have to set right here
|
||||||
yield* this.setOperation(right)
|
yield* this.setOperation(right)
|
||||||
}
|
}
|
||||||
// o may originate in another operation.
|
|
||||||
// Since o is deleted, we have to reset o.origin's `originOf` property
|
|
||||||
if (o.origin != null) {
|
|
||||||
var origin = yield* this.getOperation(o.origin)
|
|
||||||
origin.originOf = origin.originOf.filter(function (_id) {
|
|
||||||
return !Y.utils.compareIds(id, _id)
|
|
||||||
})
|
|
||||||
yield* this.setOperation(origin)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
// o may originate in another operation.
|
||||||
|
// Since o is deleted, we have to reset o.origin's `originOf` property
|
||||||
|
if (o.origin != null) {
|
||||||
|
var origin = yield* this.getOperation(o.origin)
|
||||||
|
origin.originOf = origin.originOf.filter(function (_id) {
|
||||||
|
return !Y.utils.compareIds(id, _id)
|
||||||
|
})
|
||||||
|
yield* this.setOperation(origin)
|
||||||
|
}
|
||||||
|
var parent
|
||||||
if (o.parent != null) {
|
if (o.parent != null) {
|
||||||
// remove gc'd op from parent, if it exists
|
parent = yield* this.getOperation(o.parent)
|
||||||
var parent /* MapOperation */ = yield* this.getOperation(o.parent)
|
}
|
||||||
|
// remove gc'd op from parent, if it exists
|
||||||
|
if (parent != null) {
|
||||||
var setParent = false // whether to save parent to the os
|
var setParent = false // whether to save parent to the os
|
||||||
if (o.parentSub != null) {
|
if (o.parentSub != null) {
|
||||||
if (Y.utils.compareIds(parent.map[o.parentSub], o.id)) {
|
if (Y.utils.compareIds(parent.map[o.parentSub], o.id)) {
|
||||||
@@ -1871,6 +1999,18 @@ module.exports = function (Y/* :any */) {
|
|||||||
state.clock = Math.max(state.clock, n.id[1] + n.len)
|
state.clock = Math.max(state.clock, n.id[1] + n.len)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
* updateState (user) {
|
||||||
|
var state = yield* this.getState(user)
|
||||||
|
yield* this.checkDeleteStoreForState(state)
|
||||||
|
var o = yield* this.getOperation([user, state.clock])
|
||||||
|
while (o != null && o.id[1] === state.clock && user === o.id[0]) {
|
||||||
|
// either its a new operation (1. case), or it is an operation that was deleted, but is not yet in the OS
|
||||||
|
state.clock++
|
||||||
|
yield* this.checkDeleteStoreForState(state)
|
||||||
|
o = yield* this.os.findNext(o.id)
|
||||||
|
}
|
||||||
|
yield* this.setState(state)
|
||||||
|
}
|
||||||
/*
|
/*
|
||||||
apply a delete set in order to get
|
apply a delete set in order to get
|
||||||
the state of the supplied ds
|
the state of the supplied ds
|
||||||
@@ -2007,7 +2147,7 @@ module.exports = function (Y/* :any */) {
|
|||||||
}
|
}
|
||||||
* addOperation (op) {
|
* addOperation (op) {
|
||||||
yield* this.os.put(op)
|
yield* this.os.put(op)
|
||||||
if (!this.store.y.connector.isDisconnected() && this.store.forwardAppliedOperations && op.id[0] !== '_') {
|
if (!this.store.y.connector.isDisconnected() && this.store.forwardAppliedOperations && typeof op.id[1] !== 'string') {
|
||||||
// is connected, and this is not going to be send in addOperation
|
// is connected, and this is not going to be send in addOperation
|
||||||
this.store.y.connector.broadcastOps([op])
|
this.store.y.connector.broadcastOps([op])
|
||||||
}
|
}
|
||||||
@@ -2017,18 +2157,21 @@ module.exports = function (Y/* :any */) {
|
|||||||
if (o != null || id[0] !== '_') {
|
if (o != null || id[0] !== '_') {
|
||||||
return o
|
return o
|
||||||
} else {
|
} else {
|
||||||
// need to generate this operation
|
// generate this operation?
|
||||||
if (this.store._nextUserId == null) {
|
var comp = id[1].split('_')
|
||||||
var struct = id[1].split('_')[0]
|
if (comp.length > 1) {
|
||||||
// this.store._nextUserId = id
|
var struct = comp[0]
|
||||||
var op = Y.Struct[struct].create(id)
|
var op = Y.Struct[struct].create(id)
|
||||||
|
op.type = comp[1]
|
||||||
yield* this.setOperation(op)
|
yield* this.setOperation(op)
|
||||||
// delete this.store._nextUserId
|
|
||||||
return op
|
return op
|
||||||
} else {
|
} else {
|
||||||
// Can only generate one operation at a time
|
// won't be called. but just in case..
|
||||||
|
console.error('Unexpected case. How can this happen?')
|
||||||
|
debugger // eslint-disable-line
|
||||||
return null
|
return null
|
||||||
}
|
}
|
||||||
|
return null
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
* removeOperation (id) {
|
* removeOperation (id) {
|
||||||
@@ -2253,7 +2396,40 @@ module.exports = function (Y/* :any */) {
|
|||||||
module.exports = function (Y /* : any*/) {
|
module.exports = function (Y /* : any*/) {
|
||||||
Y.utils = {}
|
Y.utils = {}
|
||||||
|
|
||||||
class EventHandler {
|
class EventListenerHandler {
|
||||||
|
constructor () {
|
||||||
|
this.eventListeners = []
|
||||||
|
}
|
||||||
|
destroy () {
|
||||||
|
this.eventListeners = null
|
||||||
|
}
|
||||||
|
/*
|
||||||
|
Basic event listener boilerplate...
|
||||||
|
*/
|
||||||
|
addEventListener (f) {
|
||||||
|
this.eventListeners.push(f)
|
||||||
|
}
|
||||||
|
removeEventListener (f) {
|
||||||
|
this.eventListeners = this.eventListeners.filter(function (g) {
|
||||||
|
return f !== g
|
||||||
|
})
|
||||||
|
}
|
||||||
|
removeAllEventListeners () {
|
||||||
|
this.eventListeners = []
|
||||||
|
}
|
||||||
|
callEventListeners (event) {
|
||||||
|
for (var i = 0; i < this.eventListeners.length; i++) {
|
||||||
|
try {
|
||||||
|
this.eventListeners[i](event)
|
||||||
|
} catch (e) {
|
||||||
|
console.error('User events must not throw Errors!')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Y.utils.EventListenerHandler = EventListenerHandler
|
||||||
|
|
||||||
|
class EventHandler extends EventListenerHandler {
|
||||||
/* ::
|
/* ::
|
||||||
waiting: Array<Insertion | Deletion>;
|
waiting: Array<Insertion | Deletion>;
|
||||||
awaiting: number;
|
awaiting: number;
|
||||||
@@ -2268,16 +2444,16 @@ module.exports = function (Y /* : any*/) {
|
|||||||
all prematurely called operations were executed ("waiting operations")
|
all prematurely called operations were executed ("waiting operations")
|
||||||
*/
|
*/
|
||||||
constructor (onevent /* : Function */) {
|
constructor (onevent /* : Function */) {
|
||||||
|
super()
|
||||||
this.waiting = []
|
this.waiting = []
|
||||||
this.awaiting = 0
|
this.awaiting = 0
|
||||||
this.onevent = onevent
|
this.onevent = onevent
|
||||||
this.eventListeners = []
|
|
||||||
}
|
}
|
||||||
destroy () {
|
destroy () {
|
||||||
|
super.destroy()
|
||||||
this.waiting = null
|
this.waiting = null
|
||||||
this.awaiting = null
|
this.awaiting = null
|
||||||
this.onevent = null
|
this.onevent = null
|
||||||
this.eventListeners = null
|
|
||||||
}
|
}
|
||||||
/*
|
/*
|
||||||
Call this when a new operation arrives. It will be executed right away if
|
Call this when a new operation arrives. It will be executed right away if
|
||||||
@@ -2299,30 +2475,6 @@ module.exports = function (Y /* : any*/) {
|
|||||||
this.awaiting++
|
this.awaiting++
|
||||||
this.onevent(ops)
|
this.onevent(ops)
|
||||||
}
|
}
|
||||||
/*
|
|
||||||
Basic event listener boilerplate...
|
|
||||||
TODO: maybe put this in a different type..
|
|
||||||
*/
|
|
||||||
addEventListener (f) {
|
|
||||||
this.eventListeners.push(f)
|
|
||||||
}
|
|
||||||
removeEventListener (f) {
|
|
||||||
this.eventListeners = this.eventListeners.filter(function (g) {
|
|
||||||
return f !== g
|
|
||||||
})
|
|
||||||
}
|
|
||||||
removeAllEventListeners () {
|
|
||||||
this.eventListeners = []
|
|
||||||
}
|
|
||||||
callEventListeners (event) {
|
|
||||||
for (var i = 0; i < this.eventListeners.length; i++) {
|
|
||||||
try {
|
|
||||||
this.eventListeners[i](event)
|
|
||||||
} catch (e) {
|
|
||||||
console.log('User events must not throw Errors!') // eslint-disable-line
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
/*
|
/*
|
||||||
Call this when you successfully awaited the execution of n Insert operations
|
Call this when you successfully awaited the execution of n Insert operations
|
||||||
*/
|
*/
|
||||||
@@ -2419,10 +2571,26 @@ module.exports = function (Y /* : any*/) {
|
|||||||
this.initType = def.initType
|
this.initType = def.initType
|
||||||
this.class = def.class
|
this.class = def.class
|
||||||
this.name = def.name
|
this.name = def.name
|
||||||
|
if (def.appendAdditionalInfo != null) {
|
||||||
|
this.appendAdditionalInfo = def.appendAdditionalInfo
|
||||||
|
}
|
||||||
|
this.parseArguments = (def.parseArguments || function () {
|
||||||
|
return [this]
|
||||||
|
}).bind(this)
|
||||||
|
this.parseArguments.typeDefinition = this
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Y.utils.CustomType = CustomType
|
Y.utils.CustomType = CustomType
|
||||||
|
|
||||||
|
Y.utils.isTypeDefinition = function isTypeDefinition (v) {
|
||||||
|
if (v != null) {
|
||||||
|
if (v instanceof Y.utils.CustomType) return [v]
|
||||||
|
else if (v.constructor === Array && v[0] instanceof Y.utils.CustomType) return v
|
||||||
|
else if (v instanceof Function && v.typeDefinition instanceof Y.utils.CustomType) return [v.typeDefinition]
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
Make a flat copy of an object
|
Make a flat copy of an object
|
||||||
(just copy properties)
|
(just copy properties)
|
||||||
@@ -2630,7 +2798,11 @@ module.exports = Y
|
|||||||
Y.requiringModules = requiringModules
|
Y.requiringModules = requiringModules
|
||||||
|
|
||||||
Y.extend = function (name, value) {
|
Y.extend = function (name, value) {
|
||||||
Y[name] = value
|
if (value instanceof Y.utils.CustomType) {
|
||||||
|
Y[name] = value.parseArguments
|
||||||
|
} else {
|
||||||
|
Y[name] = value
|
||||||
|
}
|
||||||
if (requiringModules[name] != null) {
|
if (requiringModules[name] != null) {
|
||||||
requiringModules[name].resolve()
|
requiringModules[name].resolve()
|
||||||
delete requiringModules[name]
|
delete requiringModules[name]
|
||||||
@@ -2645,9 +2817,10 @@ function requestModules (modules) {
|
|||||||
var extention = typeof regeneratorRuntime !== 'undefined' ? '.js' : '.es6'
|
var extention = typeof regeneratorRuntime !== 'undefined' ? '.js' : '.es6'
|
||||||
var promises = []
|
var promises = []
|
||||||
for (var i = 0; i < modules.length; i++) {
|
for (var i = 0; i < modules.length; i++) {
|
||||||
var modulename = 'y-' + modules[i].toLowerCase()
|
var module = modules[i].split('(')[0]
|
||||||
if (Y[modules[i]] == null) {
|
var modulename = 'y-' + module.toLowerCase()
|
||||||
if (requiringModules[modules[i]] == null) {
|
if (Y[module] == null) {
|
||||||
|
if (requiringModules[module] == null) {
|
||||||
// module does not exist
|
// module does not exist
|
||||||
if (typeof window !== 'undefined' && window.Y !== 'undefined') {
|
if (typeof window !== 'undefined' && window.Y !== 'undefined') {
|
||||||
var imported = document.createElement('script')
|
var imported = document.createElement('script')
|
||||||
@@ -2655,7 +2828,7 @@ function requestModules (modules) {
|
|||||||
document.head.appendChild(imported)
|
document.head.appendChild(imported)
|
||||||
|
|
||||||
let requireModule = {}
|
let requireModule = {}
|
||||||
requiringModules[modules[i]] = requireModule
|
requiringModules[module] = requireModule
|
||||||
requireModule.promise = new Promise(function (resolve) {
|
requireModule.promise = new Promise(function (resolve) {
|
||||||
requireModule.resolve = resolve
|
requireModule.resolve = resolve
|
||||||
})
|
})
|
||||||
@@ -2708,13 +2881,21 @@ function Y (opts/* :YOptions */) /* :Promise<YConfig> */ {
|
|||||||
}
|
}
|
||||||
Y.sourceDir = opts.sourceDir
|
Y.sourceDir = opts.sourceDir
|
||||||
return Y.requestModules(modules).then(function () {
|
return Y.requestModules(modules).then(function () {
|
||||||
return new Promise(function (resolve) {
|
return new Promise(function (resolve, reject) {
|
||||||
var yconfig = new YConfig(opts)
|
if (opts == null) reject('An options object is expected! ')
|
||||||
yconfig.db.whenUserIdSet(function () {
|
else if (opts.connector == null) reject('You must specify a connector! (missing connector property)')
|
||||||
yconfig.init(function () {
|
else if (opts.connector.name == null) reject('You must specify connector name! (missing connector.name property)')
|
||||||
resolve(yconfig)
|
else if (opts.db == null) reject('You must specify a database! (missing db property)')
|
||||||
|
else if (opts.connector.name == null) reject('You must specify db name! (missing db.name property)')
|
||||||
|
else if (opts.share == null) reject('You must specify a set of shared types!')
|
||||||
|
else {
|
||||||
|
var yconfig = new YConfig(opts)
|
||||||
|
yconfig.db.whenUserIdSet(function () {
|
||||||
|
yconfig.init(function () {
|
||||||
|
resolve(yconfig)
|
||||||
|
})
|
||||||
})
|
})
|
||||||
})
|
}
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
@@ -2738,15 +2919,20 @@ class YConfig {
|
|||||||
this.db.requestTransaction(function * requestTransaction () {
|
this.db.requestTransaction(function * requestTransaction () {
|
||||||
// create shared object
|
// create shared object
|
||||||
for (var propertyname in opts.share) {
|
for (var propertyname in opts.share) {
|
||||||
var typename = opts.share[propertyname]
|
var typeConstructor = opts.share[propertyname].split('(')
|
||||||
var id = ['_', Y[typename].struct + '_' + propertyname]
|
var typeName = typeConstructor.splice(0, 1)
|
||||||
var op = yield* this.getOperation(id)
|
var args = []
|
||||||
if (op.type !== typename) {
|
if (typeConstructor.length === 1) {
|
||||||
// not already in the db
|
try {
|
||||||
op.type = typename
|
args = JSON.parse('[' + typeConstructor[0].split(')')[0] + ']')
|
||||||
yield* this.setOperation(op)
|
} catch (e) {
|
||||||
|
throw new Error('Was not able to parse type definition! (share.' + propertyname + ')')
|
||||||
|
}
|
||||||
}
|
}
|
||||||
share[propertyname] = yield* this.getType(id)
|
var type = Y[typeName]
|
||||||
|
var typedef = type.typeDefinition
|
||||||
|
var id = ['_', typedef.struct + '_' + typeName + '_' + propertyname + '_' + typeConstructor]
|
||||||
|
share[propertyname] = yield* this.createType(type.apply(typedef, args), id)
|
||||||
}
|
}
|
||||||
this.store.whenTransactionsFinished()
|
this.store.whenTransactionsFinished()
|
||||||
.then(callback)
|
.then(callback)
|
||||||
|
|||||||
Reference in New Issue
Block a user