Add mergerino type definitions (#37712)

This commit is contained in:
fivitti 2019-08-20 00:58:49 +02:00 committed by Sheetal Nandi
parent 99bff85ef4
commit 21a2f12b2b
4 changed files with 329 additions and 0 deletions

87
types/mergerino/index.d.ts vendored Normal file
View File

@ -0,0 +1,87 @@
// Type definitions for mergerino 0.4
// Project: https://github.com/fuzetsu/mergerino#readme
// Definitions by: Slawomir "Fivitti" Figiel <https://github.com/fivitti>
// Definitions: https://github.com/DefinitelyTyped/DefinitelyTyped
// TypeScript Version: 3.4
// TypeScript in version below 3.4 doesn't correctly support FunctionPatch. Arguments have "any" type.
/**
* Nice side effect of flattening array arguments is that you can easily
* add conditions to your patches using nested arrays.
*
* Arrays may be nested in any depth.
*/
export interface DeepArray<T> extends ReadonlyArray<T | DeepArray<T>> { }
/**
* If you want to fully remove a property from an object specify undefined as the value.
*/
export type DeletePatch = undefined;
/**
* If you want to replace a property based on its current value, use a function.
*
* If you pass a function it will receive the current value as the first argument
* and the merge function as the second. The return value will be the replacement.
* The value you return will bypass merging logic and simply overwrite the property.
*/
export type FunctionPatch<T> = (val: T, merge: Merge<T extends object ? T : never>) => T;
/**
* If you want to replace a array specify new array as the value.
*
* If you want edit array's item or insert new item specify object as the value.
* Keys of this object are array's indexes, values are patches of array's items.
*/
export type ArrayPatch<T> = T extends Array<infer V> ? ObjectPatch<Record<number, V>> : never;
/**
* Mergerino merges immutably meaning that the target object will never be mutated (changed).
* Instead each object along the path your patch specifies will be shallow copied into a new object.
*/
export type NestedPatch<T> = T extends object ? ObjectPatch<T> : never;
/**
* 1. Each object along the path your patch specifies will be shallow copied into a new object.
* 2. Specify undefined as the value fully remove a property from an object.
* 3. Use a function if you want to replace a property based on its current value.
*/
export type ObjectPatch<S extends object> = { [K in keyof S]?: S[K] | DeletePatch | FunctionPatch<S[K]> | NestedPatch<S[K]> | ArrayPatch<S[K]> };
/**
* Falsy patches are ignored
*/
export type Falsy = false | 0 | '' | null | undefined;
/**
* Passing a function as a top level patch acts exactly the same as a function
* passed to a specific property. It receives the full state object as the first
* argument, the merge function as the second.
*/
export type TopLevelPatch<S extends object> = FunctionPatch<S> | ObjectPatch<S> | ArrayPatch<S> | Falsy;
/**
* You can pass multiple patches in a single merge call, array arguments will
* be flattened before processing.
*/
export type MultipleTopLevelPatch<S extends object> = TopLevelPatch<S> | DeepArray<TopLevelPatch<S>>;
/**
* Main Mergerino function. An immutable merge util for state management.
*
* You can pass multiple patches in a single merge call, array arguments will be flattened before processing.
* Since falsy patches are ignored.
*/
export type Merge<S extends object> = (source: S, ...patches: Array<MultipleTopLevelPatch<S>>) => S;
/**
* Main Mergerino function. An immutable merge util for state management.
*
* You can pass multiple patches in a single merge call, array arguments will be flattened before processing.
* Since falsy patches are ignored.
*/
// tslint:disable-next-line:npm-naming
export default function merge<S extends object>(source: S, ...patches: Array<MultipleTopLevelPatch<S>>): S;
// Mergerino uses "default export", but no in minified version which is checked by dtslint.
// This line supress error: The types for mergerino specify 'export default' but the source does not mention 'default' anywhere.

View File

@ -0,0 +1,216 @@
import merge from 'mergerino';
function deletingWorks() {
interface State {
deep: {
prop?: string;
};
fake?: any;
other: boolean | null;
prop: boolean;
}
const state: State = {
prop: true,
other: true,
deep: { prop: 'foo' },
};
const newState = merge(state, {
deep: { prop: undefined },
fake: undefined, // deleting non existent key
other: null,
prop: undefined,
});
}
function functionSubWorks() {
interface State {
age: number;
name: string;
obj: {
prop?: boolean;
replaced?: boolean;
};
}
const state: State = {
age: 10,
name: 'bob',
obj: { prop: true }
};
const newState = merge(state, {
age: x => x * 10,
name: (x, m) => {
return x;
},
obj: () => ({ replaced: true }),
});
}
function deepFunctionSubToUncreatedObjectPath() {
interface State {
add?: {
stats: {
count: number;
};
};
orig: boolean;
}
const state: State = {
orig: true
};
const newState = merge(
state,
{
add: {
stats: {
count: x => x + 1
},
}
}
);
}
function addNestedObject() {
interface State {
age: number;
add?: {
sub: boolean;
};
}
const state: State = { age: 10 };
const add = { sub: true };
const newState = merge(state, { add });
}
function deepMergeObjects() {
interface State {
age: number;
sub: {
sub: {
prop: boolean;
newProp?: boolean;
};
};
}
const state: State = {
age: 10, sub: {
sub: { prop: true },
},
};
const newState = merge(
state,
{
sub: {
sub:
{
newProp: true,
}
},
},
);
}
function functionPatch() {
interface State {
age: number;
foo: string;
prop?: boolean;
}
const state: State = { age: 10, foo: 'bar' };
const newState = merge(state, (s, m) => {
return merge(s, { prop: true });
});
}
function multiArrayFalsyPatches() {
interface State {
age?: number;
foo: string;
baz?: number;
hello?: boolean;
arr?: number[];
prop?: boolean;
}
const state: State = { foo: 'bar' };
const newState = merge(
state,
{ baz: 5 },
{ hello: false },
[{ arr: [1, 2, 3] }, [[{ prop: true }]], false, null],
undefined,
'',
0,
null,
(s, m) => m(s, { age: 10 }),
[[[[[[[{ age: (x: number) => x * 3 }]]]]]]],
);
}
function arrayPatches() {
const arr = [1, 2, 3];
const newArr = merge(arr, { 2: 100 }, { 0: undefined }, { 0: 200 });
}
function deepMergeWithArr() {
interface State {
foo: string;
deep: {
arr: number[];
prop: boolean;
};
}
const state: State = { foo: 'bar', deep: { arr: [1, 2, 3], prop: false } };
const newState = merge(state, { deep: { arr: { 1: 20 } } });
}
// ToDo: It shoudn't be allowed, but it occurs when I use "infer" to get array type
function arrayObjectPatchNonExisitngProperty() {
interface State {
arr: Array<{
prop: boolean;
}>;
}
const state: State = { arr: [{ prop: true }] };
const newState = merge(state, { arr: { 0: { prop: false, nonExists: 42 } } });
}
function topLevelFunctionPatch() {
type State =
| {
age: number;
foo: string;
}
| { replaced: boolean };
const state = { age: 20, foo: 'bar' };
const replacement = { replaced: true };
const newState = merge<State>(state, () => replacement);
}
function reuseObjectIfSameRefWhenPatching() {
const state = { deep: { prop: true } };
const newState = merge(state, { deep: state.deep });
}
function replacePrimitiveWithObjectAndViceVersa() {
interface State {
count:
| number
| {
prop: boolean;
};
foo:
| number
| {
prop: boolean;
};
}
const state: State = { count: 10, foo: { prop: true } };
const newState = merge(state, { count: { prop: true }, foo: 10 });
}

View File

@ -0,0 +1,23 @@
{
"compilerOptions": {
"module": "commonjs",
"lib": [
"es6"
],
"noImplicitAny": true,
"noImplicitThis": true,
"strictFunctionTypes": true,
"strictNullChecks": true,
"baseUrl": "../",
"typeRoots": [
"../"
],
"types": [],
"noEmit": true,
"forceConsistentCasingInFileNames": true
},
"files": [
"index.d.ts",
"mergerino-tests.ts"
]
}

View File

@ -0,0 +1,3 @@
{
"extends": "dtslint/dt.json"
}