evolve-ts
Version:
Immutably update nested objects with patches containing values or functions to update values
134 lines (133 loc) • 7.03 kB
TypeScript
declare class Unset {
}
/** sentinel value that will cause its key to be removed */
export declare const unset: typeof Unset;
type Unarray<T> = T extends Array<infer U> ? U : T;
type OptionalKeysHelper<T> = {
[K in keyof T]-?: Record<string, unknown> extends Pick<T, K> ? K : never;
}[keyof T];
type IsOptional<T, K> = string extends keyof T ? true : [OptionalKeysHelper<T>] extends [never] ? false : K extends OptionalKeysHelper<T> ? true : false;
export type Patch<Target extends {
[key: string]: any;
}> = {
[K in keyof Target]?: Target[K] extends any[] ? IsOptional<Target, K> extends true ? Target[K] | ((value: Target[K]) => Target[K] | typeof Unset) | typeof Unset : Target[K] | ((value: Target[K]) => Target[K]) : Target[K] extends {
[key: string]: any;
} ? IsOptional<Target, K> extends true ? Target[K] | ((value: Target[K]) => Target[K] | typeof Unset) | typeof Unset | Patch<Target[K]> : Target[K] | ((value: Target[K]) => Target[K]) | Patch<Target[K]> : IsOptional<Target, K> extends true ? Target[K] | ((value: Target[K]) => Target[K] | typeof Unset) | typeof Unset : Target[K] | ((value: Target[K]) => Target[K]);
};
export type ShallowPatch<Target extends {
[key: string]: any;
}> = {
[K in keyof Target]?: IsOptional<Target, K> extends true ? Target[K] | ((value: Target[K]) => Target[K] | typeof Unset) | typeof Unset : Target[K] | ((value: Target[K]) => Target[K]);
};
export interface Evolve {
<Context>(patch: Patch<Context extends (...args: infer Param) => any ? Param : Context extends {
[key: string]: any;
} ? Context : never>): Context extends (...args: any) => any ? Context : (target: Context) => Context;
<Target extends {
[key: string]: any;
}>(patch: Patch<Target>): (target: Target) => Target;
<Target extends {
[key: string]: any;
}>(patch: Patch<Target>, target: Target): Target;
}
export interface ShallowEvolve {
<Context>(patch: ShallowPatch<Context extends (...args: infer Param) => any ? Param : Context extends {
[key: string]: any;
} ? Context : never>): Context extends (...args: any) => any ? Context : (target: Context) => Context;
<Target extends {
[key: string]: any;
}>(patch: ShallowPatch<Target>): (target: Target) => Target;
<Target extends {
[key: string]: any;
}>(patch: ShallowPatch<Target>, target: Target): Target;
}
export interface Adjust {
/** curried form for use within evolve or setState */
<Arr extends any[]>(indexOrPredicate: number | ((item: Unarray<Arr>) => any), patchOrUpdater: Patch<Unarray<Arr>> | ((item: Unarray<Arr>) => Unarray<Arr>)): (array: Arr) => Arr;
/** uncurried form and explicit array type */
<Arr extends any[]>(indexOrPredicate: number | ((item: Unarray<Arr>) => any), patchOrUpdater: Patch<Unarray<Arr>> | ((item: Unarray<Arr>) => Unarray<Arr>), array: Arr): Arr;
}
export interface ShallowAdjust {
/** curried form for use within evolve or setState */
<Arr extends any[]>(indexOrPredicate: number | ((item: Unarray<Arr>) => any), patchOrUpdater: ShallowPatch<Unarray<Arr>> | ((item: Unarray<Arr>) => Unarray<Arr>)): (array: Arr) => Arr;
/** uncurried form and explicit array type */
<Arr extends any[]>(indexOrPredicate: number | ((item: Unarray<Arr>) => any), patchOrUpdater: ShallowPatch<Unarray<Arr>> | ((item: Unarray<Arr>) => Unarray<Arr>), array: Arr): Arr;
}
export interface MapArray {
/** curried form for use within evolve or setState */
<Arr extends any[]>(patchOrUpdater: Patch<Unarray<Arr>> | ((item: Unarray<Arr>) => Unarray<Arr>)): (array: Arr) => Arr;
/** uncurried form and explicit array type */
<Arr extends any[]>(patchOrUpdater: Patch<Unarray<Arr>> | ((item: Unarray<Arr>) => Unarray<Arr>), array: Arr): Arr;
}
export interface ShallowMapArray {
/** curried form for use within evolve or setState */
<Arr extends any[]>(patchOrUpdater: ShallowPatch<Unarray<Arr>> | ((item: Unarray<Arr>) => Unarray<Arr>)): (array: Arr) => Arr;
/** uncurried form and explicit array type */
<Arr extends any[]>(patchOrUpdater: ShallowPatch<Unarray<Arr>> | ((item: Unarray<Arr>) => Unarray<Arr>), array: Arr): Arr;
}
export interface Evolve_ {
<Patch extends {
[key: string]: any;
}>(patch: Patch): <Target>(target: Target) => MergeLeft<ParsePatch<Patch, Target>, Target>;
<Patch extends {
[key: string]: any;
}, Target>(patch: Patch, target: Target): MergeLeft<ParsePatch<Patch, Target>, Target>;
}
type ValueOf<T> = T[keyof T];
type Id<T> = T extends infer U ? {
[K in keyof U]: U[K];
} : never;
type NonNeverKeys<T extends {
[key: string]: any;
}> = {
[K in keyof T]: T[K] extends never ? never : K;
}[keyof T];
type FilterNeverKeys<T extends {
[key: string]: any;
}> = {
[K in NonNeverKeys<T>]: T[K];
};
type KeysOfType<T, U> = {
[K in keyof T]: T[K] extends U ? K : never;
}[keyof T];
type RequiredKeys<T> = Exclude<KeysOfType<T, Exclude<T[keyof T], undefined>>, undefined>;
type OptionalKeys<T> = Exclude<keyof T, RequiredKeys<T>>;
type IfEquals<A, B, T, F> = A extends B ? (B extends A ? T : F) : F;
type ParsePatch<Patch, Target> = Patch extends any[] ? Patch : Patch extends (...args: any[]) => any ? Exclude<ReturnType<Patch>, typeof Unset> : Patch extends typeof Unset ? never : Patch extends {
[key: string]: any;
} ? string extends keyof Target ? {
[key: string]: ParsePatch<[
Exclude<ValueOf<Patch>, Unset>
] extends [never] ? any : Exclude<ValueOf<Patch>, Unset>, unknown>;
} : Target extends {
[key: string]: any;
} ? Id<{
[K in Exclude<keyof Patch, keyof Target>]: ParsePatch<Patch[K], unknown>;
} & Partial<FilterNeverKeys<{
[K in OptionalKeys<Pick<Target, keyof Patch & keyof Target>>]: IfEquals<Patch[K], Unset, never, ParsePatch<Patch[K], Target[K]>>;
}>> & {
[K in RequiredKeys<Pick<Target, keyof Patch & keyof Target>>]: IfEquals<Patch[K], Unset, never, ParsePatch<Patch[K], Target[K]>>;
}> : {
[K in keyof Patch]: Patch[K] extends Unset ? never : ParsePatch<Patch[K], unknown>;
} : Patch;
export type MergeLeft<L, R> = L extends any[] ? L : string extends keyof L ? string extends keyof R ? {
[key: string]: MergeLeft<ValueOf<L>, ValueOf<R>>;
} : R extends {
[key: string]: any;
} ? {
[key: string]: ValueOf<L> | ValueOf<R>;
} : L : L extends {
[key: string]: any;
} ? string extends keyof R ? {
[key: string]: ValueOf<L> | ValueOf<R>;
} : R extends {
[key: string]: any;
} ? Id<Pick<R, Exclude<keyof R, keyof L>> & // unique keys in R
Pick<L, Exclude<keyof L, keyof R>> & // unique keys in L
Partial<FilterNeverKeys<{
[K in OptionalKeys<Pick<L, keyof L & keyof R>>]: MergeLeft<L[K], R[K]>;
}>> & // merge optional shared keys
FilterNeverKeys<{
[K in RequiredKeys<Pick<L, keyof L & keyof R>>]: MergeLeft<L[K], R[K]>;
}>> : L : L extends R ? R : L;
export {};