Skip to content

Commit

Permalink
Revert "Revert "fix(store): do not infer T from argument to patch (#1806
Browse files Browse the repository at this point in the history
)""

This reverts commit eb56df0.
The effectively reinstates the changes related to #1808 by reapplying
the changes from commit 8834f50.
  • Loading branch information
markwhitfeld committed Jun 10, 2022
1 parent 075490b commit 3387e65
Show file tree
Hide file tree
Showing 15 changed files with 224 additions and 192 deletions.
8 changes: 4 additions & 4 deletions packages/store/operators/src/append.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
import { StateOperator } from '@ngxs/store';
import { RepairType } from './utils';
import { RepairType, NoInfer } from './utils';

/**
* @param items - Specific items to append to the end of an array
*/
export function append<T>(items: T[]): StateOperator<RepairType<T>[]> {
export function append<T>(items: NoInfer<T>[]): StateOperator<RepairType<T>[]> {
return function appendOperator(existing: Readonly<RepairType<T>[]>): RepairType<T>[] {
// If `items` is `undefined` or `null` or `[]` but `existing` is provided
// just return `existing`
Expand All @@ -14,11 +14,11 @@ export function append<T>(items: T[]): StateOperator<RepairType<T>[]> {
}

if (Array.isArray(existing)) {
return existing.concat(items as RepairType<T>[]);
return existing.concat((items as unknown) as RepairType<T>[]);
}

// For example if some property is added dynamically
// and didn't exist before thus it's not `ArrayLike`
return items as RepairType<T>[];
return (items as unknown) as RepairType<T>[];
};
}
3 changes: 2 additions & 1 deletion packages/store/operators/src/compose.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { StateOperator } from '@ngxs/store';
import { NoInfer } from './utils';

export function compose<T>(...operators: StateOperator<T>[]): StateOperator<T> {
export function compose<T>(...operators: NoInfer<StateOperator<T>[]>): StateOperator<T> {
return function composeOperator(existing: Readonly<T>): T {
return operators.reduce((accumulator, operator) => operator(accumulator), existing);
};
Expand Down
8 changes: 4 additions & 4 deletions packages/store/operators/src/iif.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { StateOperator } from '@ngxs/store';

import { isStateOperator, isUndefined, isPredicate, RepairType } from './utils';
import { isStateOperator, isUndefined, isPredicate, RepairType, NoInfer } from './utils';
import { Predicate } from './internals';

function retrieveValue<T>(
Expand Down Expand Up @@ -32,9 +32,9 @@ function retrieveValue<T>(
* @param elseOperatorOrValue - Any value or a state operator
*/
export function iif<T>(
condition: Predicate<T> | boolean,
trueOperatorOrValue: StateOperator<T> | T,
elseOperatorOrValue?: StateOperator<T> | T
condition: NoInfer<Predicate<T>> | boolean,
trueOperatorOrValue: NoInfer<StateOperator<T>> | T,
elseOperatorOrValue?: NoInfer<StateOperator<T>> | T
): StateOperator<RepairType<T>> {
return function iifOperator(existing: Readonly<RepairType<T>>): RepairType<T> {
// Convert the value to a boolean
Expand Down
2 changes: 1 addition & 1 deletion packages/store/operators/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,6 @@ export { compose } from './compose';
export { iif } from './iif';
export { insertItem } from './insert-item';
export { patch } from './patch';
export { isStateOperator } from './utils';
export { isStateOperator, NoInfer } from './utils';
export { updateItem } from './update-item';
export { removeItem } from './remove-item';
8 changes: 4 additions & 4 deletions packages/store/operators/src/insert-item.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
import { StateOperator } from '@ngxs/store';
import { isNil, RepairType } from './utils';
import { isNil, RepairType, NoInfer } from './utils';

/**
* @param value - Value to insert
* @param [beforePosition] - Specified index to insert value before, optional
*/
export function insertItem<T>(
value: T,
value: NoInfer<T>,
beforePosition?: number
): StateOperator<RepairType<T>[]> {
return function insertItemOperator(existing: Readonly<RepairType<T>[]>): RepairType<T>[] {
Expand All @@ -18,7 +18,7 @@ export function insertItem<T>(

// Property may be dynamic and might not existed before
if (!Array.isArray(existing)) {
return [value as RepairType<T>];
return [(value as unknown) as RepairType<T>];
}

const clone = existing.slice();
Expand All @@ -32,7 +32,7 @@ export function insertItem<T>(
index = beforePosition!;
}

clone.splice(index, 0, value as RepairType<T>);
clone.splice(index, 0, (value as unknown) as RepairType<T>);
return clone;
};
}
12 changes: 5 additions & 7 deletions packages/store/operators/src/patch.ts
Original file line number Diff line number Diff line change
@@ -1,20 +1,18 @@
import { StateOperator } from '@ngxs/store';
import { isStateOperator } from './utils';
import { isStateOperator, NoInfer } from './utils';

export type PatchSpec<T> = { [P in keyof T]?: T[P] | StateOperator<NonNullable<T[P]>> };

type PatchValues<T> = {
readonly [P in keyof T]?: T[P] extends (...args: any[]) => infer R ? R : T[P];
};

type PatchOperator<T> = <U extends PatchValues<T>>(existing: Readonly<U>) => U;

export function patch<T>(patchObject: PatchSpec<T>): PatchOperator<T> {
return function patchStateOperator<U extends PatchValues<T>>(existing: Readonly<U>): U {
export function patch<T>(patchObject: NoInfer<PatchSpec<T>>): StateOperator<NonNullable<T>> {
return (function patchStateOperator(existing: Readonly<PatchValues<T>>): NonNullable<T> {
let clone = null;
for (const k in patchObject) {
const newValue = patchObject[k];
const existingPropValue = existing[k];
const existingPropValue = existing[k as Extract<keyof T, string>];
const newPropValue = isStateOperator(newValue)
? newValue(<any>existingPropValue)
: newValue;
Expand All @@ -26,5 +24,5 @@ export function patch<T>(patchObject: PatchSpec<T>): PatchOperator<T> {
}
}
return clone || existing;
};
} as unknown) as StateOperator<NonNullable<T>>;
}
22 changes: 15 additions & 7 deletions packages/store/operators/src/update-item.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,13 @@
import { StateOperator } from '@ngxs/store';

import { isStateOperator, isPredicate, isNumber, invalidIndex, RepairType } from './utils';
import {
isStateOperator,
isPredicate,
isNumber,
invalidIndex,
RepairType,
NoInfer
} from './utils';
import { Predicate } from './internals';

/**
Expand All @@ -10,14 +17,14 @@ import { Predicate } from './internals';
* function that can be applied to an existing value
*/
export function updateItem<T>(
selector: number | Predicate<T>,
operatorOrValue: T | StateOperator<T>
selector: number | NoInfer<Predicate<T>>,
operatorOrValue: NoInfer<T> | NoInfer<StateOperator<T>>
): StateOperator<RepairType<T>[]> {
return function updateItemOperator(existing: Readonly<RepairType<T>[]>): RepairType<T>[] {
let index = -1;

if (isPredicate(selector)) {
index = existing.findIndex(selector);
index = existing.findIndex(selector as Predicate<T>);
} else if (isNumber(selector)) {
index = selector;
}
Expand All @@ -29,10 +36,11 @@ export function updateItem<T>(
let value: T = null!;
// Need to check if the new item value will change the existing item value
// then, only if it will change it then clone the array and set the item
if (isStateOperator(operatorOrValue)) {
value = operatorOrValue(existing[index] as Readonly<T>);
const theOperatorOrValue = operatorOrValue as T | StateOperator<T>;
if (isStateOperator(theOperatorOrValue)) {
value = theOperatorOrValue(existing[index] as Readonly<T>);
} else {
value = operatorOrValue;
value = theOperatorOrValue;
}

// If the value hasn't been mutated
Expand Down
4 changes: 3 additions & 1 deletion packages/store/operators/src/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,4 +26,6 @@ export function isNil<T>(value: T | null | undefined): value is null | undefined
return value === null || isUndefined(value);
}

export type RepairType<T> = T extends true ? boolean : (T extends false ? boolean : T);
export type RepairType<T> = T extends true ? boolean : T extends false ? boolean : T;

export type NoInfer<T> = T extends infer S ? S : never;
2 changes: 1 addition & 1 deletion packages/store/operators/tests/update-item.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -105,7 +105,7 @@ describe('update item', () => {
};

// Act
const newValue = patch({
const newValue = patch<typeof original>({
a: updateItem(item => item!.name === 'Artur', { name: 'Mark' })
})(original);

Expand Down
16 changes: 8 additions & 8 deletions packages/store/types/tests/state-operator.lint.ts
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,7 @@ export class AnimalsState {
@Action(AddZebra)
addZebra(ctx: StateContext<AnimalsStateModel>, { payload }: AddZebra) {
ctx.setState(
// $ExpectType PatchOperator<{ zebras: string[] | StateOperator<string[]>; }>
// $ExpectType StateOperator<AnimalsStateModel>
patch({
// $ExpectType StateOperator<string[]>
zebras: append([payload])
Expand All @@ -66,7 +66,7 @@ export class AnimalsState {
@Action(RemovePanda)
removePanda(ctx: StateContext<AnimalsStateModel>, { payload }: RemovePanda) {
ctx.setState(
// $ExpectType PatchOperator<{ pandas: string[] | StateOperator<string[]>; }>
// $ExpectType StateOperator<AnimalsStateModel>
patch({
// $ExpectType StateOperator<string[]>
pandas: removeItem<string>(name => name === payload)
Expand All @@ -77,7 +77,7 @@ export class AnimalsState {
@Action(Test)
test(ctx: StateContext<AnimalsStateModel>, { payload }: Test) {
ctx.setState(
// $ExpectType PatchOperator<{ pandas: string[] | StateOperator<string[]>; }>
// $ExpectType StateOperator<AnimalsStateModel>
patch({
// $ExpectType StateOperator<string[]>
pandas: insertItem<string>(payload)
Expand All @@ -88,7 +88,7 @@ export class AnimalsState {
@Action(ChangePandaName)
changePandaName(ctx: StateContext<AnimalsStateModel>, { payload }: ChangePandaName) {
ctx.setState(
// $ExpectType PatchOperator<{ pandas: string[] | StateOperator<string[]>; zebras: string[] | StateOperator<string[]>; }>
// $ExpectType StateOperator<AnimalsStateModel>
patch({
// $ExpectType StateOperator<string[]>
pandas: updateItem(name => name === payload.name, payload.newName),
Expand All @@ -104,7 +104,7 @@ export class AnimalsState {
{ payload: { zebras, pandas } }: ComposePanda
) {
ctx.setState(
// $ExpectType PatchOperator<{ zebras: string[] | StateOperator<string[]>; pandas: string[] | StateOperator<string[]>; }>
// $ExpectType StateOperator<AnimalsStateModel>
patch({
// $ExpectType StateOperator<string[]>
zebras: compose(append(zebras)),
Expand All @@ -117,16 +117,16 @@ export class AnimalsState {
@Action({ type: 'patchExplicit' })
patchExplicit(ctx: StateContext<AnimalsStateModel>) {
ctx.setState(
// $ExpectType PatchOperator<AnimalsStateModel>
// $ExpectType StateOperator<AnimalsStateModel>
patch<AnimalsStateModel>({ zebras: [] })
);
}

@Action({ type: 'patchImplicit' })
patchImplicit(ctx: StateContext<AnimalsStateModel>) {
ctx.setState(
// $ExpectType PatchOperator<{ zebras: never[]; }>
patch({ zebras: [] }) // $ExpectError
// $ExpectType StateOperator<AnimalsStateModel>
patch({ zebras: [] })
);
}
}
34 changes: 17 additions & 17 deletions packages/store/types/tests/state-operators.append.lint.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,22 +15,25 @@ describe('[TEST]: the append State Operator', () => {
bools: [true, false]
};

patch<Original>({ nums: append<number>([]) })(original); // $ExpectType { nums: number[]; strs: string[]; bools: boolean[]; }
patch<Original>({ nums: append(null!) })(original); // $ExpectType { nums: number[]; strs: string[]; bools: boolean[]; }
patch<Original>({ nums: append(undefined!) })(original); // $ExpectType { nums: number[]; strs: string[]; bools: boolean[]; }
patch<Original>({ nums: append([1, 2]) })(original); // $ExpectType { nums: number[]; strs: string[]; bools: boolean[]; }
patch<Original>({ nums: append([]) })(original); // $ExpectType Original
patch<Original>({ nums: append<number>([]) })(original); // $ExpectType Original
patch<Original>({ nums: append(null!) })(original); // $ExpectType Original
patch<Original>({ nums: append(undefined!) })(original); // $ExpectType Original
patch<Original>({ nums: append([1, 2]) })(original); // $ExpectType Original

patch<Original>({ strs: append<string>([]) })(original); // $ExpectType { nums: number[]; strs: string[]; bools: boolean[]; }
patch<Original>({ strs: append(null!) })(original); // $ExpectType { nums: number[]; strs: string[]; bools: boolean[]; }
patch<Original>({ strs: append(undefined!) })(original); // $ExpectType { nums: number[]; strs: string[]; bools: boolean[]; }
patch<Original>({ strs: append(['1', '2']) })(original); // $ExpectType { nums: number[]; strs: string[]; bools: boolean[]; }
patch<Original>({ strs: append([]) })(original); // $ExpectType Original
patch<Original>({ strs: append<string>([]) })(original); // $ExpectType Original
patch<Original>({ strs: append(null!) })(original); // $ExpectType Original
patch<Original>({ strs: append(undefined!) })(original); // $ExpectType Original
patch<Original>({ strs: append(['1', '2']) })(original); // $ExpectType Original

patch<Original>({ bools: append<boolean>([]) })(original); // $ExpectType { nums: number[]; strs: string[]; bools: boolean[]; }
patch<Original>({ bools: append(null!) })(original); // $ExpectType { nums: number[]; strs: string[]; bools: boolean[]; }
patch<Original>({ bools: append(undefined!) })(original); // $ExpectType { nums: number[]; strs: string[]; bools: boolean[]; }
patch<Original>({ bools: append([true, false]) })(original); // $ExpectType { nums: number[]; strs: string[]; bools: boolean[]; }
patch<Original>({ bools: append([true]) })(original); // $ExpectType { nums: number[]; strs: string[]; bools: boolean[]; }
patch<Original>({ bools: append([false]) })(original); // $ExpectType { nums: number[]; strs: string[]; bools: boolean[]; }
patch<Original>({ bools: append([]) })(original); // $ExpectType Original
patch<Original>({ bools: append<boolean>([]) })(original); // $ExpectType Original
patch<Original>({ bools: append(null!) })(original); // $ExpectType Original
patch<Original>({ bools: append(undefined!) })(original); // $ExpectType Original
patch<Original>({ bools: append([true, false]) })(original); // $ExpectType Original
patch<Original>({ bools: append([true]) })(original); // $ExpectType Original
patch<Original>({ bools: append([false]) })(original); // $ExpectType Original
});

it('should have the following valid complex usage', () => {
Expand Down Expand Up @@ -106,21 +109,18 @@ describe('[TEST]: the append State Operator', () => {
bools: [true, false]
};

patch<Original>({ nums: append([]) })(original); // $ExpectError
patch<Original>({ nums: append<string>([]) })(original); // $ExpectError
patch<Original>({ nums: append([null]) })(original); // $ExpectError
patch<Original>({ nums: append([undefined]) })(original); // $ExpectError
patch<Original>({ nums: append(['1', 2]) })(original); // $ExpectError
patch<Original>({ nums: append(['4', '5']) })(original); // $ExpectError

patch<Original>({ strs: append([]) })(original); // $ExpectError
patch<Original>({ strs: append<number>([]) })(original); // $ExpectError
patch<Original>({ strs: append([null]) })(original); // $ExpectError
patch<Original>({ strs: append([undefined]) })(original); // $ExpectError
patch<Original>({ strs: append([1, '2']) })(original); // $ExpectError
patch<Original>({ strs: append([4, 5]) })(original); // $ExpectError

patch<Original>({ bools: append([]) })(original); // $ExpectError
patch<Original>({ bools: append<number>([]) })(original); // $ExpectError
patch<Original>({ bools: append([null]) })(original); // $ExpectError
patch<Original>({ bools: append([undefined]) })(original); // $ExpectError
Expand Down
Loading

0 comments on commit 3387e65

Please sign in to comment.