updated whenOperationExists
This commit is contained in:
175
src/OperationStores/IndexedDB.js
Normal file
175
src/OperationStores/IndexedDB.js
Normal file
@@ -0,0 +1,175 @@
|
||||
|
||||
type State = {
|
||||
user: string,
|
||||
clock: number
|
||||
};
|
||||
|
||||
type StateVector = Array<State>;
|
||||
|
||||
type StateSet = Object;
|
||||
|
||||
type IDBTransaction = Function;
|
||||
type IDBObjectStore = Function;
|
||||
type IDBRequest = Function;
|
||||
type IDBCursor = Function;
|
||||
type IDBKeyRange = Function;
|
||||
|
||||
type IDBOpenDBRequest = Function;
|
||||
|
||||
declare var indexedDB : Object;
|
||||
|
||||
declare var setTimeout : Function;
|
||||
|
||||
var IndexedDB = (function(){ //eslint-disable-line no-unused-vars
|
||||
class Transaction extends AbstractTransaction { //eslint-disable-line
|
||||
transaction: IDBTransaction;
|
||||
sv: IDBObjectStore;
|
||||
os: IDBObjectStore;
|
||||
store: OperationStore;
|
||||
|
||||
constructor (store : OperationStore) {
|
||||
super(store);
|
||||
this.transaction = store.db.transaction(["OperationStore", "StateVector"], "readwrite");
|
||||
this.sv = this.transaction.objectStore("StateVector");
|
||||
this.os = this.transaction.objectStore("OperationStore");
|
||||
this.buffer = {};
|
||||
}
|
||||
*setOperation (op) {
|
||||
yield this.os.put(op);
|
||||
this.buffer[JSON.stringify(op.uid)] = op;
|
||||
return op;
|
||||
}
|
||||
*getOperation (id) {
|
||||
var op = this.buffer[JSON.stringify(id)];
|
||||
if (op == null) {
|
||||
op = yield this.os.get(id);
|
||||
this.buffer[JSON.stringify(id)] = op;
|
||||
}
|
||||
return op;
|
||||
}
|
||||
*removeOperation (id) {
|
||||
this.buffer[JSON.stringify(id)] = null;
|
||||
return yield this.os.delete(id);
|
||||
}
|
||||
*setState (state : State) : State {
|
||||
return yield this.sv.put(state);
|
||||
}
|
||||
*getState (user : string) : State {
|
||||
var state;
|
||||
if ((state = yield this.sv.get(user)) != null){
|
||||
return state;
|
||||
} else {
|
||||
return {
|
||||
user: user,
|
||||
clock: 0
|
||||
};
|
||||
}
|
||||
}
|
||||
*getStateVector () : StateVector {
|
||||
var stateVector = [];
|
||||
var cursorResult = this.sv.openCursor();
|
||||
var cursor;
|
||||
while ((cursor = yield cursorResult) != null) {
|
||||
stateVector.push(cursor.value);
|
||||
cursor.continue();
|
||||
}
|
||||
return stateVector;
|
||||
}
|
||||
*getStateSet () : StateSet {
|
||||
var sv : StateVector = yield* this.getStateVector();
|
||||
var ss : StateSet = {};
|
||||
for (var state of sv){
|
||||
ss[state.user] = state.clock;
|
||||
}
|
||||
return ss;
|
||||
}
|
||||
|
||||
*getOperations (startSS : StateSet) {
|
||||
if (startSS == null){
|
||||
startSS = {};
|
||||
}
|
||||
var ops = [];
|
||||
|
||||
var endSV : StateVector = yield* this.getStateVector();
|
||||
for (var endState of endSV) {
|
||||
var user = endState.user;
|
||||
var startPos = startSS[user] || 0;
|
||||
var endPos = endState.clock;
|
||||
var range = IDBKeyRange.bound([user, startPos], [user, endPos]);
|
||||
var cursorResult = this.os.openCursor(range);
|
||||
var cursor;
|
||||
while ((cursor = yield cursorResult) != null) {
|
||||
ops.push(cursor.value);
|
||||
cursor.continue();
|
||||
}
|
||||
}
|
||||
return ops;
|
||||
}
|
||||
}
|
||||
class OperationStore extends AbstractOperationStore { //eslint-disable-line no-undef
|
||||
namespace: string;
|
||||
ready: Promise;
|
||||
whenReadyListeners: Array<Function>;
|
||||
constructor (namespace : string) {
|
||||
super();
|
||||
this.whenReadyListeners = [];
|
||||
this.namespace = namespace;
|
||||
this.ready = false;
|
||||
|
||||
var req = indexedDB.open(namespace, 2); //eslint-disable-line no-undef
|
||||
req.onerror = function(){
|
||||
throw new Error("Couldn't open the IndexedDB database!");
|
||||
};
|
||||
req.onsuccess = (event)=>{
|
||||
this.db = event.target.result;
|
||||
this.whenReadyListeners.forEach(function(f){
|
||||
setTimeout(f, 0);
|
||||
});
|
||||
this.whenReadyListeners = null;
|
||||
this.ready = true;
|
||||
};
|
||||
req.onupgradeneeded = function(event){
|
||||
var db = event.target.result;
|
||||
db.createObjectStore("OperationStore", {keyPath: "id"});
|
||||
db.createObjectStore("StateVector", {keyPath: "user"});
|
||||
};
|
||||
}
|
||||
whenReady (f : Function) {
|
||||
if (this.ready){
|
||||
setTimeout(f, 0);
|
||||
} else {
|
||||
this.whenReadyListeners.push(f);
|
||||
}
|
||||
}
|
||||
requestTransaction (makeGen : Function) {
|
||||
this.whenReady(()=>{
|
||||
var transaction = new Transaction(this);
|
||||
var gen = makeGen.apply(transaction);
|
||||
|
||||
function handle(res : any){
|
||||
var request : any = res.value;
|
||||
if (res.done){
|
||||
return;
|
||||
} else if (request.constructor === IDBRequest
|
||||
|| request.constructor === IDBCursor
|
||||
|| request.constructor === IDBOpenDBRequest) {
|
||||
request.onsuccess = function(){
|
||||
handle(gen.next(request.result));
|
||||
};
|
||||
request.onerror = function(err){
|
||||
gen.throw(err);
|
||||
};
|
||||
} else {
|
||||
gen.throw("You can not yield this type!");
|
||||
}
|
||||
}
|
||||
handle(gen.next());
|
||||
});
|
||||
}
|
||||
*removeDatabase () {
|
||||
this.db.close();
|
||||
yield indexedDB.deleteDatabase(this.namespace);
|
||||
}
|
||||
}
|
||||
return OperationStore;
|
||||
})();
|
||||
113
src/OperationStores/IndexedDB.spec.js
Normal file
113
src/OperationStores/IndexedDB.spec.js
Normal file
@@ -0,0 +1,113 @@
|
||||
/* @flow */
|
||||
/*eslint-env browser,jasmine */
|
||||
|
||||
if(typeof window !== "undefined"){
|
||||
describe("IndexedDB", function() {
|
||||
var ob = new IndexedDB("Test");
|
||||
|
||||
it("can add and get operation", function(done) {
|
||||
ob.requestTransaction(function*(){
|
||||
var op = yield* this.setOperation({
|
||||
"id": ["1", 0],
|
||||
"stuff": true
|
||||
});
|
||||
expect(yield* this.getOperation(["1", 0]))
|
||||
.toEqual(op);
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it("can remove operation", function(done) {
|
||||
ob.requestTransaction(function*(){
|
||||
var op = yield* this.setOperation({
|
||||
"id": ["1", 0],
|
||||
"stuff": true
|
||||
});
|
||||
expect(yield* this.getOperation(["1", 0]))
|
||||
.toEqual(op);
|
||||
yield* this.removeOperation(["1", 0]);
|
||||
expect(yield* this.getOperation(["1", 0]))
|
||||
.toBeUndefined();
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it("getOperation(op) returns undefined if op does not exist", function(done){
|
||||
ob.requestTransaction(function*(){
|
||||
var op = yield* this.getOperation("plzDon'tBeThere");
|
||||
expect(op).toBeUndefined();
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it("yield throws if request is unknown", function(done){
|
||||
ob.requestTransaction(function*(){
|
||||
try {
|
||||
yield this.getOperations(["u1", 0]);
|
||||
} catch (e) {
|
||||
expect(true).toEqual(true);
|
||||
done();
|
||||
return;
|
||||
}
|
||||
expect("Expected an Error!").toEqual(true);
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it("sets and gets stateVector", function(done){
|
||||
ob.requestTransaction(function*(){
|
||||
var s1 = {user: "1", clock: 1};
|
||||
var s2 = {user: "2", clock: 3};
|
||||
yield* this.setState(s1);
|
||||
yield* this.setState(s2);
|
||||
var sv = yield* this.getStateVector();
|
||||
expect(sv).not.toBeUndefined();
|
||||
expect(sv).toEqual([s1, s2]);
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it("gets stateSet", function(done){
|
||||
ob.requestTransaction(function*(){
|
||||
var s1 = {user: "1", clock: 1};
|
||||
var s2 = {user: "2", clock: 3};
|
||||
yield* this.setState(s1);
|
||||
yield* this.setState(s2);
|
||||
var sv = yield* this.getStateSet();
|
||||
expect(sv).not.toBeUndefined();
|
||||
expect(sv).toEqual({
|
||||
"1": 1,
|
||||
"2": 3
|
||||
});
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it("getOperations returns operations (no parameters)", function(done){
|
||||
ob.requestTransaction(function*(){
|
||||
var s1 = {user: "1", clock: 55};
|
||||
yield* this.setState(s1);
|
||||
var op1 = yield* this.setOperation({
|
||||
"id": ["1", 0],
|
||||
"stuff": true
|
||||
});
|
||||
var op2 = yield* this.setOperation({
|
||||
"id": ["1", 3],
|
||||
"stuff": true
|
||||
});
|
||||
var ops = yield* this.getOperations();
|
||||
expect(ops.length).toBeGreaterThan(1);
|
||||
expect(ops[0]).toEqual(op1);
|
||||
expect(ops[1]).toEqual(op2);
|
||||
done();
|
||||
});
|
||||
});
|
||||
afterAll(function(done){
|
||||
ob.requestTransaction(function*(){
|
||||
yield* ob.removeDatabase();
|
||||
ob = null;
|
||||
done();
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
Reference in New Issue
Block a user