repos / starfx

supercharged async flow control library.
git clone https://github.com/neurosnap/starfx.git

starfx / store
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}