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. limitations under the License.
*/ */
import {iterateCursor, reqAsPromise} from "./utils.js"; import {iterateCursor, reqAsPromise} from "./utils";
export class QueryTarget { export class QueryTarget {
constructor(target) { constructor(target) {

View file

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

View file

@ -15,7 +15,7 @@ limitations under the License.
*/ */
import {Storage} from "./Storage.js"; import {Storage} from "./Storage.js";
import { openDatabase, reqAsPromise } from "./utils.js"; import { openDatabase, reqAsPromise } from "./utils";
import { exportSession, importSession } from "./export.js"; import { exportSession, importSession } from "./export.js";
import { schema } from "./schema.js"; import { schema } from "./schema.js";
import { detectWebkitEarlyCloseTxnBug } from "./quirks.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. limitations under the License.
*/ */
import {txnAsPromise} from "./utils.js"; import {txnAsPromise} from "./utils";
import {StorageError} from "../common"; import {StorageError} from "../common";
import {Store} from "./Store.js"; import {Store} from "./Store.js";
import {SessionStore} from "./stores/SessionStore.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. limitations under the License.
*/ */
import { iterateCursor, txnAsPromise } from "./utils.js"; import { iterateCursor, txnAsPromise } from "./utils";
import { STORE_NAMES } from "../common"; import { STORE_NAMES } from "../common";
export async function exportSession(db) { 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 // filed as https://bugs.webkit.org/show_bug.cgi?id=222746
export async function detectWebkitEarlyCloseTxnBug(idbFactory) { 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 {RoomMember, EVENT_TYPE as MEMBER_EVENT_TYPE} from "../../room/members/RoomMember.js";
import {RoomMemberStore} from "./stores/RoomMemberStore.js"; import {RoomMemberStore} from "./stores/RoomMemberStore.js";
import {SessionStore} from "./stores/SessionStore.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. limitations under the License.
*/ */
import { encodeUint32, decodeUint32 } from "../utils.js"; import { encodeUint32, decodeUint32 } from "../utils";
import {KeyLimits} from "../../common"; import {KeyLimits} from "../../common";
function encodeKey(roomId, queueIndex) { function encodeKey(roomId, queueIndex) {

View file

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

View file

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

View file

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

View file

@ -16,7 +16,7 @@ limitations under the License.
// polyfills needed for IE11 // polyfills needed for IE11
import Promise from "../../../lib/es6-promise/index.js"; 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") { if (typeof window.Promise === "undefined") {
window.Promise = Promise; window.Promise = Promise;