UNPKG

11.4 kBJavaScriptView Raw
1'use strict';
2
3Object.defineProperty(exports, '__esModule', {
4 value: true
5});
6exports['default'] = Store;
7
8function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { 'default': obj }; }
9
10var _nodeUuid = require('node-uuid');
11
12var _nodeUuid2 = _interopRequireDefault(_nodeUuid);
13
14var _stampit = require('stampit');
15
16var _stampit2 = _interopRequireDefault(_stampit);
17
18var _rx = require('rx');
19
20var _invariant = require('invariant');
21
22var _invariant2 = _interopRequireDefault(_invariant);
23
24var _debug = require('debug');
25
26var _debug2 = _interopRequireDefault(_debug);
27
28var _supermixer = require('supermixer');
29
30var _utils = require('./utils');
31
32var assign = Object.assign;
33var debug = (0, _debug2['default'])('thundercats:store');
34var __DEV__ = process.env.NODE_ENV !== 'production';
35
36function validateObservable(observable) {
37 /* istanbul ignore else */
38 if (__DEV__) {
39 (0, _invariant2['default'])((0, _utils.isObservable)(observable), 'register should get observables but was given %s', observable);
40 }
41 return observable;
42}
43
44function addOperation(observable, validateItem, map) {
45 return validateObservable(observable).tap(validateItem).map(map);
46}
47
48function registerObservable(obs, actionsArr, storeName) {
49 actionsArr = actionsArr.slice();
50 (0, _invariant2['default'])((0, _utils.isObservable)(obs), '%s should register observables but was given %s', storeName, obs);
51
52 debug('%s registering action', storeName);
53
54 actionsArr.push(obs);
55 return actionsArr;
56}
57
58var Optimism = {
59 confirm: function confirm(uid, history) {
60 checkId(uid, history);
61 history.get(uid).confirmed = true;
62 history.forEach(function (operation, uid) {
63 /* istanbul ignore else */
64 if (operation.confirmed) {
65 history['delete'](uid);
66 }
67 });
68 return history;
69 },
70 revert: function revert(uid, history) {
71 checkId(uid, history);
72 // initial value
73 var value = history.get(uid).oldValue;
74 var found = false;
75 history.forEach(function (descriptor, _uid) {
76 if (uid === _uid) {
77 found = true;
78 return;
79 }
80 if (!found) {
81 return;
82 }
83 descriptor.oldValue = value;
84 value = applyOperation(value, descriptor.operation);
85 });
86
87 history['delete'](uid);
88 return {
89 history: history,
90 value: value
91 };
92 }
93};
94
95exports.Optimism = Optimism;
96function applyOperation(oldValue, operation) {
97 var replace = operation.replace;
98 var transform = operation.transform;
99 var set = operation.set;
100
101 if (replace) {
102 return replace;
103 } else if (transform) {
104 return transform(oldValue);
105 } else if (set) {
106 return assign({}, oldValue, set);
107 } else {
108 return oldValue;
109 }
110}
111
112function notifyObservers(value, observers) {
113 debug('starting notify cycle');
114 observers.forEach(function (observer, uid) {
115 debug('notifying %s', uid);
116 observer.onNext(value);
117 });
118}
119
120function _dispose(subscription) {
121 if (subscription) {
122 subscription.dispose();
123 }
124 return new Map();
125}
126
127function checkId(id, history) {
128 (0, _invariant2['default'])(history.has(id), 'an unknown operation id was used that is not within its history.' + 'it may have been called outside of context');
129}
130
131var methods = {
132 register: function register(observable) {
133 this.actions = registerObservable(observable, this.actions, (0, _utils.getName)(this));
134 return this;
135 },
136
137 hasObservers: function hasObservers() {
138 return !!this.observers.size;
139 },
140
141 _init: function _init() {
142 debug('initiating %s', (0, _utils.getName)(this));
143 this.history = _dispose(this._operationsSubscription, this.history);
144
145 (0, _invariant2['default'])(this.actions.length, '%s must have at least one action to listen to but has %s', (0, _utils.getName)(this), this.actions.length);
146
147 var operations = [];
148 this.actions.forEach(function (observable) {
149 operations.push(observable);
150 });
151
152 (0, _invariant2['default'])((0, _utils.areObservable)(operations), '"%s" actions should be an array of observables', (0, _utils.getName)(this));
153
154 this._operationsSubscription = _rx.Observable.merge(operations).doOnNext(function (operation) {
155 (0, _invariant2['default'])(typeof operation !== 'undefined' && !!operation, 'operation should be an object but was given %s', operation);
156 }).filter(function (operation) {
157 return typeof operation.replace === 'object' ? !!operation.replace : true;
158 }).filter(function (operation) {
159 return typeof operation.set === 'object' ? !!operation.set : true;
160 }).doOnNext(function (operation) {
161 (0, _invariant2['default'])(typeof operation === 'object', 'invalid operation, operations should be an object, given : %s', operation);
162
163 (0, _invariant2['default'])(typeof operation.replace === 'object' || typeof operation.transform === 'function' || typeof operation.set === 'object', 'invalid operation, ' + 'operations should have a replace(an object), ' + 'transform(a function), or set(an object) property but got %s', Object.keys(operation));
164
165 if ('optimistic' in operation) {
166 (0, _invariant2['default'])((0, _utils.isPromise)(operation.optimistic) || (0, _utils.isObservable)(operation.optimistic), 'invalid operation, optimistic should be a ' + 'promise or observable,' + 'given : %s', operation.optimistic);
167 }
168 }).subscribe(this._opsOnNext.bind(this), this.opsOnError.bind(this), this.opsOnCompleted.bind(this));
169 },
170
171 _opsOnNext: function _opsOnNext(operation) {
172 var _this = this;
173
174 var ops = assign({}, operation);
175
176 debug('on next called');
177 var oldValue = this.value;
178 var newValue = applyOperation(this.value, ops);
179
180 if (!newValue) {
181 debug('%s operational noop', (0, _utils.getName)(this));
182 // do not change value
183 // do not update history
184 // do not collect 200 dollars
185 return;
186 }
187
188 // if shouldStoreNotify returns false
189 // do not change value or update history
190 // else continue as normal
191 if (this.shouldStoreNotify && typeof this.shouldStoreNotify === 'function' && !this.shouldStoreNotify(oldValue, newValue)) {
192 debug('%s will not notify', (0, _utils.getName)(this));
193 return;
194 }
195
196 this.value = newValue;
197 notifyObservers(this.value, this.observers);
198
199 var uid = _nodeUuid2['default'].v1();
200
201 this.history.set(uid, {
202 operation: ops,
203 oldValue: oldValue
204 });
205
206 if ('optimistic' in ops) {
207 var optimisticObs = (0, _utils.isPromise)(ops.optimistic) ? _rx.Observable.fromPromise(ops.optimistic) : ops.optimistic;
208
209 optimisticObs.first().subscribe(function () {}, function (err) {
210 debug('optimistic error. reverting changes', err);
211
212 var _Optimism$revert = Optimism.revert(uid, _this.history);
213
214 var value = _Optimism$revert.value;
215 var history = _Optimism$revert.history;
216
217 _this.history = history;
218 _this.value = value;
219 notifyObservers(value, _this.observers);
220 }, function () {
221 return _this.history = Optimism.confirm(uid, _this.history);
222 });
223 } else {
224 Optimism.confirm(uid, this.history);
225 }
226 },
227
228 opsOnError: function opsOnError(err) {
229 throw new Error('An error has occurred in the operations observer: ' + err);
230 },
231
232 opsOnCompleted: function opsOnCompleted() {
233 console.warn('operations observable has terminated without error');
234 },
235
236 _subscribe: function _subscribe(observer) {
237 var _this2 = this;
238
239 var uid = _nodeUuid2['default'].v1();
240
241 /* istanbul ignore else */
242 if (!this.hasObservers()) {
243 this._init();
244 }
245
246 debug('adding observer %s', uid);
247 this.observers.set(uid, observer);
248
249 observer.onNext(this.value);
250
251 return _rx.Disposable.create(function () {
252 debug('Disposing obserable %s', uid);
253 _this2.observers['delete'](uid);
254 /* istanbul ignore else */
255 if (!_this2.hasObservers()) {
256 debug('all observers cleared');
257 _this2.dispose();
258 }
259 });
260 },
261
262 dispose: function dispose() {
263 debug('disposing %s', (0, _utils.getName)(this));
264 this.observers = new Map();
265 this.history = _dispose(this._operationsSubscription, this.history);
266 },
267
268 serialize: function serialize() {
269 return this.value ? JSON.stringify(this.value) : '';
270 },
271
272 deserialize: function deserialize(stringyData) {
273 var data = JSON.parse(stringyData);
274 (0, _invariant2['default'])(data && typeof data === 'object', '%s deserialize must return an object but got: %s', (0, _utils.getName)(this), data);
275 this.value = data;
276 return this.value;
277 }
278};
279
280var staticMethods = {
281 createRegistrar: function createRegistrar(store) {
282 function register(observable) {
283 store.actions = registerObservable(observable, store.actions, (0, _utils.getName)(store));
284 return store;
285 }
286 return register;
287 },
288
289 fromMany: function fromMany() {
290 return _rx.Observable.from(arguments).tap(validateObservable).toArray().flatMap(function (observables) {
291 return _rx.Observable.merge(observables);
292 });
293 },
294
295 replacer: function replacer(observable) {
296 return addOperation(observable, (0, _utils.createObjectValidator)('setter should receive objects but was given %s'), function (replace) {
297 return { replace: replace };
298 });
299 },
300
301 setter: function setter(observable) {
302 return addOperation(observable, (0, _utils.createObjectValidator)('setter should receive objects but was given %s'), function (set) {
303 return { set: set };
304 });
305 },
306
307 transformer: function transformer(observable) {
308 return addOperation(observable, function (fun) {
309 /* istanbul ignore else */
310 if (__DEV__) {
311 (0, _invariant2['default'])(typeof fun === 'function', 'transform should receive functions but was given %s', fun);
312 }
313 }, function (transform) {
314 return { transform: transform };
315 });
316 }
317};
318
319// Store is a stamp factory
320// It returns a factory that creates store instances
321
322function Store() {
323 var stampSpec = arguments.length <= 0 || arguments[0] === undefined ? {} : arguments[0];
324 var _stampSpec$init = stampSpec.init;
325 var init = _stampSpec$init === undefined ? [] : _stampSpec$init;
326 var _stampSpec$refs = stampSpec.refs;
327 var refs = _stampSpec$refs === undefined ? {} : _stampSpec$refs;
328 var _stampSpec$props = stampSpec.props;
329 var props = _stampSpec$props === undefined ? {} : _stampSpec$props;
330 var _stampSpec$statics = stampSpec.statics;
331 var statics = _stampSpec$statics === undefined ? {} : _stampSpec$statics;
332 var _refs$value = refs.value;
333 var value = _refs$value === undefined ? {} : _refs$value;
334
335 var stamp = (0, _stampit2['default'])();
336 stamp.fixed.refs = stamp.fixed.state = (0, _supermixer.mergeChainNonFunctions)(stamp.fixed.refs, _rx.Observable.prototype);
337 assign(stamp, assign(stamp.fixed['static'], _rx.Observable));
338
339 (0, _supermixer.mixinChainFunctions)(stamp.fixed.methods, _rx.Observable.prototype);
340 return stamp.refs({
341 value: value,
342 _operationsSubscription: null
343 })['static'](staticMethods).methods(methods).init(function (_ref) {
344 var instance = _ref.instance;
345
346 instance.observers = new Map();
347 instance.history = new Map();
348 instance.actions = [];
349 _rx.Observable.call(instance);
350 return instance;
351 }).props(props).refs(refs)['static'](statics).init(init);
352}
353
354// Make static methods also available on stamp factory
355assign(Store, staticMethods);
\No newline at end of file