hydrogen-web/prototypes/partialkey.html

194 lines
4.9 KiB
HTML
Raw Permalink Normal View History

<html>
<head><meta charset="utf-8"></head>
<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 = (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 selectLast(roomId, amount) {
const tx = this._database.transaction(["timeline"], "readonly");
const store = tx.objectStore("timeline");
const maxKey = new Key();
maxKey.gapKey = Number.MAX_SAFE_INTEGER;
maxKey.eventKey = Number.MAX_SAFE_INTEGER;
const range = IDBKeyRange.upperBound([roomId, maxKey.buffer()]);
const cursor = store.openCursor(range, "prev");
const events = await fetchResults(cursor,
(results) => results.length === amount,
(cursor) => cursor.value);
events.reverse();
return events;
}
async selectFirst(roomId, amount) {
const tx = this._database.transaction(["timeline"], "readonly");
const store = tx.objectStore("timeline");
const minKey = new Key();
const range = IDBKeyRange.lowerBound([roomId, minKey.buffer()]);
const cursor = store.openCursor(range, "next");
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.selectFirst(roomId, 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.selectFirst(roomId, 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>