cumqueoptio
Version:
The meta-framework suite designed from scratch for frontend-focused modern web development.
129 lines (110 loc) • 3.21 kB
text/typescript
import { createPlugin } from '@modern-js-reduck/store';
import { Model } from '@modern-js-reduck/store/types';
import { createPromise } from 'redux-promise-middleware';
type AsyncEffect = (...args: any[]) => Promise<any>;
type VoidEffect = (...args: any[]) => void;
type ThunkEffect = (...args: any[]) => () => any;
interface Effects {
[key: string]: AsyncEffect | VoidEffect | ThunkEffect | Effects;
}
declare module '@modern-js-reduck/store' {
// Add `effects` type when use model({effects}).
interface ModelDesc {
effects?: Effects;
}
// Overload GetActions interface to add actions type to useModel's return
interface GetActions<M extends Model> {
effectsActions: M['_']['effects'] & {
[key in keyof M['_']['actions']]: unknown;
};
}
}
const isReduxPromiseFulfilled = (data: any) => {
return (
typeof data === 'object' &&
data.hasOwnProperty('action') &&
data.hasOwnProperty('value')
);
};
const isPromise = (value: any) => {
if (value !== null && typeof value === 'object') {
return value && typeof value.then === 'function';
}
return false;
};
/**
* Generate dispatch action from effects definitions.
*/
const createDispatchActionsFromEffects = (
store: any,
name: string,
effects: Effects,
setDispatchAction: (path: string[], action: any) => void,
) => {
const path = [name];
const traverse = (_effects: Effects[string]) => {
if (typeof _effects === 'function') {
const type = path.join('/').toUpperCase();
setDispatchAction(path.slice(), (...args: any[]) => {
const value = (_effects as (..._args: any[]) => any)(...args);
const dispatch = (payload: any) =>
store.dispatch({
type,
payload,
});
// Handled by promise middleware or redux thunk
// Otherwise, do not dispatch action, just exec the effect function.
if (isPromise(value) || typeof value === 'function') {
const res = dispatch(value);
if (isPromise(res)) {
// parse redux-promise result, return orginal value of the effect
return res.then((data: any) =>
isReduxPromiseFulfilled(data) ? data.value : data,
);
}
return res;
}
return value;
});
} else {
Object.keys(_effects).forEach(key => {
path.push(key);
traverse(_effects[key]);
path.pop();
});
}
};
traverse(effects);
};
const plugin = createPlugin(context => ({
config(storeConfig) {
return {
...storeConfig,
middlewares: [
createPromise({ promiseTypeDelimiter: '/' }),
// middlewares from config are at the end
...(storeConfig.middlewares || []),
],
};
},
modelMount({ modelDesc, mountedModel }, { setDispatchAction }) {
const { effects } = modelDesc;
if (!effects) {
return {
modelDesc,
mountedModel,
};
}
createDispatchActionsFromEffects(
context.store,
modelDesc.name,
modelDesc.effects!,
setDispatchAction,
);
return {
modelDesc,
mountedModel,
} as any;
},
}));
export default plugin;