forked from mystiq/hydrogen-web
idb sort key prototype
This commit is contained in:
parent
0cf9e84bdd
commit
25a84d41a5
2 changed files with 189 additions and 1 deletions
13
matrix.mjs
13
matrix.mjs
|
@ -2,7 +2,7 @@
|
||||||
/*
|
/*
|
||||||
idb stores:
|
idb stores:
|
||||||
all in one database per stored session:
|
all in one database per stored session:
|
||||||
- session
|
- session (store all sessions in localStorage object?)
|
||||||
- device id
|
- device id
|
||||||
- last sync token
|
- last sync token
|
||||||
- access token
|
- access token
|
||||||
|
@ -44,6 +44,17 @@ all in one database per stored session:
|
||||||
- members_{room_id}
|
- members_{room_id}
|
||||||
historical?
|
historical?
|
||||||
- timeline_{room_id}
|
- timeline_{room_id}
|
||||||
|
how to store timeline events in order they should be shown?
|
||||||
|
what's the key they should be sorted by?
|
||||||
|
|
||||||
|
start with origin_server_ts of first event as 0 and add / subtract from there
|
||||||
|
in case of gaps, take the max(last_ts + 1000, origin_server_ts) again to get an idea of how many
|
||||||
|
numbers are in between, and go down/up one again for events filling the gap
|
||||||
|
|
||||||
|
when closing the gap, we notice there are not enough numbers between the PK
|
||||||
|
of both sides of the gap (because more than 1 event / millisecond was sent, or server clocks were off),
|
||||||
|
what do we do? floating point?
|
||||||
|
|
||||||
- search?
|
- search?
|
||||||
|
|
||||||
where to store avatars?
|
where to store avatars?
|
||||||
|
|
177
typedarray.html
Normal file
177
typedarray.html
Normal file
|
@ -0,0 +1,177 @@
|
||||||
|
<html>
|
||||||
|
<body>
|
||||||
|
<ul id="messages"></ul>
|
||||||
|
<script type="text/javascript">
|
||||||
|
class Key {
|
||||||
|
constructor() {
|
||||||
|
this._keys = new Int32Array(2);
|
||||||
|
}
|
||||||
|
|
||||||
|
get gapKey() {
|
||||||
|
return this._keys[0];
|
||||||
|
}
|
||||||
|
|
||||||
|
set gapKey(value) {
|
||||||
|
this._keys[0] = value;
|
||||||
|
}
|
||||||
|
|
||||||
|
get eventKey() {
|
||||||
|
return this._keys[1];
|
||||||
|
}
|
||||||
|
|
||||||
|
set eventKey(value) {
|
||||||
|
this._keys[1] = value;
|
||||||
|
}
|
||||||
|
|
||||||
|
buffer() {
|
||||||
|
return this._keys.buffer;
|
||||||
|
}
|
||||||
|
|
||||||
|
nextKeyWithGap() {
|
||||||
|
const k = new Key();
|
||||||
|
k.gapKey = this.gapKey + 1;
|
||||||
|
k.eventKey = 0;
|
||||||
|
return k;
|
||||||
|
}
|
||||||
|
|
||||||
|
nextKey() {
|
||||||
|
const k = new Key();
|
||||||
|
k.gapKey = this.gapKey;
|
||||||
|
k.eventKey = this.eventKey + 1;
|
||||||
|
return k;
|
||||||
|
}
|
||||||
|
|
||||||
|
previousKey() {
|
||||||
|
const k = new Key();
|
||||||
|
k.gapKey = this.gapKey;
|
||||||
|
k.eventKey = this.eventKey - 1;
|
||||||
|
return k;
|
||||||
|
}
|
||||||
|
|
||||||
|
clone() {
|
||||||
|
const k = new Key();
|
||||||
|
k.gapKey = this.gapKey;
|
||||||
|
k.eventKey = this.eventKey;
|
||||||
|
return k;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function reqAsPromise(req) {
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
req.onsuccess = () => resolve(req);
|
||||||
|
req.onerror = (err) => reject(err);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function fetchResults(cursor, isDone, resultMapper) {
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
const results = [];
|
||||||
|
cursor.onerror = (event) => {
|
||||||
|
reject(new Error("Query failed: " + event.target.errorCode));
|
||||||
|
};
|
||||||
|
// collect results
|
||||||
|
cursor.onsuccess = (event) => {
|
||||||
|
console.log("got a result");
|
||||||
|
const cursor = event.target.result;
|
||||||
|
if (!cursor) {
|
||||||
|
resolve(results);
|
||||||
|
return; // end of results
|
||||||
|
}
|
||||||
|
results.push(resultMapper(cursor));
|
||||||
|
if (!isDone(results)) {
|
||||||
|
cursor.continue();
|
||||||
|
} else {
|
||||||
|
resolve(results);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
class Storage {
|
||||||
|
constructor(databaseName) {
|
||||||
|
this._databaseName = databaseName;
|
||||||
|
this._database = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
async open() {
|
||||||
|
const req = window.indexedDB.open(this._databaseName);
|
||||||
|
req.onupgradeneeded = async (ev) => {
|
||||||
|
const db = ev.target.result;
|
||||||
|
const oldVersion = ev.oldVersion;
|
||||||
|
this._createStores(db, oldVersion);
|
||||||
|
};
|
||||||
|
await reqAsPromise(req);
|
||||||
|
this._database = req.result;
|
||||||
|
}
|
||||||
|
|
||||||
|
_createStores(db) {
|
||||||
|
db.createObjectStore("timeline", {keyPath: ["roomId", "sortKey"]});
|
||||||
|
}
|
||||||
|
|
||||||
|
async insert(value) {
|
||||||
|
const tx = this._database.transaction(["timeline"], "readwrite");
|
||||||
|
const store = tx.objectStore("timeline");
|
||||||
|
await reqAsPromise(store.add(value));
|
||||||
|
}
|
||||||
|
|
||||||
|
async selectAt(roomId, sortKey, amount) {
|
||||||
|
const tx = this._database.transaction(["timeline"], "readonly");
|
||||||
|
const store = tx.objectStore("timeline");
|
||||||
|
const range = IDBKeyRange.lowerBound([roomId, sortKey.buffer()]);
|
||||||
|
const cursor = store.openCursor(range);
|
||||||
|
return await fetchResults(cursor,
|
||||||
|
(results) => results.length === amount,
|
||||||
|
(cursor) => cursor.value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
(async () => {
|
||||||
|
const initialSortKey = new Key();
|
||||||
|
initialSortKey.gapKey = 1000;
|
||||||
|
const roomId = "!abc:hs.tld";
|
||||||
|
const storage = new Storage("mysession");
|
||||||
|
await storage.open();
|
||||||
|
|
||||||
|
let records = await storage.selectAt(roomId, initialSortKey, 15);
|
||||||
|
if (!records.length) {
|
||||||
|
// insert first batch backwards,
|
||||||
|
// to see we're not assuming insertion order to sort
|
||||||
|
let sortKey = initialSortKey.clone();
|
||||||
|
sortKey.eventKey = 10;
|
||||||
|
for (var i = 10; i > 0; i--) {
|
||||||
|
await storage.insert({
|
||||||
|
roomId,
|
||||||
|
sortKey: sortKey.buffer(),
|
||||||
|
message: `message ${i} before gap`
|
||||||
|
});
|
||||||
|
sortKey = sortKey.previousKey();
|
||||||
|
}
|
||||||
|
sortKey = sortKey.nextKeyWithGap();
|
||||||
|
await storage.insert({
|
||||||
|
roomId,
|
||||||
|
sortKey: sortKey.buffer(),
|
||||||
|
message: `event to represent gap!`
|
||||||
|
});
|
||||||
|
for (var i = 1; i <= 10; i++) {
|
||||||
|
sortKey = sortKey.nextKey();
|
||||||
|
await storage.insert({
|
||||||
|
roomId,
|
||||||
|
sortKey: sortKey.buffer(),
|
||||||
|
message: `message ${i} after gap`
|
||||||
|
});
|
||||||
|
}
|
||||||
|
records = await storage.selectAt(roomId, initialSortKey, 15);
|
||||||
|
}
|
||||||
|
console.log(records, "records");
|
||||||
|
const nodes = records.map(r => {
|
||||||
|
const li = document.createElement("li");
|
||||||
|
li.appendChild(document.createTextNode(r.message));
|
||||||
|
return li;
|
||||||
|
});
|
||||||
|
const parentNode = document.getElementById("messages");
|
||||||
|
nodes.forEach(n => parentNode.appendChild(n));
|
||||||
|
})();
|
||||||
|
|
||||||
|
</script>
|
||||||
|
</body>
|
||||||
|
</html>
|
Loading…
Reference in a new issue