Merge pull request #72 from Beraliv/feat/partial-deep

refact: PartialDeep alignment for different structures
This commit is contained in:
Gabriel Vergnaud
2023-03-26 20:04:28 +02:00
committed by GitHub
3 changed files with 301 additions and 53 deletions

View File

@@ -339,7 +339,7 @@ export namespace Objects {
* @returns The object with its properties made readonly
* @example
* ```ts
* type T0 = Call<Objects.Readonly, {a: 1; b: true }>; // { readonly a:1; readonly b: true}
* type T0 = Call<Objects.Readonly, { a: 1; b: true }>; // { readonly a:1; readonly b: true}
* type T1 = Eval<Objects.Readonly<{ a: 1; b: true }>>; // { readonly a:1; readonly b: true}
* ```
*/
@@ -356,7 +356,7 @@ export namespace Objects {
* @returns The object with its properties made required
* @example
* ```ts
* type T0 = Call<Objects.Required, {a?: 1; b?: true }>; // { a:1; b: true}
* type T0 = Call<Objects.Required, { a?: 1; b?: true }>; // { a:1; b: true}
* type T1 = Eval<Objects.Required<{ a?: 1; b?: true }>>; // { a:1; b: true}
* ```
*/
@@ -373,7 +373,7 @@ export namespace Objects {
* @returns The object with its properties made optional
* @example
* ```ts
* type T0 = Call<Objects.Partial, {a: 1; b: true }>; // { a?:1; b?: true}
* type T0 = Call<Objects.Partial, { a: 1; b: true }>; // { a?:1; b?: true}
* type T1 = Eval<Objects.Partial<{ a: 1; b: true }>>; // { a?:1; b?: true}
* ```
*/
@@ -383,6 +383,23 @@ export namespace Objects {
return: this["args"] extends [infer value] ? Std._Partial<value> : never;
}
/**
* Make all properties of an object mutable
* @param value - The object to make properties mutable
* @returns The object with its properties made mutable
* @example
* ```ts
* type T0 = Call<Objects.Mutable, { readonly a: 1; readonly b: true }>; // { a:1; b: true }
* ```
*/
export type Mutable<obj = unset> = PartialApply<MutableFn, [obj]>;
interface MutableFn extends Fn {
return: this["args"] extends [infer obj, ...any]
? { -readonly [key in keyof obj]: obj[key] }
: never;
}
/**
* Makes all levels of an object optional
* @description This function is used to make all levels of an object optional
@@ -391,15 +408,88 @@ export namespace Objects {
*
* @example
* ```ts
* type T0 = Call<Objects.PartialDeep, {a: 1; b: true }>; // { a?:1; b?: true}
* type T1 = Call<Objects.PartialDeep, {a: 1; b: { c: true } }>; // { a?:1; b?: { c?: true } }
* type T2 = Call<Objects.PartialDeep, {a: 1; b: { c: true, d: { e: false } } }>; // { a?:1; b?: { c?: true, d?: { e?: false } } }
* type T0 = Call<Objects.PartialDeep, { a: 1; b: true }>;
* // ^? { a?:1; b?: true}
* type T1 = Call<Objects.PartialDeep, { a: 1; b: { c: true } }>;
* // ^? { a?:1; b?: { c?: true } }
* type T2 = Call<Objects.PartialDeep, { a: 1; b: { c: true, d: { e: false } } }>;
* // ^? { a?:1; b?: { c?: true, d?: { e?: false } } }
*/
export type PartialDeep<obj = unset> = PartialApply<PartialDeepFn, [obj]>;
interface PartialDeepFn extends Fn {
return: this["args"] extends [infer obj] ? Impl.PartialDeep<obj> : never;
return: this["args"] extends [infer obj]
? Impl.TransformObjectDeep<PartialFn, obj>
: never;
}
/**
* Makes all levels of an object required
* @description This function is used to make all levels of an object required
* @param obj - The object to make levels required
* @returns The object with its levels made required
*
* @example
* ```ts
* type T0 = Call<Objects.RequiredDeep, { a?:1; b?: true }>;
* // ^? { a: 1; b: true }
* type T1 = Call<Objects.RequiredDeep, { a?:1; b?: { c?: true } }>;
* // ^? { a: 1; b: { c: true } }
* type T2 = Call<Objects.RequiredDeep, { a?:1; b?: { c?: true, d?: { e?: false } } }>;
* // ^? { a: 1; b: { c: true, d: { e: false } } }
*/
export type RequiredDeep<obj = unset> = PartialApply<RequiredDeepFn, [obj]>;
interface RequiredDeepFn extends Fn {
return: this["args"] extends [infer obj]
? Impl.TransformObjectDeep<RequiredFn, obj>
: never;
}
/**
* Makes all levels of an object readonly
* @description This function is used to make all levels of an object readonly
* @param obj - The object to make levels readonly
* @returns The object with its levels made readonly
*
* @example
* ```ts
* type T0 = Call<Objects.ReadonlyDeep, { a:1; b: true }>;
* // ^? { readonly a: 1; readonly b: true }
* type T1 = Call<Objects.ReadonlyDeep, { a:1; b: { c: true } }>;
* // ^? { readonly a: 1; readonly b: { readonly c: true } }
* type T2 = Call<Objects.ReadonlyDeep, { a:1; b: { c: true, d: { e: false } } }>;
* // ^? { readonly a: 1; readonly b: { readonly c: true, d: { readonly e: false } } }
*/
export type ReadonlyDeep<obj = unset> = PartialApply<ReadonlyDeepFn, [obj]>;
interface ReadonlyDeepFn extends Fn {
return: this["args"] extends [infer obj]
? Impl.TransformObjectDeep<ReadonlyFn, obj>
: never;
}
/**
* Makes all levels of an object mutable
* @description This function is used to make all levels of an object mutable
* @param obj - The object to make levels mutable
* @returns The object with its levels made mutable
*
* @example
* ```ts
* type T0 = Call<Objects.MutableDeep, { readonly a: 1; readonly b: true }>;
* // ^? { a:1; b: true }
* type T1 = Call<Objects.MutableDeep, { readonly a: 1; readonly b: { readonly c: true } }>;
* // ^? { a:1; b: { c: true } }
* type T2 = Call<Objects.MutableDeep, { readonly a: 1; readonly b: { readonly c: true, d: { readonly e: false } } }>;
* // ^? { a:1; b: { c: true, d: { e: false } } }
*/
export type MutableDeep<obj = unset> = PartialApply<MutableDeepFn, [obj]>;
interface MutableDeepFn extends Fn {
return: this["args"] extends [infer obj]
? Impl.TransformObjectDeep<MutableFn, obj>
: never;
}
/**

View File

@@ -1,6 +1,12 @@
import { Apply, Call, Fn } from "../../core/Core";
import { Strings } from "../../strings/Strings";
import { Equal, Prettify, Primitive, UnionToIntersection } from "../../helpers";
import {
Equal,
IsTuple,
Prettify,
Primitive,
UnionToIntersection,
} from "../../helpers";
export type Keys<src> = src extends readonly unknown[]
? {
@@ -58,9 +64,36 @@ type RecursiveGet<Obj, pathList> = Obj extends any
: Obj
: never;
export type PartialDeep<T> = T extends object
? { [P in keyof T]?: PartialDeep<T[P]> }
: T;
export type TransformObjectDeep<fn extends Fn, type> = type extends
| Function
| Date
? type
: type extends Map<infer keys, infer values>
? Map<TransformObjectDeep<fn, keys>, TransformObjectDeep<fn, values>>
: type extends ReadonlyMap<infer keys, infer values>
? ReadonlyMap<TransformObjectDeep<fn, keys>, TransformObjectDeep<fn, values>>
: type extends WeakMap<infer keys, infer values>
? WeakMap<
Extract<TransformObjectDeep<fn, keys>, object>,
TransformObjectDeep<fn, values>
>
: type extends Set<infer values>
? Set<TransformObjectDeep<fn, values>>
: type extends ReadonlySet<infer values>
? ReadonlySet<TransformObjectDeep<fn, values>>
: type extends WeakSet<infer values>
? WeakSet<Extract<TransformObjectDeep<fn, values>, object>>
: type extends Array<infer values>
? IsTuple<type> extends true
? Call<fn, { [Key in keyof type]: TransformObjectDeep<fn, type[Key]> }>
: Array<TransformObjectDeep<fn, values> | undefined>
: type extends Promise<infer value>
? Promise<TransformObjectDeep<fn, value>>
: type extends object
? Call<fn, { [Key in keyof type]: TransformObjectDeep<fn, type[Key]> }>
: Equal<type, unknown> extends true
? unknown
: Partial<type>;
export type Update<obj, path, fnOrValue> = RecursiveUpdate<
obj,

View File

@@ -95,47 +95,6 @@ describe("Objects", () => {
});
});
it("PartialDeep", () => {
type res0 = Call<Objects.PartialDeep, { a: 1; b: 2 }>;
// ^?
type test0 = Expect<Equal<res0, { a?: 1; b?: 2 }>>;
type res1 = Call<Objects.PartialDeep, { a: 1; b: { c: 2 } }>;
// ^?
type test1 = Expect<Equal<res1, { a?: 1; b?: { c?: 2 } }>>;
type res2 = Call<Objects.PartialDeep, { a: 1; b: { c: 2; d: { e: 3 } } }>;
// ^?
type test2 = Expect<Equal<res2, { a?: 1; b?: { c?: 2; d?: { e?: 3 } } }>>;
type tuple = [string, number];
type res3 = Call<Objects.PartialDeep, tuple>;
// ^?
type test3 = Expect<Equal<res3, [string?, number?]>>;
type res4 = Call<Objects.PartialDeep, [string, tuple]>;
// ^?
type test4 = Expect<Equal<res4, [string?, [string?, number?]?]>>;
type res5 = Call<
Objects.PartialDeep,
{ tuple: tuple; tuple2: { tuple3: tuple } }
>;
// ^?
type test5 = Expect<
Equal<
res5,
{ tuple?: [string?, number?]; tuple2?: { tuple3?: [string?, number?] } }
>
>;
type res6 = Call<Objects.PartialDeep, { tuple: [string, tuple] }>;
// ^?
type test6 = Expect<
Equal<res6, { tuple?: [string?, [string?, number?]?] }>
>;
});
describe("Update", () => {
it("basic", () => {
type res0 = Call<Objects.Update<"a", Numbers.Add<1>>, { a: 1; b: 1 }>;
@@ -301,6 +260,172 @@ describe("Objects", () => {
type test1 = Expect<Equal<res1, { b: true }>>;
});
describe("PartialDeep", () => {
it("primitives", () => {
type res0 = Call<Objects.PartialDeep, number>;
type test0 = Expect<Equal<res0, number>>;
type res1 = Call<Objects.PartialDeep, string>;
type test1 = Expect<Equal<res1, string>>;
type res2 = Call<Objects.PartialDeep, boolean>;
type test2 = Expect<Equal<res2, boolean>>;
type res3 = Call<Objects.PartialDeep, bigint>;
type test3 = Expect<Equal<res3, bigint>>;
type res4 = Call<Objects.PartialDeep, symbol>;
type test4 = Expect<Equal<res4, symbol>>;
type res5 = Call<Objects.PartialDeep, undefined>;
type test5 = Expect<Equal<res5, undefined>>;
type res6 = Call<Objects.PartialDeep, null>;
type test6 = Expect<Equal<res6, null>>;
type res7 = Call<Objects.PartialDeep, Function>;
type test7 = Expect<Equal<res7, Function>>;
});
it("Map & Set", () => {
type res0 = Call<Objects.PartialDeep, Map<string, boolean>>;
type test0 = Expect<Equal<res0, Map<string, boolean>>>;
type res1 = Call<Objects.PartialDeep, Map<string, { a: number }>>;
type test1 = Expect<Equal<res1, Map<string, { a?: number }>>>;
type res2 = Call<Objects.PartialDeep, ReadonlyMap<string, boolean>>;
type test2 = Expect<Equal<res2, ReadonlyMap<string, boolean>>>;
type res3 = Call<
Objects.PartialDeep,
ReadonlyMap<string, { checked: boolean }>
>;
type test3 = Expect<
Equal<res3, ReadonlyMap<string, { checked?: boolean }>>
>;
type res4 = Call<Objects.PartialDeep, WeakMap<{ key: string }, boolean>>;
type test4 = Expect<Equal<res4, WeakMap<{ key?: string }, boolean>>>;
type res5 = Call<
Objects.PartialDeep,
WeakMap<{ key: string }, { value: boolean }>
>;
type test5 = Expect<
Equal<res5, WeakMap<{ key?: string }, { value?: boolean }>>
>;
type res6 = Call<Objects.PartialDeep, Set<string>>;
type test6 = Expect<Equal<res6, Set<string>>>;
type res7 = Call<Objects.PartialDeep, Set<number[]>>;
type test7 = Expect<Equal<res7, Set<(number | undefined)[]>>>;
type res8 = Call<Objects.PartialDeep, ReadonlySet<string>>;
type test8 = Expect<Equal<res8, ReadonlySet<string>>>;
});
it("Objects and Arrays", () => {
type res1 = Call<Objects.PartialDeep, []>;
type test1 = Expect<Equal<res1, []>>;
type res2 = Call<Objects.PartialDeep, never[]>;
type test2 = Expect<Equal<res2, undefined[]>>;
type res3 = Call<Objects.PartialDeep, [1, 2, 3]>;
type test3 = Expect<
Equal<res3, [(1 | undefined)?, (2 | undefined)?, (3 | undefined)?]>
>;
type res4 = Call<Objects.PartialDeep, readonly number[]>;
type test4 = Expect<Equal<res4, readonly (number | undefined)[]>>;
type res5 = Call<Objects.PartialDeep, number[]>;
type test5 = Expect<Equal<res5, (number | undefined)[]>>;
type res6 = Call<Objects.PartialDeep, Array<number>>;
type test6 = Expect<Equal<res6, Array<number | undefined>>>;
type res7 = Call<
Objects.PartialDeep,
{ readonly obj: unknown; readonly arr: readonly unknown[] }
>;
type test7 = Expect<
Equal<
res7,
{
readonly obj?: unknown | undefined;
readonly arr?: readonly unknown[] | undefined;
}
>
>;
type res8 = Call<Objects.PartialDeep, { a: 1; b: 2; c: 3 }>;
type test8 = Expect<Equal<res8, { a?: 1; b?: 2; c?: 3 }>>;
type res9 = Call<Objects.PartialDeep, { foo: () => void }>;
type test9 = Expect<Equal<res9, { foo?: () => void }>>;
});
it("Promises", () => {
type res0 = Call<Objects.PartialDeep, Promise<number>>;
type test0 = Expect<Equal<res0, Promise<number>>>;
type res1 = Call<
Objects.PartialDeep,
Promise<{ api: () => { play: () => void; pause: () => void } }>
>;
type test1 = Expect<
Equal<
res1,
Promise<{ api?: () => { play: () => void; pause: () => void } }>
>
>;
});
it("Complex structures", () => {
type ComplexNestedRequired = {
simple: number;
nested: {
date: Date;
func: () => string;
array: { bar: number }[];
tuple: [string, number, { good: boolean }];
set: Set<{ name: string }>;
map: Map<
string,
{
name: string;
}
>;
promise: Promise<{ foo: string; bar: number }>;
};
};
type ComplexNestedPartial = {
simple?: number;
nested?: {
date?: Date;
func?: () => string;
array?: ({ bar?: number } | undefined)[];
set?: Set<{ name?: string }>;
tuple?: [string?, number?, { good?: boolean }?];
map?: Map<
string,
{
name?: string;
}
>;
promise?: Promise<{ foo?: string; bar?: number }>;
};
};
type res1 = Call<Objects.PartialDeep, ComplexNestedRequired>;
type test1 = Expect<Equal<res1, ComplexNestedPartial>>;
});
});
describe("Assign", () => {
it("can be called without any pre-filled arguments", () => {
type res1 = Call<