From 49235fe80728a3803a16251d4c163f002b4bb29f Mon Sep 17 00:00:00 2001 From: Mikhail Nasyrov Date: Wed, 21 Jul 2021 00:00:57 +0700 Subject: [PATCH] fix: Types for actions and effects --- packages/rx-effects/src/action.test.ts | 22 +++++++++++++ packages/rx-effects/src/action.ts | 11 +++---- packages/rx-effects/src/effect.ts | 44 ++++++++++++------------- packages/rx-effects/src/effectScope.ts | 10 +++--- packages/rx-effects/src/example.test.ts | 25 +++++++------- packages/rx-effects/src/stateQuery.ts | 8 ++--- packages/rx-effects/src/stateStore.ts | 33 +++++++++---------- packages/rx-effects/src/stateUtils.ts | 2 +- 8 files changed, 87 insertions(+), 68 deletions(-) create mode 100644 packages/rx-effects/src/action.test.ts diff --git a/packages/rx-effects/src/action.test.ts b/packages/rx-effects/src/action.test.ts new file mode 100644 index 0000000..8fe57d6 --- /dev/null +++ b/packages/rx-effects/src/action.test.ts @@ -0,0 +1,22 @@ +import { firstValueFrom } from 'rxjs'; +import { createAction } from './action'; + +describe('Action', () => { + it('should emit the event', async () => { + const action = createAction(); + + const promise = firstValueFrom(action.event$); + action(1); + + expect(await promise).toBe(1); + }); + + it('should use void type and undefined value if a generic type is not specified', async () => { + const action = createAction(); + + const promise = firstValueFrom(action.event$); + action(); + + expect(await promise).toBe(undefined); + }); +}); diff --git a/packages/rx-effects/src/action.ts b/packages/rx-effects/src/action.ts index 953e32c..0facfed 100644 --- a/packages/rx-effects/src/action.ts +++ b/packages/rx-effects/src/action.ts @@ -2,17 +2,16 @@ import { Observable, Subject } from 'rxjs'; export type Action = { readonly event$: Observable; - - // TODO: Check typings - (): void; (event: Event): void; -}; +} & (Event extends undefined | void + ? { (event?: Event): void } + : { (event: Event): void }); -export function createAction(): Action { +export function createAction(): Action { const source$ = new Subject(); const emitter = (event: Event): void => source$.next(event); emitter.event$ = source$.asObservable(); - return emitter as Action; + return emitter as unknown as Action; } diff --git a/packages/rx-effects/src/effect.ts b/packages/rx-effects/src/effect.ts index 4e803c1..7048742 100644 --- a/packages/rx-effects/src/effect.ts +++ b/packages/rx-effects/src/effect.ts @@ -11,24 +11,24 @@ import { Action } from './action'; import { StateQuery } from './stateQuery'; import { createStateStore } from './stateStore'; -export type Effect = Readonly<{ - result$: Observable; - done$: Observable<{ event: Event; result: Result }>; - error$: Observable<{ event: Event; error: ErrorType }>; - final$: Observable; - pending: StateQuery; - pendingCount: StateQuery; - - handle: ((event$: Observable) => Subscription) & +export type Effect = { + readonly result$: Observable; + readonly done$: Observable<{ event: Event; result: Result }>; + readonly error$: Observable<{ event: Event; error: ErrorType }>; + readonly final$: Observable; + readonly pending: StateQuery; + readonly pendingCount: StateQuery; + + readonly handle: ((event$: Observable) => Subscription) & ((action: Action) => Subscription); - destroy: () => void; -}>; + readonly destroy: () => void; +}; export type EffectHandler = ( event: Event, ) => Result | Promise | Observable; -export function createEffect( +export function createEffect( handler: EffectHandler, scopeSubscriptions?: Subscription, ): Effect { @@ -79,16 +79,14 @@ export function createEffect( try { const handlerResult = handler(event); - if (handlerResult) { - if ('then' in handlerResult) { - executePromise(event, handlerResult); - return; - } + if (handlerResult instanceof Promise) { + executePromise(event, handlerResult); + return; + } - if (handlerResult instanceof Observable) { - executeObservable(event, handlerResult); - return; - } + if (handlerResult instanceof Observable) { + executeObservable(event, handlerResult); + return; } pendingCount.update(decreaseCount); @@ -112,7 +110,9 @@ export function createEffect( }; function handle(source: Observable | Action): Subscription { - const observable = source instanceof Observable ? source : source.event$; + const observable = ( + source instanceof Observable ? source : source.event$ + ) as Observable; const subscription = observable.subscribe(observer); subscriptions.add(subscription); diff --git a/packages/rx-effects/src/effectScope.ts b/packages/rx-effects/src/effectScope.ts index 6ac09f0..c24ad14 100644 --- a/packages/rx-effects/src/effectScope.ts +++ b/packages/rx-effects/src/effectScope.ts @@ -1,14 +1,14 @@ import { Subscription, TeardownLogic } from 'rxjs'; import { createEffect, Effect, EffectHandler } from './effect'; -export type EffectScope = Readonly<{ - add: (teardown: TeardownLogic) => void; - destroy: () => void; +export type EffectScope = { + readonly add: (teardown: TeardownLogic) => void; + readonly destroy: () => void; - createEffect: ( + readonly createEffect: ( handler: EffectHandler, ) => Effect; -}>; +}; export function createEffectScope(): EffectScope { const subscriptions = new Subscription(); diff --git a/packages/rx-effects/src/example.test.ts b/packages/rx-effects/src/example.test.ts index 7360f64..b444ead 100644 --- a/packages/rx-effects/src/example.test.ts +++ b/packages/rx-effects/src/example.test.ts @@ -1,6 +1,6 @@ import { firstValueFrom } from 'rxjs'; import { take, toArray } from 'rxjs/operators'; -import { createAction, Action } from './action'; +import { Action, createAction } from './action'; import { Effect } from './effect'; import { createEffectScope, EffectScope } from './effectScope'; import { declareState, StateDeclaration } from './stateDeclaration'; @@ -26,22 +26,23 @@ function createCalculatorEffects( scope: EffectScope, store: CalculatorStore, ): { - incrementEffect: Effect; + incrementEffect: Effect; + decrementEffect: Effect; sumEffect: Effect; - decrementEffect: Effect; - subtractEffect: Effect; - resetEffect: Effect; + subtractEffect: Effect; + resetEffect: Effect; } { const incrementEffect = scope.createEffect(() => store.update(addValue(1))); const decrementEffect = scope.createEffect(() => store.update(addValue(-1))); - const sumEffect = scope.createEffect((value) => + const sumEffect = scope.createEffect((value: number) => store.update(addValue(value)), ); - const subtractEffect = scope.createEffect((value) => - store.update(addValue(-value)), - ); + const subtractEffect = scope.createEffect((value: number) => { + store.update(addValue(-value)); + return -value; + }); const resetEffect = createResetStoreEffect( store, @@ -77,11 +78,11 @@ function createCalculatorController( } { const scope = createEffectScope(); - const incrementAction = createAction(); - const decrementAction = createAction(); + const incrementAction = createAction(); + const decrementAction = createAction(); const sumAction = createAction(); const subtractAction = createAction(); - const resetAction = createAction(); + const resetAction = createAction(); const { incrementEffect, diff --git a/packages/rx-effects/src/stateQuery.ts b/packages/rx-effects/src/stateQuery.ts index d3fd942..25f663d 100644 --- a/packages/rx-effects/src/stateQuery.ts +++ b/packages/rx-effects/src/stateQuery.ts @@ -1,10 +1,10 @@ import { Observable } from 'rxjs'; import { map } from 'rxjs/operators'; -export type StateQuery = Readonly<{ - get: () => T; - value$: Observable; -}>; +export type StateQuery = { + readonly get: () => T; + readonly value$: Observable; +}; export function mapStateQuery( query: StateQuery, diff --git a/packages/rx-effects/src/stateStore.ts b/packages/rx-effects/src/stateStore.ts index 394ecc3..3f27b23 100644 --- a/packages/rx-effects/src/stateStore.ts +++ b/packages/rx-effects/src/stateStore.ts @@ -3,25 +3,22 @@ import { distinctUntilChanged, map, shareReplay } from 'rxjs/operators'; import { StateMutation } from './stateMutation'; import { StateQuery } from './stateQuery'; -export type StateReader = Readonly< - StateQuery & { - select: ( - selector: (state: State) => R, - compare?: (v1: R, v2: R) => boolean, - ) => Observable; - query: ( - selector: (state: State) => R, - compare?: (v1: R, v2: R) => boolean, - ) => StateQuery; - } ->; +export type StateReader = StateQuery & { + readonly select: ( + selector: (state: State) => R, + compare?: (v1: R, v2: R) => boolean, + ) => Observable; + + readonly query: ( + selector: (state: State) => R, + compare?: (v1: R, v2: R) => boolean, + ) => StateQuery; +}; -export type StateStore = Readonly< - StateReader & { - set: (state: State) => void; - update: (mutation: StateMutation) => void; - } ->; +export type StateStore = StateReader & { + readonly set: (state: State) => void; + readonly update: (mutation: StateMutation) => void; +}; export function createStateStore( initialState: State, diff --git a/packages/rx-effects/src/stateUtils.ts b/packages/rx-effects/src/stateUtils.ts index 40311ac..c63e5af 100644 --- a/packages/rx-effects/src/stateUtils.ts +++ b/packages/rx-effects/src/stateUtils.ts @@ -17,7 +17,7 @@ export function createUpdateStoreEffect( export function createResetStoreEffect( store: StateStore, nextState: State, -): Effect { +): Effect { return createEffect(() => store.set(nextState)); }