Eric Bower
·
23 Feb 24
fx.ts
1import { Operation, Result } from "../deps.ts";
2import type { ActionFnWithPayload, AnyState } from "../types.ts";
3import type { FxStore, StoreUpdater, UpdaterCtx } from "./types.ts";
4import { StoreContext } from "./context.ts";
5import { LoaderOutput } from "./slice/loaders.ts";
6import { parallel, safe } from "../fx/mod.ts";
7import { ThunkAction } from "../query/mod.ts";
8import { getIdFromAction, take } from "../action.ts";
9
10export function* updateStore<S extends AnyState>(
11 updater: StoreUpdater<S> | StoreUpdater<S>[],
12): Operation<UpdaterCtx<S>> {
13 const store = yield* StoreContext;
14 // had to cast the store since StoreContext has a generic store type
15 const st = store as FxStore<S>;
16 const ctx = yield* st.update(updater);
17 return ctx;
18}
19
20export function select<S, R>(selectorFn: (s: S) => R): Operation<R>;
21export function select<S, R, P>(
22 selectorFn: (s: S, p: P) => R,
23 p: P,
24): Operation<R>;
25export function* select<S, R, P>(
26 selectorFn: (s: S, p?: P) => R,
27 p?: P,
28): Operation<R> {
29 const store = yield* StoreContext;
30 return selectorFn(store.getState() as S, p);
31}
32
33export function* waitForLoader<M extends AnyState>(
34 loaders: LoaderOutput<M, AnyState>,
35 action: ThunkAction | ActionFnWithPayload,
36) {
37 const id = getIdFromAction(action);
38 const selector = (s: AnyState) => loaders.selectById(s, { id });
39
40 // check for done state on init
41 let loader = yield* select(selector);
42 if (loader.isSuccess || loader.isError) {
43 return loader;
44 }
45
46 while (true) {
47 yield* take("*");
48 loader = yield* select(selector);
49 if (loader.isSuccess || loader.isError) {
50 return loader;
51 }
52 }
53}
54
55export function* waitForLoaders<M extends AnyState>(
56 loaders: LoaderOutput<M, AnyState>,
57 actions: (ThunkAction | ActionFnWithPayload)[],
58) {
59 const group = yield* parallel(
60 actions.map((action) => waitForLoader(loaders, action)),
61 );
62 return yield* group;
63}
64
65export function createTracker<T, M extends Record<string, unknown>>(
66 loader: LoaderOutput<M, AnyState>,
67) {
68 return (id: string) => {
69 return function* (op: () => Operation<Result<T>>) {
70 yield* updateStore(loader.start({ id }));
71 const result = yield* safe(op);
72 if (result.ok) {
73 yield* updateStore(loader.success({ id }));
74 } else {
75 yield* updateStore(
76 loader.error({
77 id,
78 message: result.error.message,
79 }),
80 );
81 }
82 return result;
83 };
84 };
85}