seqda 1.0.2 → 1.1.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (2) hide show
  1. package/package.json +1 -1
  2. package/src/index.js +159 -58
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "seqda",
3
- "version": "1.0.2",
3
+ "version": "1.1.2",
4
4
  "description": "Sequential Data Store",
5
5
  "main": "src/index.js",
6
6
  "type": "commonjs",
package/src/index.js CHANGED
@@ -6,15 +6,65 @@ const EventEmitter = require('events');
6
6
  const QUEUE_CHANGE_EVENT = Symbol.for('__seqdaQueueChangeEvent');
7
7
  const QUEUE_CHANGE_INFO = Symbol.for('__seqdaQueueChangeInfo');
8
8
  const INTERNAL_STATE = Symbol.for('__seqdaInternalState');
9
+ const UNBOUND_METHOD = Symbol.for('__seqdaUnboundMethod');
10
+ const DISALLOW_WRITE = Symbol.for('__seqdaDisallowWrite');
11
+
12
+ function cloneStore(store, readyOnly) {
13
+ const cloneScope = (scope, _newStore) => {
14
+ let keys = Object.keys(scope);
15
+ let newScope = (!_newStore) ? new EventEmitter() : {};
16
+ let newStore = _newStore || newScope;
17
+
18
+ if (!_newStore)
19
+ newStore.setMaxListeners(Infinity);
20
+
21
+ for (let i = 0, il = keys.length; i < il; i++) {
22
+ let key = keys[i];
23
+ if (key === '_events' || key === '_eventsCount' || key === '_maxListeners')
24
+ continue;
25
+
26
+ let value = scope[key];
27
+ if (typeof value === 'function')
28
+ newScope[key] = storeUnboundMethod(value[UNBOUND_METHOD].bind(newStore), value[UNBOUND_METHOD]);
29
+ else
30
+ newScope[key] = cloneScope(value, newStore);
31
+ }
32
+
33
+ return newScope;
34
+ };
35
+
36
+ let clonedStore = cloneScope(store);
37
+ let clonedInternalState = Object.assign({}, store[INTERNAL_STATE]);
38
+
39
+ Object.defineProperties(clonedStore, {
40
+ [INTERNAL_STATE]: {
41
+ writable: (readyOnly !== true) ? true : false,
42
+ enumberable: false,
43
+ configurable: false,
44
+ value: (readyOnly !== true) ? clonedInternalState : Object.freeze(clonedInternalState),
45
+ },
46
+ });
47
+
48
+ return initializeStore(clonedStore, readyOnly);
49
+ }
9
50
 
10
51
  function queueChangeEvent(path) {
11
52
  let info = this[QUEUE_CHANGE_INFO];
12
53
  if (!info.promise) {
13
54
  info.promise = Promise.resolve();
14
55
  info.promise.then(() => {
15
- this.emit('update', { store: this, modified: Array.from(Object.keys(info.eventQueue)) });
56
+ let modified = Array.from(Object.keys(info.eventQueue));
57
+ let previousStore = info.previousStore;
58
+
16
59
  info.eventQueue = {};
17
60
  info.promise = null;
61
+ info.previousStore = cloneStore(this, true);
62
+
63
+ this.emit('update', {
64
+ store: this,
65
+ previousStore,
66
+ modified,
67
+ });
18
68
  });
19
69
  }
20
70
 
@@ -92,8 +142,22 @@ function getPath(...parts) {
92
142
  return parts.filter(Boolean).join('.');
93
143
  }
94
144
 
95
- function createStoreSubsection(options, store, sectionTemplate, path) {
96
- const isCacheInvalid = (scopeName, args) => {
145
+ function storeUnboundMethod(boundMethod, method) {
146
+ Object.defineProperty(boundMethod, UNBOUND_METHOD, {
147
+ writable: false,
148
+ enumerable: false,
149
+ configurable: false,
150
+ value: method,
151
+ });
152
+
153
+ return boundMethod;
154
+ }
155
+
156
+ function createStoreSubsection(options, sectionTemplate, path) {
157
+ function isCacheInvalid(scopeName, args) {
158
+ if (this[DISALLOW_WRITE])
159
+ return true;
160
+
97
161
  let thisCache = cache[scopeName];
98
162
  if (!thisCache)
99
163
  return true;
@@ -108,61 +172,78 @@ function createStoreSubsection(options, store, sectionTemplate, path) {
108
172
  }
109
173
 
110
174
  return false;
111
- };
175
+ }
176
+
177
+ function setCache(scopeName, args, result) {
178
+ if (this[DISALLOW_WRITE])
179
+ return;
112
180
 
113
- const setCache = (scopeName, args, result) => {
114
181
  cache[scopeName] = {
115
182
  args,
116
183
  result,
117
184
  };
118
- };
185
+ }
119
186
 
120
187
  const createScopeMethod = (scopeName, func) => {
121
- return (...args) => {
122
- if (isCacheInvalid(scopeName, args)) {
123
- let result = func({ get, set, store }, ...args);
124
- setCache(scopeName, args, result);
188
+ let method = function(...args) {
189
+ if (isCacheInvalid.call(this, scopeName, args)) {
190
+ let result = func({
191
+ get: getState.bind(this),
192
+ set: setState.bind(this),
193
+ store: this,
194
+ }, ...args);
195
+
196
+ setCache.call(this, scopeName, args, result);
197
+
125
198
  return result;
126
199
  } else {
127
200
  return cache[scopeName].result;
128
201
  }
129
202
  };
203
+
204
+ return storeUnboundMethod(method.bind(this), method);
130
205
  };
131
206
 
132
- const get = () => {
207
+ function getState() {
133
208
  if (options.emitOnFetch === true)
134
- store.emit('fetchScope', { store, scopeName: path });
209
+ this.emit('fetchScope', { store: this, scopeName: path });
135
210
 
136
- let currentState = Nife.get(store[INTERNAL_STATE], path);
211
+ let currentState = Nife.get(this[INTERNAL_STATE], path);
137
212
  return currentState;
138
- };
213
+ }
139
214
 
140
- const set = (value) => {
141
- let currentState = Nife.get(store[INTERNAL_STATE], path);
215
+ function setState(value) {
216
+ if (this[DISALLOW_WRITE])
217
+ return;
218
+
219
+ let currentState = Nife.get(this[INTERNAL_STATE], path);
142
220
  if (value && typeof value === 'object' && value === currentState)
143
221
  throw new Error(`Error: "${getPath(path)}" the state value is the same, but it is required to be different.`);
144
222
 
223
+ if (!Nife.propsDiffer(value, currentState))
224
+ return;
225
+
145
226
  let previousState = currentState;
146
- store[INTERNAL_STATE] = setPath(store[INTERNAL_STATE], path, value);
227
+ this[INTERNAL_STATE] = setPath(this[INTERNAL_STATE], path, value);
147
228
 
148
229
  cache = {};
149
230
 
150
- if (store[QUEUE_CHANGE_EVENT])
151
- store[QUEUE_CHANGE_EVENT](path, value, previousState);
231
+ if (this[QUEUE_CHANGE_EVENT])
232
+ this[QUEUE_CHANGE_EVENT](path, value, previousState);
152
233
 
153
234
  return value;
154
- };
235
+ }
155
236
 
156
237
  if (path && !Object.prototype.hasOwnProperty.call(sectionTemplate, '_'))
157
238
  throw new Error(`Error: "${getPath}._" default value must be defined.`);
158
239
 
159
- const scope = (!path) ? store : {};
240
+ const scope = (!path) ? this : {};
160
241
  let keys = Object.keys(sectionTemplate || {});
161
242
  let subScopes = [];
162
243
  let cache = {};
163
244
 
164
245
  if (path)
165
- set(clone(sectionTemplate._));
246
+ setState.call(this, clone(sectionTemplate._));
166
247
 
167
248
  for (let i = 0, il = keys.length; i < il; i++) {
168
249
  let key = keys[i];
@@ -171,7 +252,7 @@ function createStoreSubsection(options, store, sectionTemplate, path) {
171
252
 
172
253
  let value = sectionTemplate[key];
173
254
  if (Nife.instanceOf(value, 'object')) {
174
- scope[key] = createStoreSubsection(options, store, value, getPath(path, key));
255
+ scope[key] = createStoreSubsection.call(this, options, value, getPath(path, key));
175
256
  subScopes.push(key);
176
257
  continue;
177
258
  }
@@ -183,11 +264,61 @@ function createStoreSubsection(options, store, sectionTemplate, path) {
183
264
  }
184
265
 
185
266
  if (!path)
186
- return scope; // We can't freeze the store
267
+ return this; // We can't freeze the store
187
268
  else
188
269
  return Object.freeze(scope);
189
270
  }
190
271
 
272
+ function initializeStore(store, readyOnly) {
273
+ Object.defineProperties(store, {
274
+ 'getState': {
275
+ writable: false,
276
+ enumberable: false,
277
+ configurable: false,
278
+ value: () => store[INTERNAL_STATE],
279
+ },
280
+ });
281
+
282
+ if (readyOnly !== true) {
283
+ Object.defineProperties(store, {
284
+ 'hydrate': {
285
+ writable: false,
286
+ enumberable: false,
287
+ configurable: false,
288
+ value: (value) => {
289
+ store[INTERNAL_STATE] = Object.freeze(clone(value));
290
+ queueChangeEvent.call(store, '*');
291
+ },
292
+ },
293
+ [QUEUE_CHANGE_EVENT]: {
294
+ writable: false,
295
+ enumberable: false,
296
+ configurable: false,
297
+ value: queueChangeEvent.bind(store),
298
+ },
299
+ [QUEUE_CHANGE_INFO]: {
300
+ writable: true,
301
+ enumberable: false,
302
+ configurable: false,
303
+ value: {
304
+ previousStore: cloneStore(store, true),
305
+ },
306
+ },
307
+ });
308
+ } else {
309
+ Object.defineProperties(store, {
310
+ [DISALLOW_WRITE]: {
311
+ writable: false,
312
+ enumberable: false,
313
+ configurable: false,
314
+ value: true,
315
+ },
316
+ });
317
+ }
318
+
319
+ return store;
320
+ }
321
+
191
322
  function createStore(template, _options) {
192
323
  if (!Nife.instanceOf(template, 'object'))
193
324
  throw new TypeError('createStore: provided "template" must be an object.');
@@ -200,45 +331,15 @@ function createStore(template, _options) {
200
331
  Object.defineProperty(store, INTERNAL_STATE, {
201
332
  writable: true,
202
333
  enumerable: false,
203
- configurable: true,
334
+ configurable: false,
204
335
  value: {},
205
336
  });
206
337
 
207
- let constructedStore = createStoreSubsection(options, store, template);
208
-
209
- Object.defineProperties(constructedStore, {
210
- 'getState': {
211
- writable: false,
212
- enumberable: false,
213
- configurable: false,
214
- value: () => constructedStore[INTERNAL_STATE],
215
- },
216
- 'hydrate': {
217
- writable: false,
218
- enumberable: false,
219
- configurable: false,
220
- value: (value) => {
221
- constructedStore[INTERNAL_STATE] = Object.freeze(clone(value));
222
- queueChangeEvent.call(constructedStore, '*');
223
- },
224
- },
225
- [QUEUE_CHANGE_EVENT]: {
226
- writable: false,
227
- enumberable: false,
228
- configurable: false,
229
- value: queueChangeEvent.bind(constructedStore),
230
- },
231
- [QUEUE_CHANGE_INFO]: {
232
- writable: true,
233
- enumberable: false,
234
- configurable: false,
235
- value: {},
236
- },
237
- });
238
-
239
- return constructedStore;
338
+ let constructedStore = createStoreSubsection.call(store, options, template);
339
+ return initializeStore(constructedStore);
240
340
  }
241
341
 
242
342
  module.exports = {
343
+ cloneStore,
243
344
  createStore,
244
345
  };