repos / starfx

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

starfx / src / mdw
Eric Bower  ·  2025-06-06

query.ts

  1import { type Callable, call } from "effection";
  2import { put } from "../action.js";
  3import { compose } from "../compose.js";
  4import { safe } from "../fx/index.js";
  5import type {
  6  ApiCtx,
  7  ApiRequest,
  8  FetchJsonCtx,
  9  MiddlewareApi,
 10  PerfCtx,
 11  RequiredApiRequest,
 12  ThunkCtx,
 13} from "../query/types.js";
 14import { mergeRequest } from "../query/util.js";
 15import type { AnyAction, Next } from "../types.js";
 16import * as fetchMdw from "./fetch.js";
 17export * from "./fetch.js";
 18
 19/**
 20 * This middleware will catch any errors in the pipeline
 21 * and `console.error` the context object.
 22 *
 23 * You are highly encouraged to ditch this middleware if you need something
 24 * more custom.
 25 *
 26 * It also sets `ctx.result` which informs us whether the entire
 27 * middleware pipeline succeeded or not. Think the `.catch()` case for
 28 * `window.fetch`.
 29 */
 30export function* err<Ctx extends ThunkCtx = ThunkCtx>(ctx: Ctx, next: Next) {
 31  ctx.result = yield* safe(next);
 32  if (!ctx.result.ok) {
 33    const message = `Error: ${ctx.result.error.message}.  Check the endpoint [${ctx.name}]`;
 34    console.error(message, ctx);
 35    yield* put({
 36      type: "error:query",
 37      payload: {
 38        message,
 39        ctx,
 40      },
 41    });
 42  }
 43}
 44
 45/**
 46 * This middleware allows the user to override the default key provided
 47 * to every pipeline function and instead use whatever they want.
 48 *
 49 * @example
 50 * ```ts
 51 * import { createPipe } from 'starfx';
 52 *
 53 * const thunk = createPipe();
 54 * thunk.use(customKey);
 55 *
 56 * const doit = thunk.create('some-action', function*(ctx, next) {
 57 *   ctx.key = 'something-i-want';
 58 * })
 59 * ```
 60 */
 61export function* customKey<Ctx extends ThunkCtx = ThunkCtx>(
 62  ctx: Ctx,
 63  next: Next,
 64) {
 65  if (
 66    ctx?.key &&
 67    ctx?.action?.payload?.key &&
 68    ctx.key !== ctx.action.payload.key
 69  ) {
 70    const newKey = `${ctx.name.split("|")[0]}|${ctx.key}`;
 71    ctx.key = newKey;
 72    ctx.action.payload.key = newKey;
 73  }
 74  yield* next();
 75}
 76
 77/**
 78 * This middleware sets up the context object with some values that are
 79 * necessary for {@link createApi} to work properly.
 80 */
 81export function* queryCtx<Ctx extends ApiCtx = ApiCtx>(ctx: Ctx, next: Next) {
 82  if (!ctx.req) {
 83    ctx.req = (r?: ApiRequest): RequiredApiRequest =>
 84      mergeRequest(ctx.request, r);
 85  }
 86  if (!ctx.request) ctx.request = ctx.req();
 87  if (!ctx.response) ctx.response = null;
 88  if (!ctx.json) ctx.json = { ok: false, error: {} };
 89  if (!ctx.actions) ctx.actions = [];
 90  if (!ctx.bodyType) ctx.bodyType = "json";
 91  yield* next();
 92}
 93
 94/**
 95 * This middleware will take the result of `ctx.actions` and dispatch them
 96 * as a single batch.
 97 *
 98 * @remarks This is useful because sometimes there are a lot of actions that need dispatched
 99 * within the pipeline of the middleware and instead of dispatching them serially this
100 * improves performance by only hitting the reducers once.
101 */
102export function* actions(ctx: { actions: AnyAction[] }, next: Next) {
103  if (!ctx.actions) ctx.actions = [];
104  yield* next();
105  if (ctx.actions.length === 0) return;
106  yield* put(ctx.actions);
107}
108
109/**
110 * This middleware will add `performance.now()` before and after your
111 * middleware pipeline.
112 */
113export function* perf<Ctx extends PerfCtx = PerfCtx>(ctx: Ctx, next: Next) {
114  const t0 = performance.now();
115  yield* next();
116  const t1 = performance.now();
117  ctx.performance = t1 - t0;
118}
119
120/**
121 * This middleware is a composition of other middleware required to
122 * use `window.fetch` {@link https://developer.mozilla.org/en-US/docs/Web/API/Fetch_API}
123 * with {@link createApi}
124 */
125export function fetch<CurCtx extends FetchJsonCtx = FetchJsonCtx>(
126  {
127    baseUrl = "",
128  }: {
129    baseUrl?: string;
130  } = { baseUrl: "" },
131) {
132  return compose<CurCtx>([
133    fetchMdw.composeUrl(baseUrl),
134    fetchMdw.payload,
135    fetchMdw.request,
136    fetchMdw.json,
137  ]);
138}
139
140/**
141 * This middleware will only be activated if predicate is true.
142 */
143export function predicate<Ctx extends ApiCtx = ApiCtx>(
144  predicate: ((ctx: Ctx) => boolean) | ((ctx: Ctx) => Callable<boolean>),
145) {
146  return (mdw: MiddlewareApi) => {
147    return function* (ctx: Ctx, next: Next) {
148      const valid = yield* call(() => predicate(ctx));
149      if (!valid) {
150        yield* next();
151        return;
152      }
153
154      yield* mdw(ctx, next);
155    };
156  };
157}