11import {
2- ActionCreatorWithOptionalPayload ,
3- ActionCreatorWithoutPayload ,
4- ActionCreatorWithPreparedPayload ,
2+ type ActionCreatorWithOptionalPayload ,
3+ type ActionCreatorWithoutPayload ,
4+ type ActionCreatorWithPreparedPayload ,
55 createAction
66} from '@reduxjs/toolkit' ;
7- import * as Sentry from '@sentry/browser' ;
8- import { SagaIterator } from 'redux-saga' ;
9- import { StrictEffect , takeEvery } from 'redux-saga/effects' ;
7+ import type { SagaIterator } from 'redux-saga' ;
8+ import { type StrictEffect , takeEvery , takeLatest , takeLeading } from 'redux-saga/effects' ;
9+
10+ import { safeTakeEvery , wrapSaga } from '../sagas/SafeEffects' ;
11+ import type { SourceActionType } from '../utils/ActionsHelper' ;
12+ import { type ActionTypeToCreator , objectEntries } from '../utils/TypeHelper' ;
1013
1114/**
1215 * Creates actions, given a base name and base actions
1316 * @param baseName The base name of the actions
14- * @param baseActions The base actions. Use a falsy value to create an action without a payload.
17+ * @param baseActions The base actions. Use a non function value to create an action without a payload.
1518 * @returns An object containing the actions
1619 */
1720export function createActions < BaseName extends string , BaseActions extends Record < string , any > > (
@@ -21,11 +24,12 @@ export function createActions<BaseName extends string, BaseActions extends Recor
2124 return Object . entries ( baseActions ) . reduce (
2225 ( res , [ name , func ] ) => ( {
2326 ...res ,
24- [ name ] : func
25- ? createAction ( `${ baseName } /${ name } ` , ( ...args : any ) => ( { payload : func ( ...args ) } ) )
26- : createAction ( `${ baseName } /${ name } ` )
27+ [ name ] :
28+ typeof func === 'function'
29+ ? createAction ( `${ baseName } /${ name } ` , ( ...args : any ) => ( { payload : func ( ...args ) } ) )
30+ : createAction ( `${ baseName } /${ name } ` )
2731 } ) ,
28- { } as {
32+ { } as Readonly < {
2933 [ K in keyof BaseActions ] : K extends string
3034 ? BaseActions [ K ] extends ( ...args : any ) => any
3135 ? ActionCreatorWithPreparedPayload <
@@ -35,35 +39,40 @@ export function createActions<BaseName extends string, BaseActions extends Recor
3539 >
3640 : ActionCreatorWithoutPayload < `${BaseName } /${K } `>
3741 : never ;
38- }
42+ } >
3943 ) ;
4044}
4145
42- export function combineSagaHandlers <
43- TActions extends Record <
44- string ,
45- ActionCreatorWithPreparedPayload < any , any > | ActionCreatorWithoutPayload < any >
46- >
47- > (
48- actions : TActions ,
49- handlers : {
50- // TODO: Maybe this can be stricter? And remove the optional type after
51- // migration is fully done
52- [ K in keyof TActions ] ?: ( action : ReturnType < TActions [ K ] > ) => SagaIterator ;
53- } ,
54- others ?: ( takeEvery : typeof saferTakeEvery ) => SagaIterator
55- ) : ( ) => SagaIterator {
56- const sagaHandlers = Object . entries ( handlers ) . map ( ( [ actionName , saga ] ) =>
57- saferTakeEvery ( actions [ actionName ] , saga )
58- ) ;
46+ type SagaHandler < T extends SourceActionType [ 'type' ] > = (
47+ action : ReturnType < ActionTypeToCreator < T > >
48+ ) => Generator < StrictEffect > ;
49+
50+ type SagaHandlers = {
51+ [ K in SourceActionType [ 'type' ] ] ?:
52+ | SagaHandler < K >
53+ | Partial < Record < 'takeEvery' | 'takeLatest' | 'takeLeading' , SagaHandler < K > > > ;
54+ } ;
55+
56+ export function combineSagaHandlers ( handlers : SagaHandlers ) {
5957 return function * ( ) : SagaIterator {
60- yield * sagaHandlers ;
61- if ( others ) {
62- const obj = others ( saferTakeEvery ) ;
63- while ( true ) {
64- const { done, value } = obj . next ( ) ;
65- if ( done ) break ;
66- yield value ;
58+ for ( const [ actionName , saga ] of objectEntries ( handlers ) ) {
59+ if ( saga === undefined ) {
60+ continue ;
61+ } else if ( typeof saga === 'function' ) {
62+ yield takeEvery ( actionName , wrapSaga ( saga ) ) ;
63+ continue ;
64+ }
65+
66+ if ( saga . takeEvery ) {
67+ yield takeEvery ( actionName , wrapSaga ( saga . takeEvery ) ) ;
68+ }
69+
70+ if ( saga . takeLeading ) {
71+ yield takeLeading ( actionName , wrapSaga ( saga . takeLeading ) ) ;
72+ }
73+
74+ if ( saga . takeLatest ) {
75+ yield takeLatest ( actionName , wrapSaga ( saga . takeLatest ) ) ;
6776 }
6877 }
6978 } ;
@@ -74,26 +83,5 @@ export function saferTakeEvery<
7483 | ActionCreatorWithOptionalPayload < any >
7584 | ActionCreatorWithPreparedPayload < any [ ] , any >
7685> ( actionPattern : Action , fn : ( action : ReturnType < Action > ) => Generator < StrictEffect < any > > ) {
77- function * wrapper ( action : ReturnType < Action > ) {
78- try {
79- yield * fn ( action ) ;
80- } catch ( error ) {
81- handleUncaughtError ( error ) ;
82- }
83- }
84-
85- return takeEvery ( actionPattern . type , wrapper ) ;
86- }
87-
88- function handleUncaughtError ( error : any ) {
89- if ( process . env . NODE_ENV === 'development' ) {
90- // react-error-overlay is a "special" package that's automatically included
91- // in development mode by CRA
92-
93- // eslint-disable-next-line @typescript-eslint/ban-ts-comment
94- // @ts -ignore
95- import ( 'react-error-overlay' ) . then ( reo => reo . reportRuntimeError ( error ) ) ;
96- }
97- Sentry . captureException ( error ) ;
98- console . error ( error ) ;
86+ return safeTakeEvery ( actionPattern . type , fn ) ;
9987}
0 commit comments