feat(typescript-types): Make Assign assignable to Generics by removing optionals
This commit is contained in:
@@ -24,6 +24,7 @@ The types included in this library are categorized by their purpose.
|
|||||||
| --------------------------------------- | --------------------------------------------------------------------------------------------------------- |
|
| --------------------------------------- | --------------------------------------------------------------------------------------------------------- |
|
||||||
| [`IsAny<T>`][] | `true` if `T` is `any`, `false` otherwise (`null`, `undefined` also yield `false`) |
|
| [`IsAny<T>`][] | `true` if `T` is `any`, `false` otherwise (`null`, `undefined` also yield `false`) |
|
||||||
| [`IsNever<T>`][] | `true` if `T` is `never`, `false` otherwise (`null`, `undefined`, `any` also yield `false`) |
|
| [`IsNever<T>`][] | `true` if `T` is `never`, `false` otherwise (`null`, `undefined`, `any` also yield `false`) |
|
||||||
|
| [`IsUndefined<T>`][] | `true` if `T` is `undefined`, `false` otherwise (`null`, `never`, `any` also yield `false`). |
|
||||||
| [`If<Test, TrueBranch, FalseBranch>`][] | Returns `TrueBranch` if `Test` is `true`, `FalseBranch` otherwise[^if_remark]. |
|
| [`If<Test, TrueBranch, FalseBranch>`][] | Returns `TrueBranch` if `Test` is `true`, `FalseBranch` otherwise[^if_remark]. |
|
||||||
| [`IsKeyOf<T, K>`][] | `true` if `K` is a key of `T`, `false` otherwise. If `T` is `any`, any `K` but `never` will yield `true`. |
|
| [`IsKeyOf<T, K>`][] | `true` if `K` is a key of `T`, `false` otherwise. If `T` is `any`, any `K` but `never` will yield `true`. |
|
||||||
| [`IsEmptyString<S>`][] | `true` if `S` is the empty string `''`, `false` otherwise.[^is-empty-string_remark] |
|
| [`IsEmptyString<S>`][] | `true` if `S` is the empty string `''`, `false` otherwise.[^is-empty-string_remark] |
|
||||||
@@ -34,6 +35,7 @@ The types included in this library are categorized by their purpose.
|
|||||||
|
|
||||||
[`IsAny<T>`]: src/is-any.ts
|
[`IsAny<T>`]: src/is-any.ts
|
||||||
[`IsNever<T>`]: src/is-never.ts
|
[`IsNever<T>`]: src/is-never.ts
|
||||||
|
[`IsUndefined<T>`]: src/is-undefined.ts
|
||||||
[`If<Test, TrueBranch, FalseBranch>`]: src/if.ts
|
[`If<Test, TrueBranch, FalseBranch>`]: src/if.ts
|
||||||
[`IsKeyOf<T, K>`]: src/is-key-of.ts
|
[`IsKeyOf<T, K>`]: src/is-key-of.ts
|
||||||
[`IsEmptyString<S>`]: src/is-empty-string.ts
|
[`IsEmptyString<S>`]: src/is-empty-string.ts
|
||||||
@@ -41,12 +43,14 @@ The types included in this library are categorized by their purpose.
|
|||||||
#### Extraction Types
|
#### Extraction Types
|
||||||
|
|
||||||
| Type | Description |
|
| Type | Description |
|
||||||
| ----------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
| -------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
||||||
| [`OptionalKeysOf<T>`][] | A union of all keys of `T` that are marked as optional. If `T` is a union, a union of the optional keys of all union members of `T` is returned[^optional-keys-of_remark]. |
|
| [`OptionalKeysOf<T>`][] | A union of all keys of `T` that are marked as optional. If `T` is a union, a union of the optional keys of all union members of `T` is returned[^optional-keys-of_remark]. |
|
||||||
|
| [`PickAssignable<T, K>`][] | Return a mapped type with all keys of `T` that extend `K`. If no key does extend `K` an empty type is returned. |
|
||||||
|
|
||||||
[^optional-keys-of_remark]: If `T` is `any`, it returns a union of string and number 'string | number'.
|
[^optional-keys-of_remark]: If `T` is `any`, it returns a union of string and number 'string | number'.
|
||||||
|
|
||||||
[`OptionalKeysOf<T>`]: src/optional-keys-of.ts
|
[`OptionalKeysOf<T>`]: src/optional-keys-of.ts
|
||||||
|
[`PickAssignable<T, K>`]: src/pick-assignable.ts
|
||||||
|
|
||||||
#### Conversion Types
|
#### Conversion Types
|
||||||
|
|
||||||
|
|||||||
@@ -1,15 +1,19 @@
|
|||||||
import type { OptionalKeysOf } from './optional-keys-of.js'
|
import type { OptionalKeysOf } from './optional-keys-of.js'
|
||||||
|
import type { Simplify } from './simplify.js'
|
||||||
|
import type { PickAssignable } from './pick-assignable.js'
|
||||||
|
|
||||||
type GetNonOptionalValueAt<Obj, Key, Default> = Key extends keyof Obj
|
type RemoveOptionalValues<Obj extends object> = Omit<Obj, OptionalKeysOf<Obj>>
|
||||||
? undefined extends Obj[Key]
|
type RemoveRequiredKeysOfFrom<Obj1 extends object, Obj2> = Omit<Obj2, keyof RemoveOptionalValues<Obj1>>
|
||||||
? Default
|
type MergeInto<Target, Source extends object> = RemoveRequiredKeysOfFrom<Source, Target> &
|
||||||
: Obj[Key]
|
RemoveOptionalValues<Source>
|
||||||
: Default
|
type MergeDefaults<
|
||||||
|
Shape extends object,
|
||||||
|
Defaults extends Pick<Required<Shape>, OptionalKeysOf<Shape>>,
|
||||||
|
Obj extends Shape
|
||||||
|
> = PickAssignable<MergeInto<Defaults, Obj>, keyof Shape>
|
||||||
|
|
||||||
export type Assign<
|
export type Assign<
|
||||||
Shape extends object,
|
Shape extends object,
|
||||||
Defaults extends Pick<Required<Shape>, OptionalKeysOf<Shape>>,
|
Defaults extends Pick<Required<Shape>, OptionalKeysOf<Shape>>,
|
||||||
Obj extends Shape
|
Obj extends Shape
|
||||||
> = {
|
> = Simplify<Required<Shape> & MergeDefaults<Shape, Defaults, Obj>>
|
||||||
[K in keyof Shape]-?: GetNonOptionalValueAt<Obj, K, K extends keyof Defaults ? Defaults[K] : never>
|
|
||||||
}
|
|
||||||
|
|||||||
9
packages/typescript-types/src/is-undefined.ts
Normal file
9
packages/typescript-types/src/is-undefined.ts
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
import type { If } from './if.js'
|
||||||
|
import type { IsAny } from './is-any.js'
|
||||||
|
import type { IsNever } from './is-never.js'
|
||||||
|
|
||||||
|
export type IsUndefined<T> = If<
|
||||||
|
IsAny<T>,
|
||||||
|
false,
|
||||||
|
If<IsNever<T>, false, T extends undefined ? (undefined extends T ? true : false) : false>
|
||||||
|
>
|
||||||
15
packages/typescript-types/src/pick-assignable.ts
Normal file
15
packages/typescript-types/src/pick-assignable.ts
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
import type { If } from './if.js'
|
||||||
|
import type { IsNever } from './is-never.js'
|
||||||
|
import type { IsUndefined } from './is-undefined.js'
|
||||||
|
|
||||||
|
export type PickAssignable<T, Keys> = If<
|
||||||
|
IsNever<T>,
|
||||||
|
{},
|
||||||
|
If<
|
||||||
|
IsUndefined<T>,
|
||||||
|
{},
|
||||||
|
{
|
||||||
|
[K in keyof T as K extends Keys ? K : never]: T[K]
|
||||||
|
}
|
||||||
|
>
|
||||||
|
>
|
||||||
@@ -11,21 +11,21 @@ interface OptionsWithRequired {
|
|||||||
interface DefaultValues {
|
interface DefaultValues {
|
||||||
tag: 'Max Mustermann'
|
tag: 'Max Mustermann'
|
||||||
age: 17
|
age: 17
|
||||||
relations: []
|
relations: Array<OptionsWithRequired>
|
||||||
}
|
}
|
||||||
|
|
||||||
expect<Assign<OptionsWithRequired, DefaultValues, { name: 'Another Name' }>>().type.toBe<{
|
expect<Assign<OptionsWithRequired, DefaultValues, { name: 'Another Name' }>>().type.toBe<{
|
||||||
name: 'Another Name'
|
name: 'Another Name'
|
||||||
tag: 'Max Mustermann'
|
tag: 'Max Mustermann'
|
||||||
age: 17
|
age: 17
|
||||||
relations: []
|
relations: Array<OptionsWithRequired>
|
||||||
}>()
|
}>()
|
||||||
|
|
||||||
expect<Assign<OptionsWithRequired, DefaultValues, { name: 'Another Name'; age: 18 }>>().type.toBe<{
|
expect<Assign<OptionsWithRequired, DefaultValues, { name: 'Another Name'; age: 18 }>>().type.toBe<{
|
||||||
name: 'Another Name'
|
name: 'Another Name'
|
||||||
tag: 'Max Mustermann'
|
tag: 'Max Mustermann'
|
||||||
age: 18
|
age: 18
|
||||||
relations: []
|
relations: Array<OptionsWithRequired>
|
||||||
}>()
|
}>()
|
||||||
|
|
||||||
interface Options {
|
interface Options {
|
||||||
@@ -39,8 +39,14 @@ expect<Assign<Options, DefaultValues, {}>>().type.toBe<{
|
|||||||
}>()
|
}>()
|
||||||
|
|
||||||
interface SpecifiedOptions extends Options {}
|
interface SpecifiedOptions extends Options {}
|
||||||
|
|
||||||
expect<Assign<Options, DefaultValues, SpecifiedOptions>>().type.toBe<{
|
expect<Assign<Options, DefaultValues, SpecifiedOptions>>().type.toBe<{
|
||||||
tag: 'Max Mustermann'
|
tag: 'Max Mustermann'
|
||||||
age: 17
|
age: 17
|
||||||
}>()
|
}>()
|
||||||
|
|
||||||
|
type SomethingRequiringAllOptions<Opts extends Required<Options>> = Opts
|
||||||
|
type SomethingProvidingDefaultOptions<Opts extends Options = {}> = SomethingRequiringAllOptions<
|
||||||
|
Assign<Options, DefaultValues, Opts>
|
||||||
|
>
|
||||||
|
|
||||||
|
expect<SomethingProvidingDefaultOptions<{ age: 5 }>>().type.toBe<{ tag: 'Max Mustermann'; age: 5 }>()
|
||||||
|
|||||||
11
packages/typescript-types/test/is-undefined.tst.ts
Normal file
11
packages/typescript-types/test/is-undefined.tst.ts
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
import type { IsUndefined } from '@/is-undefined.js'
|
||||||
|
import { expect } from 'tstyche'
|
||||||
|
|
||||||
|
expect<IsUndefined<undefined>>().type.toBe<true>()
|
||||||
|
|
||||||
|
expect<IsUndefined<1>>().type.toBe<false>()
|
||||||
|
expect<IsUndefined<'somestring'>>().type.toBe<false>()
|
||||||
|
expect<IsUndefined<null>>().type.toBe<false>()
|
||||||
|
expect<IsUndefined<unknown>>().type.toBe<false>()
|
||||||
|
expect<IsUndefined<any>>().type.toBe<false>()
|
||||||
|
expect<IsUndefined<never>>().type.toBe<false>()
|
||||||
21
packages/typescript-types/test/pick-assignable.tst.ts
Normal file
21
packages/typescript-types/test/pick-assignable.tst.ts
Normal file
@@ -0,0 +1,21 @@
|
|||||||
|
import type { PickAssignable } from '@/pick-assignable.js'
|
||||||
|
import { expect } from 'tstyche'
|
||||||
|
|
||||||
|
interface Example {
|
||||||
|
name: string
|
||||||
|
age: number
|
||||||
|
}
|
||||||
|
|
||||||
|
expect<PickAssignable<Example, 'name' | 'age'>>().type.toBe<Example>()
|
||||||
|
expect<PickAssignable<Example, any>>().type.toBe<Example>()
|
||||||
|
|
||||||
|
expect<PickAssignable<Example, 'name'>>().type.toBe<{ name: string }>()
|
||||||
|
|
||||||
|
expect<PickAssignable<Example, 'nonexisting'>>().type.toBe<{}>()
|
||||||
|
expect<PickAssignable<Example, never>>().type.toBe<{}>()
|
||||||
|
|
||||||
|
expect<PickAssignable<unknown, 'age'>>().type.toBe<{}>()
|
||||||
|
expect<PickAssignable<any, 'age'>>().type.toBe<{}>()
|
||||||
|
|
||||||
|
expect<PickAssignable<undefined, 'age'>>().type.toBe<{}>()
|
||||||
|
expect<PickAssignable<never, 'age'>>().type.toBe<{}>()
|
||||||
Reference in New Issue
Block a user