This repository has been archived on 2022-08-19. You can view files and clone it, but cannot push or open issues or pull requests.
hydrogen-web/src/matrix/storage/idb/QueryTarget.ts

251 lines
9.6 KiB
TypeScript
Raw Normal View History

2020-08-05 22:08:55 +05:30
/*
Copyright 2020 Bruno Windels <bruno@windels.cloud>
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.
*/
2021-08-10 22:28:33 +05:30
import {iterateCursor, DONE, NOT_DONE, reqAsPromise} from "./utils";
2021-08-10 22:28:33 +05:30
type Reducer<A,B> = (acc: B, val: A) => B
export type IDBQuery = IDBValidKey | IDBKeyRange | undefined | null
interface QueryTargetInterface<T> {
2021-08-20 05:14:50 +05:30
openCursor(range?: IDBQuery, direction?: IDBCursorDirection | undefined): IDBRequest<IDBCursorWithValue | null>;
openKeyCursor(range?: IDBQuery, direction?: IDBCursorDirection | undefined): IDBRequest<IDBCursor | null>;
supports(method: string): boolean;
2021-08-10 22:28:33 +05:30
keyPath: string | string[];
2021-08-20 05:14:50 +05:30
get(key: IDBValidKey | IDBKeyRange): IDBRequest<T | null>;
getKey(key: IDBValidKey | IDBKeyRange): IDBRequest<IDBValidKey | undefined>;
2021-08-10 22:28:33 +05:30
}
export class QueryTarget<T> {
protected _target: QueryTargetInterface<T>;
constructor(target: QueryTargetInterface<T>) {
2019-06-27 02:01:36 +05:30
this._target = target;
}
2019-02-07 05:50:27 +05:30
_openCursor(range?: IDBQuery, direction?: IDBCursorDirection): IDBRequest<IDBCursorWithValue | null> {
if (range && direction) {
return this._target.openCursor(range, direction);
} else if (range) {
return this._target.openCursor(range);
} else if (direction) {
return this._target.openCursor(null, direction);
} else {
return this._target.openCursor();
}
}
2021-08-10 22:28:33 +05:30
supports(methodName: string): boolean {
return this._target.supports(methodName);
}
2021-08-10 22:28:33 +05:30
get(key: IDBValidKey | IDBKeyRange): Promise<T | null> {
return reqAsPromise(this._target.get(key));
}
2021-08-10 22:28:33 +05:30
getKey(key: IDBValidKey | IDBKeyRange): Promise<IDBValidKey | undefined> {
2020-09-09 16:12:26 +05:30
if (this._target.supports("getKey")) {
return reqAsPromise(this._target.getKey(key));
} else {
return reqAsPromise(this._target.get(key)).then(value => {
if (value) {
2021-08-10 22:28:33 +05:30
let keyPath = this._target.keyPath;
if (typeof keyPath === "string") {
keyPath = [keyPath];
}
return keyPath.reduce((obj, key) => obj[key], value);
2020-09-09 16:12:26 +05:30
}
});
}
}
2021-08-10 22:28:33 +05:30
reduce<B>(range: IDBQuery, reducer: Reducer<T,B>, initialValue: B): Promise<boolean> {
2019-06-27 02:01:36 +05:30
return this._reduce(range, reducer, initialValue, "next");
}
2021-08-10 22:28:33 +05:30
reduceReverse<B>(range: IDBQuery, reducer: Reducer<T,B>, initialValue: B): Promise<boolean> {
2019-06-27 02:01:36 +05:30
return this._reduce(range, reducer, initialValue, "prev");
}
2021-08-10 22:28:33 +05:30
selectLimit(range: IDBQuery, amount: number): Promise<T[]> {
2019-06-27 02:01:36 +05:30
return this._selectLimit(range, amount, "next");
}
2021-08-10 22:28:33 +05:30
selectLimitReverse(range: IDBQuery, amount: number): Promise<T[]> {
2019-06-27 02:01:36 +05:30
return this._selectLimit(range, amount, "prev");
}
2021-08-10 22:28:33 +05:30
selectWhile(range: IDBQuery, predicate: (v: T) => boolean): Promise<T[]> {
return this._selectWhile(range, predicate, "next");
2019-06-27 02:01:36 +05:30
}
2021-08-10 22:28:33 +05:30
selectWhileReverse(range: IDBQuery, predicate: (v: T) => boolean): Promise<T[]> {
return this._selectWhile(range, predicate, "prev");
2019-06-27 02:01:36 +05:30
}
2021-08-10 22:28:33 +05:30
async selectAll(range?: IDBQuery, direction?: IDBCursorDirection): Promise<T[]> {
2019-06-27 02:01:36 +05:30
const cursor = this._openCursor(range, direction);
2021-08-10 22:28:33 +05:30
const results: T[] = [];
await iterateCursor<T>(cursor, (value) => {
2019-06-27 02:01:36 +05:30
results.push(value);
2021-08-10 22:28:33 +05:30
return NOT_DONE;
2019-06-27 02:01:36 +05:30
});
return results;
}
2021-08-10 22:28:33 +05:30
selectFirst(range: IDBQuery): Promise<T | undefined> {
2019-06-27 02:01:36 +05:30
return this._find(range, () => true, "next");
}
2021-08-10 22:28:33 +05:30
selectLast(range: IDBQuery): Promise<T | undefined> {
2019-06-27 02:01:36 +05:30
return this._find(range, () => true, "prev");
}
2021-08-10 22:28:33 +05:30
find(range: IDBQuery, predicate: (v: T) => boolean): Promise<T | undefined> {
2019-06-27 02:01:36 +05:30
return this._find(range, predicate, "next");
}
2021-08-10 22:28:33 +05:30
findReverse(range: IDBQuery, predicate: (v : T) => boolean): Promise<T | undefined> {
2019-06-27 02:01:36 +05:30
return this._find(range, predicate, "prev");
}
2021-08-10 22:28:33 +05:30
async findMaxKey(range: IDBQuery): Promise<IDBValidKey | undefined> {
2019-07-01 13:30:29 +05:30
const cursor = this._target.openKeyCursor(range, "prev");
let maxKey;
await iterateCursor(cursor, (_, key) => {
maxKey = key;
2021-08-10 22:28:33 +05:30
return DONE;
2019-07-01 13:30:29 +05:30
});
return maxKey;
}
async iterateValues(range: IDBQuery, callback: (val: T, key: IDBValidKey, cur: IDBCursorWithValue) => boolean): Promise<void> {
const cursor = this._target.openCursor(range, "next");
2021-08-10 22:28:33 +05:30
await iterateCursor<T>(cursor, (value, key, cur) => {
return {done: callback(value, key, cur)};
});
}
async iterateKeys(range: IDBQuery, callback: (key: IDBValidKey, cur: IDBCursor) => boolean): Promise<void> {
const cursor = this._target.openKeyCursor(range, "next");
await iterateCursor(cursor, (_, key, cur) => {
return {done: callback(key, cur)};
});
}
/**
* Checks if a given set of keys exist.
* Calls `callback(key, found)` for each key in `keys`, in key sorting order (or reversed if backwards=true).
* If the callback returns true, the search is halted and callback won't be called again.
* `callback` is called with the same instances of the key as given in `keys`, so direct comparison can be used.
*/
async findExistingKeys(keys: IDBValidKey[], backwards: boolean, callback: (key: IDBValidKey, found: boolean) => boolean): Promise<void> {
const direction = backwards ? "prev" : "next";
const compareKeys = (a, b) => backwards ? -indexedDB.cmp(a, b) : indexedDB.cmp(a, b);
const sortedKeys = keys.slice().sort(compareKeys);
const firstKey = backwards ? sortedKeys[sortedKeys.length - 1] : sortedKeys[0];
const lastKey = backwards ? sortedKeys[0] : sortedKeys[sortedKeys.length - 1];
const cursor = this._target.openKeyCursor(IDBKeyRange.bound(firstKey, lastKey), direction);
let i = 0;
let consumerDone = false;
await iterateCursor(cursor, (value, key) => {
// while key is larger than next key, advance and report false
while(i < sortedKeys.length && compareKeys(sortedKeys[i], key) < 0 && !consumerDone) {
consumerDone = callback(sortedKeys[i], false);
++i;
}
if (i < sortedKeys.length && compareKeys(sortedKeys[i], key) === 0 && !consumerDone) {
consumerDone = callback(sortedKeys[i], true);
++i;
}
const done = consumerDone || i >= sortedKeys.length;
2021-08-10 22:28:33 +05:30
let jumpTo;
if (!done) {
jumpTo = sortedKeys[i];
}
return {done, jumpTo};
});
// report null for keys we didn't to at the end
while (!consumerDone && i < sortedKeys.length) {
consumerDone = callback(sortedKeys[i], false);
++i;
}
}
2021-08-10 22:28:33 +05:30
_reduce<B>(range: IDBQuery, reducer: (reduced: B, value: T) => B, initialValue: B, direction: IDBCursorDirection): Promise<boolean> {
2019-06-27 02:01:36 +05:30
let reducedValue = initialValue;
const cursor = this._openCursor(range, direction);
2021-08-10 22:28:33 +05:30
return iterateCursor<T>(cursor, (value) => {
2019-06-27 02:01:36 +05:30
reducedValue = reducer(reducedValue, value);
2021-08-10 22:28:33 +05:30
return NOT_DONE;
2019-06-27 02:01:36 +05:30
});
}
2021-08-10 22:28:33 +05:30
_selectLimit(range: IDBQuery, amount: number, direction: IDBCursorDirection): Promise<T[]> {
return this._selectUntil(range, (results) => {
2019-06-27 02:01:36 +05:30
return results.length === amount;
}, direction);
2019-06-27 02:01:36 +05:30
}
2021-08-10 22:28:33 +05:30
async _selectUntil(range: IDBQuery, predicate: (vs: T[], v: T) => boolean, direction: IDBCursorDirection): Promise<T[]> {
2019-06-27 02:01:36 +05:30
const cursor = this._openCursor(range, direction);
2021-08-10 22:28:33 +05:30
const results: T[] = [];
await iterateCursor<T>(cursor, (value) => {
results.push(value);
return {done: predicate(results, value)};
});
return results;
}
// allows you to fetch one too much that won't get added when the predicate fails
2021-08-10 22:28:33 +05:30
async _selectWhile(range: IDBQuery, predicate: (v: T) => boolean, direction: IDBCursorDirection): Promise<T[]> {
const cursor = this._openCursor(range, direction);
2021-08-10 22:28:33 +05:30
const results: T[] = [];
await iterateCursor<T>(cursor, (value) => {
const passesPredicate = predicate(value);
if (passesPredicate) {
2020-08-19 19:43:30 +05:30
results.push(value);
}
return {done: !passesPredicate};
2019-06-27 02:01:36 +05:30
});
return results;
}
async iterateWhile(range: IDBQuery, predicate: (v: T) => boolean): Promise<void> {
const cursor = this._openCursor(range, "next");
2021-08-10 22:28:33 +05:30
await iterateCursor<T>(cursor, (value) => {
const passesPredicate = predicate(value);
return {done: !passesPredicate};
});
}
2021-08-10 22:28:33 +05:30
async _find(range: IDBQuery, predicate: (v: T) => boolean, direction: IDBCursorDirection): Promise<T | undefined> {
2019-06-27 02:01:36 +05:30
const cursor = this._openCursor(range, direction);
let result;
2021-08-10 22:28:33 +05:30
const found = await iterateCursor<T>(cursor, (value) => {
2019-06-27 02:01:36 +05:30
const found = predicate(value);
if (found) {
result = value;
}
return {done: found};
});
if (found) {
return result;
}
}
2019-03-22 02:06:02 +05:30
}