UNPKG

@rematch/core

Version:
139 lines (119 loc) 3.87 kB
import { Middleware } from 'redux' import { Action, Config, ExposedFunction, Models, NamedModel, ObjectNotAFunction, Plugin, RematchBag, RematchStore, ModelDispatcher, RematchDispatch, } from './types' import createReduxStore, { createModelReducer, createRootReducer, } from './reduxStore' import createDispatcher from './dispatcher' import { validateModel } from './validate' import createRematchBag from './bag' export default function createRematchStore< TModels extends Models<TModels> = Record<string, any>, TExtraModels extends Models<TModels> = Record<string, any> >(config: Config<TModels, TExtraModels>): RematchStore<TModels, TExtraModels> { // setup rematch 'bag' for storing useful values and functions const bag = createRematchBag(config) // add middleware for handling effects bag.reduxConfig.middlewares.push(createEffectsMiddleware(bag)) // collect middlewares from plugins bag.forEachPlugin('createMiddleware', (createMiddleware) => { bag.reduxConfig.middlewares.push(createMiddleware(bag)) }) const reduxStore = createReduxStore(bag) let rematchStore = { ...reduxStore, name: config.name, addModel(model: NamedModel<TModels>) { validateModel(model) createModelReducer(bag, model) prepareModel(this, bag, model) this.replaceReducer(createRootReducer(bag)) reduxStore.dispatch({ type: '@@redux/REPLACE' }) }, } as RematchStore<TModels, TExtraModels> addExposed(rematchStore, config.plugins) rematchStore.addModel.bind(rematchStore) // generate dispatch[modelName][actionName] for all reducers and effects bag.models.forEach((model) => prepareModel(rematchStore, bag, model)) bag.forEachPlugin('onStoreCreated', (onStoreCreated) => { rematchStore = onStoreCreated(rematchStore, bag) || rematchStore }) return rematchStore } function createEffectsMiddleware< TModels extends Models<TModels> = Record<string, any> >(bag: RematchBag<TModels>): Middleware { return (store) => (next) => (action: Action): any => { if (action.type in bag.effects) { // first run reducer action if exists next(action) // then run the effect and return its result return bag.effects[action.type]( action.payload, store.getState(), action.meta ) } return next(action) } } function prepareModel< TModels extends Models<TModels> = Record<string, any>, TModel extends NamedModel<TModels> = NamedModel >( rematchStore: RematchStore<TModels>, bag: RematchBag<TModels>, model: TModel ): void { const modelDispatcher = {} as ModelDispatcher<TModel, TModels> // inject model so effects creator can access it rematchStore.dispatch[ `${model.name}` as keyof RematchDispatch<TModels> ] = modelDispatcher createDispatcher(rematchStore, bag, model) bag.forEachPlugin('onModel', (onModel) => { onModel(model, rematchStore) }) } /** * Adds properties exposed by plugins into the Rematch instance. If a exposed * property is a function, it passes rematch as the first argument. * * If you're implementing a plugin in TypeScript, extend Rematch namespace by * adding the properties that you exposed from your plugin. */ function addExposed< TModels extends Models<TModels> = Record<string, any>, TExtraModels extends Models<TModels> = Record<string, any> >( store: RematchStore<TModels, TExtraModels>, plugins: Plugin<TModels, TExtraModels>[] ): void { plugins.forEach((plugin) => { if (!plugin.exposed) return const pluginKeys = Object.keys(plugin.exposed) pluginKeys.forEach((key) => { if (!plugin.exposed) return const exposedItem = plugin.exposed[key] as | ExposedFunction | ObjectNotAFunction const isExposedFunction = typeof exposedItem === 'function' store[key] = isExposedFunction ? (...params: any[]): any => (exposedItem as ExposedFunction)(store, ...params) : Object.create(plugin.exposed[key]) }) }) }