UNPKG

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