UNPKG

@freeword/meta

Version:

Meta package for Freeword: exports all core types, constants, and utilities from the src/ directory.

195 lines (173 loc) 8.39 kB
import _ /**/ from 'lodash' type Fieldname = string; type Bag<VT> = Record<string, VT>; type AnyBag = Bag<any> /** Assign a **non-enumerable**, writable, configurable property to an object * See also {@link setNormalProp} and {@link decorate} * @param obj - The object to decorate * @param key - The key to decorate the object with * @param value - The value to decorate the object with * @returns The value */ export function adorn<VT>(obj: object, key: string, value: VT): VT { Object.defineProperty(obj, key, { value, enumerable: false, writable: false, configurable: true }) return value } /** Assign an enumerable, writable, configurable property to an object * See also {@link adorn} and {@link decorate} * @param obj - The object to decorate * @param key - The key to decorate the object with * @param value - The value to decorate the object with * @returns The value */ export function setNormalProp<VT>(obj: object, key: string, value: VT): VT { Object.defineProperty(obj, key, { value, enumerable: true, writable: true, configurable: true }) return value } export function setNormalProps<OT extends Record<string, any>, VT extends Record<string, any>>(obj: OT, vals: VT): OT & VT { Object.entries(vals).forEach(([key, value]) => { Object.defineProperty(obj, key, { value, enumerable: true, writable: true, configurable: true }) }) return obj as OT & VT } export function setHiddenProps<OT extends Record<string, any>, VT extends Record<string, any>>(obj: OT, vals: VT): OT & VT { Object.entries(vals).forEach(([key, value]) => { Object.defineProperty(obj, key, { value, enumerable: false, writable: true, configurable: true }) }) return obj as OT & VT } /** Assign non-enumerable, writable, configurable properties to an object * See also {@link adorn} and {@link decorate} * @param obj - The object to decorate * @param vals - The values to decorate the object with * @returns The decorated object */ export function decorate<OT extends Record<string, any>, VT extends Record<string, any>>(obj: OT, vals: VT): OT & VT { Object.entries(vals).forEach(([key, value]) => { Object.defineProperty(obj, key, { value, enumerable: false, writable: false, configurable: true }) }) return obj as OT & VT } /** Get the own properties of an object * * @param obj - The object to get the own properties of * @returns The own properties of the object; empty object if nil */ export function ownProps(obj: object | null | undefined): Bag<TypedPropertyDescriptor<any>> { if (_.isNil(obj)) { return {} } return Object.getOwnPropertyDescriptors(obj) } /** Get the own property names of an object * * @param obj - The object to get the own property names of * @returns The own property names of the object; empty array if nil */ export function ownPropnames(obj: object | null | undefined): string[] { if (_.isNil(obj)) { return [] } return Object.getOwnPropertyNames(obj) } /** Get the property names of the **first parent prototype** of an object * * @param obj - The object to get the prototype property names of * @returns The prototype property names of the object; empty array if nil */ export function protoPropnames(obj: object | null | undefined): string[] { if (_.isNil(obj)) { return [] } const proto = Object.getPrototypeOf(obj) return ownPropnames(proto) } /** Get the property descriptor of a property of the **first parent prototype** of an object * * @param obj - The object to get the property descriptor of * @param propname - The name of the property to get the descriptor of * @returns The property descriptor of the property; undefined if not found */ export function protoProp<VT>(obj: object, propname: Fieldname): TypedPropertyDescriptor<VT> | undefined { return Object.getOwnPropertyDescriptor(Object.getPrototypeOf(obj), propname) } /** Get the property descriptor of a property of an object * * @param obj - The object to get the property descriptor of * @param propname - The name of the property to get the descriptor of * @returns The property descriptor of the property; undefined if not found */ export function ownProp<VT>(obj: object, propname: Fieldname): TypedPropertyDescriptor<VT> | undefined { return Object.getOwnPropertyDescriptor(obj, propname) } /** Get the first property descriptor found ascending the prototype chain * for a given property name * * @param obj - The object to get the property descriptor of * @param propname - The name of the property to get the descriptor of * @param depth - The depth of the prototype chain to search * @returns The property descriptor of the property; undefined if not found */ export function getProp<VT>(obj: object, propname: Fieldname, depth: number = 0): TypedPropertyDescriptor<VT> | undefined { if (depth < 0) { return undefined } const val = Object.getOwnPropertyDescriptor(obj, propname) if (val) { return val } const proto = Object.getPrototypeOf(obj) if (! proto) { return undefined } return getProp(proto, propname, depth - 1) } export function bagsize(bag: AnyBag | any[]): number { if (_.isArray(bag)) { return bag.length } return _.keys(bag).length } export function scrubNil<VT>(vals: (VT | undefined)[]): NonNullable<VT>[] export function scrubNil<VT>(vals: VT): { [KT in keyof VT]: NonNullable<VT[KT]> } export function scrubNil<VT>(vals: (VT | undefined)[]): NonNullable<VT>[] { if (_.isArray(vals)) { return _.reject(vals, _.isNil) as NonNullable<VT>[] } return _.omitBy(vals, _.isNil) as any } export function scrubVoid<VT>(vals: (VT | undefined)[]): NonNullable<VT>[] export function scrubVoid<VT>(vals: VT): { [KT in keyof VT]: NonNullable<VT[KT]> } export function scrubVoid<VT>(vals: (VT | undefined)[]): NonNullable<VT>[] { if (_.isArray(vals)) { return _.reject(vals, isVoid) as NonNullable<VT>[] } return _.omitBy(vals, isVoid) as any } const BLANKS = new Set([null, undefined, '']) /** Returns `true` for null, undefined, empty string, {}, and [], empty map, empty set, empty buffer. Returns **`false`** for zero 0, false, and NaN * Boolean and Number values are never void: `false`, `0`, `-0`, `+-` and `Nan` are all non-void, * @param obj - The object to check * @returns true if the object is void, false otherwise */ export function isVoid(obj: any): boolean { if (_.isNumber(obj) || _.isBoolean(obj)) { return false } // common-case non-void items if (BLANKS.has(obj) || _.isEqual(obj, {}) || _.isEqual(obj, [])) { return true } // common-case void items if (_.isMap(obj) || _.isSet(obj) || _.isBuffer(obj)) { return _.isEmpty(obj) } // empty maps, sets and buffers are void // anything else is non-void return false } /** Returns `true` for null, undefined, empty string. * Returns **`false`** for everything else -- * meaning zero 0, false, NaN, +0, -0, {}, [], * and other empty values are non-blank * @param obj - The object to check * @returns true if the object is blank, false otherwise */ export function isBlank(obj: any): boolean { return (obj !== undefined && obj !== '' && obj !== null) } // returns true if you're likely to have success spreading arr into an array: [...arr] export function arrayish(arr: any): boolean { if (_.isArray(arr)) { return true } if (_.isMap(arr) || _.isString(arr)) { return false } return (!! arr?.[Symbol.iterator]) } export function isAnyIterable(obj: any): boolean { return (!! (obj?.[Symbol.iterator] || obj?.[Symbol.asyncIterator])) } // true for objects and Maps that are not Useful.arrayish and not RegExp, Function, String, Class or other decoys export function baggish<RT extends AnyBag>(obj: any): obj is RT { if (_.isPlainObject(obj)) { return true } if (! _.isObjectLike(obj)) { return false } if ('isBaggish' in obj) { return obj.isBaggish } if (isAnyIterable(obj)) { return false } if (_.isRegExp(obj) || (obj instanceof Promise)) { return false } return _.isObject(obj) } export function objectish<RT extends AnyBag | Map<any, any>>(obj: any): obj is RT { return baggish(obj) || _.isMap(obj) } export function arrayOrBag(objOrArr: any): boolean { return _.isArray(objOrArr) || baggish(objOrArr) }