repos / starfx

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

commit
0ba489e
parent
41b8fca
author
Eric Bower
date
2023-05-21 15:01:28 +0000 UTC
chore: refactor redux store creator
17 files changed,  +153, -206
M compose.ts
+1, -1
1@@ -1,7 +1,7 @@
2 import { call } from "./fx/index.ts";
3 import type { Instruction, Operation, Result } from "./deps.ts";
4 import { Err, Ok } from "./deps.ts";
5-import type { Next } from './query/index.ts';
6+import type { Next } from "./query/index.ts";
7 
8 // deno-lint-ignore no-explicit-any
9 export type BaseCtx = Record<string, any>;
M deps.ts
+2, -0
 1@@ -33,8 +33,10 @@ export {
 2 
 3 export type {
 4   Action,
 5+  AnyAction,
 6   Middleware,
 7   Reducer,
 8+  ReducersMapObject,
 9 } from "https://esm.sh/@reduxjs/toolkit@1.9.5?pin=v122";
10 export {
11   combineReducers,
M fx/watch.ts
+1, -1
1@@ -13,5 +13,5 @@ export function supervise<T>(op: OpFn<T>) {
2 
3 export function* keepAlive(ops: OpFn[]) {
4   const results = yield* parallel(ops.map(supervise));
5-  yield* results;
6+  return yield* results;
7 }
M query/api.test.ts
+28, -28
  1@@ -1,16 +1,17 @@
  2 import { describe, expect, it } from "../test.ts";
  3 
  4 import { call } from "../fx/index.ts";
  5-import { put, takeEvery } from "../redux/index.ts";
  6+import { configureStore, put, takeEvery } from "../redux/index.ts";
  7 import { createAction, createReducerMap, createTable } from "../deps.ts";
  8 import type { MapEntity } from "../deps.ts";
  9 
 10 import { queryCtx, requestMonitor, urlParser } from "./middleware.ts";
 11 import { createApi } from "./api.ts";
 12-import { setupStore, sleep } from "./util.ts";
 13+import { sleep } from "./util.ts";
 14 import { createKey } from "./create-key.ts";
 15 import type { ApiCtx } from "./types.ts";
 16-import { poll } from "./saga.ts";
 17+import { poll } from "./supervisor.ts";
 18+import { keepAlive } from "../index.ts";
 19 
 20 interface User {
 21   id: string;
 22@@ -24,6 +25,8 @@ const jsonBlob = (data: unknown) => {
 23   return JSON.stringify(data);
 24 };
 25 
 26+const reducers = { init: () => null };
 27+
 28 const tests = describe("createApi()");
 29 
 30 it(tests, "createApi - POST", async () => {
 31@@ -81,8 +84,8 @@ it(tests, "createApi - POST", async () => {
 32   );
 33 
 34   const reducers = createReducerMap(cache);
 35-  const { store, run } = setupStore(reducers, { fx: query.bootup });
 36-  run();
 37+  const { store, fx } = configureStore({ reducers });
 38+  fx.run(query.bootup);
 39 
 40   store.dispatch(createUser({ email: mockUser.email }));
 41   await sleep(150);
 42@@ -134,8 +137,8 @@ it(tests, "POST with uri", () => {
 43   });
 44 
 45   const reducers = createReducerMap(cache);
 46-  const { store, run } = setupStore(reducers, { fx: query.bootup });
 47-  run();
 48+  const { store, fx } = configureStore({ reducers });
 49+  fx.run(query.bootup);
 50 
 51   store.dispatch(createUser({ email: mockUser.email }));
 52 });
 53@@ -151,10 +154,8 @@ it(tests, "middleware - with request fn", () => {
 54     yield* next();
 55   });
 56   const createUser = query.create("/users", query.request({ method: "POST" }));
 57-  const { store, run } = setupStore({ def: (s) => s || null }, {
 58-    fx: query.bootup,
 59-  });
 60-  run();
 61+  const { store, fx } = configureStore({ reducers });
 62+  fx.run(query.bootup);
 63 
 64   store.dispatch(createUser());
 65 });
 66@@ -177,8 +178,8 @@ it(tests, "run() on endpoint action - should run the effect", () => {
 67     expect(acc).toEqual("ab");
 68   });
 69 
 70-  const { store, run } = setupStore({ def: () => null }, { fx: api.bootup });
 71-  run();
 72+  const { store, fx } = configureStore({ reducers });
 73+  fx.run(api.bootup);
 74 
 75   store.dispatch(action2());
 76 });
 77@@ -211,11 +212,8 @@ it(tests, "run() from a normal saga", () => {
 78     yield* task;
 79   }
 80 
 81-  const { store, run } = setupStore({ def: () => null }, {
 82-    api: api.bootup,
 83-    watchAction,
 84-  });
 85-  run();
 86+  const { store, fx } = configureStore({ reducers });
 87+  fx.run(() => keepAlive([api.bootup, watchAction]));
 88 
 89   store.dispatch(action2());
 90 });
 91@@ -264,8 +262,9 @@ it(tests, "createApi with hash key on a large post", async () => {
 92   const email = mockUser.email + "9";
 93   const largetext = "abc-def-ghi-jkl-mno-pqr".repeat(100);
 94   const reducers = createReducerMap();
 95-  const { store, run } = setupStore(reducers, { fx: query.bootup });
 96-  run();
 97+
 98+  const { store, fx } = configureStore({ reducers });
 99+  fx.run(query.bootup);
100 
101   store.dispatch(createUserDefaultKey({ email, largetext }));
102   await sleep(150);
103@@ -302,8 +301,9 @@ it(tests, "createApi - two identical endpoints", async () => {
104     },
105   );
106 
107-  const { store, run } = setupStore({ def: () => null }, { fx: api.bootup });
108-  run();
109+  const { store, fx } = configureStore({ reducers });
110+  fx.run(api.bootup);
111+
112   store.dispatch(first());
113   store.dispatch(second());
114 
115@@ -343,8 +343,8 @@ it(tests, "ensure types for get() endpoint", () => {
116     },
117   );
118 
119-  const { store, run } = setupStore({ def: () => null }, { fx: api.bootup });
120-  run();
121+  const { store, fx } = configureStore({ reducers });
122+  fx.run(api.bootup);
123 
124   store.dispatch(action1({ id: "1" }));
125   expect(acc).toEqual(["1", "wow"]);
126@@ -379,8 +379,8 @@ it(tests, "ensure ability to cast `ctx` in function definition", () => {
127     },
128   );
129 
130-  const { store, run } = setupStore({ def: () => null }, { fx: api.bootup });
131-  run();
132+  const { store, fx } = configureStore({ reducers });
133+  fx.run(api.bootup);
134 
135   store.dispatch(action1({ id: "1" }));
136   expect(acc).toEqual(["1", "wow"]);
137@@ -414,8 +414,8 @@ it(
138       },
139     );
140 
141-    const { store, run } = setupStore({ def: () => null }, { fx: api.bootup });
142-    run();
143+    const { store, fx } = configureStore({ reducers });
144+    fx.run(api.bootup);
145 
146     store.dispatch(action1());
147     expect(acc).toEqual(["wow"]);
M query/create-key.test.ts
+1, -1
1@@ -1,7 +1,7 @@
2 import { describe, expect, it } from "../test.ts";
3 import type { ActionWithPayload } from "./types.ts";
4 import { createApi } from "./api.ts";
5-import { poll } from "./saga.ts";
6+import { poll } from "./supervisor.ts";
7 
8 const getKeyOf = (action: ActionWithPayload<{ key: string }>): string =>
9   action.payload.key;
M query/fetch.test.ts
+25, -21
  1@@ -1,14 +1,15 @@
  2 import { describe, expect, install, it, mock } from "../test.ts";
  3+import { configureStore } from "../redux/index.ts";
  4 
  5 import { fetcher, fetchRetry } from "./fetch.ts";
  6 import { createApi } from "./api.ts";
  7-import { setupStore } from "./util.ts";
  8 import { requestMonitor } from "./middleware.ts";
  9 
 10 install();
 11 
 12 const baseUrl = "https://saga-query.com";
 13 const mockUser = { id: "1", email: "test@saga-query.com" };
 14+const reducers = { init: () => null };
 15 
 16 const delay = (n = 200) =>
 17   new Promise((resolve) => {
 18@@ -45,8 +46,8 @@ it(
 19       expect(ctx.json).toEqual({ ok: true, data: mockUser });
 20     });
 21 
 22-    const { store, run } = setupStore({ def: () => null }, { fx: api.bootup });
 23-    run();
 24+    const { store, fx } = configureStore({ reducers });
 25+    fx.run(api.bootup);
 26 
 27     const action = fetchUsers();
 28     store.dispatch(action);
 29@@ -78,8 +79,8 @@ it(
 30       expect(ctx.json).toEqual({ ok: true, data: "this is some text" });
 31     });
 32 
 33-    const { store, run } = setupStore({ def: () => null }, { fx: api.bootup });
 34-    run();
 35+    const { store, fx } = configureStore({ reducers });
 36+    fx.run(api.bootup);
 37 
 38     const action = fetchUsers();
 39     store.dispatch(action);
 40@@ -111,8 +112,9 @@ it(tests, "fetch - error handling", async () => {
 41     expect(ctx.json).toEqual({ ok: false, data: errMsg });
 42   });
 43 
 44-  const { store, run } = setupStore({ def: () => null }, { fx: api.bootup });
 45-  run();
 46+  const { store, fx } = configureStore({ reducers });
 47+  fx.run(api.bootup);
 48+
 49   const action = fetchUsers();
 50   store.dispatch(action);
 51 
 52@@ -144,8 +146,9 @@ it(tests, "fetch - status 204", async () => {
 53     expect(ctx.json).toEqual({ ok: true, data: {} });
 54   });
 55 
 56-  const { store, run } = setupStore({ def: () => null }, { fx: api.bootup });
 57-  run();
 58+  const { store, fx } = configureStore({ reducers });
 59+  fx.run(api.bootup);
 60+
 61   const action = fetchUsers();
 62   store.dispatch(action);
 63 
 64@@ -183,8 +186,8 @@ it(tests, "fetch - malformed json", async () => {
 65     });
 66   });
 67 
 68-  const { store, run } = setupStore({ def: () => null }, { fx: api.bootup });
 69-  run();
 70+  const { store, fx } = configureStore({ reducers });
 71+  fx.run(api.bootup);
 72 
 73   const action = fetchUsers();
 74   store.dispatch(action);
 75@@ -219,8 +222,9 @@ it(tests, "fetch - POST", async () => {
 76     expect(ctx.json).toEqual({ ok: true, data: mockUser });
 77   });
 78 
 79-  const { store, run } = setupStore({ def: () => null }, { fx: api.bootup });
 80-  run();
 81+  const { store, fx } = configureStore({ reducers });
 82+  fx.run(api.bootup);
 83+
 84   const action = fetchUsers();
 85   store.dispatch(action);
 86 
 87@@ -277,8 +281,8 @@ it(tests, "fetch - POST multiple endpoints with same uri", async () => {
 88     },
 89   );
 90 
 91-  const { store, run } = setupStore({ def: () => null }, { fx: api.bootup });
 92-  run();
 93+  const { store, fx } = configureStore({ reducers });
 94+  fx.run(api.bootup);
 95 
 96   store.dispatch(fetchUsers({ id: "1" }));
 97   store.dispatch(fetchUsersSecond({ id: "1" }));
 98@@ -311,8 +315,8 @@ it(
 99       },
100     );
101 
102-    const { store, run } = setupStore({ def: () => null }, { fx: api.bootup });
103-    run();
104+    const { store, fx } = configureStore({ reducers });
105+    fx.run(api.bootup);
106 
107     const action = fetchUsers({ id: "" });
108     store.dispatch(action);
109@@ -355,8 +359,8 @@ it(
110       fetchRetry((n) => (n > 4 ? -1 : 10)),
111     ]);
112 
113-    const { store, run } = setupStore({ def: () => null }, { fx: api.bootup });
114-    run();
115+    const { store, fx } = configureStore({ reducers });
116+    fx.run(api.bootup);
117 
118     const action = fetchUsers();
119     store.dispatch(action);
120@@ -392,8 +396,8 @@ it.ignore(
121       fetchRetry((n) => (n > 2 ? -1 : 10)),
122     ]);
123 
124-    const { store, run } = setupStore({ def: () => null }, { fx: api.bootup });
125-    run();
126+    const { store, fx } = configureStore({ reducers });
127+    fx.run(api.bootup);
128 
129     const action = fetchUsers();
130     store.dispatch(action);
M query/index.ts
+1, -2
1@@ -6,7 +6,6 @@ export * from "./types.ts";
2 export * from "./fetch.ts";
3 export * from "./middleware.ts";
4 export * from "./constant.ts";
5-export * from "./store.ts";
6 export * from "./slice.ts";
7 export * from "./create-key.ts";
8-export * from "./saga.ts";
9+export * from "./supervisor.ts";
M query/middleware.test.ts
+24, -26
  1@@ -1,6 +1,6 @@
  2 import { assertLike, asserts, describe, expect, it } from "../test.ts";
  3 
  4-import { put, takeLatest } from "../redux/index.ts";
  5+import { configureStore, put, takeLatest } from "../redux/index.ts";
  6 import {
  7   createReducerMap,
  8   createTable,
  9@@ -20,7 +20,7 @@ import {
 10 } from "./middleware.ts";
 11 import type { UndoCtx } from "./middleware.ts";
 12 import type { ApiCtx } from "./types.ts";
 13-import { setupStore, sleep } from "./util.ts";
 14+import { sleep } from "./util.ts";
 15 import { createKey } from "./create-key.ts";
 16 import {
 17   createQueryState,
 18@@ -97,8 +97,8 @@ it(tests, "basic", () => {
 19   );
 20 
 21   const reducers = createReducerMap(cache);
 22-  const { store, run } = setupStore(reducers, { fx: query.bootup });
 23-  run();
 24+  const { store, fx } = configureStore({ reducers });
 25+  fx.run(query.bootup);
 26 
 27   store.dispatch(fetchUsers());
 28   expect(store.getState()).toEqual({
 29@@ -141,8 +141,8 @@ it(tests, "with loader", () => {
 30   );
 31 
 32   const reducers = createReducerMap(users);
 33-  const { store, run } = setupStore(reducers, { fx: api.bootup });
 34-  run();
 35+  const { store, fx } = configureStore({ reducers });
 36+  fx.run(api.bootup);
 37 
 38   store.dispatch(fetchUsers());
 39   assertLike(store.getState(), {
 40@@ -184,8 +184,8 @@ it(tests, "with item loader", () => {
 41   );
 42 
 43   const reducers = createReducerMap(users);
 44-  const { store, run } = setupStore(reducers, { fx: api.bootup });
 45-  run();
 46+  const { store, fx } = configureStore({ reducers });
 47+  fx.run(api.bootup);
 48 
 49   const action = fetchUser({ id: mockUser.id });
 50   store.dispatch(action);
 51@@ -251,8 +251,9 @@ it(tests, "with POST", () => {
 52   );
 53 
 54   const reducers = createReducerMap(cache);
 55-  const { store, run } = setupStore(reducers, { fx: query.bootup });
 56-  run();
 57+  const { store, fx } = configureStore({ reducers });
 58+  fx.run(query.bootup);
 59+
 60   store.dispatch(createUser({ email: mockUser.email }));
 61 });
 62 
 63@@ -268,10 +269,8 @@ it(tests, "simpleCache", () => {
 64   });
 65 
 66   const fetchUsers = api.get("/users", api.cache());
 67-  const { store, run } = setupStore({ def: (s) => s || null }, {
 68-    fx: api.bootup,
 69-  });
 70-  run();
 71+  const { store, fx } = configureStore({ reducers: { init: () => null } });
 72+  fx.run(api.bootup);
 73 
 74   const action = fetchUsers();
 75   store.dispatch(action);
 76@@ -321,8 +320,8 @@ it(tests, "overriding default loader behavior", () => {
 77   );
 78 
 79   const reducers = createReducerMap(users);
 80-  const { store, run } = setupStore(reducers, { fx: api.bootup });
 81-  run();
 82+  const { store, fx } = configureStore({ reducers });
 83+  fx.run(api.bootup);
 84 
 85   store.dispatch(fetchUsers());
 86   assertLike(store.getState(), {
 87@@ -356,10 +355,8 @@ it(tests, "undo", () => {
 88     yield* next();
 89   });
 90 
 91-  const { store, run } = setupStore({ def: (s) => s || null }, {
 92-    fx: api.bootup,
 93-  });
 94-  run();
 95+  const { store, fx } = configureStore({ reducers: { init: () => null } });
 96+  fx.run(api.bootup);
 97 
 98   const action = createUser();
 99   store.dispatch(action);
100@@ -397,8 +394,9 @@ it(tests, "requestMonitor - error handler", () => {
101   const fetchUsers = query.create(`/users`);
102 
103   const reducers = createReducerMap(cache);
104-  const { store, run } = setupStore(reducers, { fx: query.bootup });
105-  run();
106+  const { store, fx } = configureStore({ reducers });
107+  fx.run(query.bootup);
108+
109   store.dispatch(fetchUsers());
110 });
111 
112@@ -447,8 +445,8 @@ it(tests, "createApi with own key", async () => {
113   );
114   const newUEmail = mockUser.email + ".org";
115   const reducers = createReducerMap();
116-  const { store, run } = setupStore(reducers, { fx: query.bootup });
117-  run();
118+  const { store, fx } = configureStore({ reducers });
119+  fx.run(query.bootup);
120 
121   store.dispatch(createUserCustomKey({ email: newUEmail }));
122   await sleep(150);
123@@ -512,8 +510,8 @@ it(tests, "createApi with custom key but no payload", async () => {
124   );
125 
126   const reducers = createReducerMap();
127-  const { store, run } = setupStore(reducers, { fx: query.bootup });
128-  run();
129+  const { store, fx } = configureStore({ reducers });
130+  fx.run(query.bootup);
131 
132   store.dispatch(getUsers());
133   await sleep(150);
M query/middleware.ts
+5, -4
 1@@ -132,15 +132,16 @@ export function* loadingMonitorSimple<Ctx extends LoaderCtx = LoaderCtx>(
 2       setLoaderStart({ id: ctx.key }),
 3     ]),
 4   );
 5-  if (!ctx.loader) ctx.loader = {} as any;
 6+  if (!ctx.loader) {
 7+    ctx.loader = {};
 8+  }
 9 
10   yield* next();
11 
12-  const payload = ctx.loader || {};
13   yield* put(
14     batchActions([
15-      setLoaderSuccess({ id: ctx.name, ...payload }),
16-      setLoaderSuccess({ id: ctx.key, ...payload }),
17+      setLoaderSuccess({ ...ctx.loader, id: ctx.name }),
18+      setLoaderSuccess({ ...ctx.loader, id: ctx.key }),
19     ]),
20   );
21 }
M query/pipe.test.ts
+5, -5
 1@@ -1,12 +1,12 @@
 2 import { assertLike, asserts, describe, it } from "../test.ts";
 3 import { call } from "../fx/index.ts";
 4-import { put } from "../redux/index.ts";
 5+import { configureStore, put } from "../redux/index.ts";
 6 import { createReducerMap, createTable, sleep as delay } from "../deps.ts";
 7 import type { Action, MapEntity } from "../deps.ts";
 8 
 9 import { createPipe } from "./pipe.ts";
10 import type { Next, PipeCtx } from "./types.ts";
11-import { setupStore as prepStore, sleep } from "./util.ts";
12+import { sleep } from "./util.ts";
13 import { createQueryState } from "./slice.ts";
14 import { OpFn } from "../types.ts";
15 
16@@ -133,9 +133,9 @@ function* saveToRedux(ctx: RoboCtx, next: Next) {
17   yield* next();
18 }
19 
20-function setupStore(fx: OpFn) {
21-  const store = prepStore(reducers, { fx });
22-  return store;
23+function setupStore(op: OpFn) {
24+  const { store, fx } = configureStore({ reducers });
25+  return { store, run: () => fx.run(op) };
26 }
27 
28 const tests = describe("createPipe()");
M query/react.test.ts
+4, -8
 1@@ -13,10 +13,10 @@ import {
 2   sleep as delay,
 3   useSelector,
 4 } from "../deps.ts";
 5+import { configureStore } from "../redux/index.ts";
 6 
 7 import { createApi } from "./api.ts";
 8 import { requestMonitor } from "./middleware.ts";
 9-import { setupStore } from "./util.ts";
10 import { useApi } from "./react.ts";
11 import { selectDataById } from "./slice.ts";
12 import { createKey } from "./create-key.ts";
13@@ -52,13 +52,9 @@ const setupTest = () => {
14     slice.actions.set(ctx.json.data);
15   });
16 
17-  const { store, run } = setupStore(
18-    { user: slice.reducer },
19-    {
20-      fx: api.bootup,
21-    },
22-  );
23-  run();
24+  const { store, fx } = configureStore({ reducers: { user: slice.reducer } });
25+  fx.run(api.bootup);
26+
27   return { store, fetchUser, api };
28 };
29 
D query/store.ts
+0, -64
 1@@ -1,64 +0,0 @@
 2-import type { Middleware, Reducer, Result, Task } from "../deps.ts";
 3-import { combineReducers, enableBatching } from "../deps.ts";
 4-import type { OpFn } from "../types.ts";
 5-import { createFxMiddleware } from "../redux/index.ts";
 6-import { keepAlive } from "../index.ts";
 7-
 8-import type { QueryState } from "./slice.ts";
 9-import { reducers as sagaQueryReducers } from "./slice.ts";
10-
11-export interface PrepareStore<
12-  S extends { [key: string]: any } = { [key: string]: any },
13-> {
14-  reducer: Reducer<S & QueryState>;
15-  middleware: Middleware<any, S, any>[];
16-  run: (...args: any[]) => Task<Result<unknown>>;
17-}
18-
19-interface Props<S extends { [key: string]: any } = { [key: string]: any }> {
20-  reducers: { [key in keyof S]: Reducer<S[key]> };
21-  fx: { [key: string]: OpFn };
22-}
23-
24-/**
25- * This will setup `redux-batched-actions` to work with `redux-saga`.
26- * It will also add some reducers to your `redux` store for decoupled loaders
27- * and a simple data cache.
28- *
29- * @example
30- * ```ts
31- * import { prepareStore } from 'saga-query';
32- * import { configureStore } from '@reduxjs/toolkit';
33- *
34- * const { middleware, reducer, run } = prepareStore({
35- *  reducers: { users: (state, action) => state },
36- *  fx: { api: api.saga() },
37- * });
38- *
39- * const store = configureStore({
40- *  reducer,
41- *  middleware,
42- * });
43- *
44- * // you must call `.run(...args: any[])` in order for the sagas to bootup.
45- * run();
46- * ```
47- */
48-export function prepareStore<
49-  S extends { [key: string]: unknown } = { [key: string]: unknown },
50->({ reducers, fx }: Props<S>): PrepareStore<S> {
51-  const middleware: Middleware<unknown, S>[] = [];
52-
53-  const fxMiddleware = createFxMiddleware();
54-  middleware.push(fxMiddleware.middleware);
55-
56-  const reducer = combineReducers({ ...sagaQueryReducers, ...reducers });
57-  const rootReducer = enableBatching(reducer as Reducer);
58-  const run = () => fxMiddleware.run(() => keepAlive(Object.values(fx)));
59-
60-  return {
61-    middleware,
62-    reducer: rootReducer as Reducer,
63-    run,
64-  };
65-}
R query/saga.ts => query/supervisor.ts
+0, -0
M query/types.ts
+2, -2
 1@@ -24,8 +24,8 @@ export interface PipeCtx<P = any> extends Payload<P> {
 2   >;
 3 }
 4 
 5-export interface LoaderCtx<P = any> extends PipeCtx<P> {
 6-  loader: LoadingMapPayload<Record<string, any>> | null;
 7+export interface LoaderCtx<P = unknown> extends PipeCtx<P> {
 8+  loader: Partial<LoadingItemState> | null;
 9 }
10 
11 export interface ApiFetchSuccess<ApiSuccess = any> {
M query/util.ts
+3, -25
 1@@ -1,9 +1,4 @@
 2-import { configureStore } from "../deps.ts";
 3-import type { Reducer } from "../deps.ts";
 4-import type { OpFn } from "../types.ts";
 5-
 6 import type { ApiRequest, RequiredApiRequest } from "./types.ts";
 7-import { prepareStore } from "./store.ts";
 8 import { API_ACTION_PREFIX } from "./constant.ts";
 9 
10 export const noop = () => {};
11@@ -19,26 +14,9 @@ export const createAction = (curType: string) => {
12   return action;
13 };
14 
15-export function setupStore(
16-  reducers: { [key: string]: Reducer } = {},
17-  fx: { [key: string]: OpFn },
18-) {
19-  const fxx = typeof fx === "function" ? { fx } : fx;
20-  const prepared = prepareStore({
21-    reducers,
22-    fx: fxx,
23-  });
24-  const store = configureStore({
25-    reducer: prepared.reducer,
26-    middleware: (getDefaultMiddleware) =>
27-      getDefaultMiddleware().concat(...prepared.middleware),
28-  });
29-  return { store, run: prepared.run };
30-}
31-
32 export const mergeHeaders = (
33-  cur?: { [key: string]: string },
34-  next?: { [key: string]: string },
35+  cur?: HeadersInit,
36+  next?: HeadersInit,
37 ): HeadersInit => {
38   if (!cur && !next) return {};
39   if (!cur && next) return next;
40@@ -58,7 +36,7 @@ export const mergeRequest = (
41     ...defaultReq,
42     ...cur,
43     ...next,
44-    headers: mergeHeaders((cur as any).headers, (next as any).headers),
45+    headers: mergeHeaders(cur?.headers, next?.headers),
46   };
47 };
48 
M redux/index.ts
+48, -16
  1@@ -1,17 +1,27 @@
  2-import { BATCH, Channel, Instruction, Operation, Scope } from "../deps.ts";
  3+import {
  4+  BATCH,
  5+  Channel,
  6+  Middleware,
  7+  Operation,
  8+  ReducersMapObject,
  9+  Scope,
 10+} from "../deps.ts";
 11 import type { Action, ActionWPayload, OpFn, StoreLike } from "../types.ts";
 12 import type { ActionPattern } from "../matcher.ts";
 13 
 14 import {
 15-  configureStore,
 16+  combineReducers,
 17+  configureStore as reduxStore,
 18   createChannel,
 19   createContext,
 20   createScope,
 21+  enableBatching,
 22   spawn,
 23 } from "../deps.ts";
 24 import { contextualize } from "../context.ts";
 25 import { call, cancel, emit, parallel } from "../fx/index.ts";
 26 import { once } from "../iter.ts";
 27+import { reducers as queryReducers } from "../query/index.ts";
 28 
 29 export const ActionContext = createContext<Channel<Action, void>>(
 30   "redux:action",
 31@@ -25,18 +35,13 @@ export function* select<S, R>(selectorFn: (s: S) => R) {
 32   return selectorFn(store.getState() as S);
 33 }
 34 
 35-// https://github.com/microsoft/TypeScript/issues/31751#issuecomment-498526919
 36-export function* take<P = never>(
 37-  pattern: ActionPattern,
 38-): Generator<
 39-  Instruction,
 40-  [P] extends [never] ? Action : ActionWPayload<P>
 41-> {
 42+export function take<P>(pattern: ActionPattern): Operation<ActionWPayload<P>>;
 43+export function* take(pattern: ActionPattern): Operation<Action> {
 44   const action = yield* once({
 45     channel: ActionContext,
 46     pattern,
 47   });
 48-  return action as any;
 49+  return action as Action;
 50 }
 51 
 52 export function* takeEvery<T>(
 53@@ -143,15 +148,42 @@ export function createFxMiddleware(scope: Scope = createScope()) {
 54   return { run, scope, middleware };
 55 }
 56 
 57-interface SetupStoreProps<S = unknown> {
 58-  reducer: (s: S, _: Action) => S;
 59+// deno-lint-ignore no-explicit-any
 60+interface SetupStoreProps<S = any> {
 61+  reducers: ReducersMapObject<S>;
 62+  middleware?: Middleware[];
 63 }
 64 
 65-export function setupStore({ reducer }: SetupStoreProps) {
 66+/**
 67+ * This function will integrate `starfx` and `redux`.
 68+ *
 69+ * In order to enable `starfx/query`, it will add some reducers to your `redux`
 70+ * store for decoupled loaders and a simple data cache.
 71+ *
 72+ * It also adds `redux-batched-actions` which is critical for `starfx`.
 73+ *
 74+ * @example
 75+ * ```ts
 76+ * import { configureStore } from 'starfx/redux';
 77+ *
 78+ * const { store, fx } = prepareStore({
 79+ *  reducers: { users: (state, action) => state },
 80+ * });
 81+ *
 82+ * fx.run(function*() {
 83+ *  yield* put({ type: 'LOADING' });
 84+ *  yield* fetch('https://bower.sh');
 85+ *  yield* put({ type: 'LOADING_COMPLETE' });
 86+ * });
 87+ * ```
 88+ */
 89+export function configureStore({ reducers, middleware = [] }: SetupStoreProps) {
 90   const fx = createFxMiddleware();
 91-  const store = configureStore({
 92-    reducer,
 93-    middleware: [fx.middleware],
 94+  const rootReducer = combineReducers({ ...queryReducers, ...reducers });
 95+  const store = reduxStore({
 96+    reducer: enableBatching(rootReducer),
 97+    middleware: (getDefaultMiddleware) =>
 98+      getDefaultMiddleware().concat([fx.middleware, ...middleware]),
 99   });
100 
101   return { store, fx };
M redux/take-helper.test.ts
+3, -2
 1@@ -2,7 +2,8 @@ import { describe, expect, it } from "../test.ts";
 2 import { cancel } from "../fx/index.ts";
 3 import type { Action } from "../types.ts";
 4 
 5-import { setupStore, take, takeEvery } from "./index.ts";
 6+import { configureStore, take, takeEvery } from "./index.ts";
 7+const reducers = { init: () => null };
 8 
 9 const testEvery = describe("takeEvery()");
10 
11@@ -23,7 +24,7 @@ it(testEvery, "should work", async () => {
12     actual.push([arg1, arg2, action.payload]);
13   }
14 
15-  const { store, fx } = setupStore({ reducer: (s) => s });
16+  const { store, fx } = configureStore({ reducers });
17   const task = fx.run(root);
18 
19   for (let i = 1; i <= loop / 2; i += 1) {