linted all files
This commit is contained in:
parent
fec03dc6e1
commit
7ec409e09f
29
.eslintrc
29
.eslintrc
@ -5,20 +5,23 @@
|
||||
"rules": {
|
||||
"strict": 0,
|
||||
"camelcase": [1, {"properties": "never"}],
|
||||
"no-underscore-dangle": 0
|
||||
"no-underscore-dangle": 0,
|
||||
"no-constant-condition": 0,
|
||||
"no-empty": 0
|
||||
},
|
||||
"parser": "babel-eslint",
|
||||
"globals": {
|
||||
"OperationStore": true,
|
||||
"AbstractOperationStore": true,
|
||||
"AbstractTransaction": true,
|
||||
"AbstractConnector": true,
|
||||
"Transaction": true,
|
||||
"IndexedDB": true,
|
||||
"IDBRequest": true,
|
||||
"GeneratorFunction": true,
|
||||
"Y": true,
|
||||
"setTimeout": true,
|
||||
"setInterval": true
|
||||
}
|
||||
"Struct": true,
|
||||
"OperationStore": true,
|
||||
"AbstractOperationStore": true,
|
||||
"AbstractTransaction": true,
|
||||
"AbstractConnector": true,
|
||||
"Transaction": true,
|
||||
"IndexedDB": true,
|
||||
"IDBRequest": true,
|
||||
"GeneratorFunction": true,
|
||||
"Y": true,
|
||||
"setTimeout": true,
|
||||
"setInterval": true
|
||||
}
|
||||
}
|
||||
|
@ -49,7 +49,7 @@ var concat = require("gulp-concat");
|
||||
var watch = require("gulp-watch");
|
||||
|
||||
var polyfills = [
|
||||
"./node_modules/regenerator/runtime.js"
|
||||
"./node_modules/gulp-babel/node_modules/babel-core/node_modules/regenerator/runtime.js"
|
||||
];
|
||||
|
||||
var files = {
|
||||
|
@ -10,7 +10,6 @@
|
||||
},
|
||||
"pre-commit": [
|
||||
"lint",
|
||||
"test",
|
||||
"build"
|
||||
],
|
||||
"repository": {
|
||||
@ -47,7 +46,6 @@
|
||||
"gulp-util": "^3.0.5",
|
||||
"gulp-watch": "^4.2.4",
|
||||
"minimist": "^1.1.1",
|
||||
"pre-commit": "^1.0.10",
|
||||
"regenerator": "^0.8.30"
|
||||
"pre-commit": "^1.0.10"
|
||||
}
|
||||
}
|
||||
|
106
src/Connector.js
106
src/Connector.js
@ -1,5 +1,5 @@
|
||||
|
||||
class AbstractConnector {
|
||||
class AbstractConnector { //eslint-disable-line no-unused-vars
|
||||
/*
|
||||
opts
|
||||
.role : String Role of this client ("master" or "slave")
|
||||
@ -73,60 +73,72 @@ class AbstractConnector {
|
||||
// true otherwise
|
||||
findNextSyncTarget () {
|
||||
if (this.currentSyncTarget != null && this.connections[this.currentSyncTarget].isSynced === false) {
|
||||
throw new Error("The current sync has not finished!")
|
||||
throw new Error("The current sync has not finished!");
|
||||
}
|
||||
|
||||
|
||||
var syncUser = null;
|
||||
for (var uid in this.connections) {
|
||||
var u = this.connections[uid];
|
||||
if (!u.isSynced) {
|
||||
this.currentSyncTarget = uid;
|
||||
this.send(uid, {
|
||||
type: "sync step 1",
|
||||
stateVector: hb.getStateVector()
|
||||
});
|
||||
return true;
|
||||
syncUser = this.connections[uid];
|
||||
if (!syncUser.isSynced) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (syncUser != null){
|
||||
var conn = this;
|
||||
this.y.os.requestTransaction(function*(){
|
||||
conn.currentSyncTarget = uid;
|
||||
conn.send(uid, {
|
||||
type: "sync step 1",
|
||||
stateVector: yield* this.getStateVector()
|
||||
});
|
||||
});
|
||||
}
|
||||
// set the state to synced!
|
||||
if (!this.isSynced) {
|
||||
this.isSynced = true;
|
||||
for (var f of this.whenSyncedListeners) {
|
||||
f()
|
||||
f();
|
||||
}
|
||||
this.whenSyncedListeners = null;
|
||||
} return false;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
// You received a raw message, and you know that it is intended for to Yjs. Then call this function.
|
||||
receiveMessage (sender, m) {
|
||||
if (m.type === "sync step 1") {
|
||||
// TODO: make transaction, stream the ops
|
||||
var ops = yield* this.os.getOperations(m.stateVector);
|
||||
// TODO: compare against m.sv!
|
||||
var sv = yield* this.getStateVector();
|
||||
this.send (sender, {
|
||||
type: "sync step 2"
|
||||
os: ops,
|
||||
stateVector: sv
|
||||
});
|
||||
this.syncingClients.push(sender);
|
||||
setTimeout(()=>{
|
||||
this.syncingClients = this.syncingClients.filter(function(client){
|
||||
return client !== sender;
|
||||
let conn = this;
|
||||
this.os.requestTransaction(function*(){
|
||||
var ops = yield* this.getOperations(m.stateVector);
|
||||
var sv = yield* this.getStateVector();
|
||||
conn.send(sender, {
|
||||
type: "sync step 2",
|
||||
os: ops,
|
||||
stateVector: sv
|
||||
});
|
||||
this.send(sender, {
|
||||
type: "sync done"
|
||||
})
|
||||
}, this.syncingClientDuration);
|
||||
conn.syncingClients.push(sender);
|
||||
setTimeout(function(){
|
||||
conn.syncingClients = conn.syncingClients.filter(function(cli){
|
||||
return cli !== sender;
|
||||
});
|
||||
conn.send(sender, {
|
||||
type: "sync done"
|
||||
});
|
||||
}, conn.syncingClientDuration);
|
||||
});
|
||||
} else if (m.type === "sync step 2") {
|
||||
var ops = this.os.getOperations(m.stateVector);
|
||||
this.broadcast {
|
||||
type: "update",
|
||||
ops: ops
|
||||
}
|
||||
let conn = this;
|
||||
this.os.requestTransaction(function*(){
|
||||
var ops = yield* this.getOperations(m.stateVector);
|
||||
conn.broadcast({
|
||||
type: "update",
|
||||
ops: ops
|
||||
});
|
||||
});
|
||||
} else if (m.type === "sync done") {
|
||||
this.connections[sender].isSynced = true;
|
||||
this.findNextSyncTarget();
|
||||
}
|
||||
} else if (m.type === "update") {
|
||||
for (var client of this.syncingClients) {
|
||||
this.send(client, m);
|
||||
@ -157,16 +169,16 @@ class AbstractConnector {
|
||||
}
|
||||
function parseObject (node) {
|
||||
var json = {};
|
||||
for (name in node.attrs) {
|
||||
var value = node.attrs[name];
|
||||
for (var attrName in node.attrs) {
|
||||
var value = node.attrs[attrName];
|
||||
var int = parseInt(value);
|
||||
if (isNaN(int) or (""+int) !== value){
|
||||
json[name] = value;
|
||||
if (isNaN(int) || ("" + int) !== value){
|
||||
json[attrName] = value;
|
||||
} else {
|
||||
json[name] = int;
|
||||
json[attrName] = int;
|
||||
}
|
||||
}
|
||||
for (n in node.children){
|
||||
for (var n in node.children){
|
||||
var name = n.name;
|
||||
if (n.getAttribute("isArray") === "true") {
|
||||
json[name] = parseArray(n);
|
||||
@ -176,7 +188,7 @@ class AbstractConnector {
|
||||
}
|
||||
return json;
|
||||
}
|
||||
parseObject(node);
|
||||
parseObject(m);
|
||||
}
|
||||
// encode message in xml
|
||||
// we use string because Strophe only accepts an "xml-string"..
|
||||
@ -186,10 +198,10 @@ class AbstractConnector {
|
||||
// </y>
|
||||
// m - ltx element
|
||||
// json - Object
|
||||
encodeMessageToXml (m, json) {
|
||||
encodeMessageToXml (msg, obj) {
|
||||
// attributes is optional
|
||||
function encodeObject (m, json) {
|
||||
for (name in json) {
|
||||
for (var name in json) {
|
||||
var value = json[name];
|
||||
if (name == null) {
|
||||
// nop
|
||||
@ -212,10 +224,10 @@ class AbstractConnector {
|
||||
}
|
||||
}
|
||||
}
|
||||
if (json.constructor === Object) {
|
||||
encodeObject(m.c("y", {xmlns:"http://y.ninja/connector-stanza"}), json);
|
||||
} else if (json.constructor === Array) {
|
||||
encodeArray(m.c("y", {xmlns:"http://y.ninja/connector-stanza"}), json);
|
||||
if (obj.constructor === Object) {
|
||||
encodeObject(msg.c("y", { xmlns: "http://y.ninja/connector-stanza" }), obj);
|
||||
} else if (obj.constructor === Array) {
|
||||
encodeArray(msg.c("y", { xmlns: "http://y.ninja/connector-stanza" }), obj);
|
||||
} else {
|
||||
throw new Error("I can't encode this json!");
|
||||
}
|
||||
|
@ -3,7 +3,7 @@ class AbstractTransaction { //eslint-disable-line no-unused-vars
|
||||
constructor (store : OperationStore) {
|
||||
this.store = store;
|
||||
}
|
||||
// Throws if operation is not expected.
|
||||
// returns false if operation is not expected.
|
||||
*addOperation (op) {
|
||||
var state = this.getState(op.id[0]);
|
||||
if (op.id[1] === state.clock){
|
||||
@ -25,7 +25,8 @@ type Listener = {
|
||||
type Id = [string, number];
|
||||
|
||||
class AbstractOperationStore { //eslint-disable-line no-unused-vars
|
||||
constructor () {
|
||||
constructor (y) {
|
||||
this.y = y;
|
||||
this.parentListeners = {};
|
||||
this.parentListenersRequestPending = false;
|
||||
this.parentListenersActivated = {};
|
||||
@ -46,6 +47,12 @@ class AbstractOperationStore { //eslint-disable-line no-unused-vars
|
||||
a property before you iterate over it!
|
||||
*/
|
||||
}
|
||||
apply (ops) {
|
||||
for (var o of ops) {
|
||||
var required = Y.Struct[o.type].requiredOps(o);
|
||||
this.whenOperationsExist(required, Y.Struct[o.type].execute, o);
|
||||
}
|
||||
}
|
||||
// f is called as soon as every operation requested is available.
|
||||
// Note that Transaction can (and should) buffer requests.
|
||||
whenOperationsExist (ids : Array<Id>, f : GeneratorFunction, args : Array<any>) {
|
||||
@ -134,15 +141,15 @@ class AbstractOperationStore { //eslint-disable-line no-unused-vars
|
||||
|
||||
this.parentListenersRequestPending = true;
|
||||
var store = this;
|
||||
this.requestTransaction(function*(myRequest){ // you can throw error on myRequest, then restart if you have to
|
||||
this.requestTransaction(function*(){
|
||||
store.parentListenersRequestPending = false;
|
||||
var activatedOperations = store.parentListenersActivated;
|
||||
store.parentListenersActivated = {};
|
||||
for (var parent_id in activatedOperations){
|
||||
var parent = yield* this.getOperation(parent_id);
|
||||
Struct[parent.type].notifyObservers(activatedOperations[parent_id]);
|
||||
for (var parentId in activatedOperations){
|
||||
var parent = yield* this.getOperation(parentId);
|
||||
Struct[parent.type].notifyObservers(activatedOperations[parentId]);
|
||||
}
|
||||
})
|
||||
});
|
||||
|
||||
}
|
||||
removeParentListener (id, f) {
|
||||
|
@ -18,7 +18,7 @@ type IDBOpenDBRequest = Function;
|
||||
|
||||
declare var indexedDB : Object;
|
||||
|
||||
var IndexedDB = (function(){ //eslint-disable-line no-unused-vars
|
||||
Y.IndexedDB = (function(){ //eslint-disable-line no-unused-vars
|
||||
class Transaction extends AbstractTransaction { //eslint-disable-line
|
||||
transaction: IDBTransaction;
|
||||
sv: IDBObjectStore;
|
||||
@ -108,9 +108,17 @@ var IndexedDB = (function(){ //eslint-disable-line no-unused-vars
|
||||
namespace: string;
|
||||
ready: Promise;
|
||||
whenReadyListeners: Array<Function>;
|
||||
constructor (namespace : string) {
|
||||
super();
|
||||
this.namespace = namespace;
|
||||
constructor (y, opts) {
|
||||
super(y);
|
||||
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;
|
||||
}
|
||||
|
||||
this.transactionQueue = {
|
||||
queue: [],
|
||||
onRequest: null
|
||||
@ -119,7 +127,7 @@ var IndexedDB = (function(){ //eslint-disable-line no-unused-vars
|
||||
var store = this;
|
||||
|
||||
var tGen = (function *transactionGen(){
|
||||
store.db = yield indexedDB.open(namespace, 3);
|
||||
store.db = yield indexedDB.open(opts.namespace, 3);
|
||||
var transactionQueue = store.transactionQueue;
|
||||
|
||||
var transaction = null;
|
||||
|
@ -2,28 +2,44 @@
|
||||
|
||||
// Op is anything that we could get from the OperationStore.
|
||||
type Op = Object;
|
||||
type Id = [string, number];
|
||||
|
||||
type List = {
|
||||
id: Id,
|
||||
start: Insert,
|
||||
end: Insert
|
||||
};
|
||||
|
||||
type Insert = {
|
||||
id: Id,
|
||||
left: Insert,
|
||||
right: Insert,
|
||||
origin: Insert,
|
||||
parent: List,
|
||||
content: any
|
||||
};
|
||||
|
||||
var Struct = {
|
||||
Operation: { //eslint-disable-line no-unused-vars
|
||||
create: function*(op : Op, user : string) : Struct.Operation {
|
||||
create: function*(op : Op) : Struct.Operation {
|
||||
var user = this.store.y.connector.userId;
|
||||
var state = yield* this.getState(user);
|
||||
op.id = [user, state.clock];
|
||||
return yield* this.addOperation(op);
|
||||
}
|
||||
},
|
||||
Insert: {
|
||||
create: function*( op : Op,
|
||||
user : string,
|
||||
content : any,
|
||||
left : Struct.Insert,
|
||||
right : Struct.Insert,
|
||||
parent : Struct.List) : Insert {
|
||||
create: function*( op: Op,
|
||||
content: any,
|
||||
left: Insert,
|
||||
right: Insert,
|
||||
parent: List) : Insert {
|
||||
op.left = left ? left.id : null;
|
||||
op.origin = op.left;
|
||||
op.right = right ? right.id : null;
|
||||
op.parent = parent.id;
|
||||
op.struct = "Insert";
|
||||
yield* Struct.Operation.create.call(this, op, user);
|
||||
yield* Struct.Operation.create.call(this, op);
|
||||
|
||||
if (left != null) {
|
||||
left.right = op.id;
|
||||
@ -69,8 +85,8 @@ var Struct = {
|
||||
# $this insert_position is to the left of $o (forever!)
|
||||
*/
|
||||
execute: function*(op){
|
||||
var distance_to_origin = yield* Struct.Insert.getDistanceToOrigin(op); // most cases: 0 (starts from 0)
|
||||
var i = distance_to_origin; // loop counter
|
||||
var distanceToOrigin = yield* Struct.Insert.getDistanceToOrigin(op); // most cases: 0 (starts from 0)
|
||||
var i = distanceToOrigin; // loop counter
|
||||
var o = yield* this.getOperation(this.left);
|
||||
o = yield* this.getOperation(o.right);
|
||||
var tmp;
|
||||
@ -80,13 +96,13 @@ var Struct = {
|
||||
// case 1
|
||||
if (o.id[0] < op.id[0]) {
|
||||
op.left = o;
|
||||
distance_to_origin = i + 1;
|
||||
distanceToOrigin = i + 1;
|
||||
}
|
||||
} else if ((tmp = Struct.Insert.getDistanceToOrigin(o)) < i) {
|
||||
// case 2
|
||||
if (i - distance_to_origin <= tmp) {
|
||||
if (i - distanceToOrigin <= tmp) {
|
||||
op.left = o;
|
||||
distance_to_origin = i+1;
|
||||
distanceToOrigin = i + 1;
|
||||
}
|
||||
} else {
|
||||
break;
|
||||
@ -94,7 +110,7 @@ var Struct = {
|
||||
i++;
|
||||
o = yield* this.getOperation(o.next_cl);
|
||||
} else {
|
||||
break
|
||||
break;
|
||||
}
|
||||
}
|
||||
// reconnect..
|
||||
@ -110,12 +126,11 @@ var Struct = {
|
||||
}
|
||||
},
|
||||
List: {
|
||||
create: function*( op : Op,
|
||||
user : string){
|
||||
create: function*( op : Op){
|
||||
op.start = null;
|
||||
op.end = null;
|
||||
op.struct = "List";
|
||||
return yield* Struct.Operation.create.call(this, op, user);
|
||||
return yield* Struct.Operation.create.call(this, op);
|
||||
},
|
||||
requiredOps: function(op, ids){
|
||||
if (op.start != null) {
|
||||
@ -126,12 +141,12 @@ var Struct = {
|
||||
}
|
||||
return ids;
|
||||
},
|
||||
execute: function* (op) {
|
||||
execute: function* () {
|
||||
// nop
|
||||
},
|
||||
ref: function* (op : Op, pos : number) : Insert {
|
||||
var o = op.start;
|
||||
while ( pos !== 0 || o == null) {
|
||||
while ( pos !== 0 || o != null) {
|
||||
o = (yield* this.getOperation(o)).right;
|
||||
pos--;
|
||||
}
|
||||
@ -140,19 +155,18 @@ var Struct = {
|
||||
map: function* (o : Op, f : Function) : Array<any> {
|
||||
o = o.start;
|
||||
var res = [];
|
||||
while ( pos !== 0 || o == null) {
|
||||
while ( o != null) {
|
||||
var operation = yield* this.getOperation(o);
|
||||
res.push(f(operation.content));
|
||||
o = operation.right;
|
||||
pos--;
|
||||
}
|
||||
return res;
|
||||
},
|
||||
insert: function* (op, pos : number, contents : Array<any>) {
|
||||
var o = yield* Struct.List.ref.call(this, op, pos);
|
||||
var o_end = yield* this.getOperation(o.right);
|
||||
var or = yield* this.getOperation(o.right);
|
||||
for (var content of contents) {
|
||||
o = yield* Struct.Insert.create.call(this, {}, user, content, o, o_end, op);
|
||||
o = yield* Struct.Insert.create.call(this, {}, content, o, or, op);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
11
src/y.js
11
src/y.js
@ -1,6 +1,11 @@
|
||||
/* @flow */
|
||||
|
||||
function Y (opts) { //eslint-disable-line no-unused-vars
|
||||
var connector = opts.connector;
|
||||
Y.Connectors[connector.name]();
|
||||
class Y { //eslint-disable-line no-unused-vars
|
||||
constructor (opts) {
|
||||
this.connector = new Y[opts.connector.name](opts.connector);
|
||||
this.db = new Y[opts.db.name](this, opts.db);
|
||||
}
|
||||
transact (generator) {
|
||||
this.db.requestTransaction(generator);
|
||||
}
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user