UNPKG

@lincode/utils

Version:

Generated by ambients-cli

560 lines (471 loc) 15.1 kB
export { merge, omit, upperFirst, get, has, unset, range, invert, kebabCase } from "lodash" export type valueof<T> = T extends ArrayLike<unknown> ? T[number] : T[keyof T] export type Optional<T, K extends keyof T> = Omit<T, K> & { [k in K]?: T[k] } export type Class<T = unknown> = new (...args: Array<any>) => T export type RequiredNullable<T, RT = Required<T>> = { [P in keyof RT]: RT[P] | undefined } export type DeepPartial<T> = { [P in keyof T]?: DeepPartial<T[P]> } type ForceGet = { <Key, Val>(map: Map<Key, Val>, obj: Key, factory: () => Val): Val <Key extends object, Val>( map: WeakMap<Key, Val>, obj: Key, factory: () => Val ): Val } export const forceGet: ForceGet = ( map: Map<unknown, unknown> | WeakMap<any, unknown>, obj: unknown, factory: () => unknown ) => { if (!map.has(obj)) { const item = factory() map.set(obj, item) return item } return map.get(obj) } type ForceGetInstance = { <Key, Val>( map: Map<Key, Val>, obj: Key, ValClass: Class<Val>, params?: Array<unknown> ): Val <Key extends object, Val>( map: WeakMap<Key, Val>, obj: Key, ValClass: Class<Val>, params?: Array<unknown> ): Val } export const forceGetInstance: ForceGetInstance = ( map: Map<unknown, unknown> | WeakMap<any, unknown>, obj: unknown, ValClass: Class<unknown>, params: Array<unknown> = [] ) => { if (!map.has(obj)) { const item = new ValClass(...params) map.set(obj, item) return item } return map.get(obj) } export const assert = (condition: unknown, msg?: string): asserts condition => { if (!condition) throw new Error(msg) } export const assertExhaustive = (x: never): never => { throw new Error("unexpected value: " + x) } export const isInstance = <T extends Class>( target: unknown, ClassVar: T ): target is InstanceType<T> => { //@ts-ignore return target != null && target.constructor === ClassVar } export const isNotNullish = <T>(t?: T): t is T => !!t export const tryParse = <T>( val: unknown, typeGuard?: (val: any) => val is T ): T | undefined => { if (typeof val !== "string") return try { const result = JSON.parse(val) if (!typeGuard || typeGuard(result)) return result } catch {} } export const includes = <T>( collection: Set<T> | Map<T, unknown>, value: any ): value is T => collection.has(value) export const filter = <T extends object>( obj: T, predicate: (v: valueof<T>, k: keyof T) => boolean ) => { const result: Partial<T> = {} for (const [k, v] of Object.entries(obj)) predicate(v, k as keyof T) && (result[k as keyof T] = v) return result } export const omitDeep = <T extends object>( obj: T, keys: ReadonlyArray<string> ): T => { if (Array.isArray(obj)) { const newObj = new Array(obj.length) for (let i = 0; i < obj.length; ++i) newObj[i] = omitDeep(obj[i], keys) return newObj as T } else if (obj && typeof obj === "object") { const newObj: Record<string, any> = {} for (const [k, v] of Object.entries(obj)) !keys.includes(k) && (newObj[k] = omitDeep(v, keys)) return newObj as T } return obj } export const set = <T extends object>( obj: T, path: Array<string>, value: any ) => { let target: any = obj const iMax = path.length - 1 for (let i = 0; i < iMax; ++i) target = target[path[i]] ??= {} target[path[iMax]] = value } export const traverse = ( obj: unknown, cb: ( k: string | number, v: any, parent: Record<any, any> | Array<any> ) => void, traversed = new WeakSet() ) => { if (Array.isArray(obj)) { if (traversed.has(obj)) return traversed.add(obj) for (let i = 0; i < obj.length; ++i) { const v = obj[i] cb(i, v, obj) traverse(v, cb, traversed) } } else if (obj && typeof obj === "object") { if (traversed.has(obj)) return traversed.add(obj) for (const [k, v] of Object.entries(obj)) { cb(k, v, obj) traverse(v, cb, traversed) } } } export const everyAsFirst = <T>( array: Array<T>, predicate: (item: T, first: T) => any ) => { if (array.length === 0) return false const firstItem = array[0] const firstResult = predicate(firstItem, firstItem) return array.every((item) => predicate(item, firstItem) === firstResult) } export const keepOne = <T>(items?: Array<T>): Array<T> => items?.length ? [items[0]] : [] export const pull = <T>(array: Array<T>, item: T): boolean => { const index = array.indexOf(item) if (index === -1) return false array.splice(index, 1) return true } export const insert = <T>(array: Array<T>, item: T, index: number) => { array.splice(index, 0, item) } export const dedupe = <T>(array: Array<T>) => [...new Set(array)] export const trim = (str: string, char: string) => { if (char === "]") char = "\\]" if (char === "\\") char = "\\\\" return str.replace(new RegExp("^[" + char + "]+|[" + char + "]+$", "g"), "") } export const between = (s: string, char0: string, char1: string): string => { const i0 = s.indexOf(char0) if (i0 === -1) return "" const indexStart = i0 + char0.length const i1 = s.indexOf(char1, indexStart) if (i1 === -1) return "" return s.substring(indexStart, i1) } export const splitFileName = (fileName: string): [string, string?] => { const parts = fileName.split(".") if (parts.length === 1) return parts as [string] const extension = parts.pop() return [parts.join("."), extension] } export const random = (min: number, max: number) => { return Math.random() * (max - min) + min } type Edge = "leading" | "trailing" | "both" | "trailingPromise" type Throttle = { <Args extends Array<unknown>, Result>( fn: (...args: Args) => Result, timeout: number, edge: "leading" | "both" ): (...args: Args) => Result <Args extends Array<unknown>, Result>( fn: (...args: Args) => Result, timeout: number, edge: "trailing" ): (...args: Args) => Result | undefined <Args extends Array<unknown>, Result>( fn: (...args: Args) => Result, timeout: number, edge: "trailingPromise" ): (...args: Args) => Promise<Result> } export const throttle: Throttle = <Args extends Array<unknown>, Result>( fn: (...args: Args) => Result, timeout: number, edge: Edge ) => { let result: Result let scheduled = false let latestArgs: Args let resultResolve: | ((val: Result | PromiseLike<Result>) => void) | undefined = undefined let resultPromise = edge === "trailingPromise" ? new Promise<Result>((r) => (resultResolve = r)) : undefined return (...args: Args) => { latestArgs = args if (scheduled) return resultPromise ?? result scheduled = true if (edge === "leading" || edge === "both") result = fn(...latestArgs) const cb = () => { if ( edge === "trailing" || edge === "both" || edge === "trailingPromise" ) { result = fn(...latestArgs) resultResolve?.(result) resultPromise = edge === "trailingPromise" ? new Promise<Result>((r) => (resultResolve = r)) : undefined } scheduled = false } timeout > 0 ? setTimeout(cb, timeout) : queueMicrotask(cb) return resultPromise ?? result } } export const throttleTrailing = <Args extends Array<unknown>>( fn: (...args: Args) => void, timeout = 0 ) => { let scheduled = false let latestArgs: Args return (...args: Args) => { latestArgs = args if (scheduled) return scheduled = true const cb = () => { scheduled = false fn(...latestArgs) } timeout === 0 ? queueMicrotask(cb) : setTimeout(cb, timeout) } } const cancellableMicrotask = (cb: () => void) => { let proceed = true queueMicrotask(() => proceed && cb()) return () => { proceed = false } } const cancellableTimeout = (cb: () => void, time: number): (() => void) => { if (time === 0) return cancellableMicrotask(cb) const t = setTimeout(cb, time) return () => clearTimeout(t) } type Debounce = { <Args extends Array<unknown>, Result>( fn: (...args: Args) => Result, timeout: number, edge: "leading" | "both" ): (...args: Args) => Result <Args extends Array<unknown>, Result>( fn: (...args: Args) => Result, timeout: number, edge: "trailing" ): (...args: Args) => Result | undefined <Args extends Array<unknown>, Result>( fn: (...args: Args) => Result, timeout: number, edge: "trailingPromise" ): (...args: Args) => Promise<Result> } export const debounce: Debounce = <Args extends Array<unknown>, Result>( fn: (...args: Args) => Result, timeout: number, edge: Edge ) => { let cancelTimeout: (() => void) | undefined let result: Result let args0: Args let resultResolve: | ((val: Result | PromiseLike<Result>) => void) | undefined = undefined let resultPromise = edge === "trailingPromise" ? new Promise<Result>((r) => (resultResolve = r)) : undefined return (...args: Args) => { args0 = args if (cancelTimeout) cancelTimeout() else if (edge === "leading" || edge === "both") result = fn(...args0) cancelTimeout = cancellableTimeout(() => { if ( edge === "trailing" || edge === "both" || edge === "trailingPromise" ) { result = fn(...args0) resultResolve?.(result) resultPromise = edge === "trailingPromise" ? new Promise<Result>((r) => (resultResolve = r)) : undefined } cancelTimeout = undefined }, timeout) return resultPromise ?? result } } export const isAssignable = (SubClass: Class, SuperClass: Class): boolean => { return SubClass === SuperClass || SubClass.prototype instanceof SuperClass } export const getMethods = (obj: object | null) => { const methods = new Set<string>() while (obj) { for (const key of Reflect.ownKeys(obj)) typeof key === "string" && methods.add(key) obj = Reflect.getPrototypeOf(obj) } return [...methods] } export const log = <T>(val: T) => (console.log(val), val) const callerExtendedMap = new WeakMap<Function, Set<Function>>() const callerSet = new WeakSet<Function>() const callerSetAdd = (fn: Function) => { callerSet.add(fn) return fn } export const extendFunction = <T extends Function>( fn: T | undefined, newFn: T ) => { const isCaller = fn && callerSet.has(fn) const caller = isCaller ? fn : callerSetAdd((...args: Array<unknown>) => { let result: unknown let first = true for (const cb of existingCallbacks) { if (first) { result = cb(...args) first = false } else cb(...args) } return result }) const existingCallbacks = forceGetInstance( callerExtendedMap, caller, Set<Function> ) fn && !isCaller && existingCallbacks.add(fn) existingCallbacks.add(newFn) return caller as T } export const omitFunction = <T extends Function>(fn: T, newFn: T) => { callerExtendedMap.get(fn)?.delete(newFn) return fn } const lazySet = new WeakSet<Function>() export const isLazy = <T>(val: Function): val is () => T => lazySet.has(val) export const lazy = <T, Args extends Array<any>>( factory: (...a: Args) => T ): ((...a: Args) => T) => { let result: T let has = false const lazyFn = (...a: Args): T => { if (has) return result has = true return (result = factory(...a)) } lazySet.add(lazyFn) return lazyFn } export const lazyWithInvalidate = <T, Args extends Array<any>>( factory: (...a: Args) => T, invalidateWhenReturnValueChanges: () => any ): ((...a: Args) => T) => { let result: T let has = false let valOld = invalidateWhenReturnValueChanges() const lazyFn = (...a: Args): T => { const val = invalidateWhenReturnValueChanges() if (valOld !== val) has = false valOld = val if (has) return result has = true return (result = factory(...a)) } lazySet.add(lazyFn) return lazyFn } export const applyMixins = (toClass: any, fromClasses: Array<any>) => { for (const fromClass of fromClasses) for (const name of Object.getOwnPropertyNames(fromClass.prototype)) !(name in toClass.prototype) && Object.defineProperty( toClass.prototype, name, Object.getOwnPropertyDescriptor( fromClass.prototype, name ) || Object.create(null) ) } export const preventTreeShake = <T>(obj: T) => obj export const filterBoolean = <T>(arg: T): arg is Exclude<T, null | undefined> => !!arg // pending deprecation export const debounceInstance = <Args extends Array<unknown>>( fn: (...args: Args) => void ) => { const cancelTimeoutMap = new WeakMap<any, (() => void) | undefined>() const args0Map = new WeakMap<any, Args>() return (instance: any, ...args: Args) => { args0Map.set(instance, args) cancelTimeoutMap.get(instance)?.() cancelTimeoutMap.set( instance, cancellableMicrotask(() => { fn(...args0Map.get(instance)!) cancelTimeoutMap.delete(instance) }) ) } } type Last = { (val: string, index?: number): string | undefined <T>(val: Array<T>, index?: number): T | undefined } export const last: Last = (val: string | Array<any>, index = -1) => val[val.length + index] export const replaceAll = (val: string, from: string, to: string): string => { return val.split(from).join(to) } export type Callback<Payload = void, ReturnType = void> = ( payload: Payload ) => ReturnType export const type = <T>(val: T) => val const w = globalThis as any "__LINCODE_UTILS__" in w && console.warn("multiple versions of @lincode/utils detected") w.__LINCODE_UTILS__ = true