- 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
deps.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,
+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 }
+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"]);
+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;
+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);
+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";
+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);
+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 }
+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()");
+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
+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
+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> {
+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
+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 };
+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) {