Skip to content

Commit

Permalink
fix: Types for actions and effects
Browse files Browse the repository at this point in the history
  • Loading branch information
mnasyrov committed Jul 21, 2021
1 parent 2407065 commit 49235fe
Show file tree
Hide file tree
Showing 8 changed files with 87 additions and 68 deletions.
22 changes: 22 additions & 0 deletions packages/rx-effects/src/action.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
import { firstValueFrom } from 'rxjs';
import { createAction } from './action';

describe('Action', () => {
it('should emit the event', async () => {
const action = createAction<number>();

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);
});
});
11 changes: 5 additions & 6 deletions packages/rx-effects/src/action.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,17 +2,16 @@ import { Observable, Subject } from 'rxjs';

export type Action<Event> = {
readonly event$: Observable<Event>;

// TODO: Check typings
(): void;
(event: Event): void;
};
} & (Event extends undefined | void
? { (event?: Event): void }
: { (event: Event): void });

export function createAction<Event>(): Action<Event> {
export function createAction<Event = void>(): Action<Event> {
const source$ = new Subject<Event>();

const emitter = (event: Event): void => source$.next(event);
emitter.event$ = source$.asObservable();

return emitter as Action<Event>;
return emitter as unknown as Action<Event>;
}
44 changes: 22 additions & 22 deletions packages/rx-effects/src/effect.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,24 +11,24 @@ import { Action } from './action';
import { StateQuery } from './stateQuery';
import { createStateStore } from './stateStore';

export type Effect<Event, Result = void, ErrorType = Error> = Readonly<{
result$: Observable<Result>;
done$: Observable<{ event: Event; result: Result }>;
error$: Observable<{ event: Event; error: ErrorType }>;
final$: Observable<Event>;
pending: StateQuery<boolean>;
pendingCount: StateQuery<number>;

handle: ((event$: Observable<Event>) => Subscription) &
export type Effect<Event, Result = void, ErrorType = Error> = {
readonly result$: Observable<Result>;
readonly done$: Observable<{ event: Event; result: Result }>;
readonly error$: Observable<{ event: Event; error: ErrorType }>;
readonly final$: Observable<Event>;
readonly pending: StateQuery<boolean>;
readonly pendingCount: StateQuery<number>;

readonly handle: ((event$: Observable<Event>) => Subscription) &
((action: Action<Event>) => Subscription);
destroy: () => void;
}>;
readonly destroy: () => void;
};

export type EffectHandler<Event, Result> = (
event: Event,
) => Result | Promise<Result> | Observable<Result>;

export function createEffect<Event, Result = void, ErrorType = Error>(
export function createEffect<Event = void, Result = void, ErrorType = Error>(
handler: EffectHandler<Event, Result>,
scopeSubscriptions?: Subscription,
): Effect<Event, Result, ErrorType> {
Expand Down Expand Up @@ -79,16 +79,14 @@ export function createEffect<Event, Result = void, ErrorType = Error>(
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);
Expand All @@ -112,7 +110,9 @@ export function createEffect<Event, Result = void, ErrorType = Error>(
};

function handle(source: Observable<Event> | Action<Event>): Subscription {
const observable = source instanceof Observable ? source : source.event$;
const observable = (
source instanceof Observable ? source : source.event$
) as Observable<Event>;

const subscription = observable.subscribe(observer);
subscriptions.add(subscription);
Expand Down
10 changes: 5 additions & 5 deletions packages/rx-effects/src/effectScope.ts
Original file line number Diff line number Diff line change
@@ -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: <Event, Result = void, ErrorType = Error>(
readonly createEffect: <Event = void, Result = void, ErrorType = Error>(
handler: EffectHandler<Event, Result>,
) => Effect<Event, Result, ErrorType>;
}>;
};

export function createEffectScope(): EffectScope {
const subscriptions = new Subscription();
Expand Down
25 changes: 13 additions & 12 deletions packages/rx-effects/src/example.test.ts
Original file line number Diff line number Diff line change
@@ -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';
Expand All @@ -26,22 +26,23 @@ function createCalculatorEffects(
scope: EffectScope,
store: CalculatorStore,
): {
incrementEffect: Effect<unknown>;
incrementEffect: Effect<void>;
decrementEffect: Effect<void>;
sumEffect: Effect<number>;
decrementEffect: Effect<unknown>;
subtractEffect: Effect<number>;
resetEffect: Effect<unknown>;
subtractEffect: Effect<number, number>;
resetEffect: Effect<void>;
} {
const incrementEffect = scope.createEffect(() => store.update(addValue(1)));
const decrementEffect = scope.createEffect(() => store.update(addValue(-1)));

const sumEffect = scope.createEffect<number>((value) =>
const sumEffect = scope.createEffect((value: number) =>
store.update(addValue(value)),
);

const subtractEffect = scope.createEffect<number>((value) =>
store.update(addValue(-value)),
);
const subtractEffect = scope.createEffect((value: number) => {
store.update(addValue(-value));
return -value;
});

const resetEffect = createResetStoreEffect(
store,
Expand Down Expand Up @@ -77,11 +78,11 @@ function createCalculatorController(
} {
const scope = createEffectScope();

const incrementAction = createAction();
const decrementAction = createAction();
const incrementAction = createAction<void>();
const decrementAction = createAction<void>();
const sumAction = createAction<number>();
const subtractAction = createAction<number>();
const resetAction = createAction();
const resetAction = createAction<void>();

const {
incrementEffect,
Expand Down
8 changes: 4 additions & 4 deletions packages/rx-effects/src/stateQuery.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
import { Observable } from 'rxjs';
import { map } from 'rxjs/operators';

export type StateQuery<T> = Readonly<{
get: () => T;
value$: Observable<T>;
}>;
export type StateQuery<T> = {
readonly get: () => T;
readonly value$: Observable<T>;
};

export function mapStateQuery<T, R>(
query: StateQuery<T>,
Expand Down
33 changes: 15 additions & 18 deletions packages/rx-effects/src/stateStore.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,25 +3,22 @@ import { distinctUntilChanged, map, shareReplay } from 'rxjs/operators';
import { StateMutation } from './stateMutation';
import { StateQuery } from './stateQuery';

export type StateReader<State> = Readonly<
StateQuery<State> & {
select: <R>(
selector: (state: State) => R,
compare?: (v1: R, v2: R) => boolean,
) => Observable<R>;
query: <R>(
selector: (state: State) => R,
compare?: (v1: R, v2: R) => boolean,
) => StateQuery<R>;
}
>;
export type StateReader<State> = StateQuery<State> & {
readonly select: <R>(
selector: (state: State) => R,
compare?: (v1: R, v2: R) => boolean,
) => Observable<R>;

readonly query: <R>(
selector: (state: State) => R,
compare?: (v1: R, v2: R) => boolean,
) => StateQuery<R>;
};

export type StateStore<State> = Readonly<
StateReader<State> & {
set: (state: State) => void;
update: (mutation: StateMutation<State>) => void;
}
>;
export type StateStore<State> = StateReader<State> & {
readonly set: (state: State) => void;
readonly update: (mutation: StateMutation<State>) => void;
};

export function createStateStore<State>(
initialState: State,
Expand Down
2 changes: 1 addition & 1 deletion packages/rx-effects/src/stateUtils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ export function createUpdateStoreEffect<Event, State>(
export function createResetStoreEffect<State>(
store: StateStore<State>,
nextState: State,
): Effect<unknown> {
): Effect<void> {
return createEffect(() => store.set(nextState));
}

Expand Down

0 comments on commit 49235fe

Please sign in to comment.