1 | import type {ArrayElement, ObjectValue} from './internal';
|
2 | import type {Opaque, TagContainer} from './opaque';
|
3 | import type {IsEqual} from './is-equal';
|
4 | import type {KeysOfUnion} from './keys-of-union';
|
5 |
|
6 | /**
|
7 | Create a type from `ParameterType` and `InputType` and change keys exclusive to `InputType` to `never`.
|
8 | - Generate a list of keys that exists in `InputType` but not in `ParameterType`.
|
9 | - Mark these excess keys as `never`.
|
10 | */
|
11 | type ExactObject<ParameterType, InputType> = {[Key in keyof ParameterType]: Exact<ParameterType[Key], ObjectValue<InputType, Key>>}
|
12 | & Record<Exclude<keyof InputType, KeysOfUnion<ParameterType>>, never>;
|
13 |
|
14 | /**
|
15 | Create a type that does not allow extra properties, meaning it only allows properties that are explicitly declared.
|
16 |
|
17 | 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.
|
18 |
|
19 | *Please upvote [this issue](https://github.com/microsoft/TypeScript/issues/12936) if you want to have this type as a built-in in TypeScript.*
|
20 |
|
21 | @example
|
22 | ```
|
23 | type OnlyAcceptName = {name: string};
|
24 |
|
25 | function onlyAcceptName(arguments_: OnlyAcceptName) {}
|
26 |
|
27 | // TypeScript complains about excess properties when an object literal is provided.
|
28 | onlyAcceptName({name: 'name', id: 1});
|
29 | //=> `id` is excess
|
30 |
|
31 | // TypeScript does not complain about excess properties when the provided value is a variable (not an object literal).
|
32 | const invalidInput = {name: 'name', id: 1};
|
33 | onlyAcceptName(invalidInput); // No errors
|
34 | ```
|
35 |
|
36 | Having `Exact` allows TypeScript to reject excess properties.
|
37 |
|
38 | @example
|
39 | ```
|
40 | import {Exact} from 'type-fest';
|
41 |
|
42 | type OnlyAcceptName = {name: string};
|
43 |
|
44 | function onlyAcceptNameImproved<T extends Exact<OnlyAcceptName, T>>(arguments_: T) {}
|
45 |
|
46 | const invalidInput = {name: 'name', id: 1};
|
47 | onlyAcceptNameImproved(invalidInput); // Compilation error
|
48 | ```
|
49 |
|
50 | [Read more](https://stackoverflow.com/questions/49580725/is-it-possible-to-restrict-typescript-object-to-contain-only-properties-defined)
|
51 |
|
52 | @category Utilities
|
53 | */
|
54 | export type Exact<ParameterType, InputType> =
|
55 | IsEqual<ParameterType, InputType> extends true ? ParameterType
|
56 | // Convert union of array to array of union: A[] & B[] => (A & B)[]
|
57 | : ParameterType extends unknown[] ? Array<Exact<ArrayElement<ParameterType>, ArrayElement<InputType>>>
|
58 | // In TypeScript, Array is a subtype of ReadonlyArray, so always test Array before ReadonlyArray.
|
59 | : ParameterType extends readonly unknown[] ? ReadonlyArray<Exact<ArrayElement<ParameterType>, ArrayElement<InputType>>>
|
60 | // Leave tagged types as-is. We could try to make the untagged part Exact, and just leave the tag as-is, but that seems to create instanitation excessively deep errors.
|
61 | : ParameterType extends TagContainer<unknown> ? ParameterType
|
62 | : ParameterType extends object ? ExactObject<ParameterType, InputType>
|
63 | : ParameterType;
|