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}