export function required< T extends Record, R extends Record = T, S extends Record = T & R >(source1: Partial, source2: Partial | undefined | null, source3?: Partial | null): T & R & S { return merge([source1, source2, source3], (p, e) => { return { ...p, ...e } }) } export function requiredDeep< T extends Record, R extends Record = T, S extends Record = T & R >(source1: Partial, source2: Partial | undefined | null, source3?: Partial | null): T & R & S { return merge([source1, source2, source3], (p, e) => deepmerge(p, e) as any) } function merge( entries: [Record, Record | undefined | null, Record | undefined | null], reducer: (result: Record, entry: Record | undefined | null) => Record ) { if (entries[0] === undefined || entries[0] === null) return entries[0] return entries.filter(x => !!x).reduce(reducer, {}) } function deepmerge( source1: unknown, source2: unknown ): unknown { if (typeof source1 !== 'object' || source1 === null) return source2 !== undefined ? source2 : source1 if (Array.isArray(source1)) { if (Array.isArray(source2)) return source2 if (source2 === undefined) return [...source1] return [...source1, source2] } return getAllKeys(source1).concat(getAllKeys(source2)).reduce((p, k) => { // @ts-ignore p[k] = deepmerge(source1[k], source2 && source2[k]) return p }, {} as Record) } function getAllKeys(subject: any, internal = false): string[] { if (typeof subject !== 'object') return [] const propertyNames = Object.getOwnPropertyNames(subject) const keys = internal ? propertyNames.filter(n => n !== 'constructor') : propertyNames const proto = Object.getPrototypeOf(subject) return proto !== Object.prototype ? keys.concat(getAllKeys(proto, true)) : keys } /** * Unpartial a partial type. * @type T Type of `base`. If not specified, it will be inferred from `base`. * @param base The base value to fill in needed property if not available in the `partial` value. * @param partial The partial value to be unpartialed. */ export function unpartial< T extends Record, R extends Record = Partial >(base: T, partial: R | undefined | null): Exclude extends infer K ? ([K] extends [never] ? { [P in keyof T]: P extends keyof R ? T[P] | Exclude : T[P] } : { [P in keyof T]: P extends keyof R ? T[P] | Exclude : T[P] } & Pick) : never /** * Unpartial a partial type with two base values. * This is useful when you are extending value from another package or code. * That means you have a `parent` value from the original code, * a `base` value where you add additional defaults or override existing one, * and `partial` value from input. * @deprecated please use composition instead: `unpartial(unpartial(a, b), c)` * @type R Type of `base`. This type will be used as the return type. * @param parent The default value from the original code. * @param base The extended default value of your code. * @param partial The input value. */ export function unpartial< T extends Record, R extends Record = Partial, S extends Record = Partial >(parent: T, base: R | undefined | null, partial: S | undefined | null): T & R & S export function unpartial(s1: any, s2: any, s3?: any) { // defensive check for JS if (s1 === undefined || s1 === null) return s1 return [s1, s2, s3] .filter(notNil) .reduce((p, s: any) => ({ ...p, ...trimNilProps(s) }), {} as any) } function trimNilProps(value: any) { return Object.keys(value).reduce((p, k) => { if (notNil(value[k])) p[k] = value[k] return p }, {} as any) } function notNil(value: any) { return value !== null && value !== undefined } export function unpartialRecursively< T extends Record >(base: T, partial: Record | undefined): T export function unpartialRecursively< T extends Record, R extends Record = Record >(superBase: R, base: T | undefined, partial: Record | undefined): T & R export function unpartialRecursively(arg1: any, arg2: any, arg3?: any) { // eslint-disable-next-line @typescript-eslint/no-unsafe-argument return requiredDeep(arg1, arg2, arg3) }