update store api with requirements for gap filling
This commit is contained in:
parent
7d91b2dde3
commit
aaff9eea6c
2 changed files with 169 additions and 45 deletions
|
@ -1,10 +1,14 @@
|
|||
import {iterateCursor} from "./utils.js";
|
||||
import {iterateCursor, reqAsPromise} from "./utils.js";
|
||||
|
||||
export default class QueryTarget {
|
||||
constructor(target) {
|
||||
this._target = target;
|
||||
}
|
||||
|
||||
get(key) {
|
||||
return reqAsPromise(this._target.get(key));
|
||||
}
|
||||
|
||||
reduce(range, reducer, initialValue) {
|
||||
return this._reduce(range, reducer, initialValue, "next");
|
||||
}
|
||||
|
|
|
@ -1,77 +1,197 @@
|
|||
import SortKey from "../../sortkey.js";
|
||||
|
||||
class Range {
|
||||
constructor(only, lower, upper, lowerOpen, upperOpen) {
|
||||
this._only = only;
|
||||
this._lower = lower;
|
||||
this._upper = upper;
|
||||
this._lowerOpen = lowerOpen;
|
||||
this._upperOpen = upperOpen;
|
||||
}
|
||||
|
||||
asIDBKeyRange(roomId) {
|
||||
// only
|
||||
if (this._only) {
|
||||
return IDBKeyRange.only([roomId, this._only.buffer]);
|
||||
}
|
||||
// lowerBound
|
||||
// also bound as we don't want to move into another roomId
|
||||
if (this._lower && !this._upper) {
|
||||
return IDBKeyRange.bound(
|
||||
[roomId, this._lower.buffer],
|
||||
[roomId, SortKey.maxKey.buffer],
|
||||
this._lowerOpen,
|
||||
false
|
||||
);
|
||||
}
|
||||
// upperBound
|
||||
// also bound as we don't want to move into another roomId
|
||||
if (!this._lower && this._upper) {
|
||||
return IDBKeyRange.bound(
|
||||
[roomId, SortKey.minKey.buffer],
|
||||
[roomId, this._upper.buffer],
|
||||
false,
|
||||
this._upperOpen
|
||||
);
|
||||
}
|
||||
// bound
|
||||
if (this._lower && this._upper) {
|
||||
return IDBKeyRange.bound(
|
||||
[roomId, this._lower.buffer],
|
||||
[roomId, this._upper.buffer],
|
||||
this._lowerOpen,
|
||||
this._upperOpen
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
/*
|
||||
* @typedef {Object} Gap
|
||||
* @property {?string} prev_batch the pagination token for this backwards facing gap
|
||||
* @property {?string} next_batch the pagination token for this forwards facing gap
|
||||
*
|
||||
* @typedef {Object} Event
|
||||
* @property {string} event_id the id of the event
|
||||
* @property {string} type the
|
||||
* @property {?string} state_key the state key of this state event
|
||||
*
|
||||
* @typedef {Object} Entry
|
||||
* @property {string} roomId
|
||||
* @property {SortKey} sortKey
|
||||
* @property {?Event} event if an event entry, the event
|
||||
* @property {?Gap} gap if a gap entry, the gap
|
||||
*/
|
||||
export default class RoomTimelineStore {
|
||||
constructor(timelineStore) {
|
||||
this._timelineStore = timelineStore;
|
||||
}
|
||||
|
||||
/** Creates a range that only includes the given key
|
||||
* @param {SortKey} sortKey the key
|
||||
* @return {Range} the created range
|
||||
*/
|
||||
onlyRange(sortKey) {
|
||||
return new Range(sortKey);
|
||||
}
|
||||
|
||||
/** Creates a range that includes all keys before sortKey, and optionally also the key itself.
|
||||
* @param {SortKey} sortKey the key
|
||||
* @param {boolean} [open=false] whether the key is included (false) or excluded (true) from the range at the upper end.
|
||||
* @return {Range} the created range
|
||||
*/
|
||||
upperBoundRange(sortKey, open=false) {
|
||||
return new Range(undefined, undefined, sortKey, undefined, open);
|
||||
}
|
||||
|
||||
/** Creates a range that includes all keys after sortKey, and optionally also the key itself.
|
||||
* @param {SortKey} sortKey the key
|
||||
* @param {boolean} [open=false] whether the key is included (false) or excluded (true) from the range at the lower end.
|
||||
* @return {Range} the created range
|
||||
*/
|
||||
lowerBoundRange(sortKey, open=false) {
|
||||
return new Range(undefined, sortKey, undefined, open);
|
||||
}
|
||||
|
||||
/** Creates a range that includes all keys between `lower` and `upper`, and optionally the given keys as well.
|
||||
* @param {SortKey} lower the lower key
|
||||
* @param {SortKey} upper the upper key
|
||||
* @param {boolean} [lowerOpen=false] whether the lower key is included (false) or excluded (true) from the range.
|
||||
* @param {boolean} [upperOpen=false] whether the upper key is included (false) or excluded (true) from the range.
|
||||
* @return {Range} the created range
|
||||
*/
|
||||
boundRange(lower, upper, lowerOpen=false, upperOpen=false) {
|
||||
return new Range(undefined, lower, upper, lowerOpen, upperOpen);
|
||||
}
|
||||
|
||||
/** Looks up the last `amount` entries in the timeline for `roomId`.
|
||||
* @param {string} roomId
|
||||
* @param {number} amount
|
||||
* @return {Promise<Entry[]>} a promise resolving to an array with 0 or more entries, in ascending order.
|
||||
*/
|
||||
async lastEvents(roomId, amount) {
|
||||
return this.eventsBefore(roomId, SortKey.maxKey, amount);
|
||||
}
|
||||
|
||||
/** Looks up the first `amount` entries in the timeline for `roomId`.
|
||||
* @param {string} roomId
|
||||
* @param {number} amount
|
||||
* @return {Promise<Entry[]>} a promise resolving to an array with 0 or more entries, in ascending order.
|
||||
*/
|
||||
async firstEvents(roomId, amount) {
|
||||
return this.eventsAfter(roomId, SortKey.minKey, amount);
|
||||
}
|
||||
|
||||
/** Looks up `amount` entries after `sortKey` in the timeline for `roomId`.
|
||||
* The entry for `sortKey` is not included.
|
||||
* @param {string} roomId
|
||||
* @param {SortKey} sortKey
|
||||
* @param {number} amount
|
||||
* @return {Promise<Entry[]>} a promise resolving to an array with 0 or more entries, in ascending order.
|
||||
*/
|
||||
eventsAfter(roomId, sortKey, amount) {
|
||||
const range = IDBKeyRange.bound([roomId, sortKey.buffer], [roomId, SortKey.maxKey.buffer], true, false);
|
||||
return this._timelineStore.selectLimit(range, amount);
|
||||
const idbRange = this.lowerBoundRange(sortKey, true).asIDBKeyRange(roomId);
|
||||
return this._timelineStore.selectLimit(idbRange, amount);
|
||||
}
|
||||
|
||||
/** Looks up `amount` entries before `sortKey` in the timeline for `roomId`.
|
||||
* The entry for `sortKey` is not included.
|
||||
* @param {string} roomId
|
||||
* @param {SortKey} sortKey
|
||||
* @param {number} amount
|
||||
* @return {Promise<Entry[]>} a promise resolving to an array with 0 or more entries, in ascending order.
|
||||
*/
|
||||
async eventsBefore(roomId, sortKey, amount) {
|
||||
const range = IDBKeyRange.only([roomId, sortKey.buffer]);
|
||||
const range = this.upperBoundRange(sortKey, true).asIDBKeyRange(roomId);
|
||||
const events = await this._timelineStore.selectLimitReverse(range, amount);
|
||||
events.reverse(); // because we fetched them backwards
|
||||
return events;
|
||||
}
|
||||
|
||||
nextEventFromGap(roomId, sortKey) {
|
||||
|
||||
/** Looks up the first, if any, event entry (so excluding gap entries) after `sortKey`.
|
||||
* @param {string} roomId
|
||||
* @param {SortKey} sortKey
|
||||
* @return {Promise<(?Entry)>} a promise resolving to entry, if any.
|
||||
*/
|
||||
nextEvent(roomId, sortKey) {
|
||||
const range = this.lowerBoundRange(sortKey, true).asIDBKeyRange(roomId);
|
||||
return this._timelineStore.find(range, entry => !!entry.event);
|
||||
}
|
||||
|
||||
previousEventFromGap(roomId, sortKey) {
|
||||
|
||||
/** Looks up the first, if any, event entry (so excluding gap entries) before `sortKey`.
|
||||
* @param {string} roomId
|
||||
* @param {SortKey} sortKey
|
||||
* @return {Promise<(?Entry)>} a promise resolving to entry, if any.
|
||||
*/
|
||||
previousEvent(roomId, sortKey) {
|
||||
const range = this.upperBoundRange(sortKey, true).asIDBKeyRange(roomId);
|
||||
return this._timelineStore.findReverse(range, entry => !!entry.event);
|
||||
}
|
||||
|
||||
findEntry(roomId, sortKey) {
|
||||
const range = IDBKeyRange.bound([roomId, SortKey.minKey.buffer], [roomId, sortKey.buffer], false, true);
|
||||
return this._timelineStore.selectFirst(range);
|
||||
/** Inserts a new entry into the store. The combination of roomId and sortKey should not exist yet, or an error is thrown.
|
||||
* @param {Entry} entry the entry to insert
|
||||
* @return {Promise<>} a promise resolving to undefined if the operation was successful, or a StorageError if not.
|
||||
* @throws {StorageError} ...
|
||||
*/
|
||||
insert(entry) {
|
||||
// TODO: map error? or in idb/store?
|
||||
return this._timelineStore.add(entry);
|
||||
}
|
||||
|
||||
// entry should have roomId, sortKey, event & gap keys
|
||||
add(entry) {
|
||||
this._timelineStore.add(entry);
|
||||
/** Updates the entry into the store with the given [roomId, sortKey] combination.
|
||||
* If not yet present, will insert. Might be slower than add.
|
||||
* @param {Entry} entry the entry to update.
|
||||
* @return {Promise<>} a promise resolving to undefined if the operation was successful, or a StorageError if not.
|
||||
*/
|
||||
update(entry) {
|
||||
return this._timelineStore.put(entry);
|
||||
}
|
||||
// should this happen as part of a transaction that stores all synced in changes?
|
||||
// e.g.:
|
||||
// - timeline events for all rooms
|
||||
// - latest sync token
|
||||
// - new members
|
||||
// - new room state
|
||||
// - updated/new account data
|
||||
|
||||
|
||||
|
||||
appendGap(roomId, sortKey, gap) {
|
||||
this._timelineStore.add({
|
||||
roomId: roomId,
|
||||
sortKey: sortKey.buffer,
|
||||
event: null,
|
||||
gap: gap,
|
||||
});
|
||||
}
|
||||
|
||||
appendEvent(roomId, sortKey, event) {
|
||||
console.info(`appending event for room ${roomId} with key ${sortKey}`);
|
||||
this._timelineStore.add({
|
||||
roomId: roomId,
|
||||
sortKey: sortKey.buffer,
|
||||
event: event,
|
||||
gap: null,
|
||||
});
|
||||
}
|
||||
// could be gap or event
|
||||
async removeEntry(roomId, sortKey) {
|
||||
this._timelineStore.delete([roomId, sortKey.buffer]);
|
||||
}
|
||||
get(roomId, sortKey) {
|
||||
return this._timelineStore.get([roomId, sortKey]);
|
||||
}
|
||||
// returns the entries as well!! (or not always needed? I guess not always needed, so extra method)
|
||||
removeRange(roomId, range) {
|
||||
// TODO: read the entries!
|
||||
return this._timelineStore.delete(range.asIDBKeyRange(roomId));
|
||||
}
|
||||
}
|
||||
|
|
Reference in a new issue