2022-12-27 15:37:40 +01:00
|
|
|
import { TypeRef } from "./TypeRef.js"
|
2021-11-04 14:05:23 +01:00
|
|
|
|
2022-06-08 16:18:48 +02:00
|
|
|
export interface ErrorInfo {
|
|
|
|
|
readonly name: string | null
|
|
|
|
|
readonly message: string | null
|
2022-08-24 17:18:21 +02:00
|
|
|
readonly stack?: string | null
|
2022-06-08 16:18:48 +02:00
|
|
|
}
|
|
|
|
|
|
2021-12-27 16:36:10 +01:00
|
|
|
export type lazy<T> = () => T
|
|
|
|
|
export type lazyAsync<T> = () => Promise<T>
|
|
|
|
|
export type Thunk = () => unknown
|
2021-11-04 14:05:23 +01:00
|
|
|
export type DeferredObject<T> = {
|
2021-12-27 16:36:10 +01:00
|
|
|
resolve: (arg0: T) => void
|
|
|
|
|
reject: (arg0: Error) => void
|
|
|
|
|
promise: Promise<T>
|
2021-11-04 14:05:23 +01:00
|
|
|
}
|
|
|
|
|
export type DeferredObjectWithHandler<T, U> = {
|
2021-12-27 16:36:10 +01:00
|
|
|
resolve: (arg0: T) => void
|
|
|
|
|
reject: (arg0: Error) => void
|
|
|
|
|
promise: Promise<U>
|
2021-11-04 14:05:23 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
export function defer<T>(): DeferredObject<T> {
|
2021-12-27 16:36:10 +01:00
|
|
|
let ret: DeferredObject<T> = {} as DeferredObject<T>
|
2021-11-04 14:05:23 +01:00
|
|
|
ret.promise = new Promise((resolve, reject) => {
|
|
|
|
|
ret.resolve = resolve
|
|
|
|
|
ret.reject = reject
|
|
|
|
|
})
|
|
|
|
|
return ret
|
|
|
|
|
}
|
|
|
|
|
|
2021-12-27 16:36:10 +01:00
|
|
|
export function deferWithHandler<T, U>(handler: (arg0: T) => U): DeferredObjectWithHandler<T, U> {
|
|
|
|
|
const deferred = {} as DeferredObjectWithHandler<T, U>
|
2022-12-27 15:37:40 +01:00
|
|
|
deferred.promise = new Promise((resolve, reject) => {
|
|
|
|
|
deferred.resolve = resolve
|
|
|
|
|
deferred.reject = reject
|
|
|
|
|
}).then(handler)
|
2021-11-04 14:05:23 +01:00
|
|
|
return deferred
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
export async function asyncFind<T>(
|
2022-04-14 17:31:36 +02:00
|
|
|
array: ReadonlyArray<T>,
|
|
|
|
|
finder: (item: T, index: number, arrayLength: number) => Promise<boolean>,
|
2021-12-27 16:36:10 +01:00
|
|
|
): Promise<T | null | undefined> {
|
2021-11-04 14:05:23 +01:00
|
|
|
for (let i = 0; i < array.length; i++) {
|
|
|
|
|
const item = array[i]
|
2021-12-27 16:36:10 +01:00
|
|
|
|
2021-11-04 14:05:23 +01:00
|
|
|
if (await finder(item, i, array.length)) {
|
|
|
|
|
return item
|
|
|
|
|
}
|
|
|
|
|
}
|
2021-12-27 16:36:10 +01:00
|
|
|
|
2021-11-04 14:05:23 +01:00
|
|
|
return null
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
export async function asyncFindAndMap<T, R>(
|
2022-04-14 17:31:36 +02:00
|
|
|
array: ReadonlyArray<T>,
|
|
|
|
|
finder: (item: T, index: number, arrayLength: number) => Promise<R | null>,
|
2021-12-27 16:36:10 +01:00
|
|
|
): Promise<R | null | undefined> {
|
2021-11-04 14:05:23 +01:00
|
|
|
for (let i = 0; i < array.length; i++) {
|
|
|
|
|
const item = array[i]
|
|
|
|
|
const mapped = await finder(item, i, array.length)
|
2021-12-27 16:36:10 +01:00
|
|
|
|
2021-11-04 14:05:23 +01:00
|
|
|
if (mapped) {
|
|
|
|
|
return mapped
|
|
|
|
|
}
|
|
|
|
|
}
|
2021-12-27 16:36:10 +01:00
|
|
|
|
2021-11-04 14:05:23 +01:00
|
|
|
return null
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Calls an executor function for slices of nbrOfElementsInGroup items of the given array until the executor function returns false.
|
|
|
|
|
*/
|
2022-12-27 15:37:40 +01:00
|
|
|
export function executeInGroups<T>(array: T[], nbrOfElementsInGroup: number, executor: (items: T[]) => Promise<boolean>): Promise<void> {
|
2021-11-04 14:05:23 +01:00
|
|
|
if (array.length > 0) {
|
|
|
|
|
let nextSlice = Math.min(array.length, nbrOfElementsInGroup)
|
2022-12-27 15:37:40 +01:00
|
|
|
return executor(array.slice(0, nextSlice)).then((doContinue) => {
|
2021-11-04 14:05:23 +01:00
|
|
|
if (doContinue) {
|
|
|
|
|
return executeInGroups(array.slice(nextSlice), nbrOfElementsInGroup, executor)
|
|
|
|
|
}
|
|
|
|
|
})
|
|
|
|
|
} else {
|
|
|
|
|
return Promise.resolve()
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2022-01-07 15:58:30 +01:00
|
|
|
export function neverNull<T>(object: T): NonNullable<T> {
|
2021-12-27 16:36:10 +01:00
|
|
|
return object as any
|
2021-11-04 14:05:23 +01:00
|
|
|
}
|
|
|
|
|
|
2021-12-27 16:36:10 +01:00
|
|
|
export function assertNotNull<T>(object: T | null | undefined, message: string = "null"): T {
|
2021-11-04 14:05:23 +01:00
|
|
|
if (object == null) {
|
|
|
|
|
throw new Error("AssertNotNull failed : " + message)
|
|
|
|
|
}
|
2021-12-27 16:36:10 +01:00
|
|
|
|
2021-11-04 14:05:23 +01:00
|
|
|
return object
|
|
|
|
|
}
|
|
|
|
|
|
2022-01-07 15:58:30 +01:00
|
|
|
export function isNotNull<T>(t: T | null | undefined): t is T {
|
|
|
|
|
return t != null
|
|
|
|
|
}
|
|
|
|
|
|
2021-11-04 14:05:23 +01:00
|
|
|
export function assert(assertion: MaybeLazy<boolean>, message: string) {
|
|
|
|
|
if (!resolveMaybeLazy(assertion)) {
|
|
|
|
|
throw new Error(`Assertion failed: ${message}`)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2021-12-28 13:53:11 +01:00
|
|
|
export function downcast<R = any>(object: any): R {
|
2021-12-27 16:36:10 +01:00
|
|
|
return object as any
|
2021-11-04 14:05:23 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
export function clone<T>(instance: T): T {
|
|
|
|
|
if (instance instanceof Uint8Array) {
|
|
|
|
|
return downcast<T>(instance.slice())
|
|
|
|
|
} else if (instance instanceof Array) {
|
2022-12-27 15:37:40 +01:00
|
|
|
return downcast<T>(instance.map((i) => clone(i)))
|
2021-11-04 14:05:23 +01:00
|
|
|
} else if (instance instanceof Date) {
|
2021-12-27 16:36:10 +01:00
|
|
|
return new Date(instance.getTime()) as any
|
2021-11-04 14:05:23 +01:00
|
|
|
} else if (instance instanceof TypeRef) {
|
|
|
|
|
return instance
|
|
|
|
|
} else if (instance instanceof Object) {
|
|
|
|
|
// Can only pass null or Object, cannot pass undefined
|
|
|
|
|
const copy = Object.create(Object.getPrototypeOf(instance) || null)
|
|
|
|
|
Object.assign(copy, instance)
|
2021-12-27 16:36:10 +01:00
|
|
|
|
2021-11-04 14:05:23 +01:00
|
|
|
for (let key of Object.keys(copy)) {
|
|
|
|
|
copy[key] = clone(copy[key])
|
|
|
|
|
}
|
2021-12-27 16:36:10 +01:00
|
|
|
|
|
|
|
|
return copy as any
|
2021-11-04 14:05:23 +01:00
|
|
|
} else {
|
|
|
|
|
return instance
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Function which accepts another function. On first invocation
|
|
|
|
|
* of this resulting function result will be remembered and returned
|
|
|
|
|
* on consequent invocations.
|
|
|
|
|
*/
|
|
|
|
|
export function lazyMemoized<T>(source: () => T): () => T {
|
|
|
|
|
// Using separate variable for tracking because value can be undefined and we want to the function call only once
|
|
|
|
|
let cached = false
|
2022-01-07 15:58:30 +01:00
|
|
|
let value: T
|
2021-11-04 14:05:23 +01:00
|
|
|
return () => {
|
|
|
|
|
if (cached) {
|
|
|
|
|
return value
|
|
|
|
|
} else {
|
|
|
|
|
cached = true
|
2021-12-27 16:36:10 +01:00
|
|
|
return (value = source())
|
2021-11-04 14:05:23 +01:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Returns a cached version of {@param fn}.
|
|
|
|
|
* Cached function checks that argument is the same (with ===) and if it is then it returns the cached result.
|
|
|
|
|
* If the cached argument has changed then {@param fn} will be called with new argument and result will be cached again.
|
|
|
|
|
* Only remembers the last argument.
|
|
|
|
|
*/
|
2021-12-27 16:36:10 +01:00
|
|
|
export function memoized<T, R>(fn: (arg0: T) => R): (arg0: T) => R {
|
2021-11-04 14:05:23 +01:00
|
|
|
let lastArg: T
|
|
|
|
|
let lastResult: R
|
|
|
|
|
let didCache = false
|
2022-12-27 15:37:40 +01:00
|
|
|
return (arg) => {
|
2021-11-04 14:05:23 +01:00
|
|
|
if (!didCache || arg !== lastArg) {
|
|
|
|
|
lastArg = arg
|
|
|
|
|
didCache = true
|
|
|
|
|
lastResult = fn(arg)
|
|
|
|
|
}
|
2021-12-27 16:36:10 +01:00
|
|
|
|
2021-11-04 14:05:23 +01:00
|
|
|
return lastResult
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Function which returns what was passed into it
|
|
|
|
|
*/
|
|
|
|
|
export function identity<T>(t: T): T {
|
|
|
|
|
return t
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Function which does nothing.
|
|
|
|
|
*/
|
2022-12-27 15:37:40 +01:00
|
|
|
export function noOp() {}
|
2021-11-04 14:05:23 +01:00
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Return a function, which executed {@param toThrottle} only after it is not invoked for {@param timeout} ms.
|
|
|
|
|
* Executes function with the last passed arguments
|
|
|
|
|
* @return {Function}
|
|
|
|
|
*/
|
2021-12-27 16:36:10 +01:00
|
|
|
export function debounce<F extends (...args: any) => void>(timeout: number, toThrottle: F): F {
|
2022-01-07 15:58:30 +01:00
|
|
|
let timeoutId: TimeoutID
|
2021-12-27 16:36:10 +01:00
|
|
|
let toInvoke: (...args: any) => void
|
2022-01-07 15:58:30 +01:00
|
|
|
return downcast((...args: any[]) => {
|
2021-11-04 14:05:23 +01:00
|
|
|
if (timeoutId) {
|
|
|
|
|
clearTimeout(timeoutId)
|
|
|
|
|
}
|
2021-12-27 16:36:10 +01:00
|
|
|
|
2021-11-04 14:05:23 +01:00
|
|
|
toInvoke = toThrottle.bind(null, ...args)
|
|
|
|
|
timeoutId = setTimeout(toInvoke, timeout)
|
|
|
|
|
})
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Returns a debounced function. When invoked for the first time, will just invoke
|
|
|
|
|
* {@param toThrottle}. On subsequent invocations it will either invoke it right away
|
|
|
|
|
* (if {@param timeout} has passed) or will schedule it to be run after {@param timeout}.
|
|
|
|
|
* So the first and the last invocations in a series of invocations always take place
|
|
|
|
|
* but ones in the middle (which happen too often) are discarded.}
|
|
|
|
|
*/
|
2021-12-27 16:36:10 +01:00
|
|
|
export function debounceStart<F extends (...args: any) => void>(timeout: number, toThrottle: F): F {
|
|
|
|
|
let timeoutId: ReturnType<typeof setTimeout> | null | undefined
|
2021-11-04 14:05:23 +01:00
|
|
|
let lastInvoked = 0
|
|
|
|
|
return downcast((...args: any) => {
|
|
|
|
|
if (Date.now() - lastInvoked < timeout) {
|
|
|
|
|
timeoutId && clearTimeout(timeoutId)
|
|
|
|
|
timeoutId = setTimeout(() => {
|
|
|
|
|
timeoutId = null
|
|
|
|
|
toThrottle.apply(null, args)
|
|
|
|
|
}, timeout)
|
|
|
|
|
} else {
|
|
|
|
|
toThrottle.apply(null, args)
|
|
|
|
|
}
|
2021-12-27 16:36:10 +01:00
|
|
|
|
2021-11-04 14:05:23 +01:00
|
|
|
lastInvoked = Date.now()
|
|
|
|
|
})
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
export function randomIntFromInterval(min: number, max: number): number {
|
2021-12-27 16:36:10 +01:00
|
|
|
return Math.floor(Math.random() * (max - min + 1) + min)
|
2021-11-04 14:05:23 +01:00
|
|
|
}
|
|
|
|
|
|
2022-06-08 16:18:48 +02:00
|
|
|
export function errorToString(error: ErrorInfo): string {
|
2021-11-04 14:05:23 +01:00
|
|
|
let errorString = error.name ? error.name : "?"
|
2021-12-27 16:36:10 +01:00
|
|
|
|
2021-11-04 14:05:23 +01:00
|
|
|
if (error.message) {
|
|
|
|
|
errorString += `\n Error message: ${error.message}`
|
|
|
|
|
}
|
2021-12-27 16:36:10 +01:00
|
|
|
|
2021-11-04 14:05:23 +01:00
|
|
|
if (error.stack) {
|
|
|
|
|
// the error id is included in the stacktrace
|
|
|
|
|
errorString += `\nStacktrace: \n${error.stack}`
|
|
|
|
|
}
|
2021-12-27 16:36:10 +01:00
|
|
|
|
2021-11-04 14:05:23 +01:00
|
|
|
return errorString
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Like {@link Object.entries} but preserves the type of the key and value
|
|
|
|
|
*/
|
2021-12-27 16:36:10 +01:00
|
|
|
export function objectEntries<A extends string | symbol, B>(object: Record<A, B>): Array<[A, B]> {
|
2021-11-04 14:05:23 +01:00
|
|
|
return downcast(Object.entries(object))
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* modified deepEquals from ospec is only needed as long as we use custom classes (TypeRef) and Date is not properly handled
|
|
|
|
|
*/
|
|
|
|
|
export function deepEqual(a: any, b: any): boolean {
|
|
|
|
|
if (a === b) return true
|
|
|
|
|
if (xor(a === null, b === null) || xor(a === undefined, b === undefined)) return false
|
2021-12-27 16:36:10 +01:00
|
|
|
|
2021-11-04 14:05:23 +01:00
|
|
|
if (typeof a === "object" && typeof b === "object") {
|
2021-12-27 16:36:10 +01:00
|
|
|
const aIsArgs = isArguments(a),
|
2022-04-14 17:31:36 +02:00
|
|
|
bIsArgs = isArguments(b)
|
2021-12-27 16:36:10 +01:00
|
|
|
|
|
|
|
|
if (a.length === b.length && ((a instanceof Array && b instanceof Array) || (aIsArgs && bIsArgs))) {
|
|
|
|
|
const aKeys = Object.getOwnPropertyNames(a),
|
2022-04-14 17:31:36 +02:00
|
|
|
bKeys = Object.getOwnPropertyNames(b)
|
2021-11-04 14:05:23 +01:00
|
|
|
if (aKeys.length !== bKeys.length) return false
|
2021-12-27 16:36:10 +01:00
|
|
|
|
2021-11-04 14:05:23 +01:00
|
|
|
for (let i = 0; i < aKeys.length; i++) {
|
|
|
|
|
if (!hasOwn.call(b, aKeys[i]) || !deepEqual(a[aKeys[i]], b[aKeys[i]])) return false
|
|
|
|
|
}
|
2021-12-27 16:36:10 +01:00
|
|
|
|
2021-11-04 14:05:23 +01:00
|
|
|
return true
|
|
|
|
|
}
|
2021-12-27 16:36:10 +01:00
|
|
|
|
2021-11-04 14:05:23 +01:00
|
|
|
if (a instanceof Date && b instanceof Date) return a.getTime() === b.getTime()
|
2021-12-27 16:36:10 +01:00
|
|
|
|
2021-11-04 14:05:23 +01:00
|
|
|
if (a instanceof Object && b instanceof Object && !aIsArgs && !bIsArgs) {
|
|
|
|
|
for (let i in a) {
|
2021-12-27 16:36:10 +01:00
|
|
|
if (!(i in b) || !deepEqual(a[i], b[i])) return false
|
2021-11-04 14:05:23 +01:00
|
|
|
}
|
2021-12-27 16:36:10 +01:00
|
|
|
|
2021-11-04 14:05:23 +01:00
|
|
|
for (let i in b) {
|
|
|
|
|
if (!(i in a)) return false
|
|
|
|
|
}
|
2021-12-27 16:36:10 +01:00
|
|
|
|
2021-11-04 14:05:23 +01:00
|
|
|
return true
|
|
|
|
|
}
|
2021-12-27 16:36:10 +01:00
|
|
|
|
2022-04-14 17:31:36 +02:00
|
|
|
// @ts-ignore: we would need to include all @types/node for this to work or import it explicitly. Should probably be rewritten for all typed arrays.
|
2021-11-04 14:05:23 +01:00
|
|
|
if (typeof Buffer === "function" && a instanceof Buffer && b instanceof Buffer) {
|
|
|
|
|
for (let i = 0; i < a.length; i++) {
|
|
|
|
|
if (a[i] !== b[i]) return false
|
|
|
|
|
}
|
2021-12-27 16:36:10 +01:00
|
|
|
|
2021-11-04 14:05:23 +01:00
|
|
|
return true
|
|
|
|
|
}
|
2021-12-27 16:36:10 +01:00
|
|
|
|
2021-11-04 14:05:23 +01:00
|
|
|
if (a.valueOf() === b.valueOf()) return true
|
|
|
|
|
}
|
2021-12-27 16:36:10 +01:00
|
|
|
|
2021-11-04 14:05:23 +01:00
|
|
|
return false
|
|
|
|
|
}
|
|
|
|
|
|
2022-01-07 15:58:30 +01:00
|
|
|
function xor(a: boolean, b: boolean): boolean {
|
2021-11-04 14:05:23 +01:00
|
|
|
const aBool = !!a
|
|
|
|
|
const bBool = !!b
|
|
|
|
|
return (aBool && !bBool) || (bBool && !aBool)
|
|
|
|
|
}
|
|
|
|
|
|
2022-01-07 15:58:30 +01:00
|
|
|
function isArguments(a: any) {
|
2021-11-04 14:05:23 +01:00
|
|
|
if ("callee" in a) {
|
|
|
|
|
for (let i in a) if (i === "callee") return false
|
2021-12-27 16:36:10 +01:00
|
|
|
|
2021-11-04 14:05:23 +01:00
|
|
|
return true
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2021-12-27 16:36:10 +01:00
|
|
|
const hasOwn = {}.hasOwnProperty
|
2021-11-04 14:05:23 +01:00
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* returns an array of top-level properties that are in both objA and objB, but differ in value
|
|
|
|
|
* does not handle functions or circular references
|
|
|
|
|
* treats undefined and null as equal
|
|
|
|
|
*/
|
|
|
|
|
export function getChangedProps(objA: any, objB: any): Array<string> {
|
|
|
|
|
if (objA == null || objB == null || objA === objB) return []
|
|
|
|
|
return Object.keys(objA)
|
2022-12-27 15:37:40 +01:00
|
|
|
.filter((k) => Object.keys(objB).includes(k))
|
|
|
|
|
.filter((k) => ![null, undefined].includes(objA[k]) || ![null, undefined].includes(objB[k]))
|
|
|
|
|
.filter((k) => !deepEqual(objA[k], objB[k]))
|
2021-11-04 14:05:23 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Disallow set, delete and clear on Map.
|
|
|
|
|
* Important: It is *not* a deep freeze.
|
|
|
|
|
* @param myMap
|
|
|
|
|
* @return {unknown}
|
|
|
|
|
*/
|
|
|
|
|
export function freezeMap<K, V>(myMap: Map<K, V>): Map<K, V> {
|
2021-12-27 16:36:10 +01:00
|
|
|
function mapSet(key: K, value: V): Map<K, V> {
|
|
|
|
|
throw new Error("Can't add property " + key + ", map is not extensible")
|
2021-11-04 14:05:23 +01:00
|
|
|
}
|
|
|
|
|
|
2021-12-27 16:36:10 +01:00
|
|
|
function mapDelete(key: K): boolean {
|
|
|
|
|
throw new Error("Can't delete property " + key + ", map is frozen")
|
2021-11-04 14:05:23 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function mapClear() {
|
2021-12-27 16:36:10 +01:00
|
|
|
throw new Error("Can't clear map, map is frozen")
|
2021-11-04 14:05:23 +01:00
|
|
|
}
|
|
|
|
|
|
2021-12-27 16:36:10 +01:00
|
|
|
const anyMap = downcast<Map<K, V>>(myMap)
|
2021-11-04 14:05:23 +01:00
|
|
|
anyMap.set = mapSet
|
|
|
|
|
anyMap.delete = mapDelete
|
|
|
|
|
anyMap.clear = mapClear
|
|
|
|
|
Object.freeze(anyMap)
|
|
|
|
|
return anyMap
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
export function addressDomain(senderAddress: string): string {
|
|
|
|
|
return senderAddress.slice(senderAddress.lastIndexOf("@") + 1)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Ignores the fact that Object.keys returns also not owned properties.
|
|
|
|
|
*/
|
2021-12-27 16:36:10 +01:00
|
|
|
export function typedKeys<K extends string, V>(obj: Record<K, V>): Array<K> {
|
2021-11-04 14:05:23 +01:00
|
|
|
return downcast(Object.keys(obj))
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Ignores the fact that Object.keys returns also not owned properties.
|
|
|
|
|
*/
|
2021-12-27 16:36:10 +01:00
|
|
|
export function typedEntries<K extends string, V>(obj: Record<K, V>): Array<[K, V]> {
|
2021-11-04 14:05:23 +01:00
|
|
|
return downcast(Object.entries(obj))
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Ignores the fact that Object.keys returns also not owned properties.
|
|
|
|
|
*/
|
2021-12-27 16:36:10 +01:00
|
|
|
export function typedValues<K extends string, V>(obj: Record<K, V>): Array<V> {
|
2021-11-04 14:05:23 +01:00
|
|
|
return downcast(Object.values(obj))
|
|
|
|
|
}
|
|
|
|
|
|
2021-12-27 16:36:10 +01:00
|
|
|
export type MaybeLazy<T> = T | lazy<T>
|
2021-11-04 14:05:23 +01:00
|
|
|
|
|
|
|
|
export function resolveMaybeLazy<T>(maybe: MaybeLazy<T>): T {
|
2021-12-27 16:36:10 +01:00
|
|
|
return typeof maybe === "function" ? (maybe as Function)() : maybe
|
2021-11-04 14:05:23 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
export function getAsLazy<T>(maybe: MaybeLazy<T>): lazy<T> {
|
2021-12-27 16:36:10 +01:00
|
|
|
return typeof maybe === "function" ? downcast(maybe) : () => maybe
|
2021-11-04 14:05:23 +01:00
|
|
|
}
|
|
|
|
|
|
2021-12-27 16:36:10 +01:00
|
|
|
export function mapLazily<T, U>(maybe: MaybeLazy<T>, mapping: (arg0: T) => U): lazy<U> {
|
2021-11-04 14:05:23 +01:00
|
|
|
return () => mapping(resolveMaybeLazy(maybe))
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Stricter version of parseInt() from MDN. parseInt() allows some arbitrary characters at the end of the string.
|
|
|
|
|
* Returns NaN in case there's anything non-number in the string.
|
|
|
|
|
*/
|
|
|
|
|
export function filterInt(value: string): number {
|
|
|
|
|
if (/^\d+$/.test(value)) {
|
2021-12-27 16:36:10 +01:00
|
|
|
return parseInt(value, 10)
|
2021-11-04 14:05:23 +01:00
|
|
|
} else {
|
2021-12-27 16:36:10 +01:00
|
|
|
return NaN
|
2021-11-04 14:05:23 +01:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
interface Positioned {
|
2021-12-27 16:36:10 +01:00
|
|
|
x: number
|
|
|
|
|
y: number
|
2021-11-04 14:05:23 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
interface Sized {
|
2021-12-27 16:36:10 +01:00
|
|
|
top: number
|
|
|
|
|
left: number
|
|
|
|
|
bottom: number
|
|
|
|
|
right: number
|
2021-11-04 14:05:23 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
export function insideRect(point: Positioned, rect: Sized): boolean {
|
2021-12-27 16:36:10 +01:00
|
|
|
return point.x >= rect.left && point.x < rect.right && point.y >= rect.top && point.y < rect.bottom
|
2021-11-04 14:05:23 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* If val is non null, returns the result of val passed to action, else null
|
|
|
|
|
*/
|
2021-12-27 16:36:10 +01:00
|
|
|
export function mapNullable<T, U>(val: T | null | undefined, action: (arg0: T) => U | null | undefined): U | null {
|
2021-11-04 14:05:23 +01:00
|
|
|
if (val != null) {
|
|
|
|
|
const result = action(val)
|
2021-12-27 16:36:10 +01:00
|
|
|
|
2021-11-04 14:05:23 +01:00
|
|
|
if (result != null) {
|
|
|
|
|
return result
|
|
|
|
|
}
|
|
|
|
|
}
|
2021-12-27 16:36:10 +01:00
|
|
|
|
2021-11-04 14:05:23 +01:00
|
|
|
return null
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/** Helper to take instead of `typeof setTimeout` which is hellish to reproduce */
|
2022-08-02 17:45:06 +02:00
|
|
|
export type TimeoutSetter = (fn: () => unknown, arg1: number) => ReturnType<typeof setTimeout>
|
|
|
|
|
|
|
|
|
|
export function mapObject<K extends string | number | symbol, V, R>(mapper: (arg0: V) => R, obj: Record<K, V>): Record<K, R> {
|
|
|
|
|
const newObj = {} as Record<K, R>
|
|
|
|
|
|
|
|
|
|
for (const key of Object.keys(obj)) {
|
|
|
|
|
const typedKey = key as K
|
|
|
|
|
newObj[typedKey] = mapper(obj[typedKey])
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return newObj
|
2022-12-27 15:37:40 +01:00
|
|
|
}
|