@lincode/utils
Version:
Generated by ambients-cli
560 lines (471 loc) • 15.1 kB
text/typescript
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