UNPKG

13.3 kBJavaScriptView Raw
1"use strict";
2Object.defineProperty(exports, "__esModule", { value: true });
3var rxjs_1 = require("rxjs");
4var operators_1 = require("rxjs/operators");
5var shallowEqual_1 = require("./shallowEqual");
6var isPlainObject = require("lodash.isplainobject");
7var isObject = require("lodash.isobject");
8/**
9 * Creates a state based on a stream of StateMutation functions and an initial state. The returned observable
10 * is hot and caches the last emitted value (will emit the last emitted value immediately upon subscription).
11 * @param stateMutators
12 * @param initialState
13 */
14function createState(stateMutators, initialState) {
15 var state = stateMutators.pipe(operators_1.scan(function (state, reducer) { return reducer(state); }, initialState),
16 // these two lines make our observable hot and have it emit the last state
17 // upon subscription
18 operators_1.publishReplay(1), operators_1.refCount());
19 return state;
20}
21exports.createState = createState;
22var Store = /** @class */ (function () {
23 function Store(state, stateMutators, forwardProjections, backwardProjections, onDestroy, notifyRootStateChangedSubject) {
24 /**
25 * Is completed when the slice is unsubscribed and no longer needed.
26 */
27 this._destroyed = new rxjs_1.Subject();
28 /**
29 * Used for manual dispatches without observables
30 */
31 this.actionDispatch = new rxjs_1.Subject();
32 this.state = state;
33 this.stateMutators = stateMutators;
34 this.forwardProjections = forwardProjections;
35 this.backwardProjections = backwardProjections;
36 this._destroyed.subscribe(undefined, undefined, onDestroy);
37 this.destroyed = this._destroyed.asObservable();
38 this.stateChangeNotificationSubject = notifyRootStateChangedSubject;
39 this.stateChangedNotification = this.stateChangeNotificationSubject.asObservable().pipe(operators_1.takeUntil(this.destroyed));
40 }
41 /**
42 * Create a new Store based on an initial state
43 */
44 Store.create = function (initialState) {
45 if (initialState === undefined)
46 initialState = {};
47 else {
48 if (isObject(initialState) && !Array.isArray(initialState) && !isPlainObject(initialState))
49 throw new Error("initialState must be a plain object, an array, or a primitive type");
50 }
51 var stateMutators = new rxjs_1.Subject();
52 var state = createState(stateMutators, initialState);
53 // to make publishReplay become effective, we need a subscription that lasts
54 var stateSubscription = state.subscribe();
55 var onDestroy = function () { stateSubscription.unsubscribe(); };
56 var store = new Store(state, stateMutators, [], [], onDestroy, new rxjs_1.Subject());
57 // emit a single state mutation so that we emit the initial state on subscription
58 stateMutators.next(function (s) { return s; });
59 return store;
60 };
61 /**
62 * Creates a new linked store, that Selects a slice on the main store.
63 * @deprecated
64 */
65 Store.prototype.createSlice = function (key, initialState, cleanupState) {
66 if (isObject(initialState) && !Array.isArray(initialState) && !isPlainObject(initialState))
67 throw new Error("initialState must be a plain object, an array, or a primitive type");
68 if (isObject(cleanupState) && !Array.isArray(cleanupState) && !isPlainObject(cleanupState))
69 throw new Error("cleanupState must be a plain object, an array, or a primitive type");
70 var forward = function (state) { return state[key]; };
71 var backward = function (state, parentState) {
72 parentState[key] = state;
73 return parentState;
74 };
75 var initial = initialState === undefined ? undefined : function () { return initialState; };
76 // legacy cleanup for slices
77 var cleanup = cleanupState === undefined ? undefined : function (state, parentState) {
78 if (cleanupState === "undefined") {
79 parentState[key] = undefined;
80 }
81 else if (cleanupState === "delete")
82 delete parentState[key];
83 else {
84 parentState[key] = cleanupState;
85 }
86 return parentState;
87 };
88 return this.createProjection(forward, backward, initial, cleanup);
89 };
90 /**
91 * Create a clone of the store which holds the same state. This is an alias to createProjection with
92 * the identity functions as forward/backwards projection. Usefull to unsubscribe from select()/watch()
93 * subscriptions as the destroy() event is specific to the new cloned instance (=will not destroy the original)
94 * Also usefull to scope string-based action dispatches to .dispatch() as action/reducers pairs added to the
95 * clone can not be dispatched by the original and vice versa.
96 */
97 Store.prototype.clone = function () {
98 return this.createProjection(function (s) { return s; }, function (s, p) { return s; });
99 };
100 /**
101 * Creates a new slice of the store. The slice holds a transformed state that is created by applying the
102 * forwardProjection function. To transform the slice state back to the parent state, a backward projection
103 * function must be given.
104 * @param forwardProjection - Projection function that transforms a State S to a new projected state TProjectedState
105 * @param backwardProjection - Back-Projection to obtain state S from already projected state TProjectedState
106 * @param initial - Function to be called initially with state S that must return an initial state to use for TProjected
107 * @param cleanup - Function to be called when the store is destroyed to return a cleanup state based on parent state S
108 */
109 Store.prototype.createProjection = function (forwardProjection, backwardProjection, initial, cleanup) {
110 var _this = this;
111 var state = this.state.pipe(operators_1.map(function (state) { return forwardProjection(state); }));
112 var forwardProjections = this.forwardProjections.concat([forwardProjection]);
113 var backwardProjections = [backwardProjection].concat(this.backwardProjections);
114 if (initial !== undefined) {
115 this.stateMutators.next(function (s) {
116 var initialReducer = function () { return initial(s); };
117 return mutateRootState(s, forwardProjections, backwardProjections, initialReducer);
118 });
119 }
120 var onDestroy = function () {
121 if (cleanup !== undefined) {
122 _this.stateMutators.next(function (s) {
123 var backward = [cleanup].concat(_this.backwardProjections);
124 return mutateRootState(s, forwardProjections, backward, function (s) { return s; });
125 });
126 }
127 };
128 var sliceStore = new Store(state, this.stateMutators, forwardProjections, backwardProjections, onDestroy, this.stateChangeNotificationSubject);
129 // destroy the slice if the parent gets destroyed
130 this._destroyed.subscribe(undefined, undefined, function () {
131 sliceStore.destroy();
132 });
133 return sliceStore;
134 };
135 /**
136 * Adds an Action/Reducer pair. This will make the reducer become active whenever the action observable emits a
137 * value.
138 * @param action An observable whose payload will be passed to the reducer on each emit
139 * @param reducer function
140 * @param actionName An optional name (only used during development/debugging) to assign to the action
141 */
142 Store.prototype.addReducer = function (action, reducer, actionName) {
143 var _this = this;
144 if (typeof action === "string" && typeof actionName !== "undefined")
145 throw new Error("cannot specify a string-action AND a string alias at the same time");
146 if (!rxjs_1.isObservable(action) && typeof action !== "string")
147 throw new Error("first argument must be an observable or a string");
148 if (typeof reducer !== "function")
149 throw new Error("reducer argument must be a function");
150 if ((typeof actionName === "string" && actionName.length === 0) ||
151 (typeof action === "string" && action.length === 0))
152 throw new Error("action/actionName must have non-zero length");
153 var name = typeof action === "string" ? action : actionName;
154 var actionFromStringBasedDispatch = this.actionDispatch.pipe(operators_1.filter(function (s) { return s.actionName === name; }), operators_1.map(function (s) { return s.actionPayload; }), operators_1.merge(rxjs_1.isObservable(action) ? action : rxjs_1.EMPTY), operators_1.takeUntil(this.destroyed));
155 var rootReducer = function (payload) { return function (rootState) {
156 // transform the rootstate to a slice by applying all forward projections
157 var sliceReducer = function (slice) { return reducer(slice, payload); };
158 rootState = mutateRootState(rootState, _this.forwardProjections, _this.backwardProjections, sliceReducer);
159 // Send state change notification
160 var changeNotification = {
161 actionName: name,
162 actionPayload: payload,
163 newState: rootState
164 };
165 _this.stateChangeNotificationSubject.next(changeNotification);
166 return rootState;
167 }; };
168 return actionFromStringBasedDispatch.pipe(operators_1.map(function (payload) { return rootReducer(payload); })).subscribe(function (rootStateMutation) {
169 _this.stateMutators.next(rootStateMutation);
170 });
171 };
172 /**
173 * Selects a part of the state using a selector function. If no selector function is given, the identity function
174 * is used (which returns the state of type S).
175 * Note: The returned observable does only update when the result of the selector function changed
176 * compared to a previous emit. A shallow copy test is performed to detect changes.
177 * This requires that your reducers update all nested properties in
178 * an immutable way, which is required practice with Redux and also with reactive-state.
179 * To make the observable emit any time the state changes, use .select() instead
180 * For correct nested reducer updates, see:
181 * http://redux.js.org/docs/recipes/reducers/ImmutableUpdatePatterns.html#updating-nested-objects
182 *
183 * @param selectorFn A selector function which returns a mapped/transformed object based on the state
184 * @returns An observable that emits the result of the selector function after a
185 * change of the return value of the selector function
186 */
187 Store.prototype.watch = function (selectorFn) {
188 return this.select(selectorFn).pipe(operators_1.distinctUntilChanged(function (a, b) { return shallowEqual_1.shallowEqual(a, b); }));
189 };
190 /**
191 * Same as .watch() except that EVERY state change is emitted. Use with care, you might want to pipe the output
192 * to your own implementation of .distinctUntilChanged() or use only for debugging purposes.
193 */
194 Store.prototype.select = function (selectorFn) {
195 if (!selectorFn)
196 selectorFn = function (state) { return state; };
197 var mapped = this.state.pipe(operators_1.takeUntil(this._destroyed), operators_1.map(selectorFn));
198 return mapped;
199 };
200 /**
201 * Destroys the Store/Slice. All Observables obtained via .select() will complete when called.
202 */
203 Store.prototype.destroy = function () {
204 this._destroyed.next();
205 this._destroyed.complete();
206 };
207 /**
208 * Manually dispatch an action by its actionName and actionPayload.
209 *
210 * This function exists for compatibility reasons, development and devtools. It is not adviced to use
211 * this function extensively.
212 *
213 * Note: While the observable-based actions
214 * dispatches only reducers registered for that slice, the string based action dispatch here will forward the
215 * action to ALL stores, (sub-)slice and parent alike so make sure you separate your actions based on the strings.
216 */
217 Store.prototype.dispatch = function (actionName, actionPayload) {
218 this.actionDispatch.next({ actionName: actionName, actionPayload: actionPayload });
219 };
220 return Store;
221}());
222exports.Store = Store;
223function mutateRootState(rootState, forwardProjections, backwardProjections, sliceReducer) {
224 // transform the rootstate to a slice by applying all forward projections
225 var forwardState = rootState;
226 var intermediaryState = [rootState];
227 forwardProjections.map(function (fp) {
228 forwardState = fp.call(undefined, forwardState);
229 intermediaryState.push(forwardState);
230 });
231 // perform the reduction
232 var reducedState = sliceReducer(forwardState);
233 // apply all backward projections to obtain the root state again
234 var backwardState = reducedState;
235 backwardProjections.slice().map(function (bp, index) {
236 var intermediaryIndex = intermediaryState.length - index - 2;
237 backwardState = bp.call(undefined, backwardState, intermediaryState[intermediaryIndex]);
238 });
239 return backwardState;
240}
241//# sourceMappingURL=store.js.map
\No newline at end of file