// oxlint-disable no-empty-object-type // oxlint-disable no-unsafe-function-type // oxlint-disable no-explicit-any import type { Nilable, NonNilable, NonNullable, NonUndefinedable, Nullable, Undefinedable, } from "./is.ts" import type { UnionComplement, UnionDifference, UnionIntersection } from "./union.ts" // ============================================================================ // Primitives // ============================================================================ /** * @description Strict version of `Omit`, `K` must be subset of `typeof T`. */ export type StrictOmit = Omit /** * @description 表示键为字符串的记录类型。 */ export type StringRecord = Record /** * @description 表示键为数字的记录类型。 */ export type NumberRecord = Record /** * @description 表示键为符号的记录类型。 */ export type SymbolRecord = Record /** * @description 表示键可以是字符串、数字或符号的记录类型。 */ export type HybridRecord = Record /** * @description 表示值类型为 `any` 的字符串键记录类型。 */ export type AnyStringRecord = StringRecord /** * @description 表示值类型为 `any` 的数字键记录类型。 */ export type AnyNumberRecord = NumberRecord /** * @description 表示值类型为 `any` 的符号键记录类型。 */ export type AnySymbolRecord = SymbolRecord /** * @description 表示值类型为 `any` 的混合键记录类型。 */ export type AnyHybridRecord = Record /** * @description 表示任意常见记录类型的联合。 */ export type AnyRecord = AnyStringRecord | AnyNumberRecord | AnySymbolRecord /** * @description 为对象类型附加一个依赖其键集合的品牌标记。 */ export type ObjectExact = A & { __brand: keyof A } export type ObjectPrettify = { [K in keyof T]: T[K] } & {} /** * @description 通过条件分发重新构造对象类型,以获得更易读的展开结果。 * * 这个版本会让字符串字面量泛化成 string。 */ export type ObjectPrettify2 = Target extends infer O ? { [K in keyof O]: O[K] } : never /** * @description 重新映射对象类型,使其在编辑器中以展开后的结构显示。 * * @see {@link https://stackoverflow.com/questions/78453542/intersection-with-object-in-prettify-helper-type} * @see {@link https://github.com/mylesmmurphy/prettify-ts} */ export type ObjectPrettify3 = Pick // ============================================================================ // Validation // ============================================================================ /** * @description If the `Target` is an empty Record/Object, then return `true`. * * @example * ``` * // Expect: true * type Example1 = ObjectIsEmpty<{}> * // Expect: false * type Example2 = ObjectIsEmpty<{ a: number }> * ``` */ export type ObjectIsEmpty = keyof Target extends never ? true : false /** * @description If the `Target` is an empty Record/Object, then return `never`. * * @see {@link https://github.com/ts-essentials/ts-essentials#nonemptyobject} * @see {@link https://github.com/ts-essentials/ts-essentials/tree/master/lib/non-empty-object} */ export type ObjectNonEmpty = keyof Target extends never ? never : Target // ============================================================================ // Extraction // ============================================================================ /** * @description Get the keys of an object type. * * @example * ``` * interface Props { name: string, age: number, visible: boolean } * // Expect: 'name' | 'age' | 'visible' * type Example1 = ObjectKeys * ``` */ export type ObjectKeys = Target extends AnyHybridRecord ? keyof Target : never /** * @example * ``` * interface Props { name: string, age: number, visible: boolean } * // Expect: string | number | boolean * type Example1 = ObjectValues * ``` */ export type ObjectValues = Target extends AnyHybridRecord ? Target[keyof Target] : never /** * @example * ``` * type MixedProps = {name: string; setName: (name: string) => void; someKeys?: string; someFn?: (...args: any) => any;}; * * // Expect: "setName | someFn" * type Example1 = FunctionKeys * ``` * * @see {@link https://github.com/piotrwitek/utility-types/blob/master/src/mapped-types.ts} */ export type FunctionKeys = { [Key in keyof Target]-?: NonUndefinedable extends Function ? Key : never }[keyof Target] /** * @example * ``` * type MixedProps = {name: string; setName: (name: string) => void; someKeys?: string; someFn?: (...args: any) => any;}; * * // Expect: "name | someKey" * type Example1 = NonFunctionKeys * ``` * * @see {@link https://github.com/piotrwitek/utility-types/blob/master/src/mapped-types.ts} */ export type NonFunctionKeys = { [Key in keyof Target]-?: NonUndefinedable extends Function ? never : Key }[keyof Target] /** * @see {@link https://github.com/ts-essentials/ts-essentials/blob/5aa1f264e77fb36bb3f673c49f00927c7c181a7f/lib/types.ts#L440 warning} * @see {@link https://stackoverflow.com/a/52473108/1815209 explain with comments} */ type IfEquals = (() => T extends X ? 1 : 2) extends () => T extends Y ? 1 : 2 ? A : B /** * @example * ``` * type Props = { readonly foo: string; bar: number }; * // Expect: "foo" * type Example1 = ReadonlyKeys * ``` * * @see {@link https://github.com/piotrwitek/utility-types/blob/master/src/mapped-types.ts} * @see {@link https://stackoverflow.com/questions/52443276/how-to-exclude-getter-only-properties-from-type-in-typescript} */ export type ReadonlyKeys = { [Key in keyof Target]-?: IfEquals< { [Q in Key]: Target[Key] }, { -readonly [Q in Key]: Target[Key] }, never, Key > }[keyof Target] /** * @example * ``` * interface Props { readonly foo: string, bar: number } * // Expect: "bar" * type Example1 = WritableKeys * ``` * * @see {@link https://github.com/piotrwitek/utility-types/blob/master/src/mapped-types.ts} * @see {@link https://stackoverflow.com/questions/52443276/how-to-exclude-getter-only-properties-from-type-in-typescript} */ export type WritableKeys = { [Key in keyof Target]-?: IfEquals< { [Q in Key]: Target[Key] }, { -readonly [Q in Key]: Target[Key] }, Key, never > }[keyof Target] /** * @example * ``` * type Props = { req: number; reqUndef: number | undefined; opt?: string; optUndef?: number | undefined; }; * // Expect: "req" | "reqUndef" * type Example1 = RequiredKeys * ``` * * @see {@link https://github.com/piotrwitek/utility-types/blob/master/src/mapped-types.ts} * @see {@link https://stackoverflow.com/questions/52984808/is-there-a-way-to-get-all-required-properties-of-a-typescript-object} */ export type RequiredKeys = { [Key in keyof Target]-?: Record extends Pick ? never : Key }[keyof Target] /** * @example * ``` * type Props = { req: number; reqUndef: number | undefined; opt?: string; optUndef?: number | undefined; }; * // Expect: "opt" | "optUndef" * type Example1 = OptionalKeys * ``` * * @see {@link https://github.com/piotrwitek/utility-types/blob/master/src/mapped-types.ts} * @see {@link https://stackoverflow.com/questions/52984808/is-there-a-way-to-get-all-required-properties-of-a-typescript-object} */ export type OptionalKeys = { [Key in keyof Target]-?: Record extends Pick ? Key : never }[keyof Target] /** * @example * ``` * interface Temp { req: number, reqUndef: number | undefined, opt?: string } * // Expect: 'req' * type Example1 = PickKeysByValue * // Expect: 'req' | 'reqUndef' * type Example2 = PickKeysByValue * ``` * (For ALL `ByValue` utilities) When we want to get 'opt' as result in above example, `PickKeysByValue` * will not work. We should use `PickKeysByValue` instead. It's so weird that we don't always * know whether the target key is optional or not in actual most of situations so we have to include `undefined` * in the target matching `ValueType`'s union. It's unnecessary and redundant. * * On the other hand, normal keys and optional keys are quite different. We indeed need an approach to * distinguish them. Seems like add `undefined` to the union of optional keys' value type is a reasonable way. * * Here our final choice is give `undefined` a chance until we get a more fantastic solution. Meanwhile, turn the * `exactOptionalPropertyTypes: true` typescript's compiler option on. It will make the use of `undefined` on optional * properties more reasonable. For more information on it: see {@link https://www.typescriptlang.org/tsconfig#exactOptionalPropertyTypes}, * there's also a use guide on that information, see {@link https://tkdodo.eu/blog/optional-vs-undefined}. */ export type PickKeysByValue = { [Key in keyof Target]-?: Target[Key] extends ValueType ? Key : never }[keyof Target] /** * @example * ``` * interface Temp { req: number, reqUndef: number | undefined, opt?: string } * // Expect: 'req' * type Example1 = PickKeysByValue * // Expect: 'reqUndef' * type Example2 = PickKeysByValue * ``` */ export type PickKeysByValueExact = { [Key in keyof Target]-?: [ValueType] extends [Target[Key]] ? [Target[Key]] extends [ValueType] ? Key : never : never }[keyof Target] /** * @example * ``` * interface Temp { req: number, reqUndef: number | undefined, opt?: string } * // Expect: { req: number } * type Example1 = PickByValue * // Expect: { req: number; reqUndef: number | undefined; } * type Example2 = PickByValue * ``` * * @see {@link https://medium.com/dailyjs/typescript-create-a-condition-based-subset-types-9d902cea5b8c} */ export type PickByValue = Pick> /** * @example * ``` * interface Temp { req: number, reqUndef: number | undefined, opt?: string } * // Expect: { req: number } * type Example1 = PickByValueExact * // Expect: { reqUndef: number | undefined; } * type Example2 = PickByValueExact * ``` * * @see {@link https://github.com/piotrwitek/utility-types/blob/master/src/mapped-types.ts} */ export type PickByValueExact = Pick< Target, PickKeysByValueExact > /** * @example * ``` * interface Temp { req: number, reqUndef: number | undefined, opt?: string } * // Expect: { reqUndef: number | undefined; opt?: string; } * type Example1 = OmitByValue * // Expect: { opt?: string; } * type Example2 = OmitByValue * ``` * * @see {@link https://medium.com/dailyjs/typescript-create-a-condition-based-subset-types-9d902cea5b8c} */ export type OmitByValue = Omit> /** * @example * ``` * interface Temp { req: number, reqUndef: number | undefined, opt?: string } * // Expect: { reqUndef: number | undefined; opt?: string; } * type Example1 = OmitByValueExact * // Expect: { req: number; opt?: string } * type Example2 = OmitByValueExact * ``` * * @see {@link https://github.com/piotrwitek/utility-types/blob/master/src/mapped-types.ts} */ export type OmitByValueExact = Omit< Target, PickKeysByValueExact > /** * @description ObjectNamedKeys * * @example * ``` * interface Temp { * name: string * age?: number * [key: string]: string | number * } * // Expect: 'name' | 'age' * type Example1 = ObjectNamedKeys * ``` * * @see {@link https://github.com/millsp/ts-toolbelt/blob/319e55123b9571d49f34eca3e5926e41ca73e0f3/sources/Any/KnownKeys.ts#L11} * @see {@link https://github.com/millsp/ts-toolbelt/issues/164 issue} * @see {@link https://stackoverflow.com/questions/51465182/how-to-remove-index-signature-using-mapped-types/51956054#51956054} */ export type ObjectNamedKeys = keyof { [Key in keyof Target as string extends Key ? never : number extends Key ? never : symbol extends Key ? never : Key]: Target[Key] } export type ObjectNamedParts = ObjectPrettify>> export type ObjectIndexedParts = ObjectPrettify>> export type ObjectHasNamedKeys = ObjectNamedKeys extends never ? false : true export type ObjectHasIndexedKeys = keyof ObjectIndexedParts extends never ? false : true // ============================================================================ // Manipulation // ============================================================================ /** * @example * ``` * interface Temp { a: string, b: number, c?: boolean } * // Expect: { a: string | undefined, b: number, c?: boolean } * type Example1 = UndefinedableByKeys * ``` */ export type UndefinedableByKeys = { [Key in keyof Target]: Key extends Keys ? Undefinedable : Target[Key] } export type UndefinedableAllKeys = UndefinedableByKeys /** * @example * ``` * interface Temp { a: string, b: number, c?: boolean } * // Expect: { a: string | null, b: number, c?: boolean } * type Example1 = NullableByKeys * ``` */ export type NullableByKeys = { [Key in keyof Target]: Key extends Keys ? Nullable : Target[Key] } export type NullableAllKeys = NullableByKeys /** * @example * ``` * interface Temp { a: string, b: number, c?: boolean } * // Expect: { a: string | null | undefined, b: number, c?: boolean } * type Example1 = NilableByKeys * ``` */ export type NilableByKeys = { [Key in keyof Target]: Key extends Keys ? Nilable : Target[Key] } export type NilableAllKeys = NilableByKeys /** * @example * ``` * interface Temp { a: string | undefined, b: number, c?: boolean | undefined } * // Expect: { a: string, b: number, c?: boolean } * type Example1 = NonUndefinedableAllKeys * ``` */ export type NonUndefinedableAllKeys = { [Key in keyof Target]: NonUndefinedable } /** * @example * ``` * interface Temp { a: string | undefined, b: number, c?: boolean } * // Expect: { a: string, b: number, c?: boolean } * type Example1 = NonUndefinedableByKeys * type Example2 = NonUndefinedableByKeys * * interface Temp { a: string | undefined, b: number, c?: boolean | undefined } * // Expect: { a: string, b: number, c?: boolean } * type Example3 = NonUndefinedableByKeys * // Expect: { a: string | undefined, b: number, c?: boolean } * type Example4 = NonUndefinedableByKeys * ``` */ export type NonUndefinedableByKeys = ObjectPrettify< NonUndefinedableAllKeys, Keys>>> & Pick, Keys>> & Partial, Keys>>>> & Pick, Keys>> > /** * @example * ``` * interface Temp { a: string | null, b: number, c?: boolean | null } * // Expect: { a: string, b: number, c?: boolean } * type Example1 = NonNullableAllKeys * ``` */ export type NonNullableAllKeys = { [Key in keyof Target]: NonNullable } /** * @example * ``` * interface Temp { a: string | null, b: number, c?: boolean } * // Expect: { a: string, b: number, c?: boolean } * type Example1 = NonNullableByKeys * type Example2 = NonNullableByKeys * * interface Temp { a: string | null, b: number, c?: boolean | null } * // Expect: { a: string, b: number, c?: boolean } * type Example3 = NonNullableByKeys * // Expect: { a: string | null, b: number, c?: boolean } * type Example4 = NonNullableByKeys * ``` */ export type NonNullableByKeys = ObjectPrettify< NonNullableAllKeys, Keys>>> & Pick, Keys>> & Partial, Keys>>>> & Pick, Keys>> > /** * @example * ``` * interface Temp { a: string | null | undefined, b: number, c?: boolean | null } * // Expect: { a: string, b: number, c?: boolean } * type Example1 = NonNilableAllKeys * ``` */ export type NonNilableAllKeys = { [Key in keyof Target]: NonNilable } /** * @example * ``` * interface Temp { a: string | null | undefined, b: number, c?: boolean } * // Expect: { a: string, b: number, c?: boolean } * type Example1 = NonNilableByKeys * type Example2 = NonNilableByKeys * * interface Temp { a: string | null | undefined, b: number, c?: boolean | null | undefined } * // Expect: { a: string, b: number, c?: boolean } * type Example3 = NonNilableByKeys * // Expect: { a: string | null | undefined, b: number, c?: boolean } * type Example4 = NonNilableByKeys * ``` */ export type NonNilableByKeys = ObjectPrettify< NonNilableAllKeys, Keys>>> & Pick, Keys>> & Partial, Keys>>>> & Pick, Keys>> > /** * @example * ``` * interface Temp { a?: string | undefined, b?: number, c?: boolean } * // Expect: { readonly a: string | undefined, readonly b: number, readonly c: boolean } * type Example1 = ReadonlyAllKeys * ``` */ export type ReadonlyAllKeys = { +readonly [Key in keyof Target]: Target[Key] } /** * @example * ``` * interface Temp { a?: string | undefined, b?: number, c?: boolean } * // Expect: { readonly a: string | undefined, readonly b: number, readonly c: boolean } * type Example1 = ReadonlyByKeys * ``` */ export type ReadonlyByKeys = ObjectPrettify< ReadonlyAllKeys>> & Pick> > /** * @example * ``` * interface Temp { readonly a?: string | undefined, readonly b?: number, readonly c?: boolean } * // Expect: { a: string | undefined, b: number, c: boolean } * type Example1 = WritableAllKeys * ``` */ export type WritableAllKeys = { -readonly [Key in keyof Target]: Target[Key] } /** * @example * ``` * interface Temp { readonly a?: string | undefined, readonly b?: number, readonly c?: boolean } * // Expect: { a: string | undefined, b: number, c: boolean } * type Example1 = WritableByKeys * ``` */ export type WritableByKeys = ObjectPrettify< WritableAllKeys>> & Pick> > /** * @example * ``` * interface Temp { a?: string | undefined, b?: number, c?: boolean } * // Expect: { a: string | undefined, b: number, c: boolean } * type Example1 = RequiredAllKeys * ``` */ export type RequiredAllKeys = { [Key in keyof Target]-?: Target[Key] } /** * @example * ``` * interface Temp { a?: string | undefined, b?: number, c?: boolean } * // Expect: { a: string | undefined, b: number, c: boolean } * type Example1 = RequiredByKeys * ``` */ export type RequiredByKeys = ObjectPrettify< RequiredAllKeys>> & Pick> > /** * @example * ``` * interface Temp { a: string | undefined, b: number, c?: boolean | undefined } * // Expect: { a?: string | undefined, b?: number, c?: boolean | undefined } * type Example1 = OptionalAllKeys * ``` */ export type OptionalAllKeys = { [Key in keyof Target]+?: Target[Key] } /** * @example * ``` * interface Temp { a: string | undefined, b: number, c?: boolean | undefined } * // Expect: { a?: string | undefined, b?: number, c?: boolean } * type Example1 = OptionalByKeys * ``` */ export type OptionalByKeys = ObjectPrettify< OptionalAllKeys>> & Pick> > /** * @description Given a type `{ a?: string, b: number }`, return `{ a: string, b?: number }`. * * @example * ``` * interface Temp { a: string, b?: number, c?: boolean } * // Expect: { a?: string, b: number, c: boolean } * type Example1 = ReverseOptional * ``` * * @see {@link ReverseRequired} * @see {@link https://stackoverflow.com/questions/57593022/reverse-required-and-optional-properties} */ export type ReverseOptional = ObjectPrettify< Required>> & OptionalAllKeys>> > /** * @see {@link ReverseOptional} */ export type ReverseRequired = ReverseOptional /** * @example * ``` * interface Temp { a: string, b?: number, c?: boolean } * * // Expect: { a: string, b: number, c?: boolean } * type Example1 = ReverseOptionalByKeys * ``` */ export type ReverseOptionalByKeys = ObjectPrettify< Omit & ReverseOptional> > /** * @see {@link ReverseOptionalByKeys} */ export type ReverseRequiredByKeys = ReverseOptionalByKeys< Target, Keys > /** * @example * ``` * type Props = { name: string; age: number; visible: boolean }; * type DefaultProps = { age: number }; * // Expect: { age: number; } * type Example1 = ObjectIntersection * ``` * * @see {@link https://github.com/piotrwitek/utility-types/blob/master/src/mapped-types.ts} */ export type ObjectIntersection = Pick< T, UnionIntersection > /** * @example * ``` * type Props = { name: string; age: number; visible: boolean }; * type DefaultProps = { age: number }; * // Expect: { name: string; visible: boolean; } * type Example1 = ObjectDifference * ``` * * @see {@link https://github.com/piotrwitek/utility-types/blob/master/src/mapped-types.ts} */ export type ObjectDifference = Pick< T, UnionDifference > /** * @example * ``` * type Props = { name: string; age: number; visible: boolean }; * type DefaultProps = { age: number }; * // Expect: { name: string; visible: boolean; } * type Example1 = ObjectComplement * ``` * * @see {@link https://github.com/piotrwitek/utility-types/blob/master/src/mapped-types.ts} */ export type ObjectComplement = Pick< T, UnionComplement > /** * @example * ``` * interface PropsA { name: string, age: number, visible: boolean } * interface PropsB { age: number, sex: string } * // Expect: { name: string; visible: boolean; sex: string }; * type Example1 = ObjectSymmetricDifference * // Expect: 'name' | 'visible' | 'sex' * type Example2 = keyof ObjectSymmetricDifference * ``` */ export type ObjectSymmetricDifference< T extends AnyHybridRecord, U extends AnyHybridRecord, > = ObjectPrettify & ObjectDifference> /** * @example * ``` * interface Props { name: string, age: number, visible: boolean } * interface NewProps { age: string, other: string } * // Expect: { name: string; age: string; visible: boolean; } * type Example1 = ObjectOverwrite * ``` * * @see {@link https://github.com/piotrwitek/utility-types/blob/master/src/mapped-types.ts} */ export type ObjectOverwrite< Target extends AnyHybridRecord, Patch extends AnyHybridRecord, > = ObjectPrettify & ObjectIntersection> /** * @example * ``` * interface Props { name: string, age: number, visible: boolean } * interface NewProps { age: string, other: string } * // Expect: { name: string; age: string; visible: boolean; other: string; } * type Example1 = ObjectAssign * ``` * * @see {@link https://github.com/piotrwitek/utility-types/blob/master/src/mapped-types.ts} */ export type ObjectAssign< Target extends AnyHybridRecord, Patch extends AnyHybridRecord, > = ObjectPrettify< // ObjectDifference & ObjectIntersection & ObjectDifference Omit & Patch > /** * @description Alias for `ObjectAssign`, defined for semantic of `ObjectMergeN`. * * @see {@link ObjectAssign} */ export type ObjectMerge< Target extends AnyHybridRecord, Patch extends AnyHybridRecord, > = ObjectAssign type InternalObjectMergeN< Targets extends AnyHybridRecord[], Result extends AnyHybridRecord, > = Targets extends [infer Head, ...infer Tail] ? InternalObjectMergeN< Tail extends AnyHybridRecord[] ? Tail : [], ObjectMerge > : Result /** * @example * ``` * interface a { a: string } * interface b { readonly b: string } * interface c { c?: string } * // Expect: { a: string; readonly b: string; c?: string; } * type Example1 = ObjectMergeN<[a, b, c]> * ``` */ export type ObjectMergeN = ObjectPrettify< InternalObjectMergeN > /** * @example * ``` * interface Props { name: string, age: number, visible: boolean } * // Expect: { name: string; } | { age: number; } | { visible: boolean; } * type UnionizedType = ObjectUnionize * ``` * * @see {@link https://github.com/piotrwitek/utility-types/blob/master/src/mapped-types.ts} */ export type ObjectUnionize = { [Key in keyof Target]: { [Q in Key]: Target[Key] } }[keyof Target] /** * @example * ``` * interface Temp { * readonly grandpa: { * readonly name: string * father?: { * readonly name?: string * son: { * readonly name?: string * } * } * } * } * // Expect: { grandpa?: { name?: string, father?: { name?: string, son?: { name?: string } } } } * type Example1 = ObjectDeepWritable * ``` */ export type ObjectDeepWritable = { -readonly [Key in keyof Target]: Target[Key] extends AnyStringRecord | undefined ? ObjectDeepWritable> : Target[Key] } /** * @example * ``` * interface Temp { * grandpa: { * name: string * father?: { * name?: string * son: { * name?: string * } * } * } * } * // Expect: { readonly grandpa?: { * // readonly name?: string, readonly father?: { readonly name?: string, readonly son?: { readonly name?: string } } * // } } * type Example1 = ObjectDeepReadonly * ``` */ export type ObjectDeepReadonly = { +readonly [Key in keyof Target]: Target[Key] extends AnyStringRecord | undefined ? ObjectDeepReadonly> : Target[Key] } /** * @example * ``` * interface Temp { * grandpa: { * name: string * father?: { * name?: string * son: { * name?: string * } * } * } * } * // Expect: { grandpa: { name: string, father: { name: string, son: { name: string } } } } * type Example1 = ObjectDeepRequired * ``` */ export type ObjectDeepRequired = { [Key in keyof Target]-?: Target[Key] extends AnyStringRecord | undefined ? ObjectDeepRequired> : Target[Key] } /** * @example * ``` * interface Temp { * grandpa: { * name: string * father?: { * name?: string * son: { * name?: string * } * } * } * } * // Expect: { grandpa?: { name?: string, father?: { name?: string, son?: { name?: string } } } } * type Example1 = ObjectDeepOptional * ``` */ export type ObjectDeepOptional = { [Key in keyof Target]+?: Target[Key] extends AnyStringRecord | undefined ? ObjectDeepOptional> : Target[Key] } // ============================================================================ // Conversion // ============================================================================ /** * @description Extract method signature as a standalone function. * * @example * ``` * type Obj = { method(a: number, b: string): boolean } * // Expect: (a: number, b: string) => boolean * type Example1 = ObjectToFunction * ``` */ export type ObjectToFunction = T[Key] extends (...args: infer P) => infer R ? (...args: P) => R : never