repos / starfx

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

starfx / mdw
Eric Bower · 05 Mar 24

query.ts

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