mirror of
https://github.com/gosticks/DefinitelyTyped.git
synced 2026-08-15 14:30:22 +00:00
Improve seamless-immutable definitions
- Prevent making already-immutable objects immutable again - Do not allow certain types (number, string, functions) from being made immutable - Introduce ImmutableDate - Add missing immutable-wrapper methods to ImmutableArray - Basic support for Promises
This commit is contained in:
Vendored
+69
-8
@@ -4,12 +4,16 @@
|
||||
// Stepan Burguchev <https://github.com/xsburg>
|
||||
// Geir Sagberg <https://github.com/geirsagberg>
|
||||
// Richard Honor <https://github.com/RMHonor>
|
||||
// Paul Huynh <https://github.com/pheromonez>
|
||||
// Definitions: https://github.com/DefinitelyTyped/DefinitelyTyped
|
||||
// TypeScript Version: 2.8
|
||||
|
||||
export = SeamlessImmutable;
|
||||
|
||||
declare namespace SeamlessImmutable {
|
||||
/** From type T, take all properties except those specified by K. */
|
||||
type Omit<T, K extends keyof T> = Pick<T, Exclude<keyof T, K>>;
|
||||
|
||||
type DeepPartial<T> = {
|
||||
[P in keyof T]?: DeepPartial<T[P]>;
|
||||
};
|
||||
@@ -84,18 +88,75 @@ declare namespace SeamlessImmutable {
|
||||
|
||||
replace<S>(valueObj: S, options?: ReplaceConfig): Immutable<S>;
|
||||
}
|
||||
type ImmutableObject<T> = ImmutableObjectMixin<T> & { readonly [P in keyof T]: Immutable<T[P]> };
|
||||
|
||||
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[]>;
|
||||
/** An ImmutableArray provides read-only access to the array elements, and provides functions (such as `map()`) that return immutable data structures. */
|
||||
type ImmutableArray<T> = Readonly<ImmutableArray.Remaining<T> & ImmutableArray.Additions<T> & ImmutableArray.Overrides<T>>;
|
||||
namespace ImmutableArray {
|
||||
/** New methods added by seamless-immutable. */
|
||||
interface Additions<T> {
|
||||
asMutable(opts?: AsMutableOptions): T[];
|
||||
asObject(toKeyValue: (item: T) => [string, any]): Immutable<object>;
|
||||
flatMap<TTarget>(mapFunction: (item: T) => TTarget): Immutable<TTarget extends any[] ? TTarget : TTarget[]>;
|
||||
}
|
||||
|
||||
/** Custom implementation of the array functions, which return Immutable. */
|
||||
interface Overrides<T> {
|
||||
map<TTarget>(mapFuction: (item: T) => TTarget): Immutable<TTarget[]>;
|
||||
filter(filterFunction: (item: T) => boolean): Immutable<T[]>;
|
||||
slice(start?: number, end?: number): Immutable<T[]>;
|
||||
concat(...arr: T[]): Immutable<T[]>;
|
||||
reduce(callbackfn: (previousValue: T, currentValue: T, currentIndex: number, array: T[]) => T): Immutable<T>;
|
||||
reduce<TTarget>(callbackfn: (previousValue: TTarget, currentValue: T, currentIndex: number, array: T[]) => TTarget, initialValue?: TTarget): Immutable<TTarget>;
|
||||
reduceRight(callbackfn: (previousValue: T, currentValue: T, currentIndex: number, array: T[]) => T): Immutable<T>;
|
||||
reduceRight<TTarget>(callbackfn: (previousValue: TTarget, currentValue: T, currentIndex: number, array: T[]) => TTarget, initialValue?: TTarget): Immutable<TTarget>;
|
||||
}
|
||||
|
||||
/** These methods are banned by seamless-immutable. */
|
||||
type MutatingArrayMethods = Extract<keyof any[], 'push' | 'pop' | 'sort' | 'splice' | 'shift' | 'unshift' | 'reverse'>;
|
||||
|
||||
/** NOTE: These methods mutate data, but seamless-immutable does not ban them. We will ban them in our type definitions. */
|
||||
type AdditionalMutatingArrayMethods = Extract<keyof any[], 'copyWithin' | 'fill'>;
|
||||
|
||||
/** The remaining properties on Array<T>, after we remove the mutating functions and the wrapped non-mutating functions. */
|
||||
type Remaining<T> = Omit<T[], MutatingArrayMethods | AdditionalMutatingArrayMethods | keyof Overrides<any>>;
|
||||
}
|
||||
|
||||
type BaseImmutable<T> = T extends any[] ? ImmutableArrayMixin<T> : ImmutableObjectMixin<T>;
|
||||
/** An ImmutableDate disables the use of mutating functions like `setDate` and `setFullYear`. */
|
||||
type ImmutableDate = ImmutableDate.Remaining & ImmutableDate.Additions;
|
||||
namespace ImmutableDate {
|
||||
/** New functions added by seamless-immutable. */
|
||||
interface Additions {
|
||||
asMutable(): Date;
|
||||
}
|
||||
|
||||
type Immutable<T> = {
|
||||
readonly [P in keyof T]: T[P] extends object ? Immutable<T[P]> : T[P];
|
||||
} & BaseImmutable<T>;
|
||||
// These methods are banned by seamless-immutable
|
||||
type MutatingDateMethods = Extract<keyof Date, 'setDate' | 'setFullYear' | 'setHours' | 'setMilliseconds' | 'setMinutes' | 'setMonth' | 'setSeconds' |
|
||||
'setTime' | 'setUTCDate' | 'setUTCFullYear' | 'setUTCHours' | 'setUTCMilliseconds' | 'setUTCMinutes' |
|
||||
'setUTCMonth' | 'setUTCSeconds' | 'setYear'>;
|
||||
|
||||
/** Only allows Date methods, which are the getters. */
|
||||
type Remaining = Omit<Date, MutatingDateMethods>;
|
||||
}
|
||||
|
||||
type Immutable<T, O extends object = {}> =
|
||||
T extends Promise<infer U> ? Promise<Immutable.MakeImmutable<U, O>> :
|
||||
Immutable.MakeImmutable<T, O>;
|
||||
namespace Immutable {
|
||||
type AnyFunction = (...args: any[]) => any;
|
||||
|
||||
type AlreadyImmutable<O extends object = {}> = ImmutableObject<O> | ImmutableArray<any> | ImmutableDate;
|
||||
|
||||
type Primitive = boolean | number | string | symbol | AnyFunction | undefined | null;
|
||||
|
||||
type CannotMakeImmutable<O extends object = {}> = AlreadyImmutable<O> | Primitive;
|
||||
|
||||
type MakeImmutable<T, O extends object = {}> =
|
||||
T extends CannotMakeImmutable<O> ? T :
|
||||
T extends Array<infer Element> ? ImmutableArray<Element> :
|
||||
T extends Date ? ImmutableDate :
|
||||
ImmutableObject<T>;
|
||||
}
|
||||
|
||||
function from<T>(obj: T, options?: Options): Immutable<T>;
|
||||
|
||||
|
||||
@@ -36,6 +36,37 @@ interface ExtendedUser extends User {
|
||||
lastName: 'Monkey'
|
||||
});
|
||||
const error: Error = Immutable.ImmutableError('error');
|
||||
|
||||
const date: Immutable.ImmutableDate = Immutable(new Date());
|
||||
|
||||
// Constructing with a promise wraps the result value as an immutable
|
||||
const promise = new Promise<User>(resolve => resolve({
|
||||
firstName: 'Angry',
|
||||
lastName: 'Monkey',
|
||||
}));
|
||||
const immutablePromise = Immutable(promise);
|
||||
immutablePromise.then((user: Immutable.Immutable<User>) => user.asMutable());
|
||||
|
||||
// Construction with Immutable() multiple times only creates an Immutable once
|
||||
const user3: Immutable.Immutable<User> = Immutable(Immutable(Immutable({
|
||||
firstName: 'Angry',
|
||||
lastName: 'Monkey',
|
||||
})));
|
||||
user3.asMutable();
|
||||
// Can't call asMutable() multiple times since there is only one level of immutability
|
||||
// user3.asMutable().asMutable();
|
||||
|
||||
// Primitives are not made immutable
|
||||
const str: string = Immutable("Hello World");
|
||||
const num: number = Immutable(123);
|
||||
const bool: boolean = Immutable(true);
|
||||
const sym: symbol = Immutable(Symbol("A symbol"));
|
||||
const undef: undefined = Immutable(undefined);
|
||||
const nul: null = Immutable(null);
|
||||
const fun: () => User = Immutable((): User => ({
|
||||
firstName: 'Angry',
|
||||
lastName: 'Monkey',
|
||||
}));
|
||||
}
|
||||
|
||||
//
|
||||
@@ -58,6 +89,58 @@ interface ExtendedUser extends User {
|
||||
{
|
||||
const array: Immutable.Immutable<User[]> = Immutable.from([ { firstName: 'Angry', lastName: 'Monkey' } ]);
|
||||
|
||||
// keys. Call the mutable array's 'keys' to ensure compatability
|
||||
const mutableKeys = array.asMutable().keys();
|
||||
const keys: typeof mutableKeys = array.keys();
|
||||
|
||||
// map. Call the mutable array's 'map' with the same function to ensure compatability. Make sure the output array is immutable.
|
||||
interface FirstName { firstNameOnly: string; }
|
||||
array.asMutable().map((value: User) => ({ firstNameOnly: value.firstName }));
|
||||
const map: Immutable.Immutable<FirstName[]> = array.map((value: User) => ({ firstNameOnly: value.firstName }));
|
||||
map.asMutable();
|
||||
|
||||
// filter. Call the mutable array's 'filter' with the same function to ensure compatability. Make sure the output array is immutable.
|
||||
array.asMutable().filter((value: User) => value.firstName === 'test');
|
||||
const filter: Immutable.Immutable<User[]> = array.filter((value: User) => value.firstName === 'test');
|
||||
filter.asMutable();
|
||||
|
||||
// slice. Call the mutable array's 'slice' with the same args to ensure compatability. Make sure the output array is immutable.
|
||||
array.asMutable().slice();
|
||||
const slice1: Immutable.Immutable<User[]> = array.slice();
|
||||
slice1.asMutable();
|
||||
array.asMutable().slice(1);
|
||||
const slice2: Immutable.Immutable<User[]> = array.slice(1);
|
||||
slice2.asMutable();
|
||||
array.asMutable().slice(1, 2);
|
||||
const slice3: Immutable.Immutable<User[]> = array.slice(1, 2);
|
||||
slice3.asMutable();
|
||||
array.asMutable().slice(undefined, 2);
|
||||
const slice4: Immutable.Immutable<User[]> = array.slice(undefined, 2);
|
||||
slice4.asMutable();
|
||||
|
||||
// concat. Call the mutable array's 'concat' with the same args to ensure compatability. Make sure the output array is immutable.
|
||||
array.asMutable().concat({ firstName: 'Happy', lastName: 'Cat' });
|
||||
const concat: Immutable.Immutable<User[]> = array.concat({ firstName: 'Happy', lastName: 'Cat' });
|
||||
concat.asMutable();
|
||||
|
||||
// reduce. Call the mutable array's 'reduce' with the same function to ensure compatability. Make sure the output array is immutable.
|
||||
array.asMutable().reduce((previous, current) => ({ ...previous, lastName: current.lastName }));
|
||||
const reduce1: Immutable.Immutable<User> = array.reduce((previous, current) => ({ ...previous, lastName: current.lastName }));
|
||||
reduce1.asMutable();
|
||||
// NOTE: this is effectively a map function
|
||||
array.asMutable().reduce<FirstName[]>((previous, current) => previous.concat({ firstNameOnly: current.firstName }), []);
|
||||
const reduce2: Immutable.Immutable<FirstName[]> = array.reduce<FirstName[]>((previous, current) => previous.concat({ firstNameOnly: current.firstName }), []);
|
||||
reduce2.asMutable();
|
||||
|
||||
// reduceRight. Call the mutable array's 'reduceRight' with the same function to ensure compatability. Make sure the output array is immutable.
|
||||
array.asMutable().reduceRight((previous, current) => ({ ...previous, lastName: current.lastName }));
|
||||
const reduceRight1: Immutable.Immutable<User> = array.reduceRight((previous, current) => ({ ...previous, lastName: current.lastName }));
|
||||
reduceRight1.asMutable();
|
||||
// NOTE: this is effectively a map function
|
||||
array.asMutable().reduceRight<FirstName[]>((previous, current) => previous.concat({ firstNameOnly: current.firstName }), []);
|
||||
const reduceRight2: Immutable.Immutable<FirstName[]> = array.reduceRight<FirstName[]>((previous, current) => previous.concat({ firstNameOnly: current.firstName }), []);
|
||||
reduceRight2.asMutable();
|
||||
|
||||
// asMutable
|
||||
const mutableArray1: User[] = array.asMutable();
|
||||
const mutableArray2: User[] = array.asMutable({ deep: true });
|
||||
@@ -141,3 +224,21 @@ interface ExtendedUser extends User {
|
||||
const replacedUser01 = immutableUser.replace({ firstName: 'Super', lastName: 'Monkey' });
|
||||
const replacedUser02 = immutableUser.replace({ firstName: 'Super', lastName: 'Monkey' }, { deep: true });
|
||||
}
|
||||
|
||||
//
|
||||
// Instance syntax: immutable date
|
||||
// ---------------------------------------------------------------
|
||||
|
||||
{
|
||||
// ImmutableDate cannot access mutable methods like setDate, etc, but CAN access getDate().
|
||||
// Once we make it mutable (i.e, a regular Date), we can use those methods
|
||||
const immutableDate: Immutable.ImmutableDate = Immutable.from(new Date());
|
||||
// immutableDate.setDate(1);
|
||||
immutableDate.getDate();
|
||||
immutableDate.asMutable().setDate(1);
|
||||
|
||||
const immutableDate2: Immutable.ImmutableDate = Immutable(new Date());
|
||||
// immutableDate2.setDate(1)
|
||||
immutableDate2.getDate();
|
||||
immutableDate2.asMutable().setDate(1);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user