Refactor: conditional type for object/array/primitive, and deep immutable interface for nested objects (#29699)

This commit is contained in:
Richard Honor
2018-10-15 09:40:28 -07:00
committed by Sheetal Nandi
parent 049be10668
commit f962dce1d2
2 changed files with 60 additions and 55 deletions
+35 -34
View File
@@ -3,8 +3,9 @@
// Definitions by: alex3165 <https://github.com/alex3165>
// Stepan Burguchev <https://github.com/xsburg>
// Geir Sagberg <https://github.com/geirsagberg>
// Richard Honor <https://github.com/RMHonor>
// Definitions: https://github.com/DefinitelyTyped/DefinitelyTyped
// TypeScript Version: 2.4
// TypeScript Version: 2.8
export = SeamlessImmutable;
@@ -31,17 +32,17 @@ declare namespace SeamlessImmutable {
}
interface ImmutableObjectMixin<T> {
set<K extends keyof T>(property: K, value: T[K]): ImmutableObject<T>;
set<TValue>(property: string, value: TValue): ImmutableObject<T>;
set<K extends keyof T>(property: K, value: T[K]): Immutable<T>;
set<TValue>(property: string, value: TValue): Immutable<T>;
setIn<K extends keyof T>(propertyPath: [ K ], value: T[K]): ImmutableObject<T>;
setIn<K extends keyof T, L extends keyof T[K]>(propertyPath: [ K, L ], value: T[K][L]): ImmutableObject<T>;
setIn<K extends keyof T, L extends keyof T[K], M extends keyof T[K][L]>(propertyPath: [ K, L, M ], value: T[K][L][M]): ImmutableObject<T>;
setIn<K extends keyof T>(propertyPath: [ K ], value: T[K]): Immutable<T>;
setIn<K extends keyof T, L extends keyof T[K]>(propertyPath: [ K, L ], value: T[K][L]): Immutable<T>;
setIn<K extends keyof T, L extends keyof T[K], M extends keyof T[K][L]>(propertyPath: [ K, L, M ], value: T[K][L][M]): Immutable<T>;
setIn<K extends keyof T, L extends keyof T[K], M extends keyof T[K][L], N extends keyof T[K][L][M]>(
propertyPath: [ K, L, M, N ], value: T[K][L][M][N]): ImmutableObject<T>;
propertyPath: [ K, L, M, N ], value: T[K][L][M][N]): Immutable<T>;
setIn<K extends keyof T, L extends keyof T[K], M extends keyof T[K][L], N extends keyof T[K][L][M], O extends keyof T[K][L][M][N]>(
propertyPath: [ K, L, M, N, O ], value: T[K][L][M][N][O]): ImmutableObject<T>;
setIn<TValue>(propertyPath: string[], value: TValue): ImmutableObject<T>;
propertyPath: [ K, L, M, N, O ], value: T[K][L][M][N][O]): Immutable<T>;
setIn<TValue>(propertyPath: string[], value: TValue): Immutable<T>;
getIn<K extends keyof T>(propertyPath: [ K ]): Immutable<T[K]>;
getIn<K extends keyof T>(propertyPath: [ K ], defaultValue: T[K]): Immutable<T[K]>;
@@ -59,48 +60,48 @@ declare namespace SeamlessImmutable {
asMutable(opts?: AsMutableOptions): T;
merge(part: DeepPartial<T>, config?: MergeConfig): ImmutableObject<T>;
merge(part: DeepPartial<T>, config?: MergeConfig): Immutable<T>;
update<K extends keyof T>(property: K, updaterFunction: (value: T[K], ...additionalParameters: any[]) => any, ...additionalArguments: any[]): ImmutableObject<T>;
update<TValue>(property: string, updaterFunction: (value: TValue, ...additionalParameters: any[]) => any, ...additionalArguments: any[]): ImmutableObject<T>;
update<K extends keyof T>(property: K, updaterFunction: (value: T[K], ...additionalParameters: any[]) => any, ...additionalArguments: any[]): Immutable<T>;
update<TValue>(property: string, updaterFunction: (value: TValue, ...additionalParameters: any[]) => any, ...additionalArguments: any[]): Immutable<T>;
updateIn<K extends keyof T>(
propertyPath: [ K ], updaterFunction: (value: T[K], ...additionalParameters: any[]) => any, ...additionalArguments: any[]): ImmutableObject<T>;
propertyPath: [ K ], updaterFunction: (value: T[K], ...additionalParameters: any[]) => any, ...additionalArguments: any[]): Immutable<T>;
updateIn<K extends keyof T, L extends keyof T[K]>(
propertyPath: [ K, L ], updaterFunction: (value: T[K][L], ...additionalParameters: any[]) => any, ...additionalArguments: any[]): ImmutableObject<T>;
propertyPath: [ K, L ], updaterFunction: (value: T[K][L], ...additionalParameters: any[]) => any, ...additionalArguments: any[]): Immutable<T>;
updateIn<K extends keyof T, L extends keyof T[K], M extends keyof T[K][L]>(
propertyPath: [ K, L, M ], updaterFunction: (value: T[K][L][M], ...additionalParameters: any[]) => any, ...additionalArguments: any[]): ImmutableObject<T>;
propertyPath: [ K, L, M ], updaterFunction: (value: T[K][L][M], ...additionalParameters: any[]) => any, ...additionalArguments: any[]): Immutable<T>;
updateIn<K extends keyof T, L extends keyof T[K], M extends keyof T[K][L], N extends keyof T[K][L][M]>(
propertyPath: [ K, L, M, N ], updaterFunction: (value: T[K][L][M][N], ...additionalParameters: any[]) => any, ...additionalArguments: any[]): ImmutableObject<T>;
propertyPath: [ K, L, M, N ], updaterFunction: (value: T[K][L][M][N], ...additionalParameters: any[]) => any, ...additionalArguments: any[]): Immutable<T>;
updateIn<K extends keyof T, L extends keyof T[K], M extends keyof T[K][L], N extends keyof T[K][L][M], O extends keyof T[K][L][M][N]>(
propertyPath: [ K, L, M, N, O ], updaterFunction: (value: T[K][L][M][N][O], ...additionalParameters: any[]) => any, ...additionalArguments: any[]): ImmutableObject<T>;
updateIn<TValue>(propertyPath: string[], updaterFunction: (value: TValue, ...additionalParameters: any[]) => any, ...additionalArguments: any[]): ImmutableObject<T>;
propertyPath: [ K, L, M, N, O ], updaterFunction: (value: T[K][L][M][N][O], ...additionalParameters: any[]) => any, ...additionalArguments: any[]): Immutable<T>;
updateIn<TValue>(propertyPath: string[], updaterFunction: (value: TValue, ...additionalParameters: any[]) => any, ...additionalArguments: any[]): Immutable<T>;
without<K extends keyof T>(property: K): ImmutableObject<T>;
without<K extends keyof T>(...properties: K[]): ImmutableObject<T>;
without<K extends keyof T>(filter: (value: T[K], key: K) => boolean): ImmutableObject<T>;
without<K extends keyof T>(property: K): Immutable<T>;
without<K extends keyof T>(...properties: K[]): Immutable<T>;
without<K extends keyof T>(filter: (value: T[K], key: K) => boolean): Immutable<T>;
replace<S>(valueObj: S, options?: ReplaceConfig): ImmutableObject<S>;
replace<S>(valueObj: S, options?: ReplaceConfig): Immutable<S>;
}
interface ImmutableArrayMixin<T> {
asMutable(opts?: AsMutableOptions): T[];
asObject(toKeyValue: (item: T) => [string, any]): ImmutableObject<any>;
flatMap<TTarget>(mapFunction: (item: T) => TTarget[]): ImmutableArray<TTarget>;
interface ImmutableArrayMixin<T extends Array<T[0]>> {
asMutable(opts?: AsMutableOptions): T;
asObject(toKeyValue: (item: T[0]) => [string, any]): Immutable<object>;
flatMap<TTarget>(mapFunction: (item: T[0]) => TTarget): Immutable<TTarget extends any[] ? TTarget : TTarget[]>;
}
type ImmutableObject<T> = T & ImmutableObjectMixin<T>;
type ImmutableArray<T> = T[] & ImmutableArrayMixin<T>;
type Immutable<T> = ImmutableObject<T> | ImmutableArray<T>;
type BaseImmutable<T> = (T extends any[] ? ImmutableArrayMixin<T> : ImmutableObjectMixin<T>) & T;
function from<T>(obj: T[], options?: Options): ImmutableArray<T>;
function from<T>(obj: T, options?: Options): ImmutableObject<T>;
type Immutable<T> = {
readonly [P in keyof T]: T[P] extends object ? Immutable<T[P]> : T[P]
} & BaseImmutable<T>;
function from<T>(obj: T, options?: Options): Immutable<T>;
function isImmutable(target: any): boolean;
function ImmutableError(message: string): Error;
function replace<T, S>(obj: ImmutableObject<T>, valueObj: S, options?: ReplaceConfig): ImmutableObject<S>;
function replace<T, S>(obj: Immutable<T>, valueObj: S, options?: ReplaceConfig): Immutable<S>;
}
declare function SeamlessImmutable<T>(obj: T[], options?: SeamlessImmutable.Options): SeamlessImmutable.ImmutableArray<T>;
declare function SeamlessImmutable<T>(obj: T, options?: SeamlessImmutable.Options): SeamlessImmutable.ImmutableObject<T>;
declare function SeamlessImmutable<T>(obj: T, options?: SeamlessImmutable.Options): SeamlessImmutable.Immutable<T>;
@@ -25,13 +25,13 @@ interface ExtendedUser extends User {
lastName: string;
}
const arrayOfNumbers1: Immutable.ImmutableArray<number> = Immutable.from([0, 2]);
const arrayOfNumbers2: Immutable.ImmutableArray<number> = Immutable([0, 2]);
const user1: Immutable.ImmutableObject<User> = Immutable.from({
const arrayOfNumbers1: Immutable.Immutable<number[]> = Immutable.from([0, 2]);
const arrayOfNumbers2: Immutable.Immutable<number[]> = Immutable([0, 2]);
const user1: Immutable.Immutable<User> = Immutable.from({
firstName: 'Angry',
lastName: 'Monkey'
});
const user2: Immutable.ImmutableObject<User> = Immutable({
const user2: Immutable.Immutable<User> = Immutable({
firstName: 'Angry',
lastName: 'Monkey'
});
@@ -44,7 +44,7 @@ interface ExtendedUser extends User {
{
const isImmutable: boolean = Immutable.isImmutable(Immutable.from([0, 2]));
const user1: Immutable.ImmutableObject<User> = Immutable.from({
const user1: Immutable.Immutable<User> = Immutable.from({
firstName: 'Angry',
lastName: 'Monkey'
});
@@ -56,19 +56,23 @@ interface ExtendedUser extends User {
// Instance syntax: immutable array
// ---------------------------------------------------------------
{
const array: Immutable.ImmutableArray<User> = Immutable.from<User>([ { firstName: 'Angry', lastName: 'Monkey' } ]);
const array: Immutable.Immutable<User[]> = Immutable.from([ { firstName: 'Angry', lastName: 'Monkey' } ]);
// asMutable
const mutableArray1: User[] = array.asMutable();
const mutableArray2: User[] = array.asMutable({ deep: true });
// flatMap
const flatMappedArray: Immutable.ImmutableArray<User> = array.flatMap((value: User) =>
const flatMappedArray: Immutable.Immutable<User[]> = array.flatMap((value: User) =>
[value, value]
);
const flatMappedArrayStrings: Immutable.Immutable<string[]> = array.flatMap((value: User) =>
value.firstName
);
// asObject
const arrayToObject1: Immutable.ImmutableObject<any> = array.asObject((value) => [value.toString(), value]);
const arrayToObject1: Immutable.Immutable<object> = array.asObject((value) => [value.toString(), value]);
}
//
@@ -76,11 +80,11 @@ interface ExtendedUser extends User {
// ---------------------------------------------------------------
{
const immutableUser: Immutable.ImmutableObject<User> = Immutable.from({
const immutableUser: Immutable.Immutable<User> = Immutable.from({
firstName: 'Pure',
lastName: 'Gold'
});
const immutableUserEx: Immutable.ImmutableObject<ExtendedUser> = Immutable.from({
const immutableUserEx: Immutable.Immutable<ExtendedUser> = Immutable.from({
firstName: 'Hairy',
lastName: 'Dog',
address: {
@@ -94,37 +98,37 @@ interface ExtendedUser extends User {
};
// set: property name is strongly checked
const updatedUser01: Immutable.ImmutableObject<User> = immutableUser.set('firstName', 'Whirlwind');
const updatedUser02: Immutable.ImmutableObject<User> = immutableUser.set(data.propertyId, 'Whirlwind');
const updatedUser01: Immutable.Immutable<User> = immutableUser.set('firstName', 'Whirlwind');
const updatedUser02: Immutable.Immutable<User> = immutableUser.set(data.propertyId, 'Whirlwind');
// setIn: property path is strongly checked for up to 5 arguments (helps with refactoring and intellisense)
// but will fall back to any[] if there are dynamic arguments on the way
const updatedUser11: Immutable.ImmutableObject<ExtendedUser> = immutableUserEx.setIn(['address', 'line1'], 'Small house');
const updatedUser12: Immutable.ImmutableObject<ExtendedUser> = immutableUserEx.setIn([ data.propertyId, 'line1' ], 'Small house');
const updatedUser11: Immutable.Immutable<ExtendedUser> = immutableUserEx.setIn(['address', 'line1'], 'Small house');
const updatedUser12: Immutable.Immutable<ExtendedUser> = immutableUserEx.setIn([ data.propertyId, 'line1' ], 'Small house');
// asMutable
const mutableUser21: User = immutableUser.asMutable();
const mutableUser22: User = immutableUser.asMutable({ deep: true });
// merge: merged part is strongly checked as a deeply partial object
const mergedUser: Immutable.ImmutableObject<User> = immutableUserEx.merge({ address: { line1: 'Small house' }, firstName: 'Jack' });
const mergedUser: Immutable.Immutable<User> = immutableUserEx.merge({ address: { line1: 'Small house' }, firstName: 'Jack' });
// update: property name is strongly checked
const updatedUser41: Immutable.ImmutableObject<User> = immutableUser.update('firstName', x => x.toLowerCase() + ' Whirlwind');
const updatedUser41: Immutable.Immutable<User> = immutableUser.update('firstName', x => x.toLowerCase() + ' Whirlwind');
// the type of the updated value must be explicity specified in case of fallback
const updatedUser42: Immutable.ImmutableObject<User> = immutableUser.update<string>(data.propertyId, x => x.toLowerCase() + ' Whirlwind');
const updatedUser42: Immutable.Immutable<User> = immutableUser.update<string>(data.propertyId, x => x.toLowerCase() + ' Whirlwind');
// updateIn: property path is strongly checked for up to 5 arguments (helps with refactoring and intellisense)
// but will fall back to any[] if there are dynamic arguments on the way
const updatedUser51: Immutable.ImmutableObject<User> = immutableUserEx.updateIn([ 'address', 'line1' ], x => x.toLowerCase() + ' 43');
const updatedUser51: Immutable.Immutable<User> = immutableUserEx.updateIn([ 'address', 'line1' ], x => x.toLowerCase() + ' 43');
// the type of the updated value must be explicity specified in case of fallback
const updatedUser52: Immutable.ImmutableObject<User> = immutableUserEx.updateIn<string>([ data.propertyId, 'line1' ], x => x.toLowerCase() + ' 43');
const updatedUser52: Immutable.Immutable<User> = immutableUserEx.updateIn<string>([ data.propertyId, 'line1' ], x => x.toLowerCase() + ' 43');
// without
const simpleUser1: Immutable.ImmutableObject<User> = immutableUserEx.without('address');
const simpleUser1: Immutable.Immutable<User> = immutableUserEx.without('address');
// getIn: propertyPath is strongly typed up to 5 parameters
const firstNameWithoutDefault = immutableUser.getIn(['firstName']); // infers Immutable<string>
const firstNameWithoutDefault: string = immutableUser.getIn(['firstName']); // infers Immutable<string>
const firstNameWithDefault = immutableUser.getIn(['firstName'], ''); // infers Immutable<string>
const firstNameWithDynamicPathWithoutDefault = immutableUser.getIn(['first' + 'name']);
const firstNameWithDynamicPathWithDefault = immutableUser.getIn(['first' + 'name'], '');