UNPKG

4.5 kBPlain TextView Raw
1import { Middleware } from 'redux'
2import {
3 Action,
4 Config,
5 ExposedFunction,
6 Models,
7 NamedModel,
8 ObjectNotAFunction,
9 Plugin,
10 RematchBag,
11 RematchStore,
12 ModelDispatcher,
13 RematchDispatch,
14} from './types'
15import createReduxStore, {
16 createModelReducer,
17 createRootReducer,
18} from './reduxStore'
19import { createReducerDispatcher, createEffectDispatcher } from './dispatcher'
20import { validateModel } from './validate'
21import createRematchBag from './bag'
22
23export default function createRematchStore<
24 TModels extends Models<TModels>,
25 TExtraModels extends Models<TModels>
26>(config: Config<TModels, TExtraModels>): RematchStore<TModels, TExtraModels> {
27 // setup rematch 'bag' for storing useful values and functions
28 const bag = createRematchBag(config)
29
30 // add middleware for handling effects
31 bag.reduxConfig.middlewares.push(createEffectsMiddleware(bag))
32
33 // collect middlewares from plugins
34 bag.forEachPlugin('createMiddleware', (createMiddleware) => {
35 bag.reduxConfig.middlewares.push(createMiddleware(bag))
36 })
37
38 const reduxStore = createReduxStore(bag)
39
40 let rematchStore = {
41 ...reduxStore,
42 name: config.name,
43 addModel(model: NamedModel<TModels>) {
44 validateModel(model)
45 createModelReducer(bag, model)
46 prepareModel(rematchStore, model)
47 enhanceModel(rematchStore, bag, model)
48 reduxStore.replaceReducer(createRootReducer(bag))
49 reduxStore.dispatch({ type: '@@redux/REPLACE' })
50 },
51 } as RematchStore<TModels, TExtraModels>
52
53 addExposed(rematchStore, config.plugins)
54
55 /**
56 * generate dispatch[modelName][actionName] for all reducers and effects
57 *
58 * Note: To have circular models accessible in effects method with destructing,
59 * ensure that model generation and effects generation execute in
60 * different steps.
61 */
62 bag.models.forEach((model) => prepareModel(rematchStore, model))
63 bag.models.forEach((model) => enhanceModel(rematchStore, bag, model))
64
65 bag.forEachPlugin('onStoreCreated', (onStoreCreated) => {
66 rematchStore = onStoreCreated(rematchStore, bag) || rematchStore
67 })
68
69 return rematchStore
70}
71
72function createEffectsMiddleware<
73 TModels extends Models<TModels>,
74 TExtraModels extends Models<TModels>
75>(bag: RematchBag<TModels, TExtraModels>): Middleware {
76 return (store) =>
77 (next) =>
78 (action: Action): any => {
79 if (action.type in bag.effects) {
80 // first run reducer action if exists
81 next(action)
82
83 // then run the effect and return its result
84 return (bag.effects as any)[action.type](
85 action.payload,
86 store.getState(),
87 action.meta
88 )
89 }
90
91 return next(action)
92 }
93}
94
95function prepareModel<
96 TModels extends Models<TModels>,
97 TExtraModels extends Models<TModels>,
98 TModel extends NamedModel<TModels>
99>(rematchStore: RematchStore<TModels, TExtraModels>, model: TModel): void {
100 const modelDispatcher = {} as ModelDispatcher<TModel, TModels>
101
102 // inject model so effects creator can access it
103 rematchStore.dispatch[`${model.name}` as keyof RematchDispatch<TModels>] =
104 modelDispatcher
105
106 createReducerDispatcher(rematchStore, model)
107}
108
109function enhanceModel<
110 TModels extends Models<TModels>,
111 TExtraModels extends Models<TModels>,
112 TModel extends NamedModel<TModels>
113>(
114 rematchStore: RematchStore<TModels, TExtraModels>,
115 bag: RematchBag<TModels, TExtraModels>,
116 model: TModel
117): void {
118 createEffectDispatcher(rematchStore, bag, model)
119
120 bag.forEachPlugin('onModel', (onModel) => {
121 onModel(model, rematchStore)
122 })
123}
124
125/**
126 * Adds properties exposed by plugins into the Rematch instance. If a exposed
127 * property is a function, it passes rematch as the first argument.
128 *
129 * If you're implementing a plugin in TypeScript, extend Rematch namespace by
130 * adding the properties that you exposed from your plugin.
131 */
132function addExposed<
133 TModels extends Models<TModels>,
134 TExtraModels extends Models<TModels>
135>(
136 store: RematchStore<TModels, TExtraModels>,
137 plugins: Plugin<TModels, TExtraModels>[]
138): void {
139 plugins.forEach((plugin) => {
140 if (!plugin.exposed) return
141 const pluginKeys = Object.keys(plugin.exposed)
142 pluginKeys.forEach((key) => {
143 if (!plugin.exposed) return
144 const exposedItem = plugin.exposed[key] as
145 | ExposedFunction<TModels, TExtraModels>
146 | ObjectNotAFunction
147 const isExposedFunction = typeof exposedItem === 'function'
148
149 store[key] = isExposedFunction
150 ? (...params: any[]): any =>
151 (exposedItem as ExposedFunction<TModels, TExtraModels>)(
152 store,
153 ...params
154 )
155 : Object.create(plugin.exposed[key])
156 })
157 })
158}