repos / starfx

a micro-mvc framework for react apps
git clone https://github.com/neurosnap/starfx.git

starfx / src
Vlad  ·  2025-06-15

action.ts

  1import {
  2  type Callable,
  3  type Operation,
  4  type Signal,
  5  SignalQueueFactory,
  6  type Stream,
  7  call,
  8  createContext,
  9  createSignal,
 10  each,
 11  spawn,
 12} from "effection";
 13import { type ActionPattern, matcher } from "./matcher.js";
 14import { createFilterQueue } from "./queue.js";
 15import type { Action, ActionWithPayload, AnyAction } from "./types.js";
 16import type { ActionFnWithPayload } from "./types.js";
 17
 18export const ActionContext = createContext(
 19  "starfx:action",
 20  createSignal<AnyAction, void>(),
 21);
 22
 23export function useActions(pattern: ActionPattern): Stream<AnyAction, void> {
 24  return {
 25    [Symbol.iterator]: function* () {
 26      const actions = yield* ActionContext.expect();
 27      const match = matcher(pattern);
 28      yield* SignalQueueFactory.set(() => createFilterQueue(match) as any);
 29      return yield* actions;
 30    },
 31  };
 32}
 33
 34export function emit({
 35  signal,
 36  action,
 37}: {
 38  signal: Signal<AnyAction, void>;
 39  action: AnyAction | AnyAction[];
 40}) {
 41  if (Array.isArray(action)) {
 42    if (action.length === 0) {
 43      return;
 44    }
 45    action.map((a) => signal.send(a));
 46  } else {
 47    signal.send(action);
 48  }
 49}
 50
 51export function* put(action: AnyAction | AnyAction[]) {
 52  const signal = yield* ActionContext.expect();
 53  return emit({
 54    signal,
 55    action,
 56  });
 57}
 58
 59export function take<P>(
 60  pattern: ActionPattern,
 61): Operation<ActionWithPayload<P>>;
 62export function* take(pattern: ActionPattern): Operation<Action> {
 63  const fd = useActions(pattern);
 64  for (const action of yield* each(fd)) {
 65    return action;
 66  }
 67  return { type: "take failed, this should not be possible" };
 68}
 69
 70export function* takeEvery<T>(
 71  pattern: ActionPattern,
 72  op: (action: AnyAction) => Operation<T>,
 73) {
 74  const fd = useActions(pattern);
 75  for (const action of yield* each(fd)) {
 76    yield* spawn(() => op(action));
 77    yield* each.next();
 78  }
 79}
 80
 81export function* takeLatest<T>(
 82  pattern: ActionPattern,
 83  op: (action: AnyAction) => Operation<T>,
 84) {
 85  const fd = useActions(pattern);
 86  let lastTask;
 87
 88  for (const action of yield* each(fd)) {
 89    if (lastTask) {
 90      yield* lastTask.halt();
 91    }
 92    lastTask = yield* spawn(() => op(action));
 93    yield* each.next();
 94  }
 95}
 96
 97export function* takeLeading<T>(
 98  pattern: ActionPattern,
 99  op: (action: AnyAction) => Operation<T>,
100) {
101  while (true) {
102    const action = yield* take(pattern);
103    yield* call(() => op(action));
104  }
105}
106
107export function* waitFor(predicate: Callable<boolean>) {
108  const init = yield* call(predicate as any);
109  if (init) {
110    return;
111  }
112
113  while (true) {
114    yield* take("*");
115    const result = yield* call(predicate as any);
116    if (result) {
117      return;
118    }
119  }
120}
121
122export function getIdFromAction(
123  action: ActionWithPayload<{ key: string }> | ActionFnWithPayload,
124): string {
125  return typeof action === "function" ? action.toString() : action.payload.key;
126}
127
128export const API_ACTION_PREFIX = "";
129
130export function createAction(actionType: string): () => Action;
131export function createAction<P>(
132  actionType: string,
133): (p: P) => ActionWithPayload<P>;
134export function createAction(actionType: string) {
135  if (!actionType) {
136    throw new Error("createAction requires non-empty string");
137  }
138  const fn = (payload?: unknown) => ({
139    type: actionType,
140    payload,
141  });
142  fn.toString = () => actionType;
143  Object.defineProperty(fn, "_starfx", {
144    value: true,
145    enumerable: false,
146    configurable: false,
147    writable: false,
148  });
149  return fn;
150}