1 | import type {ArrayElement, ObjectValue} from './internal';
|
2 | import type {IsEqual} from './is-equal';
|
3 | import type {KeysOfUnion} from './keys-of-union';
|
4 | import type {IsUnknown} from './is-unknown';
|
5 | import type {Primitive} from './primitive';
|
6 |
|
7 | /**
|
8 | Create a type from `ParameterType` and `InputType` and change keys exclusive to `InputType` to `never`.
|
9 | - Generate a list of keys that exists in `InputType` but not in `ParameterType`.
|
10 | - Mark these excess keys as `never`.
|
11 | */
|
12 | type ExactObject<ParameterType, InputType> = {[Key in keyof ParameterType]: Exact<ParameterType[Key], ObjectValue<InputType, Key>>}
|
13 | & Record<Exclude<keyof InputType, KeysOfUnion<ParameterType>>, never>;
|
14 |
|
15 | /**
|
16 | Create a type that does not allow extra properties, meaning it only allows properties that are explicitly declared.
|
17 |
|
18 | This is useful for function type-guarding to reject arguments with excess properties. Due to the nature of TypeScript, it does not complain if excess properties are provided unless the provided value is an object literal.
|
19 |
|
20 | *Please upvote [this issue](https://github.com/microsoft/TypeScript/issues/12936) if you want to have this type as a built-in in TypeScript.*
|
21 |
|
22 | @example
|
23 | ```
|
24 | type OnlyAcceptName = {name: string};
|
25 |
|
26 | function onlyAcceptName(arguments_: OnlyAcceptName) {}
|
27 |
|
28 | // TypeScript complains about excess properties when an object literal is provided.
|
29 | onlyAcceptName({name: 'name', id: 1});
|
30 | //=> `id` is excess
|
31 |
|
32 | // TypeScript does not complain about excess properties when the provided value is a variable (not an object literal).
|
33 | const invalidInput = {name: 'name', id: 1};
|
34 | onlyAcceptName(invalidInput); // No errors
|
35 | ```
|
36 |
|
37 | Having `Exact` allows TypeScript to reject excess properties.
|
38 |
|
39 | @example
|
40 | ```
|
41 | import {Exact} from 'type-fest';
|
42 |
|
43 | type OnlyAcceptName = {name: string};
|
44 |
|
45 | function onlyAcceptNameImproved<T extends Exact<OnlyAcceptName, T>>(arguments_: T) {}
|
46 |
|
47 | const invalidInput = {name: 'name', id: 1};
|
48 | onlyAcceptNameImproved(invalidInput); // Compilation error
|
49 | ```
|
50 |
|
51 | [Read more](https://stackoverflow.com/questions/49580725/is-it-possible-to-restrict-typescript-object-to-contain-only-properties-defined)
|
52 |
|
53 | @category Utilities
|
54 | */
|
55 | export type Exact<ParameterType, InputType> =
|
56 | // Before distributing, check if the two types are equal and if so, return the parameter type immediately
|
57 | IsEqual<ParameterType, InputType> extends true ? ParameterType
|
58 | // If the parameter is a primitive, return it as is immediately to avoid it being converted to a complex type
|
59 | : ParameterType extends Primitive ? ParameterType
|
60 | // If the parameter is an unknown, return it as is immediately to avoid it being converted to a complex type
|
61 | : IsUnknown<ParameterType> extends true ? unknown
|
62 | // If the parameter is a Function, return it as is because this type is not capable of handling function, leave it to TypeScript
|
63 | : ParameterType extends Function ? ParameterType
|
64 | // Convert union of array to array of union: A[] & B[] => (A & B)[]
|
65 | : ParameterType extends unknown[] ? Array<Exact<ArrayElement<ParameterType>, ArrayElement<InputType>>>
|
66 | // In TypeScript, Array is a subtype of ReadonlyArray, so always test Array before ReadonlyArray.
|
67 | : ParameterType extends readonly unknown[] ? ReadonlyArray<Exact<ArrayElement<ParameterType>, ArrayElement<InputType>>>
|
68 | : ExactObject<ParameterType, InputType>;
|