/* eslint-disable @typescript-eslint/no-restricted-types -- We intentionally use `object` to accept interfaces. */ import type { ObjectMerge, ReadonlyKeysOf, Simplify, } from 'type-fest'; /* Fold the sources onto the target left-to-right. Each step uses `ObjectMerge` so overlapping keys are unioned instead of intersected (which would turn conflicting types into `never`). */ type ReadonlyTargetKeys = Extract, keyof Result>; type PreserveReadonlyTargetKeys = Result extends unknown ? [ReadonlyTargetKeys] extends [never] ? Result : Simplify< Omit> & Readonly>> > : never; type NonPlainSource = | Date | RegExp | Promise | ReadonlyMap | WeakMap | ReadonlySet | WeakSet | readonly unknown[]; type SharedKeys = | Extract | Extract; type ConservativeSource = Partial, unknown>>; type ObjectAssignSource = unknown; type NoOpObjectAssignSource = null | undefined | boolean | number | bigint | symbol; type RuntimeObjectAssignSource = Exclude; /* Declared source keys are not guaranteed to be own enumerable runtime keys: pre-typed values may be class instances with prototype accessors/methods or non-enumerable properties. Source-only keys are therefore optional, while keys already present on the target are typed as target-or-source because they may or may not be overwritten. Readonly target keys are preserved later by `PreserveReadonlyTargetKeys`. */ type MaybeCopiedSource = Simplify<{ [Key in Extract]: Target[Key] | Source[Key]; } & Partial>>; type IsRecordSource = false extends (Source extends unknown ? Source extends Record ? true : false : never) ? false : true; type MergeKnownSource = ObjectMerge extends infer Result extends object ? PreserveReadonlyTargetKeys : object; type ObjectAssignMerge = [Source] extends [NonPlainSource] ? MergeKnownSource> : IsRecordSource extends true ? MergeKnownSource> : MergeKnownSource>; type ObjectAssignSourceResult = RuntimeObjectAssignSource extends infer RuntimeSource ? [RuntimeSource] extends [never] ? Target : [keyof RuntimeSource] extends [never] ? object : [RuntimeSource] extends [object] ? ObjectAssignMerge : object : Target; type ObjectAssignResult = Sources extends readonly [] ? Target : Sources extends readonly [infer First extends ObjectAssignSource, ...infer Rest extends readonly ObjectAssignSource[]] ? ObjectAssignResult, Rest> : Sources extends ReadonlyArray ? ObjectAssignSourceResult : Target; type PlainObjectTarget = Target extends (...arguments_: never[]) => unknown ? never : Target extends abstract new (...arguments_: never[]) => unknown ? never : Target; /** A strongly-typed version of `Object.assign()`. This is useful since `Object.assign()` returns the intersection `Target & Source`, which is unsound: conflicting property types are intersected (for example, `{a: number} & {a: string}` makes `a` become `never`), and unsafe access through index signatures is not caught. This function returns a conservative merged type instead, where source-only keys are optional and overlapping keys include the target and source value types. @example ``` import {objectAssign} from 'ts-extras'; const merged = objectAssign({a: 1}, {b: 2}); // => {a: number; b?: number} const overridden = objectAssign({a: 1}, {a: 'x'}); // => {a: number | string} const intersected = Object.assign({a: 1}, {a: 'x'}); // => {a: number} & {a: string}, so `a` is never ``` @category Improved builtin */ export function objectAssign( target: PlainObjectTarget, ...sources: Sources ): ObjectAssignResult { return Object.assign(target, ...sources) as ObjectAssignResult; }