Skip to content

Commit

Permalink
feat(RequiredExactlyOne): Add RequiredExactlyOne (#74)
Browse files Browse the repository at this point in the history
* feat(RequiredExactlyOne): Add RequiredExactlyOne type

* test(RequiredExactlyOne): Add RequiredExactlyOne test

* docs(RequiredExactlyOne): Add RequiredExactlyOne docs
  • Loading branch information
haejunejung authored Sep 24, 2024
1 parent b0c212d commit c90dd07
Show file tree
Hide file tree
Showing 7 changed files with 158 additions and 0 deletions.
4 changes: 4 additions & 0 deletions docs/.vitepress/en.mts
Original file line number Diff line number Diff line change
Expand Up @@ -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',
Expand Down
4 changes: 4 additions & 0 deletions docs/.vitepress/ko.mts
Original file line number Diff line number Diff line change
Expand Up @@ -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',
Expand Down
44 changes: 44 additions & 0 deletions docs/ko/reference/utilities/RequiredExactlyOne.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
# RequiredExactlyOne\<T, K>

## 개요

지정된 키 중 정확히 하나를 필수로 만들고, 나머지 지정된 키는 never로 설정하며, 나머지 타입은 변경되지 않는 새로운 타입을 생성해요

## 문법

```ts
type RequiredExactlyOne<T, K extends keyof T> = Simplify<
Omit<T, K> &
{
[P in K]: Required<Pick<T, P>> & Partial<Record<Exclude<K, P>, never>>;
}[K]
>;
```

- **T**: 키를 선택할 원본 타입이에요.
- **K**: 원본 타입 T에서 필수로 설정될 키와 never로 설정될 나머지 키를 포함하는 키의 집합이에요.

## 예제

```ts
type T0 = {
a?: number;
b: string;
c: boolean;
};
type E0 = RequiredExactlyOne<T0, 'a'>; // { a: number; b: string; c: boolean }

type T1 = {
a?: number;
b: string;
c: boolean;
};
type E1 = RequiredExactlyOne<T1, 'a' | 'b'>; // { a: number; b?: never; c: boolean } | { a?: never; b: string; c: boolean }

type T2 = {
a: number;
b: string;
c: boolean;
};
type E2 = RequiredExactlyOne<T2, 'a' | 'b'>; // { a: number; b?: never; c: boolean } | { a?: never; b: string; c: boolean }
```
44 changes: 44 additions & 0 deletions docs/reference/utilities/RequiredExactlyOne.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
# RequiredExactlyOne\<T, K>

## 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<T, K extends keyof T> = Simplify<
Omit<T, K> &
{
[P in K]: Required<Pick<T, P>> & Partial<Record<Exclude<K, P>, 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<T0, 'a'>; // { a: number; b: string; c: boolean }

type T1 = {
a?: number;
b: string;
c: boolean;
};
type E1 = RequiredExactlyOne<T1, 'a' | 'b'>; // { a: number; b?: never; c: boolean } | { a?: never; b: string; c: boolean }

type T2 = {
a: number;
b: string;
c: boolean;
};
type E2 = RequiredExactlyOne<T2, 'a' | 'b'>; // { a: number; b?: never; c: boolean } | { a?: never; b: string; c: boolean }
```
29 changes: 29 additions & 0 deletions source/utilities/RequiredExactlyOne.d.ts
Original file line number Diff line number Diff line change
@@ -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<T0, 'a' | 'b'>; // { a: number; b?: never; c: boolean } | { a?: never; b: string; c: boolean }
*/
export type RequiredExactlyOne<T, K extends keyof T> = Simplify<
Omit<T, K> &
{
[P in K]: Required<Pick<T, P>> & Partial<Record<Exclude<K, P>, never>>;
}[K]
>;
1 change: 1 addition & 0 deletions source/utilities/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand Down
32 changes: 32 additions & 0 deletions test-d/utilities/RequireExactlyOne.test-d.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
import { RequiredExactlyOne } from '@/utilities';
import { expectType } from 'tsd';

declare function requiredExactlyOne<T, K extends keyof T>(): 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<E0>(requiredExactlyOne<T0, 'a'>());

// 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<E1>(requiredExactlyOne<T1, 'a' | 'b'>());

// Should require 'a' to be present.
type T2 = {
a?: number;
b: string;
c: boolean;
};
type E2 = { a: number; b: string; c: boolean };
expectType<E2>(requiredExactlyOne<T2, 'a'>());

0 comments on commit c90dd07

Please sign in to comment.