import type {HasWidenedKey, ObjectType, ObjectWithFallbackProperty} from './internal-types.js'; type ObjectWithRequiredDefinedProperty = ObjectValue & { [Property in keyof Pick]-?: Exclude; }; type ObjectWithNarrowedDefinedProperty = ObjectValue extends unknown ? Key extends keyof ObjectValue ? ObjectWithRequiredDefinedProperty : ObjectWithFallbackProperty : never; type ObjectWithDefinedProperty = ObjectType extends infer ObjectValue ? [ObjectValue] extends [object] ? HasWidenedKey extends true // For widened keys we can only prove that the original object survives the filter. ? ObjectValue : ObjectWithNarrowedDefinedProperty : never : never; type IsPropertyDefinedPredicate = (value: Value) => value is ObjectWithDefinedProperty; /** Check whether a specific own property of a value is defined, meaning it is not `undefined`. This is useful as a type guard in `.filter()`, where TypeScript does not automatically narrow property types. Only own (not inherited) data properties are considered. @example ``` import {isPropertyDefined} from 'ts-extras'; type Item = { name: string; value: number | undefined; }; const items: Item[] = [{name: 'a', value: 1}, {name: 'b', value: undefined}]; items.filter(isPropertyDefined('value')); //=> [{name: 'a', value: 1}] // Return type is `(Item & {value: number})[]` ``` @category Type guard */ export function isPropertyDefined(key: Key): IsPropertyDefinedPredicate { return (( value: Value, ): value is ObjectWithDefinedProperty => { if ((typeof value !== 'object' && typeof value !== 'function') || value === null) { return false; } const recordValue = value as Record; const propertyDescriptor = Object.getOwnPropertyDescriptor(recordValue, key); if (propertyDescriptor === undefined || !('value' in propertyDescriptor)) { return false; } return propertyDescriptor.value !== undefined; }) as IsPropertyDefinedPredicate; }