export type UpdaterFn = (input: TInput) => TOutput export type Updater = | TOutput | UpdaterFn export function functionalUpdate( updater: Updater, input: TInput, ): TOutput { return typeof updater === 'function' ? (updater as UpdaterFn)(input) : updater } /** * Get a value from an object using a path, including dot notation. */ export function getBy(obj: any, path: any) { const pathArray = makePathArray(path) const pathObj = pathArray return pathObj.reduce((current: any, pathPart: any) => { if (typeof current !== 'undefined') { return current[pathPart] } return undefined }, obj) } /** * Set a value on an object using a path, including dot notation. */ export function setBy(obj: any, _path: any, updater: Updater) { const path = makePathArray(_path) function doSet(parent?: any): any { if (!path.length) { return functionalUpdate(updater, parent) } const key = path.shift() if (typeof key === 'string') { if (typeof parent === 'object') { return { ...parent, [key]: doSet(parent[key]), } } return { [key]: doSet(), } } if (typeof key === 'number') { if (Array.isArray(parent)) { const prefix = parent.slice(0, key) return [ ...(prefix.length ? prefix : new Array(key)), doSet(parent[key]), ...parent.slice(key + 1), ] } return [...new Array(key), doSet()] } throw new Error('Uh oh!') } return doSet(obj) } const reFindNumbers0 = /^(\d*)$/gm const reFindNumbers1 = /\.(\d*)\./gm const reFindNumbers2 = /^(\d*)\./gm const reFindNumbers3 = /\.(\d*$)/gm const reFindMultiplePeriods = /\.{2,}/gm const intPrefix = '__int__' const intReplace = `${intPrefix}$1` function makePathArray(str: string) { if (typeof str !== 'string') { throw new Error('Path must be a string.') } return str .replace('[', '.') .replace(']', '') .replace(reFindNumbers0, intReplace) .replace(reFindNumbers1, `.${intReplace}.`) .replace(reFindNumbers2, `${intReplace}.`) .replace(reFindNumbers3, `.${intReplace}`) .replace(reFindMultiplePeriods, '.') .split('.') .map((d) => { if (d.indexOf(intPrefix) === 0) { return parseInt(d.substring(intPrefix.length), 10) } return d }) } export function isNonEmptyArray(obj: any) { return !(Array.isArray(obj) && obj.length === 0) } export type RequiredByKey = Omit & Required> type ComputeRange< N extends number, Result extends Array = [], > = Result['length'] extends N ? Result : ComputeRange type Index40 = ComputeRange<40>[number] // Is this type a tuple? type IsTuple = T extends readonly any[] & { length: infer Length } ? Length extends Index40 ? T : never : never // If this type is a tuple, what indices are allowed? type AllowedIndexes< Tuple extends ReadonlyArray, Keys extends number = never, > = Tuple extends readonly [] ? Keys : Tuple extends readonly [infer _, ...infer Tail] ? AllowedIndexes : Keys export type DeepKeys = unknown extends T ? keyof T : object extends T ? string : T extends readonly any[] & IsTuple ? AllowedIndexes | DeepKeysPrefix> : T extends any[] ? DeepKeys : T extends Date ? never : T extends object ? (keyof T & string) | DeepKeysPrefix : never type DeepKeysPrefix = TPrefix extends keyof T & (number | string) ? `${TPrefix}.${DeepKeys & string}` : never export type DeepValue = T extends Record ? TProp extends `${infer TBranch}.${infer TDeepProp}` ? DeepValue : T[TProp & string] : never type Narrowable = string | number | bigint | boolean type NarrowRaw = | (A extends [] ? [] : never) | (A extends Narrowable ? A : never) | { [K in keyof A]: A[K] extends Function ? A[K] : NarrowRaw } export type Narrow = Try> type Try = A1 extends A2 ? A1 : Catch // Hack to get TypeScript to show simplified types in error messages export type Pretty = { [K in keyof T]: T[K] } & {}