209 lines
6.0 KiB
JavaScript
209 lines
6.0 KiB
JavaScript
|
|
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;
|
|
|
|
Y.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.id)] = 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 (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;
|
|
}
|
|
if (opts.idbVersion != null) {
|
|
this.idbVersion = opts.idbVersion;
|
|
} else {
|
|
this.idbVersion = 5;
|
|
}
|
|
|
|
this.transactionQueue = {
|
|
queue: [],
|
|
onRequest: null
|
|
};
|
|
|
|
var store = this;
|
|
|
|
var tGen = (function *transactionGen(){
|
|
store.db = yield indexedDB.open(opts.namespace, store.idbVersion);
|
|
var transactionQueue = store.transactionQueue;
|
|
|
|
var transaction = null;
|
|
var cont = true;
|
|
while (cont) {
|
|
var request = yield transactionQueue;
|
|
transaction = new Transaction(store);
|
|
|
|
yield* request.call(transaction, request);/*
|
|
while (transactionQueue.queue.length > 0) {
|
|
yield* transactionQueue.queue.shift().call(transaction);
|
|
}*/
|
|
}
|
|
})();
|
|
|
|
function handleTransactions(t){ //eslint-disable-line no-unused-vars
|
|
var request = t.value;
|
|
if (t.done){
|
|
return;
|
|
} else if (request.constructor === IDBRequest
|
|
|| request.constructor === IDBCursor ) {
|
|
request.onsuccess = function(){
|
|
handleTransactions(tGen.next(request.result));
|
|
};
|
|
request.onerror = function(err){
|
|
tGen.throw(err);
|
|
};
|
|
} else if (request === store.transactionQueue) {
|
|
if (request.queue.length > 0){
|
|
handleTransactions(tGen.next(request.queue.shift()));
|
|
} else {
|
|
request.onRequest = function(){
|
|
request.onRequest = null;
|
|
handleTransactions(tGen.next(request.queue.shift()));
|
|
};
|
|
}
|
|
} else if ( request.constructor === IDBOpenDBRequest ) {
|
|
request.onsuccess = function(event){
|
|
var db = event.target.result;
|
|
handleTransactions(tGen.next(db));
|
|
};
|
|
request.onerror = function(){
|
|
tGen.throw("Couldn't open IndexedDB database!");
|
|
};
|
|
request.onupgradeneeded = function(event){
|
|
var db = event.target.result;
|
|
try {
|
|
db.createObjectStore("OperationStore", {keyPath: "id"});
|
|
db.createObjectStore("StateVector", {keyPath: "user"});
|
|
} catch (e) {
|
|
// console.log("Store already exists!");
|
|
}
|
|
};
|
|
} else {
|
|
tGen.throw("You can not yield this type!");
|
|
}
|
|
}
|
|
handleTransactions(tGen.next());
|
|
|
|
}
|
|
requestTransaction (makeGen : Function) {
|
|
this.transactionQueue.queue.push(makeGen);
|
|
if (this.transactionQueue.onRequest != null) {
|
|
this.transactionQueue.onRequest();
|
|
}
|
|
}
|
|
*removeDatabase () {
|
|
this.db.close();
|
|
yield indexedDB.deleteDatabase(this.namespace);
|
|
}
|
|
}
|
|
return OperationStore;
|
|
})();
|