idb sort key prototype

This commit is contained in:
Bruno Windels 2018-12-21 18:09:01 +01:00
parent 0cf9e84bdd
commit 25a84d41a5
2 changed files with 189 additions and 1 deletions

View file

@ -2,7 +2,7 @@
/*
idb stores:
all in one database per stored session:
- session
- session (store all sessions in localStorage object?)
- device id
- last sync token
- access token
@ -44,6 +44,17 @@ all in one database per stored session:
- members_{room_id}
historical?
- 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?
where to store avatars?

177
typedarray.html Normal file
View 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>