Skip to content

Commit

Permalink
Add RequireExactlyOne type (#55)
Browse files Browse the repository at this point in the history
  • Loading branch information
kainiedziela authored and sindresorhus committed Sep 16, 2019
1 parent 80465bc commit 0a9d935
Show file tree
Hide file tree
Showing 4 changed files with 58 additions and 0 deletions.
1 change: 1 addition & 0 deletions index.d.ts
Expand Up @@ -7,6 +7,7 @@ export {Mutable} from './source/mutable';
export {Merge} from './source/merge';
export {MergeExclusive} from './source/merge-exclusive';
export {RequireAtLeastOne} from './source/require-at-least-one';
export {RequireExactlyOne} from './source/require-exactly-one';
export {PartialDeep} from './source/partial-deep';
export {ReadonlyDeep} from './source/readonly-deep';
export {LiteralUnion} from './source/literal-union';
Expand Down
1 change: 1 addition & 0 deletions readme.md
Expand Up @@ -69,6 +69,7 @@ Click the type names for complete docs.
- [`Merge`](source/merge.d.ts) - Merge two types into a new type. Keys of the second type overrides keys of the first type.
- [`MergeExclusive`](source/merge-exclusive.d.ts) - Create a type that has mutually exclusive properties.
- [`RequireAtLeastOne`](source/require-at-least-one.d.ts) - Create a type that requires at least one of the given properties.
- [`RequireExactlyOne`](source/require-one.d.ts) - Create a type that requires exactly a single property of the given properties and disallows more.
- [`PartialDeep`](source/partial-deep.d.ts) - Create a deeply optional version of another type. Use [`Partial<T>`](https://github.com/Microsoft/TypeScript/blob/2961bc3fc0ea1117d4e53bc8e97fa76119bc33e3/src/lib/es5.d.ts#L1401-L1406) if you only need one level deep.
- [`ReadonlyDeep`](source/readonly-deep.d.ts) - Create a deeply immutable version of an `object`/`Map`/`Set`/`Array` type. Use [`Readonly<T>`](https://github.com/Microsoft/TypeScript/blob/2961bc3fc0ea1117d4e53bc8e97fa76119bc33e3/src/lib/es5.d.ts#L1415-L1420) if you only need one level deep.
- [`LiteralUnion`](source/literal-union.d.ts) - Create a union type by combining primitive types and literal types without sacrificing auto-completion in IDEs for the literal type part of the union. Workaround for [Microsoft/TypeScript#29729](https://github.com/Microsoft/TypeScript/issues/29729).
Expand Down
32 changes: 32 additions & 0 deletions source/require-exactly-one.d.ts
@@ -0,0 +1,32 @@
/**
Create a type that requires exactly one of the given properties and disallows more. The remaining properties are kept as is.
Use-cases:
- Creating interfaces for components that only need one of the properties to display properly.
- Declaring generic properties in a single place for a single use-case that gets narrowed down via `RequireExactlyOne`.
The caveat with `RequireExactlyOne` is that TypeScript doesn't always know at compile time every property that will exist at runtime. Therefore `RequireExactlyOne` can't do anything to prevent extra properties it doesn't know about.
@example
```
import {RequireExactlyOne} from 'type-fest';
type Responder = {
text: () => string;
json: () => string;
secure: boolean;
};
const responder: RequireExactlyOne<Responder, 'text' | 'json'> = {
// Adding a `text` property here would cause a compile error.
json: () => '{"message": "ok"}',
secure: true
};
```
*/
export type RequireExactlyOne<ObjectType, KeysType extends keyof ObjectType = keyof ObjectType> =
{[Key in KeysType]: (
Required<Pick<ObjectType, Key>> &
Partial<Record<Exclude<KeysType, Key>, never>>
)}[KeysType] & Omit<ObjectType, KeysType>;
24 changes: 24 additions & 0 deletions test-d/require-exactly-one.ts
@@ -0,0 +1,24 @@
import {expectType, expectError} from 'tsd';
import {RequireExactlyOne} from '..';

type SystemMessages = {
default: string;

macos: string;
linux: string;

optional?: string;
};

type ValidMessages = RequireExactlyOne<SystemMessages, 'macos' | 'linux'>;
const test = (_: ValidMessages): void => {};

test({macos: 'hey', default: 'hello'});
test({linux: 'sup', optional: 'howdy', default: 'hello'});

expectError(test({}));
expectError(test({macos: 'hey', linux: 'sup', default: 'hello'}));

declare const oneWithoutKeys: RequireExactlyOne<{a: number; b: number}>;
expectType<{a: number} | {b: number}>(oneWithoutKeys);
expectError(expectType<{a: number; b: number}>(oneWithoutKeys));

0 comments on commit 0a9d935

Please sign in to comment.