mirror of
https://github.com/vector-im/hydrogen-web.git
synced 2024-12-22 19:14:52 +01:00
cleanup idb storage
This commit is contained in:
parent
13eef402aa
commit
90300dcdaf
@ -1,5 +1,6 @@
|
|||||||
import Network from "../network.js";
|
import Network from "./network.js";
|
||||||
import Session from "../session.js";
|
import Session from "./session.js";
|
||||||
|
import createIdbStorage from "./storage/idb/factory.js";
|
||||||
const HOMESERVER = "http://localhost:8008";
|
const HOMESERVER = "http://localhost:8008";
|
||||||
|
|
||||||
async function getLoginData(username, password) {
|
async function getLoginData(username, password) {
|
||||||
@ -17,7 +18,7 @@ async function getLoginData(username, password) {
|
|||||||
async function main() {
|
async function main() {
|
||||||
const loginData = await getLoginData("bruno1", "testtest");
|
const loginData = await getLoginData("bruno1", "testtest");
|
||||||
const network = new Network(HOMESERVER, loginData.access_token);
|
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);
|
const session = new Session(loginData, storage);
|
||||||
await session.load();
|
await session.load();
|
||||||
const sync = new Sync(network, session, storage);
|
const sync = new Sync(network, session, storage);
|
||||||
|
18
src/storage/idb/factory.js
Normal file
18
src/storage/idb/factory.js
Normal 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
11
src/storage/idb/index.js
Normal 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;
|
||||||
|
}
|
||||||
|
}
|
@ -1,80 +1,6 @@
|
|||||||
const SYNC_STORES = [
|
import {iterateCursor} from "./utils";
|
||||||
"sync",
|
|
||||||
"summary",
|
|
||||||
"timeline",
|
|
||||||
"members",
|
|
||||||
"state"
|
|
||||||
];
|
|
||||||
|
|
||||||
class Database {
|
export default class QueryTarget {
|
||||||
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 {
|
|
||||||
reduce(range, reducer, initialValue) {
|
reduce(range, reducer, initialValue) {
|
||||||
return this._reduce(range, reducer, initialValue, "next");
|
return this._reduce(range, reducer, initialValue, "next");
|
||||||
}
|
}
|
||||||
@ -166,27 +92,3 @@ class QueryTarget {
|
|||||||
throw new Error("override this");
|
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;
|
|
||||||
}
|
|
||||||
}
|
|
31
src/storage/idb/storage.js
Normal file
31
src/storage/idb/storage.js
Normal 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
16
src/storage/idb/store.js
Normal 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));
|
||||||
|
}
|
||||||
|
}
|
@ -6,8 +6,8 @@ function createSessionsStore(db) {
|
|||||||
|
|
||||||
function createStores(db) {
|
function createStores(db) {
|
||||||
db.createObjectStore("sync"); //sync token
|
db.createObjectStore("sync"); //sync token
|
||||||
db.createObjectStore("summary", "room_id", {unique: true});
|
db.createObjectStore("roomSummary", "room_id", {unique: true});
|
||||||
const timeline = db.createObjectStore("timeline", ["room_id", "sort_key"]);
|
const timeline = db.createObjectStore("roomTimeline", ["room_id", "sort_key"]);
|
||||||
timeline.createIndex("by_event_id", ["room_id", "event.event_id"], {unique: true});
|
timeline.createIndex("by_event_id", ["room_id", "event.event_id"], {unique: true});
|
||||||
// how to get the first/last x events for a room?
|
// 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?
|
// 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.
|
// 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
|
// SOLUTION: with numeric keys, you can just us a min/max value to get first/last
|
||||||
// db.createObjectStore("members", ["room_id", "state_key"]);
|
// 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 {
|
class Sessions {
|
@ -1,5 +1,5 @@
|
|||||||
class StateStore {
|
class StateStore {
|
||||||
constructor(roomId, db) {
|
constructor(db) {
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
@ -1,13 +0,0 @@
|
|||||||
class SyncTransaction {
|
|
||||||
setSyncToken(syncToken, lastSynced) {
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
getRoomStore(roomId) {
|
|
||||||
new RoomStore(new Database(null, this._txn), roomId)
|
|
||||||
}
|
|
||||||
|
|
||||||
result() {
|
|
||||||
return txnAsPromise(this._txn);
|
|
||||||
}
|
|
||||||
}
|
|
40
src/storage/idb/transaction.js
Normal file
40
src/storage/idb/transaction.js
Normal 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();
|
||||||
|
}
|
||||||
|
}
|
@ -62,7 +62,12 @@ export class Sync {
|
|||||||
this._currentRequest = this._network.sync(timeout, syncToken);
|
this._currentRequest = this._network.sync(timeout, syncToken);
|
||||||
const response = await this._currentRequest.response;
|
const response = await this._currentRequest.response;
|
||||||
syncToken = response.next_batch;
|
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 {
|
try {
|
||||||
session.applySync(syncToken, response.account_data, txn);
|
session.applySync(syncToken, response.account_data, txn);
|
||||||
// to_device
|
// to_device
|
||||||
|
Loading…
Reference in New Issue
Block a user