1 | 'use strict';
|
2 |
|
3 | Object.defineProperty(exports, '__esModule', {
|
4 | value: true
|
5 | });
|
6 | exports['default'] = Store;
|
7 |
|
8 | function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { 'default': obj }; }
|
9 |
|
10 | var _nodeUuid = require('node-uuid');
|
11 |
|
12 | var _nodeUuid2 = _interopRequireDefault(_nodeUuid);
|
13 |
|
14 | var _stampit = require('stampit');
|
15 |
|
16 | var _stampit2 = _interopRequireDefault(_stampit);
|
17 |
|
18 | var _rx = require('rx');
|
19 |
|
20 | var _invariant = require('invariant');
|
21 |
|
22 | var _invariant2 = _interopRequireDefault(_invariant);
|
23 |
|
24 | var _debug = require('debug');
|
25 |
|
26 | var _debug2 = _interopRequireDefault(_debug);
|
27 |
|
28 | var _supermixer = require('supermixer');
|
29 |
|
30 | var _utils = require('./utils');
|
31 |
|
32 | var assign = Object.assign;
|
33 | var debug = (0, _debug2['default'])('thundercats:store');
|
34 | var __DEV__ = process.env.NODE_ENV !== 'production';
|
35 |
|
36 | function validateObservable(observable) {
|
37 |
|
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 |
|
44 | function addOperation(observable, validateItem, map) {
|
45 | return validateObservable(observable).tap(validateItem).map(map);
|
46 | }
|
47 |
|
48 | function 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 |
|
58 | var Optimism = {
|
59 | confirm: function confirm(uid, history) {
|
60 | checkId(uid, history);
|
61 | history.get(uid).confirmed = true;
|
62 | history.forEach(function (operation, uid) {
|
63 |
|
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 |
|
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 |
|
95 | exports.Optimism = Optimism;
|
96 | function 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 |
|
112 | function 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 |
|
120 | function _dispose(subscription) {
|
121 | if (subscription) {
|
122 | subscription.dispose();
|
123 | }
|
124 | return new Map();
|
125 | }
|
126 |
|
127 | function 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 |
|
131 | var 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 |
|
183 |
|
184 |
|
185 | return;
|
186 | }
|
187 |
|
188 |
|
189 |
|
190 |
|
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 |
|
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 |
|
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 |
|
280 | var 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 |
|
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 |
|
320 |
|
321 |
|
322 | function 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 |
|
355 | assign(Store, staticMethods); |
\ | No newline at end of file |