diff --git a/gulpfile.helper.js b/gulpfile.helper.js index 7bddd86cf4d55323201993c4e5774e8823b20a37..743deabb8a3a37aad814425d3502cd247de40e5b 100644 --- a/gulpfile.helper.js +++ b/gulpfile.helper.js @@ -91,7 +91,6 @@ module.exports = function (gulp, helperOptions) { var browserify = require('browserify') var source = require('vinyl-source-stream') var buffer = require('vinyl-buffer') - return browserify({ entries: files.specs, debug: true diff --git a/src/Database.js b/src/Database.js index fe9f3eb674a087f0d5efbd12db417550c9ebe6ae..16d23314af29627926505261cc7bc444c634a855 100644 --- a/src/Database.js +++ b/src/Database.js @@ -224,6 +224,9 @@ module.exports = function (Y /* :any */) { var o = ops[i] if (o.id == null || o.id[0] !== this.y.connector.userId) { var required = Y.Struct[o.struct].requiredOps(o) + if (o.requires != null) { + required = required.concat(o.requires) + } this.whenOperationsExist(required, o) } } @@ -372,7 +375,7 @@ module.exports = function (Y /* :any */) { } // notify parent, if it was instanciated as a custom type - if (t != null) { + if (t != null && opIsDeleted) { yield* t._changed(transaction, Y.utils.copyObject(op)) } } diff --git a/src/SpecHelper.js b/src/SpecHelper.js index cce99c5a571dd9652bee29897821974238aa64fb..a78f73bee7e5291136a098dc1e995e674496577a 100644 --- a/src/SpecHelper.js +++ b/src/SpecHelper.js @@ -25,7 +25,7 @@ g.g = g g.YConcurrency_TestingMode = true -jasmine.DEFAULT_TIMEOUT_INTERVAL = 100000 +jasmine.DEFAULT_TIMEOUT_INTERVAL = 5000 g.describeManyTimes = function describeManyTimes (times, name, f) { for (var i = 0; i < times; i++) { @@ -221,15 +221,18 @@ g.compareAllUsers = async(function * compareAllUsers (users) { // TODO: make requestTransaction return a promise.. u.db.requestTransaction(function * () { yield* t2.call(this) - expect(s1).toEqual(s2) - expect(allDels1).toEqual(allDels2) // inner structure - expect(ds1).toEqual(ds2) // exported structure - var count = 0 + var db2 = [] yield* this.os.iterate(this, null, null, function * (o) { o = Y.utils.copyObject(o) delete o.origin delete o.originOf - expect(db1[count++]).toEqual(o) + db2.push(o) + }) + expect(s1).toEqual(s2) + expect(allDels1).toEqual(allDels2) // inner structure + expect(ds1).toEqual(ds2) // exported structure + db2.forEach((o, i) => { + expect(db1[i]).toEqual(o) }) }) } @@ -237,7 +240,7 @@ g.compareAllUsers = async(function * compareAllUsers (users) { } }) -g.createUsers = async(function * createUsers (self, numberOfUsers, database) { +g.createUsers = async(function * createUsers (self, numberOfUsers, database, initType) { if (Y.utils.globalRoom.users[0] != null) { yield Y.utils.globalRoom.flushAll() } @@ -246,6 +249,7 @@ g.createUsers = async(function * createUsers (self, numberOfUsers, database) { Y.utils.globalRoom.users[u].y.destroy() } self.users = null + yield wait() var promises = [] for (var i = 0; i < numberOfUsers; i++) { @@ -261,7 +265,7 @@ g.createUsers = async(function * createUsers (self, numberOfUsers, database) { debug: false }, share: { - root: 'Map' + root: initType || 'Map' } })) } diff --git a/src/Struct.js b/src/Struct.js index 7981aab3cb28707e3bb729933eb5adcb75c96171..db880cf673b34c0cb630addaf5e4603a0ae07e64 100644 --- a/src/Struct.js +++ b/src/Struct.js @@ -259,11 +259,18 @@ module.exports = function (Y/* :any */) { } }, encode: function (op) { - return { + var e = { struct: 'List', id: op.id, type: op.type } + if (op.requires != null) { + e.requires = op.requires + } + if (op.info != null) { + e.info = op.info + } + return e }, requiredOps: function () { /* @@ -332,12 +339,19 @@ module.exports = function (Y/* :any */) { } }, encode: function (op) { - return { + var e = { struct: 'Map', type: op.type, id: op.id, map: {} // overwrite map!! } + if (op.requires != null) { + e.requires = op.requires + } + if (op.info != null) { + e.info = op.info + } + return e }, requiredOps: function () { return [] diff --git a/src/Transaction.js b/src/Transaction.js index a29a96ce4124a80038685162906653a474616be3..6d354184bb2c63caeedbec11ca17b6243c3a5d84 100644 --- a/src/Transaction.js +++ b/src/Transaction.js @@ -87,25 +87,28 @@ module.exports = function (Y/* :any */) { If it does not exist yes, create it. TODO: delete type from store.initializedTypes[id] when corresponding id was deleted! */ - * getType (id) { + * getType (id, args) { var sid = JSON.stringify(id) var t = this.store.initializedTypes[sid] if (t == null) { var op/* :MapStruct | ListStruct */ = yield* this.getOperation(id) 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 } } return t } * createType (typedefinition, id) { - var structname = typedefinition.struct + var structname = typedefinition[0].struct id = id || this.store.getNextOpId() var op = Y.Struct[structname].create(id) - op.type = typedefinition.name + op.type = typedefinition[0].name + if (typedefinition[0].appendAdditionalInfo != null) { + yield* typedefinition[0].appendAdditionalInfo.call(this, op, typedefinition[1]) + } yield* this.applyCreatedOperations([op]) - return yield* this.getType(id) + return yield* this.getType(id, typedefinition[1]) } /* Apply operations that this user created (no remote ones!) @@ -117,7 +120,7 @@ module.exports = function (Y/* :any */) { for (var i = 0; i < ops.length; i++) { var op = ops[i] 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)) } } @@ -643,7 +646,7 @@ module.exports = function (Y/* :any */) { } * addOperation (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 this.store.y.connector.broadcastOps([op]) } @@ -653,12 +656,13 @@ module.exports = function (Y/* :any */) { if (o != null || id[0] !== '_') { return o } else { - // generate this operation? + /* // generate this operation? if (typeof id[1] === 'string') { var comp = id[1].split('_') - if (comp.length > 1) { + if (comp.length > 2 || id[0] === '_') { var struct = comp[0] var op = Y.Struct[struct].create(id) + op.type = comp[1] yield* this.setOperation(op) return op } else { @@ -668,9 +672,8 @@ module.exports = function (Y/* :any */) { return null } } else { - // Can only generate one operation at a time - return null - } + */ + return null } } * removeOperation (id) { diff --git a/src/Utils.js b/src/Utils.js index c0a0cc2c05b1f4adee9539e9126c6bbe1bad5001..a45469dda3b78d2b581a5a34d0f4817adfbfbddf 100644 --- a/src/Utils.js +++ b/src/Utils.js @@ -26,7 +26,40 @@ module.exports = function (Y /* : any*/) { 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>; awaiting: number; @@ -41,16 +74,16 @@ module.exports = function (Y /* : any*/) { all prematurely called operations were executed ("waiting operations") */ constructor (onevent /* : Function */) { + super() this.waiting = [] this.awaiting = 0 this.onevent = onevent - this.eventListeners = [] } destroy () { + super.destroy() this.waiting = null this.awaiting = null this.onevent = null - this.eventListeners = null } /* Call this when a new operation arrives. It will be executed right away if @@ -72,30 +105,6 @@ module.exports = function (Y /* : any*/) { this.awaiting++ 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 */ @@ -192,10 +201,26 @@ module.exports = function (Y /* : any*/) { this.initType = def.initType this.class = def.class 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.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 (just copy properties) diff --git a/src/y.js b/src/y.js index 67a5400fbe68427ce483e6537c2cac49cf348d25..58d9173791d4327a2ba3bc55da11956d592688b4 100644 --- a/src/y.js +++ b/src/y.js @@ -14,7 +14,11 @@ module.exports = Y Y.requiringModules = requiringModules 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) { requiringModules[name].resolve() delete requiringModules[name] @@ -29,9 +33,10 @@ function requestModules (modules) { var extention = typeof regeneratorRuntime !== 'undefined' ? '.js' : '.es6' 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) { + var module = modules[i].split('(')[0] + var modulename = 'y-' + module.toLowerCase() + if (Y[module] == null) { + if (requiringModules[module] == null) { // module does not exist if (typeof window !== 'undefined' && window.Y !== 'undefined') { var imported = document.createElement('script') @@ -39,7 +44,7 @@ function requestModules (modules) { document.head.appendChild(imported) let requireModule = {} - requiringModules[modules[i]] = requireModule + requiringModules[module] = requireModule requireModule.promise = new Promise(function (resolve) { requireModule.resolve = resolve }) @@ -130,15 +135,19 @@ class YConfig { this.db.requestTransaction(function * requestTransaction () { // create shared object for (var propertyname in opts.share) { - var typename = opts.share[propertyname] - var id = ['_', Y[typename].struct + '_' + propertyname] - var op = yield* this.getOperation(id) - if (op.type !== typename) { - // not already in the db - op.type = typename - yield* this.setOperation(op) + var typeConstructor = opts.share[propertyname].split('(') + var typeName = typeConstructor.splice(0, 1) + var args = [] + if (typeConstructor.length === 1) { + try { + args = JSON.parse('[' + typeConstructor[0].split(')')[0] + ']') + } catch (e) { + throw new Error('Was not able to parse type definition! (share.' + propertyname + ')') + } } - share[propertyname] = yield* this.getType(id) + var id = ['_', propertyname + '_' + typeConstructor] + var type = Y[typeName] + share[propertyname] = yield* this.createType(type.apply(type.typeDefinition, args), id) } this.store.whenTransactionsFinished() .then(callback)