Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add RequireAllOrNone type #254

Merged
merged 3 commits into from Sep 10, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
1 change: 1 addition & 0 deletions index.d.ts
Expand Up @@ -11,6 +11,7 @@ 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 {RequireAllOrNone} from './source/require-all-or-none';
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 @@ -100,6 +100,7 @@ Click the type names for complete docs.
- [`MergeExclusive`](source/merge-exclusive.d.ts) - Create a type that has mutually exclusive keys.
- [`RequireAtLeastOne`](source/require-at-least-one.d.ts) - Create a type that requires at least one of the given keys.
- [`RequireExactlyOne`](source/require-exactly-one.d.ts) - Create a type that requires exactly a single key of the given keys and disallows more.
- [`RequireAllOrNone`](source/require-all-or-none.d.ts) - Create a type that requires all of the given keys or none of the given keys.
- [`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
36 changes: 36 additions & 0 deletions source/require-all-or-none.ts
@@ -0,0 +1,36 @@
/**
Create a type that requires all of the given keys or none of the given keys. The remaining keys are kept as is.

Use-cases:
- Creating interfaces for components with mutually-inclusive keys.

The caveat with `RequireAllOrNone` is that TypeScript doesn't always know at compile time every key that will exist at runtime. Therefore `RequireAllOrNone` can't do anything to prevent extra keys it doesn't know about.

@example
```
import {RequireAllOrNone} from 'type-fest';

type Responder = {
text?: () => string;
json?: () => string;
secure: boolean;
};

const responder1: RequireAllOrNone<Responder, 'text' | 'json'> = {
secure: true
};

const responder2: RequireAllOrNone<Responder, 'text' | 'json'> = {
text: () => '{"message": "hi"}',
json: () => '{"message": "ok"}',
secure: true
};
```

@category Utilities
*/
export type RequireAllOrNone<ObjectType, KeysType extends keyof ObjectType = never> = (
| Required<Pick<ObjectType, KeysType>> // Require all of the given keys.
| Partial<Record<KeysType, never>> // Require none of the given keys.
) &
Omit<ObjectType, KeysType>; // The rest of the keys.
24 changes: 24 additions & 0 deletions test-d/require-all-or-none.ts
@@ -0,0 +1,24 @@
import {expectError, expectAssignable} from 'tsd';
import {RequireAllOrNone} from '../index';

type SystemMessages = {
default: string;

macos: string;
linux: string;

optional?: string;
};

type ValidMessages = RequireAllOrNone<SystemMessages, 'macos' | 'linux'>;
const test = (_: ValidMessages): void => {}; // eslint-disable-line @typescript-eslint/no-empty-function

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

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

declare const oneWithoutKeys: RequireAllOrNone<{a: number; b: number}>;
expectAssignable<{a: number; b: number}>(oneWithoutKeys);