Start migrating utils.js to TypeScript
This commit is contained in:
parent
5579c018d1
commit
cd9fe360a4
12 changed files with 55 additions and 33 deletions
|
@ -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) {
|
||||||
|
|
|
@ -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";
|
||||||
|
|
||||||
|
|
|
@ -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";
|
||||||
|
|
|
@ -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";
|
||||||
|
|
|
@ -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) {
|
||||||
|
|
|
@ -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) {
|
||||||
|
|
|
@ -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";
|
||||||
|
|
|
@ -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) {
|
||||||
|
|
|
@ -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) {
|
||||||
|
|
|
@ -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)}`;
|
||||||
|
|
|
@ -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;
|
|
@ -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;
|
||||||
|
|
Reference in a new issue