redux-unfold-saga-toolkit 1.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2025 Hung Nguyen
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,460 @@
1
+ # redux-unfold-saga-toolkit
2
+
3
+ A more user-friendly, headache-free redux-saga middleware.
4
+
5
+ > This library is inspired by [redux-unfold-saga](https://github.com/manhhailua/redux-unfold-saga).
6
+
7
+ ## Getting started
8
+
9
+ #### Install
10
+
11
+ ```bash
12
+ npm install --save redux-unfold-saga-toolkit
13
+ ```
14
+
15
+ or
16
+
17
+ ```bash
18
+ yarn add redux-unfold-saga-toolkit
19
+ ```
20
+
21
+ > This library is required [redux-saga](https://redux-saga.js.org/docs/introduction/GettingStarted) and [immer](https://immerjs.github.io/immer/installation)
22
+
23
+ #### Usage example
24
+
25
+ - action
26
+
27
+ ```typescript
28
+ import { createAction } from 'redux-unfold-saga-toolkit';
29
+
30
+ const fetchPosts = createAction('FETCH_POSTS');
31
+
32
+ dispatch(
33
+ fetchPosts(
34
+ { category: 'HOT' },
35
+ {
36
+ onBegin: () => {
37
+ // Do something before the query
38
+ setLoading(true);
39
+ },
40
+ onFailure: (error: Error) => {
41
+ // Do something in case of caught error
42
+ },
43
+ onSuccess: (posts: IPost[]) => {
44
+ // Do something after the query succeeded
45
+ },
46
+ onFinish: () => {
47
+ // Do something after everything is done
48
+ setLoading(false);
49
+ },
50
+ },
51
+ ),
52
+ );
53
+ ```
54
+
55
+ - saga
56
+
57
+ ```typescript
58
+ import { call, takeLatest } from 'redux-saga/effects';
59
+ import { unfoldSaga } from 'redux-unfold-saga-toolkit';
60
+
61
+ function* takeQueryPosts(action: UnfoldSagaActionType): Iterable<SagaIterator> {
62
+ yield unfoldSaga({
63
+ action: action,
64
+ handler: function* () {
65
+ const data = yield call(ApiPost.listPost);
66
+ return data;
67
+ },
68
+ });
69
+ }
70
+
71
+ function* defaultSaga() {
72
+ yield takeLatest('QUERY_POSTS', takeQueryPosts);
73
+ // yield takeLatest(fetchPosts, takeQueryPosts);
74
+ // yield takeLatest(fetchPosts.type, takeQueryPosts);
75
+ }
76
+ ```
77
+
78
+ - reducer
79
+
80
+ ```typescript
81
+ import { createReducer, createStoreAction } from 'redux-unfold-saga-toolkit';
82
+
83
+ const fetchPosts = createStoreAction('FETCH_POSTS');
84
+
85
+ const initState = {
86
+ posts: [],
87
+ error: null,
88
+ loading: true,
89
+ };
90
+
91
+ const postReducer = createReducer(initState, (builder) => {
92
+ builder.addCase<void>(fetchPosts.begin, (state, action) => {
93
+ state.loading = true;
94
+ });
95
+ builder.addCase<void>(fetchPosts.finish, (state, action) => {
96
+ state.loading = false;
97
+ });
98
+ builder.addCase<IPost[]>(fetchPosts.success, (state, action) => {
99
+ state.posts = action.payload;
100
+ });
101
+ builder.addCase<Error>(fetchPosts.failure, (state, action) => {
102
+ state.error = action.payload;
103
+ });
104
+ });
105
+ ```
106
+
107
+ ## API
108
+
109
+ <!-- Generated by documentation.js. Update this documentation by updating the source code. -->
110
+
111
+ ### Table of Contents
112
+
113
+ - [createActionTypeOnBegin][1]
114
+ - [Parameters][2]
115
+ - [Examples][3]
116
+ - [createActionTypeOnFinish][4]
117
+ - [Parameters][5]
118
+ - [Examples][6]
119
+ - [createActionTypeOnSuccess][7]
120
+ - [Parameters][8]
121
+ - [Examples][9]
122
+ - [createActionTypeOnFailure][10]
123
+ - [Parameters][11]
124
+ - [Examples][12]
125
+ - [createAction][13]
126
+ - [Parameters][14]
127
+ - [Examples][15]
128
+ - [createStoreAction][16]
129
+ - [Parameters][17]
130
+ - [Examples][18]
131
+ - [createReducer][19]
132
+ - [Parameters][20]
133
+ - [Examples][21]
134
+ - [unfoldSaga][22]
135
+ - [Parameters][23]
136
+ - [Examples][24]
137
+
138
+ ## createActionTypeOnBegin
139
+
140
+ Create onBegin action type
141
+
142
+ ### Parameters
143
+
144
+ - `key` &#x20;
145
+
146
+ ### Examples
147
+
148
+ ```javascript
149
+ import { createActionTypeOnBegin } from 'redux-unfold-saga-toolkit';
150
+
151
+ createActionTypeOnBegin('DO_SOMETHING'); // DO_SOMETHING_BEGAN
152
+ ```
153
+
154
+ Returns **[string][25]** `${key}_BEGAN`
155
+
156
+ ## createActionTypeOnFinish
157
+
158
+ Create onFinish action type
159
+
160
+ ### Parameters
161
+
162
+ - `key` &#x20;
163
+
164
+ ### Examples
165
+
166
+ ```javascript
167
+ import { createActionTypeOnFinish } from 'redux-unfold-saga-toolkit';
168
+
169
+ createActionTypeOnFinish('DO_SOMETHING'); // DO_SOMETHING_FINISHED
170
+ ```
171
+
172
+ Returns **[string][25]** `${key}_FINISHED`
173
+
174
+ ## createActionTypeOnSuccess
175
+
176
+ Create onSuccess action type
177
+
178
+ ### Parameters
179
+
180
+ - `key` &#x20;
181
+
182
+ ### Examples
183
+
184
+ ```javascript
185
+ import { createActionTypeOnSuccess } from 'redux-unfold-saga-toolkit';
186
+
187
+ createActionTypeOnSuccess('DO_SOMETHING'); // DO_SOMETHING_SUCCEEDED
188
+ ```
189
+
190
+ Returns **[string][25]** `${key}_SUCCEEDED`
191
+
192
+ ## createActionTypeOnFailure
193
+
194
+ Create onFailure action type
195
+
196
+ ### Parameters
197
+
198
+ - `key` &#x20;
199
+
200
+ ### Examples
201
+
202
+ ```javascript
203
+ import { createActionTypeOnFailure } from 'redux-unfold-saga-toolkit';
204
+
205
+ createActionTypeOnFailure('DO_SOMETHING'); // DO_SOMETHING_FAILED
206
+ ```
207
+
208
+ Returns **[string][25]** `${key}_FAILED`
209
+
210
+ ## createAction
211
+
212
+ Create an action for real life usage inside or even outside of a component, no dispatch to reducer
213
+
214
+ ### Parameters
215
+
216
+ - `type` &#x20;
217
+
218
+ ### Examples
219
+
220
+ ```javascript
221
+ import {createAction} from 'redux-unfold-saga-toolkit';
222
+
223
+ const fetchPosts = createAction<IPostPayload>('FETCH_POSTS');
224
+
225
+ dispatch(
226
+ fetchPosts(
227
+ {category: 'HOT'},
228
+ {
229
+ onBegin: () => {
230
+ // Do something before the query
231
+ setLoading(true);
232
+ },
233
+ onFailure: (error: Error) => {
234
+ // Do something in case of caught error
235
+ },
236
+ onSuccess: (posts: IPost[]) => {
237
+ // Do something after the query succeeded
238
+ },
239
+ onFinish: () => {
240
+ // Do something after everything is done
241
+ setLoading(false);
242
+ },
243
+ },
244
+ ),
245
+ );
246
+ ```
247
+
248
+ Returns **UnfoldSagaActionCreator** action
249
+
250
+ ## createStoreAction
251
+
252
+ Create an action for real life usage inside or even outside of a component, dispatch to reducer with automatic create action type
253
+
254
+ ### Parameters
255
+
256
+ - `type` &#x20;
257
+
258
+ ### Examples
259
+
260
+ ```javascript
261
+ // Action
262
+ import {createStoreAction, createReducer} from 'redux-unfold-saga-toolkit';
263
+
264
+ const fetchPosts = createStoreAction<IPostPayload>('FETCH_POSTS');
265
+
266
+ dispatch(
267
+ fetchPosts(
268
+ {category: 'HOT'},
269
+ {
270
+ onBegin: () => {
271
+ // Do something before the query
272
+ setLoading(true);
273
+ },
274
+ onFailure: (error:Error) => {
275
+ // Do something in case of caught error
276
+ },
277
+ onSuccess: (posts:IPost) => {
278
+ // Do something after the query succeeded
279
+ },
280
+ onFinish: () => {
281
+ // Do something after everything is done
282
+ setLoading(false);
283
+ },
284
+ },
285
+ ),
286
+ );
287
+
288
+ // Reducer
289
+ const initState = {
290
+ posts: [],
291
+ error: null,
292
+ loading: true,
293
+ };
294
+
295
+ const postReducer = createReducer(initState, (builder) => {
296
+ builder.addCase<void>(fetchPosts.begin, (state, action) => {
297
+ state.loading = true;
298
+ });
299
+ builder.addCase<void>(fetchPosts.finish, (state, action) => {
300
+ state.loading = false;
301
+ });
302
+ builder.addCase<IPost[]>(fetchPosts.success, (state, action) => {
303
+ state.posts = action.payload;
304
+ });
305
+ builder.addCase<Error>(fetchPosts.failure, (state, action) => {
306
+ state.error = action.payload;
307
+ });
308
+ })
309
+ ```
310
+
311
+ Returns **UnfoldSagaActionCreator** action
312
+
313
+ ## createReducer
314
+
315
+ A utility function that allows defining a reducer as a mapping from action
316
+ type to _case reducer_ functions that handle these action types. The
317
+ reducer's initial state is passed as the first argument.
318
+
319
+ ### Parameters
320
+
321
+ - `initialState` &#x20;
322
+ - `builderCallback` &#x20;
323
+
324
+ ### Examples
325
+
326
+ ```javascript
327
+ // Action
328
+
329
+ import {createStoreAction, createReducer} from 'redux-unfold-saga-toolkit';
330
+
331
+ const fetchPosts = createStoreAction<IPostPayload>('FETCH_POSTS');
332
+
333
+ dispatch(
334
+ fetchPosts(
335
+ {category: 'HOT'},
336
+ {
337
+ onBegin: () => {
338
+ // Do something before the query
339
+ setLoading(true);
340
+ },
341
+ onFailure: (error:Error) => {
342
+ // Do something in case of caught error
343
+ },
344
+ onSuccess: (posts:IPost) => {
345
+ // Do something after the query succeeded
346
+ },
347
+ onFinish: () => {
348
+ // Do something after everything is done
349
+ setLoading(false);
350
+ },
351
+ },
352
+ ),
353
+ );
354
+
355
+ // Reducer
356
+ const initState = {
357
+ posts: [],
358
+ error: null,
359
+ loading: true,
360
+ };
361
+
362
+ const postReducer = createReducer(initState, (builder) => {
363
+ builder.addCase<void>(fetchPosts.begin, (state, action) => {
364
+ state.loading = true;
365
+ });
366
+ builder.addCase<void>(fetchPosts.finish, (state, action) => {
367
+ state.loading = false;
368
+ });
369
+ builder.addCase<IPost[]>(fetchPosts.success, (state, action) => {
370
+ state.posts = action.payload;
371
+ });
372
+ builder.addCase<Error>(fetchPosts.failure, (state, action) => {
373
+ state.error = action.payload;
374
+ });
375
+ })
376
+ ```
377
+
378
+ Returns **any** State of reducer has immer
379
+
380
+ ## unfoldSaga
381
+
382
+ Common saga helper that unifies handling side effects into only one standard form
383
+
384
+ ### Parameters
385
+
386
+ - `body` **UnfoldSagaHandlerType**&#x20;
387
+ - `body.action` **UnfoldSagaActionType** Action
388
+ - `body.handler` **[Function][26]** Main handler function. Its returned value will become onSuccess callback param
389
+
390
+ ### Examples
391
+
392
+ ```javascript
393
+ import {SagaIterator} from 'redux-saga';
394
+ import {call, takeLatest} from 'redux-saga/effects';
395
+ import {unfoldSaga} from 'redux-unfold-saga-toolkit';
396
+ import {fetchPosts} from './action';
397
+
398
+ // Saga function
399
+ function* takeQueryPosts(action: UnfoldSagaActionType): Iterable<SagaIterator> {
400
+ yield unfoldSaga({
401
+ action: action,
402
+ handler: function* () {
403
+ const data = yield call(ApiPost.listPost);
404
+ return data;
405
+ },
406
+ });
407
+ }
408
+
409
+ or
410
+
411
+ // Async function
412
+ function* takeQueryPosts(action: UnfoldSagaActionType): Iterable<SagaIterator> {
413
+ yield unfoldSaga({
414
+ action: action,
415
+ handler: async function () {
416
+ const data = await ApiPost.listPost();
417
+ return data;
418
+ },
419
+ });
420
+ }
421
+
422
+ function* defaultSaga() {
423
+ yield takeLatest('FETCH_POSTS', takeQueryPosts);
424
+ // yield takeLatest(fetchPosts, takeQueryPosts);
425
+ // yield takeLatest(fetchPosts.type, takeQueryPosts);
426
+ }
427
+ ```
428
+
429
+ Returns **SagaIterator** SagaIterator
430
+
431
+ [1]: #createactiontypeonbegin
432
+ [2]: #parameters
433
+ [3]: #examples
434
+ [4]: #createactiontypeonfinish
435
+ [5]: #parameters-1
436
+ [6]: #examples-1
437
+ [7]: #createactiontypeonsuccess
438
+ [8]: #parameters-2
439
+ [9]: #examples-2
440
+ [10]: #createactiontypeonfailure
441
+ [11]: #parameters-3
442
+ [12]: #examples-3
443
+ [13]: #createaction
444
+ [14]: #parameters-4
445
+ [15]: #examples-4
446
+ [16]: #createstoreaction
447
+ [17]: #parameters-5
448
+ [18]: #examples-5
449
+ [19]: #createreducer
450
+ [20]: #parameters-6
451
+ [21]: #examples-6
452
+ [22]: #unfoldsaga
453
+ [23]: #parameters-7
454
+ [24]: #examples-7
455
+ [25]: https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/String
456
+ [26]: https://developer.mozilla.org/docs/Web/JavaScript/Reference/Statements/function
457
+
458
+ ## License
459
+
460
+ MIT © [hungnguyen2809](https://github.com/hungnguyen2809)
@@ -0,0 +1,140 @@
1
+ import { UnfoldSagaActionCreator, UnfoldSagaStoreActionCreator } from '../types';
2
+ /**
3
+ * @param key
4
+ * @returns {string} `${key}_BEGAN`
5
+ * @description Create onBegin action type
6
+ * @example
7
+ * ```ts
8
+ * import {createActionTypeOnBegin} from 'redux-unfold-saga-toolkit';
9
+ *
10
+ * createActionTypeOnBegin('DO_SOMETHING') // DO_SOMETHING_BEGAN
11
+ * ```
12
+ */
13
+ export declare function createActionTypeOnBegin(key: string): string;
14
+ /**
15
+ * @param key
16
+ * @returns {string} `${key}_FINISHED`
17
+ * @description Create onFinish action type
18
+ * @example
19
+ * ```ts
20
+ * import {createActionTypeOnFinish} from 'redux-unfold-saga-toolkit';
21
+ *
22
+ * createActionTypeOnFinish('DO_SOMETHING') // DO_SOMETHING_FINISHED
23
+ * ```
24
+ */
25
+ export declare function createActionTypeOnFinish(key: string): string;
26
+ /**
27
+ * @param key
28
+ * @returns {string} `${key}_SUCCEEDED`
29
+ * @description Create onSuccess action type
30
+ * @example
31
+ * ```ts
32
+ * import {createActionTypeOnSuccess} from 'redux-unfold-saga-toolkit';
33
+ *
34
+ * createActionTypeOnSuccess('DO_SOMETHING') // DO_SOMETHING_SUCCEEDED
35
+ * ```
36
+ */
37
+ export declare function createActionTypeOnSuccess(key: string): string;
38
+ /**
39
+ * @param key
40
+ * @returns {string} `${key}_FAILED`
41
+ * @description Create onFailure action type
42
+ * @example
43
+ * ```ts
44
+ * import {createActionTypeOnFailure} from 'redux-unfold-saga-toolkit';
45
+ *
46
+ * createActionTypeOnFailure('DO_SOMETHING') // DO_SOMETHING_FAILED
47
+ * ```
48
+ */
49
+ export declare function createActionTypeOnFailure(key: string): string;
50
+ /**
51
+ * @param type
52
+ * @returns {UnfoldSagaActionCreator} action
53
+ * @description Create an action for real life usage inside or even outside of a component, no dispatch to reducer
54
+ * @example
55
+ * ```ts
56
+ * import {createAction} from 'redux-unfold-saga-toolkit';
57
+ *
58
+ * const fetchPosts = createAction<IPostPayload>('FETCH_POSTS');
59
+ *
60
+ * dispatch(
61
+ * fetchPosts(
62
+ * {category: 'HOT'},
63
+ * {
64
+ * onBegin: () => {
65
+ * // Do something before the query
66
+ * setLoading(true);
67
+ * },
68
+ * onFailure: (error: Error) => {
69
+ * // Do something in case of caught error
70
+ * },
71
+ * onSuccess: (posts: IPost[]) => {
72
+ * // Do something after the query succeeded
73
+ * },
74
+ * onFinish: () => {
75
+ * // Do something after everything is done
76
+ * setLoading(false);
77
+ * },
78
+ * },
79
+ * ),
80
+ * );
81
+ * ```
82
+ */
83
+ export declare function createAction<PayloadType = any>(type: string): UnfoldSagaActionCreator<PayloadType>;
84
+ /**
85
+ * @param type
86
+ * @returns {UnfoldSagaActionCreator} action
87
+ * @description Create an action for real life usage inside or even outside of a component, dispatch to reducer with automatic create action type
88
+ * @example
89
+ * // Action
90
+ * ```ts
91
+ * import {createStoreAction, createReducer} from 'redux-unfold-saga-toolkit';
92
+ *
93
+ * const fetchPosts = createStoreAction<IPostPayload>('FETCH_POSTS');
94
+ *
95
+ * dispatch(
96
+ * fetchPosts(
97
+ * {category: 'HOT'},
98
+ * {
99
+ * onBegin: () => {
100
+ * // Do something before the query
101
+ * setLoading(true);
102
+ * },
103
+ * onFailure: (error:Error) => {
104
+ * // Do something in case of caught error
105
+ * },
106
+ * onSuccess: (posts:IPost) => {
107
+ * // Do something after the query succeeded
108
+ * },
109
+ * onFinish: () => {
110
+ * // Do something after everything is done
111
+ * setLoading(false);
112
+ * },
113
+ * },
114
+ * ),
115
+ * );
116
+ *
117
+ * // Reducer
118
+ * const initState = {
119
+ * posts: [],
120
+ * error: null,
121
+ * loading: true,
122
+ * };
123
+ *
124
+ * const postReducer = createReducer(initState, (builder) => {
125
+ * builder.addCase<void>(fetchPosts.begin, (state, action) => {
126
+ * state.loading = true;
127
+ * });
128
+ * builder.addCase<void>(fetchPosts.finish, (state, action) => {
129
+ * state.loading = false;
130
+ * });
131
+ * builder.addCase<IPost[]>(fetchPosts.success, (state, action) => {
132
+ * state.posts = action.payload;
133
+ * });
134
+ * builder.addCase<Error>(fetchPosts.failure, (state, action) => {
135
+ * state.error = action.payload;
136
+ * });
137
+ * })
138
+ * ```
139
+ */
140
+ export declare function createStoreAction<PayloadType = any>(type: string): UnfoldSagaStoreActionCreator<PayloadType>;
@@ -0,0 +1,175 @@
1
+ /**
2
+ * @param key
3
+ * @returns {string} `${key}_BEGAN`
4
+ * @description Create onBegin action type
5
+ * @example
6
+ * ```ts
7
+ * import {createActionTypeOnBegin} from 'redux-unfold-saga-toolkit';
8
+ *
9
+ * createActionTypeOnBegin('DO_SOMETHING') // DO_SOMETHING_BEGAN
10
+ * ```
11
+ */
12
+ export function createActionTypeOnBegin(key) {
13
+ return `${key}_BEGAN`;
14
+ }
15
+ /**
16
+ * @param key
17
+ * @returns {string} `${key}_FINISHED`
18
+ * @description Create onFinish action type
19
+ * @example
20
+ * ```ts
21
+ * import {createActionTypeOnFinish} from 'redux-unfold-saga-toolkit';
22
+ *
23
+ * createActionTypeOnFinish('DO_SOMETHING') // DO_SOMETHING_FINISHED
24
+ * ```
25
+ */
26
+ export function createActionTypeOnFinish(key) {
27
+ return `${key}_FINISHED`;
28
+ }
29
+ /**
30
+ * @param key
31
+ * @returns {string} `${key}_SUCCEEDED`
32
+ * @description Create onSuccess action type
33
+ * @example
34
+ * ```ts
35
+ * import {createActionTypeOnSuccess} from 'redux-unfold-saga-toolkit';
36
+ *
37
+ * createActionTypeOnSuccess('DO_SOMETHING') // DO_SOMETHING_SUCCEEDED
38
+ * ```
39
+ */
40
+ export function createActionTypeOnSuccess(key) {
41
+ return `${key}_SUCCEEDED`;
42
+ }
43
+ /**
44
+ * @param key
45
+ * @returns {string} `${key}_FAILED`
46
+ * @description Create onFailure action type
47
+ * @example
48
+ * ```ts
49
+ * import {createActionTypeOnFailure} from 'redux-unfold-saga-toolkit';
50
+ *
51
+ * createActionTypeOnFailure('DO_SOMETHING') // DO_SOMETHING_FAILED
52
+ * ```
53
+ */
54
+ export function createActionTypeOnFailure(key) {
55
+ return `${key}_FAILED`;
56
+ }
57
+ /**
58
+ * @param type
59
+ * @returns {UnfoldSagaActionCreator} action
60
+ * @description Create an action for real life usage inside or even outside of a component, no dispatch to reducer
61
+ * @example
62
+ * ```ts
63
+ * import {createAction} from 'redux-unfold-saga-toolkit';
64
+ *
65
+ * const fetchPosts = createAction<IPostPayload>('FETCH_POSTS');
66
+ *
67
+ * dispatch(
68
+ * fetchPosts(
69
+ * {category: 'HOT'},
70
+ * {
71
+ * onBegin: () => {
72
+ * // Do something before the query
73
+ * setLoading(true);
74
+ * },
75
+ * onFailure: (error: Error) => {
76
+ * // Do something in case of caught error
77
+ * },
78
+ * onSuccess: (posts: IPost[]) => {
79
+ * // Do something after the query succeeded
80
+ * },
81
+ * onFinish: () => {
82
+ * // Do something after everything is done
83
+ * setLoading(false);
84
+ * },
85
+ * },
86
+ * ),
87
+ * );
88
+ * ```
89
+ */
90
+ export function createAction(type) {
91
+ function actionCreator(payload, callbacks = {}) {
92
+ return {
93
+ callbacks,
94
+ options: {},
95
+ payload,
96
+ type,
97
+ };
98
+ }
99
+ actionCreator.type = type;
100
+ actionCreator.toString = () => type;
101
+ return actionCreator;
102
+ }
103
+ /**
104
+ * @param type
105
+ * @returns {UnfoldSagaActionCreator} action
106
+ * @description Create an action for real life usage inside or even outside of a component, dispatch to reducer with automatic create action type
107
+ * @example
108
+ * // Action
109
+ * ```ts
110
+ * import {createStoreAction, createReducer} from 'redux-unfold-saga-toolkit';
111
+ *
112
+ * const fetchPosts = createStoreAction<IPostPayload>('FETCH_POSTS');
113
+ *
114
+ * dispatch(
115
+ * fetchPosts(
116
+ * {category: 'HOT'},
117
+ * {
118
+ * onBegin: () => {
119
+ * // Do something before the query
120
+ * setLoading(true);
121
+ * },
122
+ * onFailure: (error:Error) => {
123
+ * // Do something in case of caught error
124
+ * },
125
+ * onSuccess: (posts:IPost) => {
126
+ * // Do something after the query succeeded
127
+ * },
128
+ * onFinish: () => {
129
+ * // Do something after everything is done
130
+ * setLoading(false);
131
+ * },
132
+ * },
133
+ * ),
134
+ * );
135
+ *
136
+ * // Reducer
137
+ * const initState = {
138
+ * posts: [],
139
+ * error: null,
140
+ * loading: true,
141
+ * };
142
+ *
143
+ * const postReducer = createReducer(initState, (builder) => {
144
+ * builder.addCase<void>(fetchPosts.begin, (state, action) => {
145
+ * state.loading = true;
146
+ * });
147
+ * builder.addCase<void>(fetchPosts.finish, (state, action) => {
148
+ * state.loading = false;
149
+ * });
150
+ * builder.addCase<IPost[]>(fetchPosts.success, (state, action) => {
151
+ * state.posts = action.payload;
152
+ * });
153
+ * builder.addCase<Error>(fetchPosts.failure, (state, action) => {
154
+ * state.error = action.payload;
155
+ * });
156
+ * })
157
+ * ```
158
+ */
159
+ export function createStoreAction(type) {
160
+ function actionCreator(payload, callbacks = {}) {
161
+ return {
162
+ callbacks,
163
+ options: { stateful: true },
164
+ payload,
165
+ type,
166
+ };
167
+ }
168
+ actionCreator.type = type;
169
+ actionCreator.toString = () => type;
170
+ actionCreator.begin = createActionTypeOnBegin(type);
171
+ actionCreator.finish = createActionTypeOnFinish(type);
172
+ actionCreator.success = createActionTypeOnSuccess(type);
173
+ actionCreator.failure = createActionTypeOnFailure(type);
174
+ return actionCreator;
175
+ }
@@ -0,0 +1,47 @@
1
+ import { SagaIterator } from 'redux-saga';
2
+ import { UnfoldSagaHandlerType } from '../types';
3
+ /**
4
+ * @param {UnfoldSagaHandlerType} body
5
+ * @param {UnfoldSagaActionType} body.action Action
6
+ * @param {Function} body.handler Main handler function. Its returned value will become onSuccess callback param
7
+ * @returns {SagaIterator} SagaIterator
8
+ * @description Common saga helper that unifies handling side effects into only one standard form
9
+ * @example
10
+ * ```ts
11
+ * import {SagaIterator} from 'redux-saga';
12
+ * import {call, takeLatest} from 'redux-saga/effects';
13
+ * import {unfoldSaga} from 'redux-unfold-saga-toolkit';
14
+ * import {fetchPosts} from './action';
15
+ *
16
+ * // Saga function
17
+ * function* takeQueryPosts(action: UnfoldSagaActionType): Iterable<SagaIterator> {
18
+ * yield unfoldSaga({
19
+ * action: action,
20
+ * handler: function* () {
21
+ * const data = yield call(ApiPost.listPost);
22
+ * return data;
23
+ * },
24
+ * });
25
+ * }
26
+ *
27
+ * or
28
+ *
29
+ * // Async function
30
+ * function* takeQueryPosts(action: UnfoldSagaActionType): Iterable<SagaIterator> {
31
+ * yield unfoldSaga({
32
+ * action: action,
33
+ * handler: async function () {
34
+ * const data = await ApiPost.listPost();
35
+ * return data;
36
+ * },
37
+ * });
38
+ * }
39
+ *
40
+ * function* defaultSaga() {
41
+ * yield takeLatest('FETCH_POSTS', takeQueryPosts);
42
+ * // yield takeLatest(fetchPosts, takeQueryPosts);
43
+ * // yield takeLatest(fetchPosts.type, takeQueryPosts);
44
+ * }
45
+ * ```
46
+ */
47
+ export declare function unfoldSaga({ action, handler }: UnfoldSagaHandlerType): SagaIterator;
@@ -0,0 +1,86 @@
1
+ import { call, put } from 'redux-saga/effects';
2
+ import { createActionTypeOnBegin, createActionTypeOnFailure, createActionTypeOnFinish, createActionTypeOnSuccess, } from '../action';
3
+ import { noop } from '../helper';
4
+ /**
5
+ * @param {UnfoldSagaHandlerType} body
6
+ * @param {UnfoldSagaActionType} body.action Action
7
+ * @param {Function} body.handler Main handler function. Its returned value will become onSuccess callback param
8
+ * @returns {SagaIterator} SagaIterator
9
+ * @description Common saga helper that unifies handling side effects into only one standard form
10
+ * @example
11
+ * ```ts
12
+ * import {SagaIterator} from 'redux-saga';
13
+ * import {call, takeLatest} from 'redux-saga/effects';
14
+ * import {unfoldSaga} from 'redux-unfold-saga-toolkit';
15
+ * import {fetchPosts} from './action';
16
+ *
17
+ * // Saga function
18
+ * function* takeQueryPosts(action: UnfoldSagaActionType): Iterable<SagaIterator> {
19
+ * yield unfoldSaga({
20
+ * action: action,
21
+ * handler: function* () {
22
+ * const data = yield call(ApiPost.listPost);
23
+ * return data;
24
+ * },
25
+ * });
26
+ * }
27
+ *
28
+ * or
29
+ *
30
+ * // Async function
31
+ * function* takeQueryPosts(action: UnfoldSagaActionType): Iterable<SagaIterator> {
32
+ * yield unfoldSaga({
33
+ * action: action,
34
+ * handler: async function () {
35
+ * const data = await ApiPost.listPost();
36
+ * return data;
37
+ * },
38
+ * });
39
+ * }
40
+ *
41
+ * function* defaultSaga() {
42
+ * yield takeLatest('FETCH_POSTS', takeQueryPosts);
43
+ * // yield takeLatest(fetchPosts, takeQueryPosts);
44
+ * // yield takeLatest(fetchPosts.type, takeQueryPosts);
45
+ * }
46
+ * ```
47
+ */
48
+ export function* unfoldSaga({ action, handler }) {
49
+ let data;
50
+ const defaultCallbacks = {
51
+ onBegin: noop,
52
+ onFinish: noop,
53
+ onSuccess: noop,
54
+ onFailure: noop,
55
+ };
56
+ const defaultOptions = {
57
+ stateful: false,
58
+ };
59
+ Object.assign(defaultCallbacks, action.callbacks);
60
+ Object.assign(defaultOptions, action.options);
61
+ try {
62
+ if (defaultOptions.stateful)
63
+ yield put({ type: createActionTypeOnBegin(action.type) });
64
+ yield call(defaultCallbacks.onBegin);
65
+ if (['GeneratorFunction', 'AsyncGeneratorFunction'].includes(handler.constructor.name)) {
66
+ data = yield* handler();
67
+ }
68
+ else {
69
+ data = yield call(handler);
70
+ }
71
+ if (defaultOptions.stateful)
72
+ yield put({ type: createActionTypeOnSuccess(action.type), payload: data });
73
+ yield call(defaultCallbacks.onSuccess, data);
74
+ }
75
+ catch (error) {
76
+ if (defaultOptions.stateful)
77
+ yield put({ type: createActionTypeOnFailure(action.type), payload: error });
78
+ yield call(defaultCallbacks.onFailure, error);
79
+ }
80
+ finally {
81
+ if (defaultOptions.stateful)
82
+ yield put({ type: createActionTypeOnFinish(action.type) });
83
+ yield call(defaultCallbacks.onFinish);
84
+ }
85
+ return data;
86
+ }
@@ -0,0 +1,2 @@
1
+ declare function noop(): void;
2
+ export { noop };
@@ -0,0 +1,2 @@
1
+ function noop() { }
2
+ export { noop };
@@ -0,0 +1,4 @@
1
+ export * from './types';
2
+ export * from './action';
3
+ export * from './reducer';
4
+ export * from './core';
package/dist/index.js ADDED
@@ -0,0 +1,4 @@
1
+ export * from './types';
2
+ export * from './action';
3
+ export * from './reducer';
4
+ export * from './core';
@@ -0,0 +1,62 @@
1
+ import { UnfoldSagaActionReducerMapBuilder, UnfoldSagaActionType } from '../types';
2
+ /**
3
+ *
4
+ * @param initialState
5
+ * @param builderCallback
6
+ * @returns State of reducer has immer
7
+ * @description A utility function that allows defining a reducer as a mapping from action
8
+ * type to *case reducer* functions that handle these action types. The
9
+ * reducer's initial state is passed as the first argument.
10
+ * @example
11
+ * // Action
12
+ * ```ts
13
+ * import {createStoreAction, createReducer} from 'redux-unfold-saga-toolkit';
14
+ *
15
+ * const fetchPosts = createStoreAction<IPostPayload>('FETCH_POSTS');
16
+ *
17
+ * dispatch(
18
+ * fetchPosts(
19
+ * {category: 'HOT'},
20
+ * {
21
+ * onBegin: () => {
22
+ * // Do something before the query
23
+ * setLoading(true);
24
+ * },
25
+ * onFailure: (error:Error) => {
26
+ * // Do something in case of caught error
27
+ * },
28
+ * onSuccess: (posts:IPost) => {
29
+ * // Do something after the query succeeded
30
+ * },
31
+ * onFinish: () => {
32
+ * // Do something after everything is done
33
+ * setLoading(false);
34
+ * },
35
+ * },
36
+ * ),
37
+ * );
38
+ *
39
+ * // Reducer
40
+ * const initState = {
41
+ * posts: [],
42
+ * error: null,
43
+ * loading: true,
44
+ * };
45
+ *
46
+ * const postReducer = createReducer(initState, (builder) => {
47
+ * builder.addCase<void>(fetchPosts.begin, (state, action) => {
48
+ * state.loading = true;
49
+ * });
50
+ * builder.addCase<void>(fetchPosts.finish, (state, action) => {
51
+ * state.loading = false;
52
+ * });
53
+ * builder.addCase<IPost[]>(fetchPosts.success, (state, action) => {
54
+ * state.posts = action.payload;
55
+ * });
56
+ * builder.addCase<Error>(fetchPosts.failure, (state, action) => {
57
+ * state.error = action.payload;
58
+ * });
59
+ * })
60
+ * ```
61
+ */
62
+ export declare function createReducer<S = any>(initialState: S, builderCallback: (builder: UnfoldSagaActionReducerMapBuilder<S>) => void): (state: S | undefined, action: UnfoldSagaActionType) => S;
@@ -0,0 +1,83 @@
1
+ import { produce } from 'immer';
2
+ function executeReducerBuilderCallback(builderCallback) {
3
+ const actionsMap = {};
4
+ const builder = {
5
+ addCase: (action, reducer) => {
6
+ const type = typeof action === 'string' ? action : action.type;
7
+ actionsMap[type] = reducer;
8
+ return builder;
9
+ },
10
+ };
11
+ builderCallback(builder);
12
+ return actionsMap;
13
+ }
14
+ /**
15
+ *
16
+ * @param initialState
17
+ * @param builderCallback
18
+ * @returns State of reducer has immer
19
+ * @description A utility function that allows defining a reducer as a mapping from action
20
+ * type to *case reducer* functions that handle these action types. The
21
+ * reducer's initial state is passed as the first argument.
22
+ * @example
23
+ * // Action
24
+ * ```ts
25
+ * import {createStoreAction, createReducer} from 'redux-unfold-saga-toolkit';
26
+ *
27
+ * const fetchPosts = createStoreAction<IPostPayload>('FETCH_POSTS');
28
+ *
29
+ * dispatch(
30
+ * fetchPosts(
31
+ * {category: 'HOT'},
32
+ * {
33
+ * onBegin: () => {
34
+ * // Do something before the query
35
+ * setLoading(true);
36
+ * },
37
+ * onFailure: (error:Error) => {
38
+ * // Do something in case of caught error
39
+ * },
40
+ * onSuccess: (posts:IPost) => {
41
+ * // Do something after the query succeeded
42
+ * },
43
+ * onFinish: () => {
44
+ * // Do something after everything is done
45
+ * setLoading(false);
46
+ * },
47
+ * },
48
+ * ),
49
+ * );
50
+ *
51
+ * // Reducer
52
+ * const initState = {
53
+ * posts: [],
54
+ * error: null,
55
+ * loading: true,
56
+ * };
57
+ *
58
+ * const postReducer = createReducer(initState, (builder) => {
59
+ * builder.addCase<void>(fetchPosts.begin, (state, action) => {
60
+ * state.loading = true;
61
+ * });
62
+ * builder.addCase<void>(fetchPosts.finish, (state, action) => {
63
+ * state.loading = false;
64
+ * });
65
+ * builder.addCase<IPost[]>(fetchPosts.success, (state, action) => {
66
+ * state.posts = action.payload;
67
+ * });
68
+ * builder.addCase<Error>(fetchPosts.failure, (state, action) => {
69
+ * state.error = action.payload;
70
+ * });
71
+ * })
72
+ * ```
73
+ */
74
+ export function createReducer(initialState, builderCallback) {
75
+ const actionsMap = executeReducerBuilderCallback(builderCallback);
76
+ return function reducer(state = initialState, action) {
77
+ const caseReducer = actionsMap[action.type];
78
+ return produce(state, (draft) => {
79
+ if (caseReducer)
80
+ caseReducer(draft, action);
81
+ });
82
+ };
83
+ }
@@ -0,0 +1,40 @@
1
+ import { Draft } from 'immer';
2
+ import { AnyAction } from 'redux-saga';
3
+ export interface UnfoldSagaCallbacksType {
4
+ onBegin?: Function;
5
+ onFinish?: Function;
6
+ onSuccess?: Function;
7
+ onFailure?: Function;
8
+ }
9
+ export interface UnfoldSagaOptionsType {
10
+ stateful?: boolean;
11
+ }
12
+ export interface UnfoldSagaHandlerType {
13
+ action: UnfoldSagaActionType;
14
+ handler: Function | GeneratorFunction;
15
+ }
16
+ export interface UnfoldSagaPayloadAction<T = any> {
17
+ type: string;
18
+ payload: T;
19
+ }
20
+ export type Fn = (...args: any[]) => any;
21
+ export type UnfoldSagaCaseReducer<S, P = any> = (state: Draft<S>, action: UnfoldSagaPayloadAction<P>) => void;
22
+ export type UnfoldSagaActionReducer = UnfoldSagaActionCreator | string;
23
+ export interface UnfoldSagaActionReducerMapBuilder<S> {
24
+ addCase: <P = any>(action: UnfoldSagaActionReducer, reducer: UnfoldSagaCaseReducer<S, P>) => UnfoldSagaActionReducerMapBuilder<S>;
25
+ }
26
+ export interface UnfoldSagaActionType<PayloadType = any> extends AnyAction {
27
+ payload: PayloadType;
28
+ options: UnfoldSagaOptionsType;
29
+ callbacks: UnfoldSagaCallbacksType;
30
+ }
31
+ export interface UnfoldSagaActionCreator<PayloadType = any> {
32
+ (payload?: PayloadType, callbacks?: UnfoldSagaCallbacksType): UnfoldSagaActionType;
33
+ type: string;
34
+ }
35
+ export interface UnfoldSagaStoreActionCreator<PayloadType = any> extends UnfoldSagaActionCreator<PayloadType> {
36
+ begin: string;
37
+ finish: string;
38
+ success: string;
39
+ failure: string;
40
+ }
@@ -0,0 +1 @@
1
+ export {};
package/package.json ADDED
@@ -0,0 +1,73 @@
1
+ {
2
+ "name": "redux-unfold-saga-toolkit",
3
+ "version": "1.0.1",
4
+ "description": "A no headache middleware helper for redux-saga.",
5
+ "license": "MIT",
6
+ "main": "dist/index.js",
7
+ "types": "dist/index.d.ts",
8
+ "files": [
9
+ "dist/"
10
+ ],
11
+ "scripts": {
12
+ "test": "jest",
13
+ "build": "tsc",
14
+ "commit": "git cz",
15
+ "lint": "eslint .",
16
+ "prepare": "husky"
17
+ },
18
+ "author": {
19
+ "name": "Hung Nguyen",
20
+ "email": "hungnguyen.dev99@gmail.com",
21
+ "url": "https://github.com/hungnguyen2809"
22
+ },
23
+ "repository": {
24
+ "type": "git",
25
+ "url": "git+https://github.com/hungnguyen2809/redux-unfold-saga-toolkit.git"
26
+ },
27
+ "publishConfig": {
28
+ "registry": "https://registry.npmjs.org/",
29
+ "access": "public"
30
+ },
31
+ "keywords": [
32
+ "redux",
33
+ "redux-saga",
34
+ "redux-unfold-saga",
35
+ "redux-unfold-saga-toolkit"
36
+ ],
37
+ "peerDependencies": {
38
+ "immer": ">=10.0.0",
39
+ "redux-saga": "*"
40
+ },
41
+ "devDependencies": {
42
+ "@commitlint/cli": "^20.2.0",
43
+ "@commitlint/config-conventional": "^20.2.0",
44
+ "@eslint/js": "^9.39.1",
45
+ "@types/jest": "^30.0.0",
46
+ "commitizen": "^4.3.1",
47
+ "eslint": "^9.39.1",
48
+ "git-cz": "^4.9.0",
49
+ "husky": "^9.1.7",
50
+ "immer": "10.0.0",
51
+ "jest": "^30.2.0",
52
+ "lint-staged": "^16.2.7",
53
+ "prettier": "^3.7.4",
54
+ "redux-saga": "1.4.2",
55
+ "ts-jest": "^29.4.6",
56
+ "typescript": "^5.9.3",
57
+ "typescript-eslint": "^8.48.1"
58
+ },
59
+ "lint-staged": {
60
+ "*.{js,jsx,ts,tsx}": [
61
+ "eslint --fix",
62
+ "prettier --write"
63
+ ],
64
+ "*.{json,md,yml}": [
65
+ "prettier --write"
66
+ ]
67
+ },
68
+ "config": {
69
+ "commitizen": {
70
+ "path": "git-cz"
71
+ }
72
+ }
73
+ }