Merge pull request #473 from vector-im/snowpack-ts-storage-2
Snowpack + Typescript conversion (Part 2)
This commit is contained in:
commit
5d8e66a3f3
16 changed files with 262 additions and 149 deletions
|
@ -148,7 +148,7 @@ export class Store<T> extends QueryTarget<T> {
|
||||||
return new QueryTarget<T>(new QueryTargetWrapper<T>(this._idbStore.index(indexName)));
|
return new QueryTarget<T>(new QueryTargetWrapper<T>(this._idbStore.index(indexName)));
|
||||||
}
|
}
|
||||||
|
|
||||||
put(value: T): Promise<IDBValidKey> {
|
put(value: T): void {
|
||||||
// If this request fails, the error will bubble up to the transaction and abort it,
|
// If this request fails, the error will bubble up to the transaction and abort it,
|
||||||
// which is the behaviour we want. Therefore, it is ok to not create a promise for this
|
// which is the behaviour we want. Therefore, it is ok to not create a promise for this
|
||||||
// request and await it.
|
// request and await it.
|
||||||
|
@ -159,12 +159,12 @@ export class Store<T> extends QueryTarget<T> {
|
||||||
//
|
//
|
||||||
// Note that this can still throw synchronously, like it does for TransactionInactiveError,
|
// Note that this can still throw synchronously, like it does for TransactionInactiveError,
|
||||||
// see https://www.w3.org/TR/IndexedDB-2/#transaction-lifetime-concept
|
// see https://www.w3.org/TR/IndexedDB-2/#transaction-lifetime-concept
|
||||||
return reqAsPromise(this._idbStore.put(value));
|
this._idbStore.put(value);
|
||||||
}
|
}
|
||||||
|
|
||||||
add(value: T): Promise<IDBValidKey> {
|
add(value: T): void {
|
||||||
// ok to not monitor result of request, see comment in `put`.
|
// ok to not monitor result of request, see comment in `put`.
|
||||||
return reqAsPromise(this._idbStore.add(value));
|
this._idbStore.add(value);
|
||||||
}
|
}
|
||||||
|
|
||||||
delete(keyOrKeyRange: IDBValidKey | IDBKeyRange): Promise<undefined> {
|
delete(keyOrKeyRange: IDBValidKey | IDBKeyRange): Promise<undefined> {
|
||||||
|
|
|
@ -17,13 +17,13 @@ limitations under the License.
|
||||||
import {txnAsPromise} from "./utils";
|
import {txnAsPromise} from "./utils";
|
||||||
import {StorageError} from "../common";
|
import {StorageError} from "../common";
|
||||||
import {Store} from "./Store";
|
import {Store} from "./Store";
|
||||||
import {SessionStore} from "./stores/SessionStore.js";
|
import {SessionStore} from "./stores/SessionStore";
|
||||||
import {RoomSummaryStore} from "./stores/RoomSummaryStore.js";
|
import {RoomSummaryStore} from "./stores/RoomSummaryStore";
|
||||||
import {InviteStore} from "./stores/InviteStore.js";
|
import {InviteStore} from "./stores/InviteStore";
|
||||||
import {TimelineEventStore} from "./stores/TimelineEventStore.js";
|
import {TimelineEventStore} from "./stores/TimelineEventStore";
|
||||||
import {TimelineRelationStore} from "./stores/TimelineRelationStore.js";
|
import {TimelineRelationStore} from "./stores/TimelineRelationStore";
|
||||||
import {RoomStateStore} from "./stores/RoomStateStore.js";
|
import {RoomStateStore} from "./stores/RoomStateStore.js";
|
||||||
import {RoomMemberStore} from "./stores/RoomMemberStore.js";
|
import {RoomMemberStore} from "./stores/RoomMemberStore";
|
||||||
import {TimelineFragmentStore} from "./stores/TimelineFragmentStore.js";
|
import {TimelineFragmentStore} from "./stores/TimelineFragmentStore.js";
|
||||||
import {PendingEventStore} from "./stores/PendingEventStore.js";
|
import {PendingEventStore} from "./stores/PendingEventStore.js";
|
||||||
import {UserIdentityStore} from "./stores/UserIdentityStore.js";
|
import {UserIdentityStore} from "./stores/UserIdentityStore.js";
|
||||||
|
|
|
@ -1,10 +1,10 @@
|
||||||
import {iterateCursor, reqAsPromise} from "./utils";
|
import {iterateCursor, reqAsPromise} from "./utils";
|
||||||
import {RoomMember, EVENT_TYPE as MEMBER_EVENT_TYPE} from "../../room/members/RoomMember.js";
|
import {RoomMember, EVENT_TYPE as MEMBER_EVENT_TYPE} from "../../room/members/RoomMember.js";
|
||||||
import {addRoomToIdentity} from "../../e2ee/DeviceTracker.js";
|
import {addRoomToIdentity} from "../../e2ee/DeviceTracker.js";
|
||||||
import {RoomMemberStore} from "./stores/RoomMemberStore.js";
|
import {RoomMemberStore} from "./stores/RoomMemberStore";
|
||||||
import {SessionStore} from "./stores/SessionStore.js";
|
import {SessionStore} from "./stores/SessionStore";
|
||||||
import {encodeScopeTypeKey} from "./stores/OperationStore.js";
|
import {encodeScopeTypeKey} from "./stores/OperationStore.js";
|
||||||
import {MAX_UNICODE} from "./stores/common.js";
|
import {MAX_UNICODE} from "./stores/common";
|
||||||
|
|
||||||
// FUNCTIONS SHOULD ONLY BE APPENDED!!
|
// FUNCTIONS SHOULD ONLY BE APPENDED!!
|
||||||
// the index in the array is the database version
|
// the index in the array is the database version
|
||||||
|
|
|
@ -14,7 +14,7 @@ See the License for the specific language governing permissions and
|
||||||
limitations under the License.
|
limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import {MAX_UNICODE, MIN_UNICODE} from "./common.js";
|
import {MAX_UNICODE, MIN_UNICODE} from "./common";
|
||||||
|
|
||||||
function encodeKey(userId, deviceId) {
|
function encodeKey(userId, deviceId) {
|
||||||
return `${userId}|${deviceId}`;
|
return `${userId}|${deviceId}`;
|
||||||
|
|
|
@ -14,7 +14,7 @@ See the License for the specific language governing permissions and
|
||||||
limitations under the License.
|
limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import {MIN_UNICODE, MAX_UNICODE} from "./common.js";
|
import {MIN_UNICODE, MAX_UNICODE} from "./common";
|
||||||
|
|
||||||
function encodeKey(roomId, sessionId, messageIndex) {
|
function encodeKey(roomId, sessionId, messageIndex) {
|
||||||
return `${roomId}|${sessionId}|${messageIndex}`;
|
return `${roomId}|${sessionId}|${messageIndex}`;
|
||||||
|
|
|
@ -14,7 +14,7 @@ See the License for the specific language governing permissions and
|
||||||
limitations under the License.
|
limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import {MIN_UNICODE, MAX_UNICODE} from "./common.js";
|
import {MIN_UNICODE, MAX_UNICODE} from "./common";
|
||||||
|
|
||||||
function encodeKey(roomId, senderKey, sessionId) {
|
function encodeKey(roomId, senderKey, sessionId) {
|
||||||
return `${roomId}|${senderKey}|${sessionId}`;
|
return `${roomId}|${senderKey}|${sessionId}`;
|
||||||
|
|
51
src/matrix/storage/idb/stores/InviteStore.ts
Normal file
51
src/matrix/storage/idb/stores/InviteStore.ts
Normal file
|
@ -0,0 +1,51 @@
|
||||||
|
/*
|
||||||
|
Copyright 2021 The Matrix.org Foundation C.I.C.
|
||||||
|
|
||||||
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
you may not use this file except in compliance with the License.
|
||||||
|
You may obtain a copy of the License at
|
||||||
|
|
||||||
|
http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
|
||||||
|
Unless required by applicable law or agreed to in writing, software
|
||||||
|
distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
See the License for the specific language governing permissions and
|
||||||
|
limitations under the License.
|
||||||
|
*/
|
||||||
|
import {Store} from "../Store";
|
||||||
|
import {MemberData} from "./RoomMemberStore";
|
||||||
|
|
||||||
|
// TODO: Move to Invite when that's TypeScript.
|
||||||
|
export interface InviteData {
|
||||||
|
roomId: string;
|
||||||
|
isEncrypted: boolean;
|
||||||
|
isDirectMessage: boolean;
|
||||||
|
name?: string;
|
||||||
|
avatarUrl?: string;
|
||||||
|
avatarColorId: number;
|
||||||
|
canonicalAlias?: string;
|
||||||
|
timestamp: number;
|
||||||
|
joinRule: string;
|
||||||
|
inviter?: MemberData;
|
||||||
|
}
|
||||||
|
|
||||||
|
export class InviteStore {
|
||||||
|
private _inviteStore: Store<InviteData>;
|
||||||
|
|
||||||
|
constructor(inviteStore: Store<InviteData>) {
|
||||||
|
this._inviteStore = inviteStore;
|
||||||
|
}
|
||||||
|
|
||||||
|
getAll(): Promise<InviteData[]> {
|
||||||
|
return this._inviteStore.selectAll();
|
||||||
|
}
|
||||||
|
|
||||||
|
set(invite: InviteData): void {
|
||||||
|
this._inviteStore.put(invite);
|
||||||
|
}
|
||||||
|
|
||||||
|
remove(roomId: string): void {
|
||||||
|
this._inviteStore.delete(roomId);
|
||||||
|
}
|
||||||
|
}
|
|
@ -13,7 +13,7 @@ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
See the License for the specific language governing permissions and
|
See the License for the specific language governing permissions and
|
||||||
limitations under the License.
|
limitations under the License.
|
||||||
*/
|
*/
|
||||||
import {MIN_UNICODE, MAX_UNICODE} from "./common.js";
|
import {MIN_UNICODE, MAX_UNICODE} from "./common";
|
||||||
|
|
||||||
export function encodeScopeTypeKey(scope, type) {
|
export function encodeScopeTypeKey(scope, type) {
|
||||||
return `${scope}|${type}`;
|
return `${scope}|${type}`;
|
||||||
|
|
|
@ -15,44 +15,59 @@ See the License for the specific language governing permissions and
|
||||||
limitations under the License.
|
limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import {MAX_UNICODE} from "./common.js";
|
import {MAX_UNICODE} from "./common";
|
||||||
|
import {Store} from "../Store";
|
||||||
|
|
||||||
function encodeKey(roomId, userId) {
|
function encodeKey(roomId: string, userId: string) {
|
||||||
return `${roomId}|${userId}`;
|
return `${roomId}|${userId}`;
|
||||||
}
|
}
|
||||||
|
|
||||||
function decodeKey(key) {
|
function decodeKey(key: string): { roomId: string, userId: string } {
|
||||||
const [roomId, userId] = key.split("|");
|
const [roomId, userId] = key.split("|");
|
||||||
return {roomId, userId};
|
return {roomId, userId};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TODO: Move to RoomMember when that's TypeScript.
|
||||||
|
export interface MemberData {
|
||||||
|
roomId: string;
|
||||||
|
userId: string;
|
||||||
|
avatarUrl: string;
|
||||||
|
displayName: string;
|
||||||
|
membership: "join" | "leave" | "invite" | "ban";
|
||||||
|
}
|
||||||
|
|
||||||
|
type MemberStorageEntry = MemberData & { key: string }
|
||||||
|
|
||||||
// no historical members
|
// no historical members
|
||||||
export class RoomMemberStore {
|
export class RoomMemberStore {
|
||||||
constructor(roomMembersStore) {
|
private _roomMembersStore: Store<MemberStorageEntry>;
|
||||||
|
|
||||||
|
constructor(roomMembersStore: Store<MemberStorageEntry>) {
|
||||||
this._roomMembersStore = roomMembersStore;
|
this._roomMembersStore = roomMembersStore;
|
||||||
}
|
}
|
||||||
|
|
||||||
get(roomId, userId) {
|
get(roomId: string, userId: string): Promise<MemberStorageEntry | null> {
|
||||||
return this._roomMembersStore.get(encodeKey(roomId, userId));
|
return this._roomMembersStore.get(encodeKey(roomId, userId));
|
||||||
}
|
}
|
||||||
|
|
||||||
async set(member) {
|
set(member: MemberData): void {
|
||||||
member.key = encodeKey(member.roomId, member.userId);
|
// Object.assign would be more typesafe, but small objects
|
||||||
return this._roomMembersStore.put(member);
|
(member as any).key = encodeKey(member.roomId, member.userId);
|
||||||
|
this._roomMembersStore.put(member as MemberStorageEntry);
|
||||||
}
|
}
|
||||||
|
|
||||||
getAll(roomId) {
|
getAll(roomId: string): Promise<MemberData[]> {
|
||||||
const range = this._roomMembersStore.IDBKeyRange.lowerBound(encodeKey(roomId, ""));
|
const range = this._roomMembersStore.IDBKeyRange.lowerBound(encodeKey(roomId, ""));
|
||||||
return this._roomMembersStore.selectWhile(range, member => {
|
return this._roomMembersStore.selectWhile(range, member => {
|
||||||
return member.roomId === roomId;
|
return member.roomId === roomId;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
async getAllUserIds(roomId) {
|
async getAllUserIds(roomId: string): Promise<string[]> {
|
||||||
const userIds = [];
|
const userIds: string[] = [];
|
||||||
const range = this._roomMembersStore.IDBKeyRange.lowerBound(encodeKey(roomId, ""));
|
const range = this._roomMembersStore.IDBKeyRange.lowerBound(encodeKey(roomId, ""));
|
||||||
await this._roomMembersStore.iterateKeys(range, key => {
|
await this._roomMembersStore.iterateKeys(range, key => {
|
||||||
const decodedKey = decodeKey(key);
|
const decodedKey = decodeKey(key as string);
|
||||||
// prevent running into the next room
|
// prevent running into the next room
|
||||||
if (decodedKey.roomId === roomId) {
|
if (decodedKey.roomId === roomId) {
|
||||||
userIds.push(decodedKey.userId);
|
userIds.push(decodedKey.userId);
|
||||||
|
@ -63,7 +78,7 @@ export class RoomMemberStore {
|
||||||
return userIds;
|
return userIds;
|
||||||
}
|
}
|
||||||
|
|
||||||
removeAllForRoom(roomId) {
|
removeAllForRoom(roomId: string): void {
|
||||||
// exclude both keys as they are theoretical min and max,
|
// exclude both keys as they are theoretical min and max,
|
||||||
// but we should't have a match for just the room id, or room id with max
|
// but we should't have a match for just the room id, or room id with max
|
||||||
const range = this._roomMembersStore.IDBKeyRange.bound(roomId, `${roomId}|${MAX_UNICODE}`, true, true);
|
const range = this._roomMembersStore.IDBKeyRange.bound(roomId, `${roomId}|${MAX_UNICODE}`, true, true);
|
|
@ -15,7 +15,7 @@ See the License for the specific language governing permissions and
|
||||||
limitations under the License.
|
limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import {MAX_UNICODE} from "./common.js";
|
import {MAX_UNICODE} from "./common";
|
||||||
|
|
||||||
function encodeKey(roomId, eventType, stateKey) {
|
function encodeKey(roomId, eventType, stateKey) {
|
||||||
return `${roomId}|${eventType}|${stateKey}`;
|
return `${roomId}|${eventType}|${stateKey}`;
|
||||||
|
|
|
@ -27,31 +27,35 @@ store contains:
|
||||||
inviteCount
|
inviteCount
|
||||||
joinCount
|
joinCount
|
||||||
*/
|
*/
|
||||||
|
import {Store} from "../Store";
|
||||||
|
import {SummaryData} from "../../../room/RoomSummary";
|
||||||
|
|
||||||
/** Used for both roomSummary and archivedRoomSummary stores */
|
/** Used for both roomSummary and archivedRoomSummary stores */
|
||||||
export class RoomSummaryStore {
|
export class RoomSummaryStore {
|
||||||
constructor(summaryStore) {
|
private _summaryStore: Store<SummaryData>;
|
||||||
|
|
||||||
|
constructor(summaryStore: Store<SummaryData>) {
|
||||||
this._summaryStore = summaryStore;
|
this._summaryStore = summaryStore;
|
||||||
}
|
}
|
||||||
|
|
||||||
getAll() {
|
getAll(): Promise<SummaryData[]> {
|
||||||
return this._summaryStore.selectAll();
|
return this._summaryStore.selectAll();
|
||||||
}
|
}
|
||||||
|
|
||||||
set(summary) {
|
set(summary: SummaryData): void {
|
||||||
return this._summaryStore.put(summary);
|
this._summaryStore.put(summary);
|
||||||
}
|
}
|
||||||
|
|
||||||
get(roomId) {
|
get(roomId: string): Promise<SummaryData | null> {
|
||||||
return this._summaryStore.get(roomId);
|
return this._summaryStore.get(roomId);
|
||||||
}
|
}
|
||||||
|
|
||||||
async has(roomId) {
|
async has(roomId: string): Promise<boolean> {
|
||||||
const fetchedKey = await this._summaryStore.getKey(roomId);
|
const fetchedKey = await this._summaryStore.getKey(roomId);
|
||||||
return roomId === fetchedKey;
|
return roomId === fetchedKey;
|
||||||
}
|
}
|
||||||
|
|
||||||
remove(roomId) {
|
remove(roomId: string): Promise<undefined> {
|
||||||
return this._summaryStore.delete(roomId);
|
return this._summaryStore.delete(roomId);
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -13,28 +13,36 @@ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
See the License for the specific language governing permissions and
|
See the License for the specific language governing permissions and
|
||||||
limitations under the License.
|
limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
import {Store} from "../Store";
|
||||||
|
|
||||||
|
export interface SessionEntry {
|
||||||
|
key: string;
|
||||||
|
value: any;
|
||||||
|
}
|
||||||
|
|
||||||
export class SessionStore {
|
export class SessionStore {
|
||||||
constructor(sessionStore) {
|
private _sessionStore: Store<SessionEntry>
|
||||||
|
|
||||||
|
constructor(sessionStore: Store<SessionEntry>) {
|
||||||
this._sessionStore = sessionStore;
|
this._sessionStore = sessionStore;
|
||||||
}
|
}
|
||||||
|
|
||||||
async get(key) {
|
async get(key: string): Promise<any> {
|
||||||
const entry = await this._sessionStore.get(key);
|
const entry = await this._sessionStore.get(key);
|
||||||
if (entry) {
|
if (entry) {
|
||||||
return entry.value;
|
return entry.value;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
set(key, value) {
|
set(key: string, value: any): void {
|
||||||
this._sessionStore.put({key, value});
|
this._sessionStore.put({key, value});
|
||||||
}
|
}
|
||||||
|
|
||||||
add(key, value) {
|
add(key: string, value: any): void {
|
||||||
this._sessionStore.add({key, value});
|
this._sessionStore.add({key, value});
|
||||||
}
|
}
|
||||||
|
|
||||||
remove(key) {
|
remove(key: string): void {
|
||||||
this._sessionStore.delete(key);
|
this._sessionStore.delete(key);
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -18,23 +18,50 @@ import {EventKey} from "../../../room/timeline/EventKey.js";
|
||||||
import { StorageError } from "../../common";
|
import { StorageError } from "../../common";
|
||||||
import { encodeUint32 } from "../utils";
|
import { encodeUint32 } from "../utils";
|
||||||
import {KeyLimits} from "../../common";
|
import {KeyLimits} from "../../common";
|
||||||
|
import {Store} from "../Store";
|
||||||
|
import {TimelineEvent, StateEvent} from "../../types";
|
||||||
|
|
||||||
function encodeKey(roomId, fragmentId, eventIndex) {
|
interface Annotation {
|
||||||
|
count: number;
|
||||||
|
me: boolean;
|
||||||
|
firstTimestamp: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface StorageEntry {
|
||||||
|
roomId: string;
|
||||||
|
fragmentId: number;
|
||||||
|
eventIndex: number;
|
||||||
|
event: TimelineEvent | StateEvent;
|
||||||
|
displayName?: string;
|
||||||
|
avatarUrl?: string;
|
||||||
|
annotations?: { [key : string]: Annotation };
|
||||||
|
key: string;
|
||||||
|
eventIdKey: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
function encodeKey(roomId: string, fragmentId: number, eventIndex: number): string {
|
||||||
return `${roomId}|${encodeUint32(fragmentId)}|${encodeUint32(eventIndex)}`;
|
return `${roomId}|${encodeUint32(fragmentId)}|${encodeUint32(eventIndex)}`;
|
||||||
}
|
}
|
||||||
|
|
||||||
function encodeEventIdKey(roomId, eventId) {
|
function encodeEventIdKey(roomId: string, eventId: string): string {
|
||||||
return `${roomId}|${eventId}`;
|
return `${roomId}|${eventId}`;
|
||||||
}
|
}
|
||||||
|
|
||||||
function decodeEventIdKey(eventIdKey) {
|
function decodeEventIdKey(eventIdKey: string): { roomId: string, eventId: string } {
|
||||||
const [roomId, eventId] = eventIdKey.split("|");
|
const [roomId, eventId] = eventIdKey.split("|");
|
||||||
return {roomId, eventId};
|
return {roomId, eventId};
|
||||||
}
|
}
|
||||||
|
|
||||||
class Range {
|
class Range {
|
||||||
constructor(IDBKeyRange, only, lower, upper, lowerOpen, upperOpen) {
|
private _IDBKeyRange: typeof IDBKeyRange;
|
||||||
this._IDBKeyRange = IDBKeyRange;
|
private _only?: EventKey;
|
||||||
|
private _lower?: EventKey;
|
||||||
|
private _upper?: EventKey;
|
||||||
|
private _lowerOpen: boolean;
|
||||||
|
private _upperOpen: boolean;
|
||||||
|
|
||||||
|
constructor(_IDBKeyRange: any, only?: EventKey, lower?: EventKey, upper?: EventKey, lowerOpen: boolean = false, upperOpen: boolean = false) {
|
||||||
|
this._IDBKeyRange = _IDBKeyRange;
|
||||||
this._only = only;
|
this._only = only;
|
||||||
this._lower = lower;
|
this._lower = lower;
|
||||||
this._upper = upper;
|
this._upper = upper;
|
||||||
|
@ -42,7 +69,7 @@ class Range {
|
||||||
this._upperOpen = upperOpen;
|
this._upperOpen = upperOpen;
|
||||||
}
|
}
|
||||||
|
|
||||||
asIDBKeyRange(roomId) {
|
asIDBKeyRange(roomId: string): IDBKeyRange | undefined {
|
||||||
try {
|
try {
|
||||||
// only
|
// only
|
||||||
if (this._only) {
|
if (this._only) {
|
||||||
|
@ -99,66 +126,68 @@ class Range {
|
||||||
* @property {?Gap} gap if a gap entry, the gap
|
* @property {?Gap} gap if a gap entry, the gap
|
||||||
*/
|
*/
|
||||||
export class TimelineEventStore {
|
export class TimelineEventStore {
|
||||||
constructor(timelineStore) {
|
private _timelineStore: Store<StorageEntry>;
|
||||||
|
|
||||||
|
constructor(timelineStore: Store<StorageEntry>) {
|
||||||
this._timelineStore = timelineStore;
|
this._timelineStore = timelineStore;
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Creates a range that only includes the given key
|
/** Creates a range that only includes the given key
|
||||||
* @param {EventKey} eventKey the key
|
* @param eventKey the key
|
||||||
* @return {Range} the created range
|
* @return the created range
|
||||||
*/
|
*/
|
||||||
onlyRange(eventKey) {
|
onlyRange(eventKey: EventKey): Range {
|
||||||
return new Range(this._timelineStore.IDBKeyRange, eventKey);
|
return new Range(this._timelineStore.IDBKeyRange, eventKey);
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Creates a range that includes all keys before eventKey, and optionally also the key itself.
|
/** Creates a range that includes all keys before eventKey, and optionally also the key itself.
|
||||||
* @param {EventKey} eventKey the key
|
* @param eventKey the key
|
||||||
* @param {boolean} [open=false] whether the key is included (false) or excluded (true) from the range at the upper end.
|
* @param [open=false] whether the key is included (false) or excluded (true) from the range at the upper end.
|
||||||
* @return {Range} the created range
|
* @return the created range
|
||||||
*/
|
*/
|
||||||
upperBoundRange(eventKey, open=false) {
|
upperBoundRange(eventKey: EventKey, open=false): Range {
|
||||||
return new Range(this._timelineStore.IDBKeyRange, undefined, undefined, eventKey, undefined, open);
|
return new Range(this._timelineStore.IDBKeyRange, undefined, undefined, eventKey, undefined, open);
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Creates a range that includes all keys after eventKey, and optionally also the key itself.
|
/** Creates a range that includes all keys after eventKey, and optionally also the key itself.
|
||||||
* @param {EventKey} eventKey the key
|
* @param eventKey the key
|
||||||
* @param {boolean} [open=false] whether the key is included (false) or excluded (true) from the range at the lower end.
|
* @param [open=false] whether the key is included (false) or excluded (true) from the range at the lower end.
|
||||||
* @return {Range} the created range
|
* @return the created range
|
||||||
*/
|
*/
|
||||||
lowerBoundRange(eventKey, open=false) {
|
lowerBoundRange(eventKey: EventKey, open=false): Range {
|
||||||
return new Range(this._timelineStore.IDBKeyRange, undefined, eventKey, undefined, open);
|
return new Range(this._timelineStore.IDBKeyRange, undefined, eventKey, undefined, open);
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Creates a range that includes all keys between `lower` and `upper`, and optionally the given keys as well.
|
/** Creates a range that includes all keys between `lower` and `upper`, and optionally the given keys as well.
|
||||||
* @param {EventKey} lower the lower key
|
* @param lower the lower key
|
||||||
* @param {EventKey} upper the upper key
|
* @param upper the upper key
|
||||||
* @param {boolean} [lowerOpen=false] whether the lower key is included (false) or excluded (true) from the range.
|
* @param [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.
|
* @param [upperOpen=false] whether the upper key is included (false) or excluded (true) from the range.
|
||||||
* @return {Range} the created range
|
* @return the created range
|
||||||
*/
|
*/
|
||||||
boundRange(lower, upper, lowerOpen=false, upperOpen=false) {
|
boundRange(lower: EventKey, upper: EventKey, lowerOpen=false, upperOpen=false): Range {
|
||||||
return new Range(this._timelineStore.IDBKeyRange, undefined, lower, upper, lowerOpen, upperOpen);
|
return new Range(this._timelineStore.IDBKeyRange, undefined, lower, upper, lowerOpen, upperOpen);
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Looks up the last `amount` entries in the timeline for `roomId`.
|
/** Looks up the last `amount` entries in the timeline for `roomId`.
|
||||||
* @param {string} roomId
|
* @param roomId
|
||||||
* @param {number} fragmentId
|
* @param fragmentId
|
||||||
* @param {number} amount
|
* @param amount
|
||||||
* @return {Promise<Entry[]>} a promise resolving to an array with 0 or more entries, in ascending order.
|
* @return a promise resolving to an array with 0 or more entries, in ascending order.
|
||||||
*/
|
*/
|
||||||
async lastEvents(roomId, fragmentId, amount) {
|
async lastEvents(roomId: string, fragmentId: number, amount: number): Promise<StorageEntry[]> {
|
||||||
const eventKey = EventKey.maxKey;
|
const eventKey = EventKey.maxKey;
|
||||||
eventKey.fragmentId = fragmentId;
|
eventKey.fragmentId = fragmentId;
|
||||||
return this.eventsBefore(roomId, eventKey, amount);
|
return this.eventsBefore(roomId, eventKey, amount);
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Looks up the first `amount` entries in the timeline for `roomId`.
|
/** Looks up the first `amount` entries in the timeline for `roomId`.
|
||||||
* @param {string} roomId
|
* @param roomId
|
||||||
* @param {number} fragmentId
|
* @param fragmentId
|
||||||
* @param {number} amount
|
* @param amount
|
||||||
* @return {Promise<Entry[]>} a promise resolving to an array with 0 or more entries, in ascending order.
|
* @return a promise resolving to an array with 0 or more entries, in ascending order.
|
||||||
*/
|
*/
|
||||||
async firstEvents(roomId, fragmentId, amount) {
|
async firstEvents(roomId: string, fragmentId: number, amount: number): Promise<StorageEntry[]> {
|
||||||
const eventKey = EventKey.minKey;
|
const eventKey = EventKey.minKey;
|
||||||
eventKey.fragmentId = fragmentId;
|
eventKey.fragmentId = fragmentId;
|
||||||
return this.eventsAfter(roomId, eventKey, amount);
|
return this.eventsAfter(roomId, eventKey, amount);
|
||||||
|
@ -166,24 +195,24 @@ export class TimelineEventStore {
|
||||||
|
|
||||||
/** Looks up `amount` entries after `eventKey` in the timeline for `roomId` within the same fragment.
|
/** Looks up `amount` entries after `eventKey` in the timeline for `roomId` within the same fragment.
|
||||||
* The entry for `eventKey` is not included.
|
* The entry for `eventKey` is not included.
|
||||||
* @param {string} roomId
|
* @param roomId
|
||||||
* @param {EventKey} eventKey
|
* @param eventKey
|
||||||
* @param {number} amount
|
* @param amount
|
||||||
* @return {Promise<Entry[]>} a promise resolving to an array with 0 or more entries, in ascending order.
|
* @return a promise resolving to an array with 0 or more entries, in ascending order.
|
||||||
*/
|
*/
|
||||||
eventsAfter(roomId, eventKey, amount) {
|
eventsAfter(roomId: string, eventKey: EventKey, amount: number): Promise<StorageEntry[]> {
|
||||||
const idbRange = this.lowerBoundRange(eventKey, true).asIDBKeyRange(roomId);
|
const idbRange = this.lowerBoundRange(eventKey, true).asIDBKeyRange(roomId);
|
||||||
return this._timelineStore.selectLimit(idbRange, amount);
|
return this._timelineStore.selectLimit(idbRange, amount);
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Looks up `amount` entries before `eventKey` in the timeline for `roomId` within the same fragment.
|
/** Looks up `amount` entries before `eventKey` in the timeline for `roomId` within the same fragment.
|
||||||
* The entry for `eventKey` is not included.
|
* The entry for `eventKey` is not included.
|
||||||
* @param {string} roomId
|
* @param roomId
|
||||||
* @param {EventKey} eventKey
|
* @param eventKey
|
||||||
* @param {number} amount
|
* @param amount
|
||||||
* @return {Promise<Entry[]>} a promise resolving to an array with 0 or more entries, in ascending order.
|
* @return a promise resolving to an array with 0 or more entries, in ascending order.
|
||||||
*/
|
*/
|
||||||
async eventsBefore(roomId, eventKey, amount) {
|
async eventsBefore(roomId: string, eventKey: EventKey, amount: number): Promise<StorageEntry[]> {
|
||||||
const range = this.upperBoundRange(eventKey, true).asIDBKeyRange(roomId);
|
const range = this.upperBoundRange(eventKey, true).asIDBKeyRange(roomId);
|
||||||
const events = await this._timelineStore.selectLimitReverse(range, amount);
|
const events = await this._timelineStore.selectLimitReverse(range, amount);
|
||||||
events.reverse(); // because we fetched them backwards
|
events.reverse(); // because we fetched them backwards
|
||||||
|
@ -195,23 +224,23 @@ export class TimelineEventStore {
|
||||||
*
|
*
|
||||||
* The order in which results are returned might be different than `eventIds`.
|
* The order in which results are returned might be different than `eventIds`.
|
||||||
* Call the return value to obtain the next {id, event} pair.
|
* Call the return value to obtain the next {id, event} pair.
|
||||||
* @param {string} roomId
|
* @param roomId
|
||||||
* @param {string[]} eventIds
|
* @param eventIds
|
||||||
* @return {Function<Promise>}
|
* @return
|
||||||
*/
|
*/
|
||||||
// performance comment from above refers to the fact that there *might*
|
// performance comment from above refers to the fact that there *might*
|
||||||
// be a correlation between event_id sorting order and chronology.
|
// be a correlation between event_id sorting order and chronology.
|
||||||
// In that case we could avoid running over all eventIds, as the reported order by findExistingKeys
|
// In that case we could avoid running over all eventIds, as the reported order by findExistingKeys
|
||||||
// would match the order of eventIds. That's why findLast is also passed as backwards to keysExist.
|
// would match the order of eventIds. That's why findLast is also passed as backwards to keysExist.
|
||||||
// also passing them in chronological order makes sense as that's how we'll receive them almost always.
|
// also passing them in chronological order makes sense as that's how we'll receive them almost always.
|
||||||
async findFirstOccurringEventId(roomId, eventIds) {
|
async findFirstOccurringEventId(roomId: string, eventIds: string[]): Promise<string | undefined> {
|
||||||
const byEventId = this._timelineStore.index("byEventId");
|
const byEventId = this._timelineStore.index("byEventId");
|
||||||
const keys = eventIds.map(eventId => encodeEventIdKey(roomId, eventId));
|
const keys = eventIds.map(eventId => encodeEventIdKey(roomId, eventId));
|
||||||
const results = new Array(keys.length);
|
const results = new Array(keys.length);
|
||||||
let firstFoundKey;
|
let firstFoundKey: string | undefined;
|
||||||
|
|
||||||
// find first result that is found and has no undefined results before it
|
// find first result that is found and has no undefined results before it
|
||||||
function firstFoundAndPrecedingResolved() {
|
function firstFoundAndPrecedingResolved(): string | undefined {
|
||||||
for(let i = 0; i < results.length; ++i) {
|
for(let i = 0; i < results.length; ++i) {
|
||||||
if (results[i] === undefined) {
|
if (results[i] === undefined) {
|
||||||
return;
|
return;
|
||||||
|
@ -222,7 +251,8 @@ export class TimelineEventStore {
|
||||||
}
|
}
|
||||||
|
|
||||||
await byEventId.findExistingKeys(keys, false, (key, found) => {
|
await byEventId.findExistingKeys(keys, false, (key, found) => {
|
||||||
const index = keys.indexOf(key);
|
// T[].search(T, number), but we want T[].search(R, number), so cast
|
||||||
|
const index = (keys as IDBValidKey[]).indexOf(key);
|
||||||
results[index] = found;
|
results[index] = found;
|
||||||
firstFoundKey = firstFoundAndPrecedingResolved();
|
firstFoundKey = firstFoundAndPrecedingResolved();
|
||||||
return !!firstFoundKey;
|
return !!firstFoundKey;
|
||||||
|
@ -231,11 +261,11 @@ export class TimelineEventStore {
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Inserts a new entry into the store. The combination of roomId and eventKey should not exist yet, or an error is thrown.
|
/** Inserts a new entry into the store. The combination of roomId and eventKey should not exist yet, or an error is thrown.
|
||||||
* @param {Entry} entry the entry to insert
|
* @param entry the entry to insert
|
||||||
* @return {Promise<>} a promise resolving to undefined if the operation was successful, or a StorageError if not.
|
* @return nothing. To wait for the operation to finish, await the transaction it's part of.
|
||||||
* @throws {StorageError} ...
|
* @throws {StorageError} ...
|
||||||
*/
|
*/
|
||||||
insert(entry) {
|
insert(entry: StorageEntry): void {
|
||||||
entry.key = encodeKey(entry.roomId, entry.fragmentId, entry.eventIndex);
|
entry.key = encodeKey(entry.roomId, entry.fragmentId, entry.eventIndex);
|
||||||
entry.eventIdKey = encodeEventIdKey(entry.roomId, entry.event.event_id);
|
entry.eventIdKey = encodeEventIdKey(entry.roomId, entry.event.event_id);
|
||||||
// TODO: map error? or in idb/store?
|
// TODO: map error? or in idb/store?
|
||||||
|
@ -244,22 +274,22 @@ export class TimelineEventStore {
|
||||||
|
|
||||||
/** Updates the entry into the store with the given [roomId, eventKey] combination.
|
/** Updates the entry into the store with the given [roomId, eventKey] combination.
|
||||||
* If not yet present, will insert. Might be slower than add.
|
* If not yet present, will insert. Might be slower than add.
|
||||||
* @param {Entry} entry the entry to update.
|
* @param entry the entry to update.
|
||||||
* @return {Promise<>} a promise resolving to undefined if the operation was successful, or a StorageError if not.
|
* @return nothing. To wait for the operation to finish, await the transaction it's part of.
|
||||||
*/
|
*/
|
||||||
update(entry) {
|
update(entry: StorageEntry): void {
|
||||||
this._timelineStore.put(entry);
|
this._timelineStore.put(entry);
|
||||||
}
|
}
|
||||||
|
|
||||||
get(roomId, eventKey) {
|
get(roomId: string, eventKey: EventKey): Promise<StorageEntry | null> {
|
||||||
return this._timelineStore.get(encodeKey(roomId, eventKey.fragmentId, eventKey.eventIndex));
|
return this._timelineStore.get(encodeKey(roomId, eventKey.fragmentId, eventKey.eventIndex));
|
||||||
}
|
}
|
||||||
|
|
||||||
getByEventId(roomId, eventId) {
|
getByEventId(roomId: string, eventId: string): Promise<StorageEntry | null> {
|
||||||
return this._timelineStore.index("byEventId").get(encodeEventIdKey(roomId, eventId));
|
return this._timelineStore.index("byEventId").get(encodeEventIdKey(roomId, eventId));
|
||||||
}
|
}
|
||||||
|
|
||||||
removeAllForRoom(roomId) {
|
removeAllForRoom(roomId: string): void {
|
||||||
const minKey = encodeKey(roomId, KeyLimits.minStorageKey, KeyLimits.minStorageKey);
|
const minKey = encodeKey(roomId, KeyLimits.minStorageKey, KeyLimits.minStorageKey);
|
||||||
const maxKey = encodeKey(roomId, KeyLimits.maxStorageKey, KeyLimits.maxStorageKey);
|
const maxKey = encodeKey(roomId, KeyLimits.maxStorageKey, KeyLimits.maxStorageKey);
|
||||||
const range = this._timelineStore.IDBKeyRange.bound(minKey, maxKey);
|
const range = this._timelineStore.IDBKeyRange.bound(minKey, maxKey);
|
|
@ -13,31 +13,41 @@ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
See the License for the specific language governing permissions and
|
See the License for the specific language governing permissions and
|
||||||
limitations under the License.
|
limitations under the License.
|
||||||
*/
|
*/
|
||||||
import {MIN_UNICODE, MAX_UNICODE} from "./common.js";
|
import {MIN_UNICODE, MAX_UNICODE} from "./common";
|
||||||
|
import {Store} from "../Store";
|
||||||
|
|
||||||
function encodeKey(roomId, targetEventId, relType, sourceEventId) {
|
function encodeKey(roomId: string, targetEventId: string, relType: string, sourceEventId: string): string {
|
||||||
return `${roomId}|${targetEventId}|${relType}|${sourceEventId}`;
|
return `${roomId}|${targetEventId}|${relType}|${sourceEventId}`;
|
||||||
}
|
}
|
||||||
|
|
||||||
function decodeKey(key) {
|
interface RelationEntry {
|
||||||
|
roomId: string;
|
||||||
|
targetEventId: string;
|
||||||
|
sourceEventId: string;
|
||||||
|
relType: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
function decodeKey(key: string): RelationEntry {
|
||||||
const [roomId, targetEventId, relType, sourceEventId] = key.split("|");
|
const [roomId, targetEventId, relType, sourceEventId] = key.split("|");
|
||||||
return {roomId, targetEventId, relType, sourceEventId};
|
return {roomId, targetEventId, relType, sourceEventId};
|
||||||
}
|
}
|
||||||
|
|
||||||
export class TimelineRelationStore {
|
export class TimelineRelationStore {
|
||||||
constructor(store) {
|
private _store: Store<{ key: string }>;
|
||||||
|
|
||||||
|
constructor(store: Store<{ key: string }>) {
|
||||||
this._store = store;
|
this._store = store;
|
||||||
}
|
}
|
||||||
|
|
||||||
add(roomId, targetEventId, relType, sourceEventId) {
|
add(roomId: string, targetEventId: string, relType: string, sourceEventId: string): void {
|
||||||
return this._store.add({key: encodeKey(roomId, targetEventId, relType, sourceEventId)});
|
this._store.add({key: encodeKey(roomId, targetEventId, relType, sourceEventId)});
|
||||||
}
|
}
|
||||||
|
|
||||||
remove(roomId, targetEventId, relType, sourceEventId) {
|
remove(roomId: string, targetEventId: string, relType: string, sourceEventId: string): Promise<undefined> {
|
||||||
return this._store.delete(encodeKey(roomId, targetEventId, relType, sourceEventId));
|
return this._store.delete(encodeKey(roomId, targetEventId, relType, sourceEventId));
|
||||||
}
|
}
|
||||||
|
|
||||||
removeAllForTarget(roomId, targetId) {
|
removeAllForTarget(roomId: string, targetId: string): Promise<undefined> {
|
||||||
const range = this._store.IDBKeyRange.bound(
|
const range = this._store.IDBKeyRange.bound(
|
||||||
encodeKey(roomId, targetId, MIN_UNICODE, MIN_UNICODE),
|
encodeKey(roomId, targetId, MIN_UNICODE, MIN_UNICODE),
|
||||||
encodeKey(roomId, targetId, MAX_UNICODE, MAX_UNICODE),
|
encodeKey(roomId, targetId, MAX_UNICODE, MAX_UNICODE),
|
||||||
|
@ -47,7 +57,7 @@ export class TimelineRelationStore {
|
||||||
return this._store.delete(range);
|
return this._store.delete(range);
|
||||||
}
|
}
|
||||||
|
|
||||||
async getForTargetAndType(roomId, targetId, relType) {
|
async getForTargetAndType(roomId: string, targetId: string, relType: string): Promise<RelationEntry[]> {
|
||||||
// exclude both keys as they are theoretical min and max,
|
// exclude both keys as they are theoretical min and max,
|
||||||
// but we should't have a match for just the room id, or room id with max
|
// but we should't have a match for just the room id, or room id with max
|
||||||
const range = this._store.IDBKeyRange.bound(
|
const range = this._store.IDBKeyRange.bound(
|
||||||
|
@ -60,7 +70,7 @@ export class TimelineRelationStore {
|
||||||
return items.map(i => decodeKey(i.key));
|
return items.map(i => decodeKey(i.key));
|
||||||
}
|
}
|
||||||
|
|
||||||
async getAllForTarget(roomId, targetId) {
|
async getAllForTarget(roomId: string, targetId: string): Promise<RelationEntry[]> {
|
||||||
// exclude both keys as they are theoretical min and max,
|
// exclude both keys as they are theoretical min and max,
|
||||||
// but we should't have a match for just the room id, or room id with max
|
// but we should't have a match for just the room id, or room id with max
|
||||||
const range = this._store.IDBKeyRange.bound(
|
const range = this._store.IDBKeyRange.bound(
|
|
@ -14,20 +14,15 @@ See the License for the specific language governing permissions and
|
||||||
limitations under the License.
|
limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
export class InviteStore {
|
export type Content = { [key: string]: any }
|
||||||
constructor(inviteStore) {
|
|
||||||
this._inviteStore = inviteStore;
|
export interface TimelineEvent {
|
||||||
|
content: Content;
|
||||||
|
type: string;
|
||||||
|
event_id: string;
|
||||||
|
sender: string;
|
||||||
|
origin_server_ts: number;
|
||||||
|
unsigned?: Content;
|
||||||
}
|
}
|
||||||
|
|
||||||
getAll() {
|
export type StateEvent = TimelineEvent & { prev_content?: Content, state_key: string }
|
||||||
return this._inviteStore.selectAll();
|
|
||||||
}
|
|
||||||
|
|
||||||
set(invite) {
|
|
||||||
return this._inviteStore.put(invite);
|
|
||||||
}
|
|
||||||
|
|
||||||
remove(roomId) {
|
|
||||||
this._inviteStore.delete(roomId);
|
|
||||||
}
|
|
||||||
}
|
|
Reference in a new issue