rx-store-core
Version:
A Rxjs and Immutable based Type-safe state management tool
132 lines (126 loc) • 3.41 kB
text/typescript
import {
catchError,
exhaustMap,
from,
map,
of,
Subject,
switchMap,
tap,
} from "rxjs";
import {
Action,
AsyncReducer,
AsyncDispatchConfig,
AsyncDispatcher,
BS,
Dispatcher,
Reducer,
RxStore,
Observer,
} from "rx-store-types";
import { bound } from "./decorators/bound";
export class DispatcherImpl<S extends BS, K extends keyof S, T extends string>
implements Dispatcher<ReturnType<S[K]>, T>
{
constructor(
private reducer: Reducer<T, S, K>,
private store: RxStore<S>,
private key: K
) {}
dispatch(action: Action<ReturnType<S[K]>, T>) {
const mutation = {
[this.key]: this.reducer(this.store.getState(this.key), {
type: action.type,
payload:
action.payload !== undefined
? action.payload
: this.store.getDefault(this.key),
}),
} as {};
this.store.setState(mutation);
}
}
export class AsyncDispatcherImpl<
S extends BS,
K extends keyof S,
T extends string
> implements AsyncDispatcher<T, S, K>
{
private dispatchSignal: Subject<
Action<ReturnType<S[K]>, T> & AsyncDispatchConfig<S, K>
> = new Subject();
constructor(
private reducer: AsyncReducer<T, S, K>,
private store: RxStore<S>,
private key: K,
private config?: AsyncDispatchConfig<S, K>
) {}
observe(observer?: Observer<ReturnType<S[K]>>) {
let connect = this.config?.lazy ? exhaustMap : switchMap;
const subscription = this.dispatchSignal
.pipe(
tap(({ lazy }) => {
if (lazy) {
connect = exhaustMap;
return;
}
connect = switchMap;
}),
tap(({ start }) => {
if (start) {
start();
}
this.config?.start?.();
}),
map(({ type, payload, fail, fallback, always, success }) => {
const result$ = this.reducer(this.store.getState(this.key), {
type,
payload,
});
const converged$ =
result$ instanceof Promise ? from(result$) : result$;
return converged$.pipe(
catchError((err) => {
const getDefault = fallback ? fallback : this.config?.fallback;
const valOnErr = getDefault
? getDefault()
: this.store.getState(this.key);
if (fail) {
fail(err, valOnErr);
}
this.config?.fail?.(err, valOnErr);
return of(valOnErr);
}),
tap((resp) => {
if (success) {
success(resp);
}
this.config?.success?.(resp);
}),
tap(() => {
if (always) {
always();
}
this.config?.always?.();
})
);
}),
connect((converged$) => converged$)
)
.subscribe((value) => {
observer?.(value);
this.store.setState({ [this.key]: value } as {});
});
return () => subscription.unsubscribe();
}
async dispatch(
action: Action<ReturnType<S[K]>, T>,
config: AsyncDispatchConfig<S, K> = {}
) {
this.dispatchSignal.next({ ...action, ...config });
}
}