Start migrating utils.js to TypeScript

This commit is contained in:
Danila Fedorin 2021-08-09 13:56:20 -07:00
parent 5579c018d1
commit cd9fe360a4
12 changed files with 55 additions and 33 deletions

View file

@ -14,7 +14,7 @@ See the License for the specific language governing permissions and
limitations under the License.
*/
import {iterateCursor, reqAsPromise} from "./utils.js";
import {iterateCursor, reqAsPromise} from "./utils";
export class QueryTarget {
constructor(target) {

View file

@ -16,7 +16,7 @@ limitations under the License.
import {Transaction} from "./Transaction.js";
import { STORE_NAMES, StorageError } from "../common";
import { reqAsPromise } from "./utils.js";
import { reqAsPromise } from "./utils";
const WEBKITEARLYCLOSETXNBUG_BOGUS_KEY = "782rh281re38-boguskey";

View file

@ -15,7 +15,7 @@ limitations under the License.
*/
import {Storage} from "./Storage.js";
import { openDatabase, reqAsPromise } from "./utils.js";
import { openDatabase, reqAsPromise } from "./utils";
import { exportSession, importSession } from "./export.js";
import { schema } from "./schema.js";
import { detectWebkitEarlyCloseTxnBug } from "./quirks.js";

View file

@ -14,7 +14,7 @@ See the License for the specific language governing permissions and
limitations under the License.
*/
import {txnAsPromise} from "./utils.js";
import {txnAsPromise} from "./utils";
import {StorageError} from "../common";
import {Store} from "./Store.js";
import {SessionStore} from "./stores/SessionStore.js";

View file

@ -14,7 +14,7 @@ See the License for the specific language governing permissions and
limitations under the License.
*/
import { iterateCursor, txnAsPromise } from "./utils.js";
import { iterateCursor, txnAsPromise } from "./utils";
import { STORE_NAMES } from "../common";
export async function exportSession(db) {

View file

@ -15,7 +15,7 @@ limitations under the License.
*/
import {openDatabase, txnAsPromise, reqAsPromise} from "./utils.js";
import {openDatabase, txnAsPromise, reqAsPromise} from "./utils";
// filed as https://bugs.webkit.org/show_bug.cgi?id=222746
export async function detectWebkitEarlyCloseTxnBug(idbFactory) {

View file

@ -1,4 +1,4 @@
import {iterateCursor, reqAsPromise} from "./utils.js";
import {iterateCursor, reqAsPromise} from "./utils";
import {RoomMember, EVENT_TYPE as MEMBER_EVENT_TYPE} from "../../room/members/RoomMember.js";
import {RoomMemberStore} from "./stores/RoomMemberStore.js";
import {SessionStore} from "./stores/SessionStore.js";

View file

@ -14,7 +14,7 @@ See the License for the specific language governing permissions and
limitations under the License.
*/
import { encodeUint32, decodeUint32 } from "../utils.js";
import { encodeUint32, decodeUint32 } from "../utils";
import {KeyLimits} from "../../common";
function encodeKey(roomId, queueIndex) {

View file

@ -16,7 +16,7 @@ limitations under the License.
import {EventKey} from "../../../room/timeline/EventKey.js";
import { StorageError } from "../../common";
import { encodeUint32 } from "../utils.js";
import { encodeUint32 } from "../utils";
import {KeyLimits} from "../../common";
function encodeKey(roomId, fragmentId, eventIndex) {

View file

@ -16,7 +16,7 @@ limitations under the License.
import { StorageError } from "../../common";
import {KeyLimits} from "../../common";
import { encodeUint32 } from "../utils.js";
import { encodeUint32 } from "../utils";
function encodeKey(roomId, fragmentId) {
return `${roomId}|${encodeUint32(fragmentId)}`;

View file

@ -20,12 +20,15 @@ import { StorageError } from "../common";
let needsSyncPromise = false;
export const DONE = { done: true }
export const NOT_DONE = { done: false }
/* should be called on legacy platforms to see
if transactions close before draining the microtask queue (IE11 on Windows 7).
If this is the case, promises need to be resolved
synchronously from the idb request handler to prevent the transaction from closing prematurely.
*/
export async function checkNeedsSyncPromise() {
export async function checkNeedsSyncPromise(): Promise<boolean> {
// important to have it turned off while doing the test,
// otherwise reqAsPromise would not fail
needsSyncPromise = false;
@ -49,51 +52,57 @@ export async function checkNeedsSyncPromise() {
}
// storage keys are defined to be unsigned 32bit numbers in KeyLimits, which is assumed by idb
export function encodeUint32(n) {
export function encodeUint32(n: number): string {
const hex = n.toString(16);
return "0".repeat(8 - hex.length) + hex;
}
// used for logs where timestamp is part of key, which is larger than 32 bit
export function encodeUint64(n) {
export function encodeUint64(n: number): string {
const hex = n.toString(16);
return "0".repeat(16 - hex.length) + hex;
}
export function decodeUint32(str) {
export function decodeUint32(str: string): number {
return parseInt(str, 16);
}
export function openDatabase(name, createObjectStore, version, idbFactory = window.indexedDB) {
type CreateObjectStore = (db : IDBDatabase, txn: IDBTransaction | null, oldVersion: number, version: number) => any
export function openDatabase(name: string, createObjectStore: CreateObjectStore, version: number, idbFactory: IDBFactory = window.indexedDB): Promise<IDBDatabase> {
const req = idbFactory.open(name, version);
req.onupgradeneeded = (ev) => {
const db = ev.target.result;
const txn = ev.target.transaction;
req.onupgradeneeded = (ev : IDBVersionChangeEvent) => {
const req = ev.target as IDBRequest<IDBDatabase>;
const db = req.result;
const txn = req.transaction;
const oldVersion = ev.oldVersion;
createObjectStore(db, txn, oldVersion, version);
};
return reqAsPromise(req);
}
export function reqAsPromise(req) {
export function reqAsPromise<T>(req: IDBRequest<T>): Promise<T> {
return new Promise((resolve, reject) => {
req.addEventListener("success", event => {
resolve(event.target.result);
resolve((event.target as IDBRequest<T>).result);
// @ts-ignore
needsSyncPromise && Promise._flush && Promise._flush();
});
req.addEventListener("error", event => {
const error = new IDBRequestError(event.target);
const error = new IDBRequestError(event.target as IDBRequest<T>);
reject(error);
// @ts-ignore
needsSyncPromise && Promise._flush && Promise._flush();
});
});
}
export function txnAsPromise(txn) {
export function txnAsPromise(txn): Promise<void> {
let error;
return new Promise((resolve, reject) => {
txn.addEventListener("complete", () => {
resolve();
// @ts-ignore
needsSyncPromise && Promise._flush && Promise._flush();
});
txn.addEventListener("error", event => {
@ -112,33 +121,41 @@ export function txnAsPromise(txn) {
error = new StorageError(`Transaction on ${dbName} with stores ${storeNames} was aborted.`);
}
reject(error);
// @ts-ignore
needsSyncPromise && Promise._flush && Promise._flush();
});
});
}
export function iterateCursor(cursorRequest, processValue) {
type CursorIterator<T, I extends IDBCursor> = I extends IDBCursorWithValue ?
(value: T, key: IDBValidKey, cursor: IDBCursorWithValue) => { done: boolean, jumpTo?: IDBValidKey } :
(value: undefined, key: IDBValidKey, cursor: IDBCursor) => { done: boolean, jumpTo?: IDBValidKey }
export function iterateCursor<T, I extends IDBCursor = IDBCursorWithValue>(cursorRequest: IDBRequest<I | null>, processValue: CursorIterator<T, I>): Promise<boolean> {
// TODO: does cursor already have a value here??
return new Promise((resolve, reject) => {
return new Promise<boolean>((resolve, reject) => {
cursorRequest.onerror = () => {
reject(new IDBRequestError(cursorRequest));
// @ts-ignore
needsSyncPromise && Promise._flush && Promise._flush();
};
// collect results
cursorRequest.onsuccess = (event) => {
const cursor = event.target.result;
const cursor = (event.target as IDBRequest<I>).result;
if (!cursor) {
resolve(false);
// @ts-ignore
needsSyncPromise && Promise._flush && Promise._flush();
return; // end of results
}
const result = processValue(cursor.value, cursor.key, cursor);
const result = processValue(cursor["value"], cursor.key, cursor);
// TODO: don't use object for result and assume it's jumpTo when not === true/false or undefined
const done = result?.done;
const jumpTo = result?.jumpTo;
if (done) {
resolve(true);
// @ts-ignore
needsSyncPromise && Promise._flush && Promise._flush();
} else if(jumpTo) {
cursor.continue(jumpTo);
@ -151,16 +168,20 @@ export function iterateCursor(cursorRequest, processValue) {
});
}
export async function fetchResults(cursor, isDone) {
const results = [];
await iterateCursor(cursor, (value) => {
type Pred<T> = (value: T) => boolean
export async function fetchResults<T>(cursor: IDBRequest, isDone: Pred<T[]>): Promise<T[]> {
const results: T[] = [];
await iterateCursor<T>(cursor, (value) => {
results.push(value);
return {done: isDone(results)};
});
return results;
}
export async function select(db, storeName, toCursor, isDone) {
type ToCursor = (store: IDBObjectStore) => IDBRequest
export async function select<T>(db: IDBDatabase, storeName: string, toCursor: ToCursor, isDone: Pred<T[]>): Promise<T[]> {
if (!isDone) {
isDone = () => false;
}
@ -173,7 +194,7 @@ export async function select(db, storeName, toCursor, isDone) {
return await fetchResults(cursor, isDone);
}
export async function findStoreValue(db, storeName, toCursor, matchesValue) {
export async function findStoreValue<T>(db: IDBDatabase, storeName: string, toCursor: ToCursor, matchesValue: Pred<T>): Promise<T> {
if (!matchesValue) {
matchesValue = () => true;
}
@ -185,7 +206,8 @@ export async function findStoreValue(db, storeName, toCursor, matchesValue) {
const store = tx.objectStore(storeName);
const cursor = await reqAsPromise(toCursor(store));
let match;
const matched = await iterateCursor(cursor, (value) => {
// @ts-ignore
const matched = await iterateCursor<T>(cursor, (value) => {
if (matchesValue(value)) {
match = value;
return true;

View file

@ -16,7 +16,7 @@ limitations under the License.
// polyfills needed for IE11
import Promise from "../../../lib/es6-promise/index.js";
import {checkNeedsSyncPromise} from "../../matrix/storage/idb/utils.js";
import {checkNeedsSyncPromise} from "../../matrix/storage/idb/utils";
if (typeof window.Promise === "undefined") {
window.Promise = Promise;