basic get&set of Map properties works

This commit is contained in:
Kevin Jahns 2015-06-29 13:20:19 +02:00
parent 8f63147dbc
commit bffbb6ca27
14 changed files with 146 additions and 56 deletions

View File

@ -53,9 +53,9 @@ var polyfills = [
]; ];
var files = { var files = {
y: polyfills.concat(["src/**/*.js", "!src/**/*.spec.js"]), y: polyfills.concat(["src/y.js", "src/**/*.js", "!src/**/*.spec.js"]),
lint: ["src/**/*.js", "gulpfile.js"], lint: ["src/**/*.js", "gulpfile.js"],
test: polyfills.concat(["src/**/*.js"]), test: polyfills.concat(["src/y.js", "src/**/*.js"]),
build_test: ["build_test/y.js"] build_test: ["build_test/y.js"]
}; };
@ -119,7 +119,7 @@ gulp.task("build_jasmine_browser", function(){
}); });
gulp.task("develop", ["build_jasmine_browser", "test", "build"], function(){ gulp.task("develop", ["build_jasmine_browser", "build"], function(){
gulp.watch(files.test, ["build_jasmine_browser"]); gulp.watch(files.test, ["build_jasmine_browser"]);
gulp.watch(files.test, ["test"]); gulp.watch(files.test, ["test"]);

View File

@ -5,7 +5,8 @@ class AbstractConnector { //eslint-disable-line no-unused-vars
.role : String Role of this client ("master" or "slave") .role : String Role of this client ("master" or "slave")
.userId : String that uniquely defines the user. .userId : String that uniquely defines the user.
*/ */
constructor (opts) { constructor (y, opts) {
this.y = y;
if (opts == null){ if (opts == null){
opts = {}; opts = {};
} }
@ -23,7 +24,8 @@ class AbstractConnector { //eslint-disable-line no-unused-vars
this.currentSyncTarget = null; this.currentSyncTarget = null;
} }
setUserId (userId) { setUserId (userId) {
this.os.setUserId(userId); this.userId = userId;
this.y.db.setUserId(userId);
} }
onUserEvent (f) { onUserEvent (f) {
this.userEventListeners.push(f); this.userEventListeners.push(f);

View File

@ -16,8 +16,9 @@ var globalRoom = {
users: {}, users: {},
buffers: {}, buffers: {},
removeUser: function(user){ removeUser: function(user){
for (var u of this.users) {
u.userLeft(user); for (var i in this.users) {
this.users[i].userLeft(user);
} }
delete this.users[user]; delete this.users[user];
delete this.buffers[user]; delete this.buffers[user];
@ -49,11 +50,11 @@ setInterval(function(){
var userIdCounter = 0; var userIdCounter = 0;
class Test extends AbstractConnector { class Test extends AbstractConnector {
constructor (options) { constructor (y, options) {
if(options === undefined){ if(options === undefined){
throw new Error("Options must not be undefined!"); throw new Error("Options must not be undefined!");
} }
super({ super(y, {
role: "master" role: "master"
}); });

View File

@ -5,10 +5,11 @@ class AbstractTransaction { //eslint-disable-line no-unused-vars
} }
// returns false if operation is not expected. // returns false if operation is not expected.
*addOperation (op) { *addOperation (op) {
var state = this.getState(op.id[0]); var state = yield* this.getState(op.id[0]);
if (op.id[1] === state.clock){ if (op.id[1] === state.clock){
state.clock++; state.clock++;
yield* this.setState(state); yield* this.setState(state);
yield* this.setOperation(op);
this.store.operationAdded(op); this.store.operationAdded(op);
return true; return true;
} else { } else {
@ -16,6 +17,7 @@ class AbstractTransaction { //eslint-disable-line no-unused-vars
} }
} }
} }
Y.AbstractTransaction = AbstractTransaction;
type Listener = { type Listener = {
f : GeneratorFunction, // is called when all operations are available f : GeneratorFunction, // is called when all operations are available
@ -47,6 +49,9 @@ class AbstractOperationStore { //eslint-disable-line no-unused-vars
a property before you iterate over it! a property before you iterate over it!
*/ */
} }
setUserId (userId) {
this.userId = userId;
}
apply (ops) { apply (ops) {
for (var o of ops) { for (var o of ops) {
var required = Y.Struct[o.type].requiredOps(o); var required = Y.Struct[o.type].requiredOps(o);
@ -169,3 +174,4 @@ class AbstractOperationStore { //eslint-disable-line no-unused-vars
ls.push(f); ls.push(f);
} }
} }
Y.AbstractOperationStore = AbstractOperationStore;

View File

@ -113,11 +113,16 @@ Y.IndexedDB = (function(){ //eslint-disable-line no-unused-vars
if (opts == null) { if (opts == null) {
opts = {}; opts = {};
} }
if (opts.namespace != null || typeof opts.namespace !== "string") { if (opts.namespace == null || typeof opts.namespace !== "string") {
throw new Error("IndexedDB: expect a string (opts.namespace)!"); throw new Error("IndexedDB: expect a string (opts.namespace)!");
} else { } else {
this.namespace = opts.namespace; this.namespace = opts.namespace;
} }
if (opts.idbVersion != null) {
this.idbVersion = opts.idbVersion;
} else {
this.idbVersion = 5;
}
this.transactionQueue = { this.transactionQueue = {
queue: [], queue: [],
@ -127,7 +132,7 @@ Y.IndexedDB = (function(){ //eslint-disable-line no-unused-vars
var store = this; var store = this;
var tGen = (function *transactionGen(){ var tGen = (function *transactionGen(){
store.db = yield indexedDB.open(opts.namespace, 3); store.db = yield indexedDB.open(opts.namespace, store.idbVersion);
var transactionQueue = store.transactionQueue; var transactionQueue = store.transactionQueue;
var transaction = null; var transaction = null;
@ -174,8 +179,12 @@ Y.IndexedDB = (function(){ //eslint-disable-line no-unused-vars
}; };
request.onupgradeneeded = function(event){ request.onupgradeneeded = function(event){
var db = event.target.result; var db = event.target.result;
try {
db.createObjectStore("OperationStore", {keyPath: "id"}); db.createObjectStore("OperationStore", {keyPath: "id"});
db.createObjectStore("StateVector", {keyPath: "user"}); db.createObjectStore("StateVector", {keyPath: "user"});
} catch (e) {
// console.log("Store already exists!");
}
}; };
} else { } else {
tGen.throw("You can not yield this type!"); tGen.throw("You can not yield this type!");

View File

@ -4,7 +4,7 @@
if(typeof window !== "undefined"){ if(typeof window !== "undefined"){
jasmine.DEFAULT_TIMEOUT_INTERVAL = 1000; jasmine.DEFAULT_TIMEOUT_INTERVAL = 1000;
describe("IndexedDB", function() { describe("IndexedDB", function() {
var ob = new IndexedDB("Test"); var ob = new Y.IndexedDB(null, {namespace: "Test"});
it("can add and get operation", function(done) { it("can add and get operation", function(done) {
ob.requestTransaction(function*(){ ob.requestTransaction(function*(){

View File

@ -16,7 +16,7 @@ Y.Memory = (function(){ //eslint-disable-line no-unused-vars
constructor (store : OperationStore) { constructor (store : OperationStore) {
super(store); super(store);
this.sv = store.ss; this.ss = store.ss;
this.os = store.os; this.os = store.os;
} }
*setOperation (op) { *setOperation (op) {
@ -30,7 +30,7 @@ Y.Memory = (function(){ //eslint-disable-line no-unused-vars
delete this.os[JSON.stringify(id)]; delete this.os[JSON.stringify(id)];
} }
*setState (state : State) : State { *setState (state : State) : State {
this.sv[state.user] = state.clock; this.ss[state.user] = state.clock;
} }
*getState (user : string) : State { *getState (user : string) : State {
var clock = this.ss[user]; var clock = this.ss[user];
@ -80,18 +80,21 @@ Y.Memory = (function(){ //eslint-disable-line no-unused-vars
} }
} }
class OperationStore extends AbstractOperationStore { //eslint-disable-line no-undef class OperationStore extends AbstractOperationStore { //eslint-disable-line no-undef
namespace: string;
ready: Promise;
whenReadyListeners: Array<Function>;
constructor (y) { constructor (y) {
super(y); super(y);
this.os = {};
this.ss = {};
} }
requestTransaction (makeGen : Function) { requestTransaction (makeGen : Function) {
var t = new Transaction(this); var t = new Transaction(this);
var gen = makeGen.call(t); var gen = makeGen.call(t);
gen.next(); var res = gen.next();
if (gen.done !== true) { while(!res.done){
throw new Error("transaction is supposed to be done. Note: you may not yield with this transaction! (yield* is allowed though)"); if (res.value === "transaction") {
res = gen.next(t);
} else {
throw new Error("You may not yield this type. (Maybe you meant to use 'yield*'?)");
}
} }
} }
*removeDatabase () { *removeDatabase () {

View File

@ -19,6 +19,21 @@ type Insert = {
content: any content: any
}; };
function compareIds(id1, id2) {
if (id1 == null) {
if (id2 == null) {
return true;
} else {
return false;
}
}
if (id1[0] === id2[0] && id1[1] === id2[1]) {
return true;
} else {
return false;
}
}
var Struct = { var Struct = {
Operation: { //eslint-disable-line no-unused-vars Operation: { //eslint-disable-line no-unused-vars
create: function*(op : Op) : Struct.Operation { create: function*(op : Op) : Struct.Operation {
@ -55,6 +70,25 @@ var Struct = {
op.right.left = op.id; op.right.left = op.id;
yield* this.setOperation(op.right); yield* this.setOperation(op.right);
} }
var parent = yield* this.getOperation(op.parent);
if (op.parentSub != null){
if (compareIds(parent.map[op.parentSub], op.left)) {
parent.map[op.parentSub] = op.id;
yield* this.setOperation(parent);
}
} else {
var start = compareIds(parent.start, op.right);
var end = compareIds(parent.end, op.left);
if (start || end) {
if (start) {
parent.start = op.id;
}
if (end) {
parent.end = op.id;
}
yield* this.setOperation(parent);
}
}
return op; return op;
}, },
requiredOps: function(op, ids){ requiredOps: function(op, ids){
@ -209,6 +243,11 @@ var Struct = {
} }
}, },
Map: { Map: {
/*
{
// empty
}
*/
create: function*( op : Op ){ create: function*( op : Op ){
op.map = {}; op.map = {};
op.struct = "Map"; op.struct = "Map";
@ -224,7 +263,7 @@ var Struct = {
// nop // nop
}, },
get: function* (op, name) { get: function* (op, name) {
return yield* this.getOperation(op.map[name].end); return (yield* this.getOperation(op.map[name])).content;
}, },
set: function* (op, name, value) { set: function* (op, name, value) {
var end = op.map[name]; var end = op.map[name];
@ -243,3 +282,5 @@ var Struct = {
} }
} }
}; };
Y.Struct = Struct;

29
src/Types/Map.js Normal file
View File

@ -0,0 +1,29 @@
(function(){
class Map {
constructor (_model) {
this._model = _model;
}
*val () {
var transaction = yield "transaction";
var model = yield* transaction.getOperation(this._model);
if (arguments.length === 0) {
throw new Error("Implement this case!");
} else if (arguments.length === 1) {
return yield* Y.Struct.Map.get.call(transaction, model, arguments[0]);
} else {
return yield* Y.Struct.Map.set.call(transaction, model, arguments[0], arguments[1]);
}
}
}
Y.Map = function* YMap(){
if (this instanceof Y.AbstractOperationStore) {
var model = yield* Y.Struct.map.create.call(this);
return new Map(model);
} else {
throw new Error("Don't use `new` to create this type!");
}
};
Y.Map.Create = Map;
})();

View File

@ -1,25 +0,0 @@
(function(){
class Map {
constructor (_model) {
this._model = _model;
}
*val () {
var transaction = yield "transaction";
var model = transaction.getOperation(this._model);
if (arguments.length === 0) {
throw new Error("Implement this case!");
} else if (arguments.length === 1) {
return yield* this.Struct.Map.get.call(transaction, model, arguments[0]);
} else {
return yield* this.Struct.Map.set.call(transaction, model, arguments[0], arguments[1]);
}
}
}
Y.Map = function* YMap(){
var model = yield* this.Struct.map.create.call(this);
return new Map(model);
};
Y.Map.Create = Map;
})();

View File

@ -1,11 +1,25 @@
/* @flow */ /* @flow */
const GeneratorFunction = (function*(){}).constructor;
class Y { //eslint-disable-line no-unused-vars class Y { //eslint-disable-line no-unused-vars
constructor (opts) { constructor (opts) {
this.connector = new Y[opts.connector.name](opts.connector);
this.db = new Y[opts.db.name](this, opts.db); this.db = new Y[opts.db.name](this, opts.db);
this.connector = new Y[opts.connector.name](this, opts.connector);
var y = this;
this.db.requestTransaction(function*(){
yield* this.addOperation({
id: ["_", 0],
struct: "Map",
map: {}
});
y.root = new Y.Map.Create(["_", 0]);
});
} }
transact (generator) { transact (generator) {
if (generator.constructor !== GeneratorFunction) {
throw new Error("y.transact requires a Generator function! E.g. function*(){/*..*/}");
}
this.db.requestTransaction(generator); this.db.requestTransaction(generator);
} }
destroy () { destroy () {

View File

@ -2,6 +2,7 @@
/*eslint-env browser,jasmine */ /*eslint-env browser,jasmine */
describe("Yjs (basic)", function(){ describe("Yjs (basic)", function(){
jasmine.DEFAULT_TIMEOUT_INTERVAL = 500;
beforeEach(function(){ beforeEach(function(){
this.users = []; this.users = [];
for (var i = 0; i < 5; i++) { for (var i = 0; i < 5; i++) {
@ -21,9 +22,18 @@ describe("Yjs (basic)", function(){
} }
this.users = []; this.users = [];
}); });
it("can List.insert and get value from the other user", function(done){ it("There is an initial Map type", function(done){
this.users[0].val("name", 1); var y = this.users[0];
this.users[0].connector.whenSynced(function(){ y.transact(function*(){
expect(y.root).not.toBeUndefined();
done();
});
});
it("Basic get&set of Map property", function(done){
var y = this.users[0];
y.transact(function*(){
yield* y.root.val("stuff", "stuffy");
expect(yield* y.root.val("stuff")).toEqual("stuffy");
done(); done();
}); });
}); });

4
y.js

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long