From deef06964620fef135c6262e90644a215718c600 Mon Sep 17 00:00:00 2001 From: Piotr Witek Date: Sat, 27 Apr 2019 23:38:52 +0200 Subject: [PATCH] Added RequiredKeys and OptionalKeys #53 --- README.md | 182 ++++++++++++++++++++++++--------------- package-lock.json | 6 +- package.json | 2 +- src/index.ts | 2 + src/mapped-types.spec.ts | 140 +++++++++++++++++------------- src/mapped-types.ts | 108 ++++++++++++++--------- 6 files changed, 268 insertions(+), 172 deletions(-) diff --git a/README.md b/README.md index f47f0b5..b364d46 100644 --- a/README.md +++ b/README.md @@ -79,6 +79,8 @@ Issues can be funded by anyone and the money will be transparently distributed t * [`NonFunctionKeys`](#nonfunctionkeyst) * [`ReadonlyKeys`](#readonlykeyst) * [`WritableKeys`](#writablekeyst) +* [`RequiredKeys`](#requiredkeyst) +* [`OptionalKeys`](#optionalkeyst) * [`Partial`](#partialt) (_\*built-in_) * [`DeepPartial`](#deeppartialt) * [`Required`](#requiredt) (_\*built-in_) @@ -143,10 +145,10 @@ Set intersection of given union types `A` and `B` ```ts import { SetIntersection } from 'utility-types'; -type ResultSet = SetIntersection<'1' | '2' | '3', '2' | '3' | '4'>; // Expect: "2" | "3" -type ResultSetMixed = SetIntersection void), Function>; +type ResultSet = SetIntersection<'1' | '2' | '3', '2' | '3' | '4'>; // Expect: () => void +type ResultSetMixed = SetIntersection void), Function>; ``` [⇧ back to top](#table-of-contents) @@ -160,10 +162,10 @@ Set difference of given union types `A` and `B` ```ts import { SetDifference } from 'utility-types'; -type ResultSet = SetDifference<'1' | '2' | '3', '2' | '3' | '4'>; // Expect: "1" -type ResultSetMixed = SetDifference void), Function>; +type ResultSet = SetDifference<'1' | '2' | '3', '2' | '3' | '4'>; // Expect: string | number +type ResultSetMixed = SetDifference void), Function>; ``` [⇧ back to top](#table-of-contents) @@ -177,8 +179,8 @@ Set complement of given union types `A` and (it's subset) `A1` ```ts import { SetComplement } from 'utility-types'; -type ResultSet = SetComplement<'1' | '2' | '3', '2' | '3'>; // Expect: "1" +type ResultSet = SetComplement<'1' | '2' | '3', '2' | '3'>; ``` [⇧ back to top](#table-of-contents) @@ -192,8 +194,8 @@ Set difference of union and intersection of given union types `A` and `B` ```ts import { SymmetricDifference } from 'utility-types'; -type ResultSet = SymmetricDifference<'1' | '2' | '3', '2' | '3' | '4'>; // Expect: "1" | "4" +type ResultSet = SymmetricDifference<'1' | '2' | '3', '2' | '3' | '4'>; ``` [⇧ back to top](#table-of-contents) @@ -234,8 +236,9 @@ Get union type of keys that are functions in object type `T` import { FunctionKeys } from 'utility-types'; type MixedProps = { name: string; setName: (name: string) => void }; -type FunctionKeysProps = FunctionKeys; + // Expect: "setName" +type FunctionKeysProps = FunctionKeys; ``` [⇧ back to top](#table-of-contents) @@ -250,8 +253,77 @@ Get union type of keys that are non-functions in object type `T` import { NonFunctionKeys } from 'utility-types'; type MixedProps = { name: string; setName: (name: string) => void }; -type NonFunctionKeysProps = NonFunctionKeys; + // Expect: "name" +type NonFunctionKeysProps = NonFunctionKeys; +``` + +[⇧ back to top](#table-of-contents) + +### `ReadonlyKeys` + +Get union type of keys that are readonly in object type `T` + +**Usage:** + +```ts +import { ReadonlyKeys } from 'utility-types'; + +type Props = { readonly foo: string; bar: number }; + +// Expect: "foo" +type ReadonlyProps = ReadonlyKeys; +``` + +[⇧ back to top](#table-of-contents) + +### `WritableKeys` + +Get union type of keys that are writable (not readonly) in object type `T` + +**Usage:** + +```ts +import { WritableKeys } from 'utility-types'; + +type Props = { readonly foo: string; bar: number }; + +// Expect: "bar" +type WritableProps = WritableKeys; +``` + +[⇧ back to top](#table-of-contents) + +### `RequiredKeys` + +Get union type of keys that are required in object type `T` + +**Usage:** + +```ts +import { RequiredKeys } from 'utility-types'; + +type Props = { req: number; reqUndef: number | undefined; opt?: string; optUndef?: number | undefined; }; + +// Expect: "req" | "reqUndef" +type RequiredProps = ReadonlyKeys; +``` + +[⇧ back to top](#table-of-contents) + +### `OptionalKeys` + +Get union type of keys that are optional in object type `T` + +**Usage:** + +```ts +import { OptionalKeys } from 'utility-types'; + +type Props = { req: number; reqUndef: number | undefined; opt?: string; optUndef?: number | undefined; }; + +// Expect: "opt" | "optUndef" +type OptionalProps = OptionalKeys; ``` [⇧ back to top](#table-of-contents) @@ -267,8 +339,8 @@ From `T` pick a set of properties `K` ```ts type Props = { name: string; age: number; visible: boolean }; -type RequiredProps = Pick; // Expect: { name: string } +type RequiredProps = Pick; ``` [⇧ back to top](#table-of-contents) @@ -284,8 +356,8 @@ import { Omit } from 'utility-types'; type Props = { name: string; age: number; visible: boolean }; -type RequiredProps = Omit; // Expect: { name: string; visible: boolean; } +type RequiredProps = Omit; ``` [⇧ back to top](#table-of-contents) @@ -302,8 +374,8 @@ import { PickByValue } from 'utility-types'; type Props = { name: string; age: number; visible: boolean }; -type RequiredProps = PickByValue; // Expect: { name: string; age: number } +type RequiredProps = PickByValue; ``` [⇧ back to top](#table-of-contents) @@ -320,8 +392,8 @@ import { OmitByValue } from 'utility-types'; type Props = { name: string; age: number; visible: boolean }; -type RequiredProps = OmitByValue; // Expect: { visible: boolean } +type RequiredProps = OmitByValue; ``` [⇧ back to top](#table-of-contents) @@ -338,8 +410,8 @@ import { Intersection } from 'utility-types'; type Props = { name: string; age: number; visible: boolean }; type DefaultProps = { age: number }; -type DuplicatedProps = Intersection; // Expect: { age: number; } +type DuplicatedProps = Intersection; ``` [⇧ back to top](#table-of-contents) @@ -356,8 +428,8 @@ import { Diff } from 'utility-types'; type Props = { name: string; age: number; visible: boolean }; type DefaultProps = { age: number }; -type RequiredProps = Diff; // Expect: { name: string; visible: boolean; } +type RequiredProps = Diff; ``` [⇧ back to top](#table-of-contents) @@ -374,8 +446,8 @@ import { Subtract } from 'utility-types'; type Props = { name: string; age: number; visible: boolean }; type DefaultProps = { age: number }; -type RequiredProps = Subtract; // Expect: { name: string; visible: boolean; } +type RequiredProps = Subtract; ``` [⇧ back to top](#table-of-contents) @@ -392,8 +464,8 @@ import { Overwrite } from 'utility-types'; type Props = { name: string; age: number; visible: boolean }; type NewProps = { age: string; other: string }; -type ReplacedProps = Overwrite; // Expect: { name: string; age: string; visible: boolean; } +type ReplacedProps = Overwrite; ``` [⇧ back to top](#table-of-contents) @@ -410,40 +482,8 @@ import { Assign } from 'utility-types'; type Props = { name: string; age: number; visible: boolean }; type NewProps = { age: string; other: string }; -type ExtendedProps = Assign; // Expect: { name: string; age: number; visible: boolean; other: string; } -``` - -[⇧ back to top](#table-of-contents) - -### `ReadonlyKeys` - -Get union type of keys that are readonly in object type `T` - -**Usage:** - -```ts -import { ReadonlyKeys } from 'utility-types'; - -type Props = { readonly foo: string; bar: number }; -type ReadonlyProps = ReadonlyKeys; -// Expect: "foo" -``` - -[⇧ back to top](#table-of-contents) - -### `WritableKeys` - -Get union type of keys that are writable (not readonly) in object type `T` - -**Usage:** - -```ts -import { WritableKeys } from 'utility-types'; - -type Props = { readonly foo: string; bar: number }; -type WritableProps = WritableKeys; -// Expect: "bar" +type ExtendedProps = Assign; ``` [⇧ back to top](#table-of-contents) @@ -489,8 +529,8 @@ import { Unionize } from 'utility-types'; type Props = { name: string; age: number; visible: boolean }; -type UnionizedType = Unionize; // Expect: { name: string; } | { age: number; } | { visible: boolean; } +type UnionizedType = Unionize; ``` [⇧ back to top](#table-of-contents) @@ -504,8 +544,8 @@ Obtain Promise resolve type ```ts import { PromiseType } from 'utility-types'; -type Response = PromiseType>; // Expect: string +type Response = PromiseType>; ``` [⇧ back to top](#table-of-contents) @@ -526,7 +566,7 @@ type NestedProps = { }; }; }; -type ReadonlyNestedProps = DeepReadonly; + // Expect: { // readonly first: { // readonly second: { @@ -534,6 +574,7 @@ type ReadonlyNestedProps = DeepReadonly; // }; // }; // } +type ReadonlyNestedProps = DeepReadonly; ``` [⇧ back to top](#table-of-contents) @@ -554,7 +595,7 @@ type NestedProps = { }; }; }; -type RequiredNestedProps = DeepRequired; + // Expect: { // first: { // second: { @@ -562,6 +603,7 @@ type RequiredNestedProps = DeepRequired; // }; // }; // } +type RequiredNestedProps = DeepRequired; ``` [⇧ back to top](#table-of-contents) @@ -582,7 +624,7 @@ type NestedProps = { }; }; }; -type RequiredNestedProps = DeepNonNullable; + // Expect: { // first: { // second: { @@ -590,6 +632,7 @@ type RequiredNestedProps = DeepNonNullable; // }; // }; // } +type RequiredNestedProps = DeepNonNullable; ``` [⇧ back to top](#table-of-contents) @@ -610,7 +653,7 @@ type NestedProps = { }; }; }; -type PartialNestedProps = DeepPartial; + // Expect: { // first?: { // second?: { @@ -618,6 +661,7 @@ type PartialNestedProps = DeepPartial; // }; // }; // } +type PartialNestedProps = DeepPartial; ``` [⇧ back to top](#table-of-contents) @@ -664,8 +708,8 @@ import { $Keys } from 'utility-types'; type Props = { name: string; age: number; visible: boolean }; -type PropsKeys = $Keys; // Expect: "name" | "age" | "visible" +type PropsKeys = $Keys; ``` [⇧ back to top](#flows-utility-types) @@ -682,8 +726,8 @@ import { $Values } from 'utility-types'; type Props = { name: string; age: number; visible: boolean }; -type PropsValues = $Values; // Expect: string | number | boolean +type PropsValues = $Values; ``` [⇧ back to top](#flows-utility-types) @@ -700,8 +744,8 @@ import { $ReadOnly } from 'utility-types'; type Props = { name: string; age: number; visible: boolean }; -type ReadOnlyProps = $ReadOnly; // Expect: Readonly<{ name: string; age?: number | undefined; visible: boolean; }> +type ReadOnlyProps = $ReadOnly; ``` [⇧ back to top](#flows-utility-types) @@ -719,8 +763,8 @@ import { $Diff } from 'utility-types'; type Props = { name: string; age: number; visible: boolean }; type DefaultProps = { age: number }; -type RequiredProps = $Diff; // Expect: { name: string; visible: boolean; } +type RequiredProps = $Diff; ``` [⇧ back to top](#flows-utility-types) @@ -736,14 +780,14 @@ https://flow.org/en/docs/types/utilities/#toc-propertytype import { $PropertyType } from 'utility-types'; type Props = { name: string; age: number; visible: boolean }; -type NameType = $PropertyType; // Expect: string +type NameType = $PropertyType; type Tuple = [boolean, number]; -type A = $PropertyType; // Expect: boolean -type B = $PropertyType; +type A = $PropertyType; // Expect: number +type B = $PropertyType; ``` [⇧ back to top](#flows-utility-types) @@ -759,22 +803,22 @@ https://flow.org/en/docs/types/utilities/#toc-elementtype import { $ElementType } from 'utility-types'; type Props = { name: string; age: number; visible: boolean }; -type NameType = $ElementType; // Expect: string +type NameType = $ElementType; type Tuple = [boolean, number]; -type A = $ElementType; // Expect: boolean -type B = $ElementType; +type A = $ElementType; // Expect: number +type B = $ElementType; type Arr = boolean[]; -type ItemsType = $ElementType; // Expect: boolean +type ItemsType = $ElementType; type Obj = { [key: string]: number }; -type ValuesType = $ElementType; // Expect: number +type ValuesType = $ElementType; ``` [⇧ back to top](#flows-utility-types) @@ -818,8 +862,8 @@ import { $Shape } from 'utility-types'; type Props = { name: string; age: number; visible: boolean }; -type PartialProps = $Shape; // Expect: Partial +type PartialProps = $Shape; ``` [⇧ back to top](#flows-utility-types) @@ -836,8 +880,8 @@ import { $NonMaybeType } from 'utility-types'; type MaybeName = string | null; -type Name = $NonMaybeType; // Expect: string +type Name = $NonMaybeType; ``` [⇧ back to top](#flows-utility-types) diff --git a/package-lock.json b/package-lock.json index 28ee6fe..33a8c40 100644 --- a/package-lock.json +++ b/package-lock.json @@ -5431,9 +5431,9 @@ } }, "typescript": { - "version": "3.4.1", - "resolved": "https://registry.npmjs.org/typescript/-/typescript-3.4.1.tgz", - "integrity": "sha512-3NSMb2VzDQm8oBTLH6Nj55VVtUEpe/rgkIzMir0qVoLyjDZlnMBva0U6vDiV3IH+sl/Yu6oP5QwsAQtHPmDd2Q==", + "version": "3.5.0-dev.20190427", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-3.5.0-dev.20190427.tgz", + "integrity": "sha512-JvW5u5YEi1uyNFTTPYIKMI1zgKvvisMBXGGdF/ttHSSnNqSiMvdYuFYCCrJQzG74YWgmwZX6xweJGJO3eoOjNg==", "dev": true }, "uglify-js": { diff --git a/package.json b/package.json index 1a6205f..da67dfe 100644 --- a/package.json +++ b/package.json @@ -40,7 +40,7 @@ "prettier": "1.16.4", "ts-jest": "23.10.5", "tslint": "5.15.0", - "typescript": "3.4.1" + "typescript": "3.5.0-dev.20190427" }, "keywords": [ "typescript", diff --git a/src/index.ts b/src/index.ts index 13af3d9..b7d1c86 100644 --- a/src/index.ts +++ b/src/index.ts @@ -31,10 +31,12 @@ export { NonFunctionKeys, NonUndefined, Omit, + OptionalKeys, Overwrite, Primitive, PromiseType, ReadonlyKeys, + RequiredKeys, SetComplement, SetDifference, SetIntersection, diff --git a/src/mapped-types.spec.ts b/src/mapped-types.spec.ts index 4b3af1c..83b0ce8 100644 --- a/src/mapped-types.spec.ts +++ b/src/mapped-types.spec.ts @@ -34,6 +34,8 @@ import { _DeepRequiredObject, _DeepPartialObject, _DeepPartialArray, + RequiredKeys, + OptionalKeys, } from './mapped-types'; /** @@ -45,137 +47,167 @@ type DefaultProps = { age: number }; type NewProps = { age: string; other: string }; type MixedProps = { name: string; setName: (name: string) => void }; type ReadWriteProps = { readonly a: number; b: string }; +type RequiredOptionalProps = { + req: number; + reqUndef: number | undefined; + opt?: string; + optUndef?: number | undefined; +}; /** * Tests */ // @dts-jest:group Primitive -it('Primitive', () => { +{ // @dts-jest:pass:snap testType(); -}); +} // @dts-jest:group Falsey -it('Falsey', () => { +{ // @dts-jest:pass:snap testType(); -}); +} // @dts-jest:group SetIntersection -it('SetIntersection', () => { +{ // @dts-jest:pass:snap testType>(); // @dts-jest:pass:snap testType void), () => void>>(); -}); +} // @dts-jest:group SetDifference -it('SetDifference', () => { +{ // @dts-jest:pass:snap testType>(); // @dts-jest:pass:snap testType void), () => void>>(); -}); +} // @dts-jest:group SetComplement -it('SetComplement', () => { +{ // @dts-jest:pass:snap testType>(); -}); +} // @dts-jest:group SymmetricDifference -it('SymmetricDifference', () => { +{ // @dts-jest:pass:snap testType>(); -}); +} // @dts-jest:group NonUndefined -it('NonUndefined', () => { +{ // @dts-jest:pass:snap testType>(); // @dts-jest:pass:snap testType>(); -}); +} // @dts-jest:group FunctionKeys -it('FunctionKeys', () => { +{ // @dts-jest:pass:snap testType>(); -}); +} // @dts-jest:group NonFunctionKeys -it('NonFunctionKeys', () => { +{ // @dts-jest:pass:snap testType>(); -}); +} + +// @dts-jest:group WritableKeys +{ + // @dts-jest:pass:snap + testType>(); +} + +// @dts-jest:group ReadonlyKeys +{ + // @dts-jest:pass:snap + testType>(); +} + +// @dts-jest:group RequiredKeys +{ + // @dts-jest:pass:snap + testType>(); +} + +// @dts-jest:group OptionalKeys +{ + // @dts-jest:pass:snap + testType>(); +} // @dts-jest:group Omit -it('Omit', () => { +{ // @dts-jest:pass:snap testType>(); // @dts-jest:pass:snap testType>(); -}); +} // @dts-jest:group PickByValue -it('PickByValue', () => { +{ // @dts-jest:pass:snap testType>(); -}); +} // @dts-jest:group OmitByValue -it('OmitByValue', () => { +{ // @dts-jest:pass:snap testType>(); -}); +} // @dts-jest:group Intersection -it('Intersection', () => { +{ // @dts-jest:pass:snap testType>(); // @dts-jest:pass:snap testType>(); -}); +} // @dts-jest:group Diff -it('Diff', () => { +{ // @dts-jest:pass:snap testType>(); -}); +} // @dts-jest:group Subtract -it('Subtract', () => { +{ // @dts-jest:pass:snap testType>(); -}); +} // @dts-jest:group Overwrite -it('Overwrite', () => { +{ // @dts-jest:pass:snap testType>(); -}); +} // @dts-jest:group Assign -it('Assign', () => { +{ // @dts-jest:pass:snap testType>(); -}); +} // @dts-jest:group Unionize -it('Unionize', () => { +{ // @dts-jest:pass:snap testType>(); -}); +} // @dts-jest:group PromiseType -it('PromiseType', () => { +{ // @dts-jest:pass:snap testType>>(); -}); +} // @dts-jest:group DeepReadonly -it('DeepReadonly', () => { +{ type NestedProps = { first: { second: { @@ -213,10 +245,10 @@ it('DeepReadonly', () => { testType['first']['second']>(); // @dts-jest:pass:snap testType['first']['second']>>(); -}); +} // @dts-jest:group DeepRequired -it('DeepRequired', () => { +{ type NestedProps = { first?: { second?: { @@ -254,10 +286,10 @@ it('DeepRequired', () => { testType['first']['second']>(); // @dts-jest:pass:snap testType['first']['second']>>(); -}); +} // @dts-jest:group DeepNonNullable -it('DeepNonNullable', () => { +{ type NestedProps = { first?: null | { second?: null | { @@ -299,10 +331,10 @@ it('DeepNonNullable', () => { testType< ReturnType['first']['second']> >(); -}); +} // @dts-jest:group DeepPartial -it('DeepPartial', () => { +{ type NestedProps = { first: { second: { @@ -354,22 +386,10 @@ it('DeepPartial', () => { testType(); // @dts-jest:pass:snap testType>>(); -}); - -// @dts-jest:group WritableKeys -it('WritableKeys', () => { - // @dts-jest:pass:snap - testType>(); -}); - -// @dts-jest:group ReadonlyKeys -it('ReadonlyKeys', () => { - // @dts-jest:pass:snap - testType>(); -}); +} // @dts-jest:group Brand -it('Brand', () => { +{ // @dts-jest:pass:snap testType>(); -}); +} diff --git a/src/mapped-types.ts b/src/mapped-types.ts index 4148ade..624ff9e 100644 --- a/src/mapped-types.ts +++ b/src/mapped-types.ts @@ -8,7 +8,7 @@ * @desc Type representing primitive types in TypeScript: `number | boolean | string | symbol` * @example * // Expect: object - * // type ResultStripPrimitives = Exclude + * type ResultStripPrimitives = Exclude */ export type Primitive = number | boolean | string | symbol; @@ -17,7 +17,7 @@ export type Primitive = number | boolean | string | symbol; * @desc Type representing falsey values in TypeScript: `null | undefined | false | 0 | ''` * @example * // Expect: "a" | "b" - * // type ResultCompact = Exclude<'a' | 'b' | undefined | false, Falsey>; + * type ResultCompact = Exclude<'a' | 'b' | undefined | false, Falsey>; */ export type Falsey = null | undefined | false | 0 | ''; @@ -95,6 +95,73 @@ export type NonFunctionKeys = { [K in keyof T]: T[K] extends Function ? never : K }[keyof T]; +/** + * WritableKeys + * @desc get union type of keys that are writable in object type `T` + * Credit: Matt McCutchen + * https://stackoverflow.com/questions/52443276/how-to-exclude-getter-only-properties-from-type-in-typescript + * @example + * type Props = { readonly foo: string; bar: number }; + * + * // Expect: "bar" + * type WritableProps = WritableKeys; + */ +export type WritableKeys = { + [P in keyof T]-?: IfEquals< + { [Q in P]: T[P] }, + { -readonly [Q in P]: T[P] }, + P + > +}[keyof T]; + +/** + * ReadonlyKeys + * @desc get union type of keys that are readonly in object type `T` + * Credit: Matt McCutchen + * https://stackoverflow.com/questions/52443276/how-to-exclude-getter-only-properties-from-type-in-typescript + * @example + * type Props = { readonly foo: string; bar: number }; + * + * // Expect: "foo" + * type ReadonlyProps = ReadonlyKeys; + */ +export type ReadonlyKeys = { + [P in keyof T]-?: IfEquals< + { [Q in P]: T[P] }, + { -readonly [Q in P]: T[P] }, + never, + P + > +}[keyof T]; + +/** + * RequiredKeys + * @desc get union type of keys that are required in object type `T` + * @see https://stackoverflow.com/questions/52984808/is-there-a-way-to-get-all-required-properties-of-a-typescript-object + * @example + * type Props = { req: number; reqUndef: number | undefined; opt?: string; optUndef?: number | undefined; }; + * + * // Expect: "req" | "reqUndef" + * type RequiredProps = RequiredKeys; + */ +export type RequiredKeys = { + [K in keyof T]-?: {} extends Pick ? never : K +}[keyof T]; + +/** + * OptionalKeys + * @desc get union type of keys that are optional in object type `T` + * @see https://stackoverflow.com/questions/52984808/is-there-a-way-to-get-all-required-properties-of-a-typescript-object + * @example + * type Props = { req: number; reqUndef: number | undefined; opt?: string; optUndef?: number | undefined; }; + * + * // Expect: "opt" | "optUndef" + * type OptionalProps = OptionalKeys; + */ +export type OptionalKeys = { + [K in keyof T]-?: {} extends Pick ? K : never +}[keyof T]; + /** * Omit (complements Pick) * @desc From `T` remove a set of properties `K` @@ -392,43 +459,6 @@ type IfEquals = (() => T extends X ? A : B; -/** - * WritableKeys - * @desc get union type of keys that are writable in object type `T` - * Credit: Matt McCutchen - * https://stackoverflow.com/questions/52443276/how-to-exclude-getter-only-properties-from-type-in-typescript - * @example - * // Expect: "bar" - * type Props = { readonly foo: string; bar: number }; - * type WritableProps = WritableKeys; - */ -export type WritableKeys = { - [P in keyof T]-?: IfEquals< - { [Q in P]: T[P] }, - { -readonly [Q in P]: T[P] }, - P - > -}[keyof T]; - -/** - * ReadonlyKeys - * @desc get union type of keys that are readonly in object type `T` - * Credit: Matt McCutchen - * https://stackoverflow.com/questions/52443276/how-to-exclude-getter-only-properties-from-type-in-typescript - * @example - * // Expect: "foo" - * type Props = { readonly foo: string; bar: number }; - * type ReadonlyProps = ReadonlyKeys; - */ -export type ReadonlyKeys = { - [P in keyof T]-?: IfEquals< - { [Q in P]: T[P] }, - { -readonly [Q in P]: T[P] }, - never, - P - > -}[keyof T]; - /** * Brand * @desc Define nominal type of U based on type of T.