diff --git a/docs/.vitepress/en.mts b/docs/.vitepress/en.mts index fd853eb..aed4789 100644 --- a/docs/.vitepress/en.mts +++ b/docs/.vitepress/en.mts @@ -116,6 +116,10 @@ export default defineConfig({ text: 'PickWritable', link: '/reference/utilities/PickWritable', }, + { + text: 'RequiredExactlyOne', + link: '/reference/utilities/RequiredExactlyOne', + }, { text: 'Simplify', link: '/reference/utilities/Simplify', diff --git a/docs/.vitepress/ko.mts b/docs/.vitepress/ko.mts index 4ebd1f6..0c9c2b2 100644 --- a/docs/.vitepress/ko.mts +++ b/docs/.vitepress/ko.mts @@ -122,6 +122,10 @@ export default defineConfig({ text: 'PickWritable', link: '/ko/reference/utilities/PickWritable', }, + { + text: 'RequiredExactlyOne', + link: '/ko/reference/utilities/RequiredExactlyOne', + }, { text: 'Simplify', link: '/ko/reference/utilities/Simplify', diff --git a/docs/ko/reference/utilities/RequiredExactlyOne.md b/docs/ko/reference/utilities/RequiredExactlyOne.md new file mode 100644 index 0000000..5a202a0 --- /dev/null +++ b/docs/ko/reference/utilities/RequiredExactlyOne.md @@ -0,0 +1,44 @@ +# RequiredExactlyOne\ + +## 개요 + +지정된 키 중 정확히 하나를 필수로 만들고, 나머지 지정된 키는 never로 설정하며, 나머지 타입은 변경되지 않는 새로운 타입을 생성해요 + +## 문법 + +```ts +type RequiredExactlyOne = Simplify< + Omit & + { + [P in K]: Required> & Partial, never>>; + }[K] +>; +``` + +- **T**: 키를 선택할 원본 타입이에요. +- **K**: 원본 타입 T에서 필수로 설정될 키와 never로 설정될 나머지 키를 포함하는 키의 집합이에요. + +## 예제 + +```ts +type T0 = { + a?: number; + b: string; + c: boolean; +}; +type E0 = RequiredExactlyOne; // { a: number; b: string; c: boolean } + +type T1 = { + a?: number; + b: string; + c: boolean; +}; +type E1 = RequiredExactlyOne; // { a: number; b?: never; c: boolean } | { a?: never; b: string; c: boolean } + +type T2 = { + a: number; + b: string; + c: boolean; +}; +type E2 = RequiredExactlyOne; // { a: number; b?: never; c: boolean } | { a?: never; b: string; c: boolean } +``` diff --git a/docs/reference/utilities/RequiredExactlyOne.md b/docs/reference/utilities/RequiredExactlyOne.md new file mode 100644 index 0000000..f0c133a --- /dev/null +++ b/docs/reference/utilities/RequiredExactlyOne.md @@ -0,0 +1,44 @@ +# RequiredExactlyOne\ + +## Overview + +Creates a new type where exactly one of the specified keys is made required, and the other specified keys are set to never, while keeping the rest of the type unchanged. + +## Syntax + +```ts +type RequiredExactlyOne = Simplify< + Omit & + { + [P in K]: Required> & Partial, never>>; + }[K] +>; +``` + +- **T**: The original type from which keys are being picked. +- **K**: The keys from the original type `T` that one of the `K` is made required, and the other specified keys are set to never. + +## Example + +```ts +type T0 = { + a?: number; + b: string; + c: boolean; +}; +type E0 = RequiredExactlyOne; // { a: number; b: string; c: boolean } + +type T1 = { + a?: number; + b: string; + c: boolean; +}; +type E1 = RequiredExactlyOne; // { a: number; b?: never; c: boolean } | { a?: never; b: string; c: boolean } + +type T2 = { + a: number; + b: string; + c: boolean; +}; +type E2 = RequiredExactlyOne; // { a: number; b?: never; c: boolean } | { a?: never; b: string; c: boolean } +``` diff --git a/source/utilities/RequiredExactlyOne.d.ts b/source/utilities/RequiredExactlyOne.d.ts new file mode 100644 index 0000000..de454ec --- /dev/null +++ b/source/utilities/RequiredExactlyOne.d.ts @@ -0,0 +1,29 @@ +import { Simplify } from './Simplify'; + +/** + * @description + * Creates a new type where exactly one of the specified keys is made required, + * and the other specified keys are set to never, while keeping the rest of the type unchanged. + * + * @template T - The original type from which keys are being picked. + * @template K - The keys from the original type `T` that one of the `K` is made required and the others are set to never. + * + * @returns + * A new type where exactly one of the specified keys is required, + * and the other specified keys are set to never, with the rest of the properties from the original type unchanged. + * + * @example + * type T0 = { + * a: number; + * b: string; + * c: boolean; + * } + * + * type E0 = RequiredExactlyOne; // { a: number; b?: never; c: boolean } | { a?: never; b: string; c: boolean } + */ +export type RequiredExactlyOne = Simplify< + Omit & + { + [P in K]: Required> & Partial, never>>; + }[K] +>; diff --git a/source/utilities/index.ts b/source/utilities/index.ts index ced718a..a87f188 100644 --- a/source/utilities/index.ts +++ b/source/utilities/index.ts @@ -3,6 +3,7 @@ export type { PickOptional } from './PickOptional'; export type { PickReadonly } from './PickReadonly'; export type { PickRequired } from './PickRequired'; export type { PickWritable } from './PickWritable'; +export type { RequiredExactlyOne } from './RequiredExactlyOne'; export type { Simplify } from './Simplify'; export type { StrictExclude } from './StrictExclude'; export type { StrictExtract } from './StrictExtract'; diff --git a/test-d/utilities/RequireExactlyOne.test-d.ts b/test-d/utilities/RequireExactlyOne.test-d.ts new file mode 100644 index 0000000..9b0419e --- /dev/null +++ b/test-d/utilities/RequireExactlyOne.test-d.ts @@ -0,0 +1,32 @@ +import { RequiredExactlyOne } from '@/utilities'; +import { expectType } from 'tsd'; + +declare function requiredExactlyOne(): RequiredExactlyOne< + T, + K +>; + +// Should be exactly equal to original. +type T0 = { + a: number; + b: string; + c: boolean; +}; +type E0 = { a: number; b: string; c: boolean }; +expectType(requiredExactlyOne()); + +// Should require exactly one of 'a' or 'b'. +type T1 = T0; +type E1 = + | { a: number; b?: never; c: boolean } + | { a?: never; b: string; c: boolean }; +expectType(requiredExactlyOne()); + +// Should require 'a' to be present. +type T2 = { + a?: number; + b: string; + c: boolean; +}; +type E2 = { a: number; b: string; c: boolean }; +expectType(requiredExactlyOne());