From 9bb8bcb96bc76e7d06554da63c269d834abcf3ec Mon Sep 17 00:00:00 2001 From: Aaron Harper Date: Sat, 28 Aug 2021 14:38:14 -0400 Subject: [PATCH 1/3] Add RequireAllOrNone --- index.d.ts | 1 + readme.md | 1 + source/require-all-or-none.ts | 36 +++++++++++++++++++++++++++++++++++ test-d/require-all-or-none.ts | 25 ++++++++++++++++++++++++ 4 files changed, 63 insertions(+) create mode 100644 source/require-all-or-none.ts create mode 100644 test-d/require-all-or-none.ts diff --git a/index.d.ts b/index.d.ts index f7b4b3800..bb1ea9cc5 100644 --- a/index.d.ts +++ b/index.d.ts @@ -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'; diff --git a/readme.md b/readme.md index f0c29602d..7d67c353f 100644 --- a/readme.md +++ b/readme.md @@ -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`](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`](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). diff --git a/source/require-all-or-none.ts b/source/require-all-or-none.ts new file mode 100644 index 000000000..709b42225 --- /dev/null +++ b/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 = { + secure: true +}; + +const responder2: RequireAllOrNone = { + text: () => '{"message": "hi"}', + json: () => '{"message": "ok"}', + secure: true +}; +``` + +@category Utilities +*/ +export type RequireAllOrNone = ( + | Required> // Require all of the given keys. + | Partial> // Require none of the given keys. +) & + Omit; // The rest of the keys. diff --git a/test-d/require-all-or-none.ts b/test-d/require-all-or-none.ts new file mode 100644 index 000000000..24daf138a --- /dev/null +++ b/test-d/require-all-or-none.ts @@ -0,0 +1,25 @@ +import {expectError, expectAssignable} from 'tsd'; +import {RequireAllOrNone} from '../index'; + +type SystemMessages = { + default: string; + + macos: string; + linux: string; + + optional?: string; +}; + +type ValidMessages = RequireAllOrNone; +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); +expectError(expectAssignable<{a: number; b: number}>(oneWithoutKeys)); From 1a51cbd3b249cda7c5473c00194f86d855d82c48 Mon Sep 17 00:00:00 2001 From: Aaron Harper Date: Sat, 28 Aug 2021 14:54:15 -0400 Subject: [PATCH 2/3] Add type --- source/require-all-or-none.ts | 2 +- test-d/require-all-or-none.ts | 3 +-- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/source/require-all-or-none.ts b/source/require-all-or-none.ts index 709b42225..65dc09b1e 100644 --- a/source/require-all-or-none.ts +++ b/source/require-all-or-none.ts @@ -29,7 +29,7 @@ const responder2: RequireAllOrNone = { @category Utilities */ -export type RequireAllOrNone = ( +export type RequireAllOrNone = ( | Required> // Require all of the given keys. | Partial> // Require none of the given keys. ) & diff --git a/test-d/require-all-or-none.ts b/test-d/require-all-or-none.ts index 24daf138a..ef97a19c6 100644 --- a/test-d/require-all-or-none.ts +++ b/test-d/require-all-or-none.ts @@ -21,5 +21,4 @@ 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); -expectError(expectAssignable<{a: number; b: number}>(oneWithoutKeys)); +expectAssignable<{a: number; b: number}>(oneWithoutKeys); From 076dcf1968fb8669954edbd85e550582ed85426e Mon Sep 17 00:00:00 2001 From: Aaron Harper Date: Sun, 5 Sep 2021 18:40:37 -0400 Subject: [PATCH 3/3] Use tabs --- source/require-all-or-none.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/source/require-all-or-none.ts b/source/require-all-or-none.ts index 65dc09b1e..16faca724 100644 --- a/source/require-all-or-none.ts +++ b/source/require-all-or-none.ts @@ -30,7 +30,7 @@ const responder2: RequireAllOrNone = { @category Utilities */ export type RequireAllOrNone = ( - | Required> // Require all of the given keys. - | Partial> // Require none of the given keys. + | Required> // Require all of the given keys. + | Partial> // Require none of the given keys. ) & - Omit; // The rest of the keys. + Omit; // The rest of the keys.