From a1920cea6d426fb561a51dc38d66755da02aed6a Mon Sep 17 00:00:00 2001 From: ash-zzz Date: Sat, 30 Mar 2024 03:20:34 -0400 Subject: [PATCH 1/7] Add SingleKey and IfEmptyObject --- index.d.ts | 2 ++ readme.md | 3 ++- source/if-empty-object.d.ts | 27 +++++++++++++++++++++++++++ source/single-key.d.ts | 26 ++++++++++++++++++++++++++ test-d/single-key.ts | 15 +++++++++++++++ 5 files changed, 72 insertions(+), 1 deletion(-) create mode 100644 source/if-empty-object.d.ts create mode 100644 source/single-key.d.ts create mode 100644 test-d/single-key.ts diff --git a/index.d.ts b/index.d.ts index 92f33bd91..357a97ca5 100644 --- a/index.d.ts +++ b/index.d.ts @@ -9,6 +9,7 @@ export type {KeysOfUnion} from './source/keys-of-union'; export type {DistributedOmit} from './source/distributed-omit'; export type {DistributedPick} from './source/distributed-pick'; export type {EmptyObject, IsEmptyObject} from './source/empty-object'; +export type {IfEmptyObject} from './source/if-empty-object'; export type {NonEmptyObject} from './source/non-empty-object'; export type {UnknownRecord} from './source/unknown-record'; export type {UnknownArray} from './source/unknown-array'; @@ -23,6 +24,7 @@ export type {RequireAtLeastOne} from './source/require-at-least-one'; export type {RequireExactlyOne} from './source/require-exactly-one'; export type {RequireAllOrNone} from './source/require-all-or-none'; export type {RequireOneOrNone} from './source/require-one-or-none'; +export type {SingleKey} from './source/single-key'; export type {OmitIndexSignature} from './source/omit-index-signature'; export type {PickIndexSignature} from './source/pick-index-signature'; export type {PartialDeep, PartialDeepOptions} from './source/partial-deep'; diff --git a/readme.md b/readme.md index bcac9527a..df5dcc696 100644 --- a/readme.md +++ b/readme.md @@ -110,7 +110,6 @@ Click the type names for complete docs. ### Utilities - [`EmptyObject`](source/empty-object.d.ts) - Represents a strictly empty plain object, the `{}` value. -- [`IsEmptyObject`](source/empty-object.d.ts) - Returns a `boolean` for whether the type is strictly equal to an empty plain object, the `{}` value. - [`NonEmptyObject`](source/non-empty-object.d.ts) - Represents an object with at least 1 non-optional key. - [`UnknownRecord`](source/unknown-record.d.ts) - Represents an object with `unknown` value. You probably want this instead of `{}`. - [`UnknownArray`](source/unknown-array.d.ts) - Represents an array with `unknown` value. @@ -125,6 +124,7 @@ Click the type names for complete docs. - [`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. - [`RequireOneOrNone`](source/require-one-or-none.d.ts) - Create a type that requires exactly a single key of the given keys and disallows more, or none of the given keys. +- [`SingleKey`](source/single-key.d.ts) - Create a type that only accepts an object with a single key. - [`RequiredDeep`](source/required-deep.d.ts) - Create a deeply required version of another type. Use [`Required`](https://www.typescriptlang.org/docs/handbook/utility-types.html#requiredtype) if you only need one level deep. - [`PickDeep`](source/pick-deep.d.ts) - Pick properties from a deeply-nested object. Use [`Pick`](https://www.typescriptlang.org/docs/handbook/utility-types.html#picktype-keys) if you only need one level deep. - [`OmitDeep`](source/omit-deep.d.ts) - Omit properties from a deeply-nested object. Use [`Omit`](https://www.typescriptlang.org/docs/handbook/utility-types.html#omittype-keys) if you only need one level deep. @@ -220,6 +220,7 @@ type ShouldBeNever = IfAny<'not any', 'not never', 'never'>; - [`IsAny`](source/is-any.d.ts) - Returns a boolean for whether the given type is `any`. (Conditional version: [`IfAny`](source/if-any.d.ts).) - [`IsNever`](source/is-never.d.ts) - Returns a boolean for whether the given type is `never`. (Conditional version: [`IfNever`](source/if-never.d.ts).) - [`IsUnknown`](source/is-unknown.d.ts) - Returns a boolean for whether the given type is `unknown`. (Conditional version: [`IfUnknown`](source/if-unknown.d.ts).) +- [`IsEmptyObject`](source/empty-object.d.ts) - Returns a boolean for whether the type is strictly equal to an empty plain object, the `{}` value. (Conditional version: [`IfEmptyObject`](source/if-empty-object.d.ts)) ### JSON diff --git a/source/if-empty-object.d.ts b/source/if-empty-object.d.ts new file mode 100644 index 000000000..3299bdabb --- /dev/null +++ b/source/if-empty-object.d.ts @@ -0,0 +1,27 @@ +import type {IsEmptyObject} from './empty-object'; + +/** +An if-else-like type that resolves depending on whether the given type is `{}`. + +@see {@link IsEmptyObject} + +@example +``` +import type {IfEmptyObject} from 'type-fest'; + +type ShouldBeTrue = IfEmptyObject<{}>; +//=> true + +type ShouldBeBar = IfEmptyObject<{ key: any }, 'foo', 'bar'>; +//=> 'bar' +``` + +@category Type Guard +@category Utilities +*/ + +export type IfEmptyObject< + T, + TypeIfEmptyObject = true, + TypeIfNotEmptyObject = false, +> = IsEmptyObject extends true ? TypeIfEmptyObject : TypeIfNotEmptyObject; diff --git a/source/single-key.d.ts b/source/single-key.d.ts new file mode 100644 index 000000000..8d745aeb7 --- /dev/null +++ b/source/single-key.d.ts @@ -0,0 +1,26 @@ +import type {IfEmptyObject} from '../index'; +import type {IsUnion} from './internal'; + +/** +Create a type that only accepts an object with a single key. + +@example +``` +import type {SingleKey} from 'type-fest'; + +const processOperation = (operation: SingleKey) => {}; + +processOperation({ operation: { name: 'add' } }); + +processOperation({ operation: { name: 'add' }, 'values': [1, 2] }); // Compliation error +``` + +@category Object + */ + +// export type SingleKey = +// IsUnion extends true ? never : {} extends ObjectType ? never : ObjectType; +export type SingleKey = + IsUnion extends true + ? never + : IfEmptyObject; diff --git a/test-d/single-key.ts b/test-d/single-key.ts new file mode 100644 index 000000000..f0a247a0c --- /dev/null +++ b/test-d/single-key.ts @@ -0,0 +1,15 @@ +import {expectNever, expectError, expectAssignable} from 'tsd'; +import type {SingleKey} from '../index'; + +const test = (_: SingleKey): void => {}; // eslint-disable-line @typescript-eslint/no-empty-function + +test({key: 'value'}); + +expectError(test({})); +expectError(test({key: 'value', otherKey: 'other value'})); + +declare const validObject: SingleKey<{key: string}>; +expectAssignable<{key: string}>(validObject); + +declare const invalidObject: SingleKey<{key1: string; key2: number}>; +expectNever(invalidObject); From d8131c743d5e46eaa565e50fd632892d3c095876 Mon Sep 17 00:00:00 2001 From: Sindre Sorhus Date: Sun, 31 Mar 2024 02:10:46 +0900 Subject: [PATCH 2/7] Update single-key.d.ts --- source/single-key.d.ts | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/source/single-key.d.ts b/source/single-key.d.ts index 8d745aeb7..c71c5791a 100644 --- a/source/single-key.d.ts +++ b/source/single-key.d.ts @@ -10,9 +10,19 @@ import type {SingleKey} from 'type-fest'; const processOperation = (operation: SingleKey) => {}; -processOperation({ operation: { name: 'add' } }); +processOperation({ + operation: { + name: 'add' + } + }); -processOperation({ operation: { name: 'add' }, 'values': [1, 2] }); // Compliation error +processOperation({ + operation: { + name: 'add' + }, + 'values': [1, 2] +}); +// Compilation error ``` @category Object From ff7ccdc77005e7ae32d47b3cecab52cfa1418ae6 Mon Sep 17 00:00:00 2001 From: Sindre Sorhus Date: Sun, 31 Mar 2024 02:11:31 +0900 Subject: [PATCH 3/7] Update single-key.d.ts --- source/single-key.d.ts | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/source/single-key.d.ts b/source/single-key.d.ts index c71c5791a..76f4ad0d1 100644 --- a/source/single-key.d.ts +++ b/source/single-key.d.ts @@ -12,15 +12,15 @@ const processOperation = (operation: SingleKey) => {}; processOperation({ operation: { - name: 'add' - } - }); + name: 'add' + } +}); processOperation({ operation: { - name: 'add' - }, - 'values': [1, 2] + name: 'add' + }, + values: [1, 2] }); // Compilation error ``` From 2c2d17cb67e6fffc95ed17946b815200ad565828 Mon Sep 17 00:00:00 2001 From: ash-zzz Date: Sat, 30 Mar 2024 17:45:48 -0400 Subject: [PATCH 4/7] Rename to SingleKeyObject and clean up documentation --- index.d.ts | 2 +- readme.md | 2 +- source/single-key-object.d.ts | 24 +++++++++++++++++++++++ source/single-key.d.ts | 36 ----------------------------------- test-d/single-key-object.ts | 15 +++++++++++++++ test-d/single-key.ts | 15 --------------- 6 files changed, 41 insertions(+), 53 deletions(-) create mode 100644 source/single-key-object.d.ts delete mode 100644 source/single-key.d.ts create mode 100644 test-d/single-key-object.ts delete mode 100644 test-d/single-key.ts diff --git a/index.d.ts b/index.d.ts index 357a97ca5..5c62cd009 100644 --- a/index.d.ts +++ b/index.d.ts @@ -24,7 +24,7 @@ export type {RequireAtLeastOne} from './source/require-at-least-one'; export type {RequireExactlyOne} from './source/require-exactly-one'; export type {RequireAllOrNone} from './source/require-all-or-none'; export type {RequireOneOrNone} from './source/require-one-or-none'; -export type {SingleKey} from './source/single-key'; +export type {SingleKeyObject} from './source/single-key-object'; export type {OmitIndexSignature} from './source/omit-index-signature'; export type {PickIndexSignature} from './source/pick-index-signature'; export type {PartialDeep, PartialDeepOptions} from './source/partial-deep'; diff --git a/readme.md b/readme.md index df5dcc696..1238a0efc 100644 --- a/readme.md +++ b/readme.md @@ -124,7 +124,7 @@ Click the type names for complete docs. - [`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. - [`RequireOneOrNone`](source/require-one-or-none.d.ts) - Create a type that requires exactly a single key of the given keys and disallows more, or none of the given keys. -- [`SingleKey`](source/single-key.d.ts) - Create a type that only accepts an object with a single key. +- [`SingleKeyObject`](source/single-key-object.d.ts) - Create a type that only accepts an object with a single key. - [`RequiredDeep`](source/required-deep.d.ts) - Create a deeply required version of another type. Use [`Required`](https://www.typescriptlang.org/docs/handbook/utility-types.html#requiredtype) if you only need one level deep. - [`PickDeep`](source/pick-deep.d.ts) - Pick properties from a deeply-nested object. Use [`Pick`](https://www.typescriptlang.org/docs/handbook/utility-types.html#picktype-keys) if you only need one level deep. - [`OmitDeep`](source/omit-deep.d.ts) - Omit properties from a deeply-nested object. Use [`Omit`](https://www.typescriptlang.org/docs/handbook/utility-types.html#omittype-keys) if you only need one level deep. diff --git a/source/single-key-object.d.ts b/source/single-key-object.d.ts new file mode 100644 index 000000000..23ddd1e84 --- /dev/null +++ b/source/single-key-object.d.ts @@ -0,0 +1,24 @@ +import type {IfEmptyObject} from '../index'; +import type {IsUnion} from './internal'; + +/** +Create a type that only accepts an object with a single key. + +@example +``` +import type {SingleKeyObject} from 'type-fest'; + +const someFunction = (parameter: SingleKeyObject) => {}; + +someFunction({ value: 'some value' }); + +someFunction({ value: 'some value', otherKey: 'some other value' }); +// Error: Argument of type '{ value: string; otherKey: string; }' is not assignable to parameter of type 'never'.ts(2345) +``` + +@category Object +*/ +export type SingleKeyObject = + IsUnion extends true + ? never + : IfEmptyObject; diff --git a/source/single-key.d.ts b/source/single-key.d.ts deleted file mode 100644 index 76f4ad0d1..000000000 --- a/source/single-key.d.ts +++ /dev/null @@ -1,36 +0,0 @@ -import type {IfEmptyObject} from '../index'; -import type {IsUnion} from './internal'; - -/** -Create a type that only accepts an object with a single key. - -@example -``` -import type {SingleKey} from 'type-fest'; - -const processOperation = (operation: SingleKey) => {}; - -processOperation({ - operation: { - name: 'add' - } -}); - -processOperation({ - operation: { - name: 'add' - }, - values: [1, 2] -}); -// Compilation error -``` - -@category Object - */ - -// export type SingleKey = -// IsUnion extends true ? never : {} extends ObjectType ? never : ObjectType; -export type SingleKey = - IsUnion extends true - ? never - : IfEmptyObject; diff --git a/test-d/single-key-object.ts b/test-d/single-key-object.ts new file mode 100644 index 000000000..cc06c0829 --- /dev/null +++ b/test-d/single-key-object.ts @@ -0,0 +1,15 @@ +import {expectNever, expectError, expectAssignable} from 'tsd'; +import type {SingleKeyObject} from '../index'; + +const test = (_: SingleKeyObject): void => {}; // eslint-disable-line @typescript-eslint/no-empty-function + +test({key: 'value'}); + +expectError(test({})); +expectError(test({key: 'value', otherKey: 'other value'})); + +declare const validObject: SingleKeyObject<{key: string}>; +expectAssignable<{key: string}>(validObject); + +declare const invalidObject: SingleKeyObject<{key1: string; key2: number}>; +expectNever(invalidObject); diff --git a/test-d/single-key.ts b/test-d/single-key.ts deleted file mode 100644 index f0a247a0c..000000000 --- a/test-d/single-key.ts +++ /dev/null @@ -1,15 +0,0 @@ -import {expectNever, expectError, expectAssignable} from 'tsd'; -import type {SingleKey} from '../index'; - -const test = (_: SingleKey): void => {}; // eslint-disable-line @typescript-eslint/no-empty-function - -test({key: 'value'}); - -expectError(test({})); -expectError(test({key: 'value', otherKey: 'other value'})); - -declare const validObject: SingleKey<{key: string}>; -expectAssignable<{key: string}>(validObject); - -declare const invalidObject: SingleKey<{key1: string; key2: number}>; -expectNever(invalidObject); From bc00c5ea9e8e8471b146aa54c7dc5e1b61666676 Mon Sep 17 00:00:00 2001 From: Sindre Sorhus Date: Sun, 31 Mar 2024 14:46:38 +0900 Subject: [PATCH 5/7] Update if-empty-object.d.ts --- source/if-empty-object.d.ts | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/source/if-empty-object.d.ts b/source/if-empty-object.d.ts index 3299bdabb..b422969e9 100644 --- a/source/if-empty-object.d.ts +++ b/source/if-empty-object.d.ts @@ -12,14 +12,13 @@ import type {IfEmptyObject} from 'type-fest'; type ShouldBeTrue = IfEmptyObject<{}>; //=> true -type ShouldBeBar = IfEmptyObject<{ key: any }, 'foo', 'bar'>; +type ShouldBeBar = IfEmptyObject<{key: any}, 'foo', 'bar'>; //=> 'bar' ``` @category Type Guard @category Utilities */ - export type IfEmptyObject< T, TypeIfEmptyObject = true, From 0e09c4898c97c546d8db80ab5ed8c0230d40322d Mon Sep 17 00:00:00 2001 From: Sindre Sorhus Date: Sun, 31 Mar 2024 14:47:34 +0900 Subject: [PATCH 6/7] Update single-key-object.d.ts --- source/single-key-object.d.ts | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/source/single-key-object.d.ts b/source/single-key-object.d.ts index 23ddd1e84..b8e3b8637 100644 --- a/source/single-key-object.d.ts +++ b/source/single-key-object.d.ts @@ -10,9 +10,14 @@ import type {SingleKeyObject} from 'type-fest'; const someFunction = (parameter: SingleKeyObject) => {}; -someFunction({ value: 'some value' }); +someFunction({ + value: 'some value' +}); -someFunction({ value: 'some value', otherKey: 'some other value' }); +someFunction({ + value: 'some value', + otherKey: 'some other value' +}); // Error: Argument of type '{ value: string; otherKey: string; }' is not assignable to parameter of type 'never'.ts(2345) ``` From d6fa1d0e55ca717fa217fd6a71aa2fa1b082ee14 Mon Sep 17 00:00:00 2001 From: Sindre Sorhus Date: Sun, 31 Mar 2024 14:54:34 +0900 Subject: [PATCH 7/7] Update single-key-object.d.ts --- source/single-key-object.d.ts | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/source/single-key-object.d.ts b/source/single-key-object.d.ts index b8e3b8637..1f3930b04 100644 --- a/source/single-key-object.d.ts +++ b/source/single-key-object.d.ts @@ -11,14 +11,14 @@ import type {SingleKeyObject} from 'type-fest'; const someFunction = (parameter: SingleKeyObject) => {}; someFunction({ - value: 'some value' + value: true }); someFunction({ - value: 'some value', - otherKey: 'some other value' + value: true, + otherKey: true }); -// Error: Argument of type '{ value: string; otherKey: string; }' is not assignable to parameter of type 'never'.ts(2345) +// Error: Argument of type '{value: boolean; otherKey: boolean}' is not assignable to parameter of type 'never'.ts(2345) ``` @category Object