seqda 1.0.1 → 1.1.1

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 +162 -59
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "seqda",
3
- "version": "1.0.1",
3
+ "version": "1.1.1",
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(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,59 +172,75 @@ function createStoreSubsection(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 = () => {
133
- store.emit('fetchScope', { store, scopeName: path });
134
- let currentState = Nife.get(store[INTERNAL_STATE], path);
207
+ function getState() {
208
+ if (options.emitOnFetch === true)
209
+ this.emit('fetchScope', { store: this, scopeName: path });
210
+
211
+ let currentState = Nife.get(this[INTERNAL_STATE], path);
135
212
  return currentState;
136
- };
213
+ }
137
214
 
138
- const set = (value) => {
139
- 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);
140
220
  if (value && typeof value === 'object' && value === currentState)
141
221
  throw new Error(`Error: "${getPath(path)}" the state value is the same, but it is required to be different.`);
142
222
 
143
223
  let previousState = currentState;
144
- store[INTERNAL_STATE] = setPath(store[INTERNAL_STATE], path, value);
224
+ this[INTERNAL_STATE] = setPath(this[INTERNAL_STATE], path, value);
145
225
 
146
226
  cache = {};
147
227
 
148
- if (store[QUEUE_CHANGE_EVENT])
149
- store[QUEUE_CHANGE_EVENT](path, value, previousState);
228
+ if (this[QUEUE_CHANGE_EVENT])
229
+ this[QUEUE_CHANGE_EVENT](path, value, previousState);
150
230
 
151
231
  return value;
152
- };
232
+ }
153
233
 
154
234
  if (path && !Object.prototype.hasOwnProperty.call(sectionTemplate, '_'))
155
235
  throw new Error(`Error: "${getPath}._" default value must be defined.`);
156
236
 
157
- const scope = (!path) ? store : {};
237
+ const scope = (!path) ? this : {};
158
238
  let keys = Object.keys(sectionTemplate || {});
159
239
  let subScopes = [];
160
240
  let cache = {};
161
241
 
162
242
  if (path)
163
- set(clone(sectionTemplate._));
243
+ setState.call(this, clone(sectionTemplate._));
164
244
 
165
245
  for (let i = 0, il = keys.length; i < il; i++) {
166
246
  let key = keys[i];
@@ -169,7 +249,7 @@ function createStoreSubsection(store, sectionTemplate, path) {
169
249
 
170
250
  let value = sectionTemplate[key];
171
251
  if (Nife.instanceOf(value, 'object')) {
172
- scope[key] = createStoreSubsection(store, value, getPath(path, key));
252
+ scope[key] = createStoreSubsection.call(this, options, value, getPath(path, key));
173
253
  subScopes.push(key);
174
254
  continue;
175
255
  }
@@ -181,59 +261,82 @@ function createStoreSubsection(store, sectionTemplate, path) {
181
261
  }
182
262
 
183
263
  if (!path)
184
- return scope; // We can't freeze the store
264
+ return this; // We can't freeze the store
185
265
  else
186
266
  return Object.freeze(scope);
187
267
  }
188
268
 
189
- function createStore(template) {
269
+ function initializeStore(store, readyOnly) {
270
+ Object.defineProperties(store, {
271
+ 'getState': {
272
+ writable: false,
273
+ enumberable: false,
274
+ configurable: false,
275
+ value: () => store[INTERNAL_STATE],
276
+ },
277
+ });
278
+
279
+ if (readyOnly !== true) {
280
+ Object.defineProperties(store, {
281
+ 'hydrate': {
282
+ writable: false,
283
+ enumberable: false,
284
+ configurable: false,
285
+ value: (value) => {
286
+ store[INTERNAL_STATE] = Object.freeze(clone(value));
287
+ queueChangeEvent.call(store, '*');
288
+ },
289
+ },
290
+ [QUEUE_CHANGE_EVENT]: {
291
+ writable: false,
292
+ enumberable: false,
293
+ configurable: false,
294
+ value: queueChangeEvent.bind(store),
295
+ },
296
+ [QUEUE_CHANGE_INFO]: {
297
+ writable: true,
298
+ enumberable: false,
299
+ configurable: false,
300
+ value: {
301
+ previousStore: cloneStore(store, true),
302
+ },
303
+ },
304
+ });
305
+ } else {
306
+ Object.defineProperties(store, {
307
+ [DISALLOW_WRITE]: {
308
+ writable: false,
309
+ enumberable: false,
310
+ configurable: false,
311
+ value: true,
312
+ },
313
+ });
314
+ }
315
+
316
+ return store;
317
+ }
318
+
319
+ function createStore(template, _options) {
190
320
  if (!Nife.instanceOf(template, 'object'))
191
321
  throw new TypeError('createStore: provided "template" must be an object.');
192
322
 
323
+ const options = _options || {};
193
324
  const store = new EventEmitter();
194
325
 
326
+ store.setMaxListeners(Infinity);
327
+
195
328
  Object.defineProperty(store, INTERNAL_STATE, {
196
329
  writable: true,
197
330
  enumerable: false,
198
- configurable: true,
331
+ configurable: false,
199
332
  value: {},
200
333
  });
201
334
 
202
- let constructedStore = createStoreSubsection(store, template);
203
-
204
- Object.defineProperties(constructedStore, {
205
- 'getState': {
206
- writable: false,
207
- enumberable: false,
208
- configurable: false,
209
- value: () => constructedStore[INTERNAL_STATE],
210
- },
211
- 'hydrate': {
212
- writable: false,
213
- enumberable: false,
214
- configurable: false,
215
- value: (value) => {
216
- constructedStore[INTERNAL_STATE] = Object.freeze(clone(value));
217
- queueChangeEvent.call(constructedStore, '*');
218
- },
219
- },
220
- [QUEUE_CHANGE_EVENT]: {
221
- writable: false,
222
- enumberable: false,
223
- configurable: false,
224
- value: queueChangeEvent.bind(constructedStore),
225
- },
226
- [QUEUE_CHANGE_INFO]: {
227
- writable: true,
228
- enumberable: false,
229
- configurable: false,
230
- value: {},
231
- },
232
- });
233
-
234
- return constructedStore;
335
+ let constructedStore = createStoreSubsection.call(store, options, template);
336
+ return initializeStore(constructedStore);
235
337
  }
236
338
 
237
339
  module.exports = {
340
+ cloneStore,
238
341
  createStore,
239
342
  };