cleanup idb storage

This commit is contained in:
Bruno Windels 2019-02-04 23:21:50 +00:00
parent 13eef402aa
commit 90300dcdaf
16 changed files with 132 additions and 121 deletions

View file

@ -1,5 +1,6 @@
import Network from "../network.js";
import Session from "../session.js";
import Network from "./network.js";
import Session from "./session.js";
import createIdbStorage from "./storage/idb/factory.js";
const HOMESERVER = "http://localhost:8008";
async function getLoginData(username, password) {
@ -17,7 +18,7 @@ async function getLoginData(username, password) {
async function main() {
const loginData = await getLoginData("bruno1", "testtest");
const network = new Network(HOMESERVER, loginData.access_token);
const storage = new IdbStorage("morpheus_session");
const storage = await createIdbStorage("morpheus_session");
const session = new Session(loginData, storage);
await session.load();
const sync = new Sync(network, session, storage);

View file

@ -0,0 +1,18 @@
export default async function createIdbStorage(databaseName) {
const db = await openDatabase(databaseName, createStores);
return new Storage(db);
}
function createStores(db) {
db.createObjectStore("sync"); //sync token
db.createObjectStore("roomSummary", "room_id", {unique: true});
const timeline = db.createObjectStore("roomTimeline", ["room_id", "sort_key"]);
timeline.createIndex("by_event_id", ["room_id", "event.event_id"], {unique: true});
// how to get the first/last x events for a room?
// we don't want to specify the sort key, but would need an index for the room_id?
// take sort_key as primary key then and have index on event_id?
// still, you also can't have a PK of [room_id, sort_key] and get the last or first events with just the room_id? the only thing that changes it that the PK will provide an inherent sorting that you inherit in an index that only has room_id as keyPath??? There must be a better way, need to write a prototype test for this.
// SOLUTION: with numeric keys, you can just us a min/max value to get first/last
// db.createObjectStore("members", ["room_id", "state_key"]);
const state = db.createObjectStore("roomState", ["event.room_id", "event.type", "event.state_key"]);
}

11
src/storage/idb/index.js Normal file
View file

@ -0,0 +1,11 @@
import QueryTarget from "./query-target.js";
export default class Index extends QueryTarget {
constructor(index) {
this._index = index;
}
_queryTarget() {
return this._index;
}
}

View file

@ -1,80 +1,6 @@
const SYNC_STORES = [
"sync",
"summary",
"timeline",
"members",
"state"
];
import {iterateCursor} from "./utils";
class Database {
constructor(idbDatabase) {
this._db = idbDatabase;
this._syncTxn = null;
}
async startSyncTxn() {
const txn = this._db.transaction(SYNC_STORES, "readwrite");
return new Transaction(txn, SYNC_STORES);
}
startReadOnlyTxn(storeName) {
if (this._syncTxn && SYNC_STORES.includes(storeName)) {
return this._syncTxn;
} else {
return this._db.transaction([storeName], "readonly");
}
}
startReadWriteTxn(storeName) {
if (this._syncTxn && SYNC_STORES.includes(storeName)) {
return this._syncTxn;
} else {
return this._db.transaction([storeName], "readwrite");
}
}
store(storeName) {
return new ObjectStore(this, storeName);
}
}
class Transaction {
constructor(txn, allowedStoreNames) {
this._txn = txn;
this._stores = {
sync: null,
summary: null,
timeline: null,
state: null,
};
this._allowedStoreNames = allowedStoreNames;
}
_idbStore(name) {
if (!this._allowedStoreNames.includes(name)) {
throw new Error(`Invalid store for transaction: ${name}, only ${this._allowedStoreNames.join(", ")} are allowed.`);
}
return new ObjectStore(this._txn.getObjectStore(name));
}
get timeline() {
if (!this._stores.timeline) {
const idbStore = this._idbStore("timeline");
this._stores.timeline = new TimelineStore(idbStore);
}
return this._stores.timeline;
}
complete() {
return txnAsPromise(this._txn);
}
abort() {
this._txn.abort();
}
}
class QueryTarget {
export default class QueryTarget {
reduce(range, reducer, initialValue) {
return this._reduce(range, reducer, initialValue, "next");
}
@ -166,27 +92,3 @@ class QueryTarget {
throw new Error("override this");
}
}
class ObjectStore extends QueryTarget {
constructor(store) {
this._store = store;
}
_queryTarget() {
return this._store;
}
index(indexName) {
return new Index(this._store.index(indexName));
}
}
class Index extends QueryTarget {
constructor(index) {
this._index = index;
}
_queryTarget() {
return this._index;
}
}

View file

@ -0,0 +1,31 @@
export const STORE_NAMES = ["sync", "roomState", "roomSummary", "roomTimeline"];
export default class Storage {
constructor(idbDatabase) {
this._db = idbDatabase;
const nameMap = STORE_NAMES.reduce((nameMap, name) => {
nameMap[name] = name;
return nameMap;
}, {});
this.storeNames = Object.freeze(nameMap);
}
_validateStoreNames(storeNames) {
const unknownStoreName = storeNames.find(name => !STORE_NAMES.includes(name));
if (unknownStoreName) {
throw new Error(`Tried to open a transaction for unknown store ${unknownStoreName}`);
}
}
startReadOnlyTxn(storeNames) {
this._validateStoreNames(storeNames);
const txn = this._db.transaction(storeNames, "readonly");
return new Transaction(txn, storeNames);
}
startReadWriteTxn(storeNames) {
this._validateStoreNames(storeNames);
const txn = this._db.transaction(storeNames, "readwrite");
return new Transaction(txn, storeNames);
}
}

16
src/storage/idb/store.js Normal file
View file

@ -0,0 +1,16 @@
import QueryTarget from "./query-target.js";
import Index from "./index.js";
export default class Store extends QueryTarget {
constructor(store) {
this._store = store;
}
_queryTarget() {
return this._store;
}
index(indexName) {
return new Index(this._store.index(indexName));
}
}

View file

@ -6,8 +6,8 @@ function createSessionsStore(db) {
function createStores(db) {
db.createObjectStore("sync"); //sync token
db.createObjectStore("summary", "room_id", {unique: true});
const timeline = db.createObjectStore("timeline", ["room_id", "sort_key"]);
db.createObjectStore("roomSummary", "room_id", {unique: true});
const timeline = db.createObjectStore("roomTimeline", ["room_id", "sort_key"]);
timeline.createIndex("by_event_id", ["room_id", "event.event_id"], {unique: true});
// how to get the first/last x events for a room?
// we don't want to specify the sort key, but would need an index for the room_id?
@ -15,7 +15,7 @@ function createStores(db) {
// still, you also can't have a PK of [room_id, sort_key] and get the last or first events with just the room_id? the only thing that changes it that the PK will provide an inherent sorting that you inherit in an index that only has room_id as keyPath??? There must be a better way, need to write a prototype test for this.
// SOLUTION: with numeric keys, you can just us a min/max value to get first/last
// db.createObjectStore("members", ["room_id", "state_key"]);
const state = db.createObjectStore("state", ["event.room_id", "event.type", "event.state_key"]);
const state = db.createObjectStore("roomState", ["event.room_id", "event.type", "event.state_key"]);
}
class Sessions {

View file

@ -1,5 +1,5 @@
class StateStore {
constructor(roomId, db) {
constructor(db) {
}

View file

@ -1,13 +0,0 @@
class SyncTransaction {
setSyncToken(syncToken, lastSynced) {
}
getRoomStore(roomId) {
new RoomStore(new Database(null, this._txn), roomId)
}
result() {
return txnAsPromise(this._txn);
}
}

View file

@ -0,0 +1,40 @@
import {txnAsPromise} from "./utils";
import Store from "./store.js";
import TimelineStore from "./stores/timeline.js";
export default class Transaction {
constructor(txn, allowedStoreNames) {
this._txn = txn;
this._allowedStoreNames = allowedStoreNames;
this._stores = {
sync: null,
summary: null,
timeline: null,
state: null,
};
}
_store(name) {
if (!this._allowedStoreNames.includes(name)) {
// more specific error? this is a bug, so maybe not ...
throw new Error(`Invalid store for transaction: ${name}, only ${this._allowedStoreNames.join(", ")} are allowed.`);
}
return new Store(this._txn.getObjectStore(name));
}
get timeline() {
if (!this._stores.timeline) {
const idbStore = this._idbStore("timeline");
this._stores.timeline = new TimelineStore(idbStore);
}
return this._stores.timeline;
}
complete() {
return txnAsPromise(this._txn);
}
abort() {
this._txn.abort();
}
}

View file

@ -62,7 +62,12 @@ export class Sync {
this._currentRequest = this._network.sync(timeout, syncToken);
const response = await this._currentRequest.response;
syncToken = response.next_batch;
const txn = this._storage.startSyncTxn();
const storeNames = this._storage.storeNames;
const txn = this._storage.startReadWriteTxn([
storeNames.timeline,
storeNames.sync,
storeNames.state
]);
try {
session.applySync(syncToken, response.account_data, txn);
// to_device