@onehat/data 1.23.1 → 1.23.3

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.
@@ -29,16 +29,6 @@ async function beforeEach(that) {
29
29
  that.repository = that.oneHatData.getRepositoryById('foo');
30
30
  }
31
31
 
32
- async function waitForRepositoryInitialization(repository, timeout = 2000) {
33
- const start = Date.now();
34
- while (!repository.isInitialized) {
35
- if (Date.now() - start > timeout) {
36
- throw new Error('Repository did not initialize in time: ' + repository.name);
37
- }
38
- await new Promise((resolve) => setTimeout(resolve, 5));
39
- }
40
- }
41
-
42
32
  function afterEach(that) {
43
33
  that.oneHatData.destroy();
44
34
  }
@@ -278,36 +268,34 @@ describe('OneHatData', function() {
278
268
 
279
269
  const
280
270
  repo1 = that.oneHatData.getRepository('bar'),
281
- repo2 = that.oneHatData.getRepository('bar', true);
271
+ repo2 = await that.oneHatData.getUniqueRepository('bar');
282
272
  expect(repo1 !== repo2).to.be.true;
273
+ expect(repo2.isInitialized).to.be.true;
283
274
 
284
275
  afterEach(that);
285
276
  })();
286
277
  });
287
278
 
288
- it('getRepositoryAsync returns initialized unique repository', function() {
279
+ it('getRepository(name, true) throws migration error', function() {
289
280
  (async () => {
290
281
  const that = {};
291
282
  await beforeEach(that);
292
283
 
293
- const repository = await that.oneHatData.getRepositoryAsync('bar', true);
294
-
295
- expect(repository).to.be.ok;
296
- expect(repository.isUnique).to.be.true;
297
- expect(repository.isInitialized).to.be.true;
284
+ expect(() => that.oneHatData.getRepository('bar', true)).to.throw('Use await this.getUniqueRepository(name) instead.');
298
285
 
299
286
  afterEach(that);
300
287
  })();
301
288
  });
302
289
 
303
- it('getRepositoryAsync returns initialized bound repository', function() {
290
+ it('getUniqueRepository returns initialized unique repository', function() {
304
291
  (async () => {
305
292
  const that = {};
306
293
  await beforeEach(that);
307
294
 
308
- const repository = await that.oneHatData.getRepositoryAsync('bar');
295
+ const repository = await that.oneHatData.getUniqueRepository('bar');
309
296
 
310
- expect(repository).to.be.eq(that.repository);
297
+ expect(repository).to.be.ok;
298
+ expect(repository.isUnique).to.be.true;
311
299
  expect(repository.isInitialized).to.be.true;
312
300
 
313
301
  afterEach(that);
@@ -321,9 +309,7 @@ describe('OneHatData', function() {
321
309
 
322
310
  const
323
311
  boundRepository = that.oneHatData.getRepository('bar'),
324
- uniqueRepository = that.oneHatData.getRepository('bar', true);
325
-
326
- await waitForRepositoryInitialization(uniqueRepository);
312
+ uniqueRepository = await that.oneHatData.getUniqueRepository('bar');
327
313
 
328
314
  boundRepository.filter('key', 'bound-only');
329
315
  expect(boundRepository.hasFilterValue('key', 'bound-only')).to.be.true;
@@ -338,7 +324,7 @@ describe('OneHatData', function() {
338
324
  })();
339
325
  });
340
326
 
341
- it('getRepository unique allows pre-init setBaseParams for Ajax repositories', function() {
327
+ it('getUniqueRepository allows setBaseParams for Ajax repositories', function() {
342
328
  (async () => {
343
329
  const that = {};
344
330
  await beforeEach(that);
@@ -362,7 +348,7 @@ describe('OneHatData', function() {
362
348
  });
363
349
  await that.oneHatData.createRepository('meters', true);
364
350
 
365
- const uniqueRepository = that.oneHatData.getRepository('meters', true);
351
+ const uniqueRepository = await that.oneHatData.getUniqueRepository('meters');
366
352
 
367
353
  expect(() => {
368
354
  uniqueRepository.setBaseParams({
@@ -376,6 +362,41 @@ describe('OneHatData', function() {
376
362
  })();
377
363
  });
378
364
 
365
+ it('getOrCreateUniqueRepository reuses existing mapped repository', function() {
366
+ (async () => {
367
+ const that = {};
368
+ await beforeEach(that);
369
+
370
+ const repository1 = await that.oneHatData.getOrCreateUniqueRepository('partsMap', 'bar');
371
+ const repository2 = await that.oneHatData.getOrCreateUniqueRepository('partsMap', 'bar');
372
+
373
+ expect(repository1).to.be.eq(repository2);
374
+ expect(repository1.isUnique).to.be.true;
375
+
376
+ afterEach(that);
377
+ })();
378
+ });
379
+
380
+ it('getOrCreateUniqueRepository recreates repository when mapped id is stale', function() {
381
+ (async () => {
382
+ const that = {};
383
+ await beforeEach(that);
384
+
385
+ const repository1 = await that.oneHatData.getOrCreateUniqueRepository('partsMap', 'bar');
386
+ const originalId = repository1.id;
387
+
388
+ that.oneHatData.deleteRepository(originalId);
389
+
390
+ const repository2 = await that.oneHatData.getOrCreateUniqueRepository('partsMap', 'bar');
391
+
392
+ expect(repository2).to.be.ok;
393
+ expect(repository2.id).to.not.eq(originalId);
394
+ expect(that.oneHatData.uniqueRepositoryIdsMap.partsMap).to.be.eq(repository2.id);
395
+
396
+ afterEach(that);
397
+ })();
398
+ });
399
+
379
400
  it('getRepositoriesBy', function() {
380
401
  (async function() {
381
402
  await beforeEach();
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@onehat/data",
3
- "version": "1.23.1",
3
+ "version": "1.23.3",
4
4
  "description": "JS data modeling package with adapters for many storage mediums.",
5
5
  "main": "src/index.js",
6
6
  "type": "module",
@@ -35,7 +35,7 @@ export default function useOneHatData(schemaName, uniqueRepository = false) {
35
35
 
36
36
  if (uniqueRepository) {
37
37
  if (_.isString(schemaName)) {
38
- repository = await oneHatData.getRepositoryAsync(schemaName, true);
38
+ repository = await oneHatData.getUniqueRepository(schemaName);
39
39
  } else {
40
40
  repository = await oneHatData.createRepository(schemaName);
41
41
  repository.isUnique = true;
@@ -49,7 +49,7 @@ export default function useOneHatData(schemaName, uniqueRepository = false) {
49
49
  repository = await oneHatData.createRepository(schemaName)
50
50
  }
51
51
  } else {
52
- repository = await oneHatData.getRepositoryAsync(schemaName); // Get initialized bound Repository for this schema
52
+ repository = oneHatData.getRepository(schemaName); // Get bound Repository for this schema
53
53
  }
54
54
 
55
55
  if (!isMounted || !repository) {
package/src/OneHatData.js CHANGED
@@ -479,247 +479,70 @@ export class OneHatData extends EventEmitter {
479
479
  if (this.isDestroyed) {
480
480
  throw new Error('this.getRepository is no longer valid. OneHatData has been destroyed.');
481
481
  }
482
+ if (unique) {
483
+ throw new Error('this.getRepository(name, true) is no longer supported. Use await this.getUniqueRepository(name) instead.');
484
+ }
482
485
  const schema = this.getSchema(name);
483
486
  if (!schema) {
484
487
  return null;
485
488
  }
486
- if (unique) {
487
- return this._createUniqueRepositorySync(name);
488
- }
489
489
  return schema.getBoundRepository();
490
490
  }
491
491
 
492
492
  /**
493
- * Async variant of getRepository.
494
- * Useful when unique repositories are needed in frameworks that expect
495
- * fully initialized objects before first use.
493
+ * Gets a fully initialized unique Repository for the supplied schema.
496
494
  * @param {string} name - Name of Schema
497
- * @param {boolean} unique - Whether to create a unique repository
498
- * @param {number} timeout - Max ms to wait for initialization
499
495
  * @return {Promise<Repository>} repository
500
496
  */
501
- getRepositoryAsync = async (name, unique = false, timeout = 10000) => {
502
- if (this.isDestroyed) {
503
- throw new Error('this.getRepositoryAsync is no longer valid. OneHatData has been destroyed.');
504
- }
505
-
506
- const repository = this.getRepository(name, unique);
507
- if (!repository) {
508
- return null;
509
- }
510
-
511
- await this._waitForRepositoryInitialization(repository, timeout);
512
- return repository;
513
- }
514
-
515
- /**
516
- * Creates an unbound unique Repository synchronously.
517
- * It returns the instance immediately and initializes in background.
518
- * @param {string} name - Name of Schema
519
- * @return {Repository} repository
520
- */
521
- _createUniqueRepositorySync = (name) => {
497
+ getUniqueRepository = async (name) => {
522
498
  if (this.isDestroyed) {
523
- throw new Error('this._createUniqueRepositorySync is no longer valid. OneHatData has been destroyed.');
499
+ throw new Error('this.getUniqueRepository is no longer valid. OneHatData has been destroyed.');
524
500
  }
525
501
 
526
502
  const schema = this.getSchema(name);
527
503
  if (!schema) {
528
- return null;
504
+ throw new Error('this.getUniqueRepository: Schema not found. Name: ' + name);
529
505
  }
530
506
 
531
507
  const boundRepository = schema.getBoundRepository();
532
508
  if (!boundRepository) {
533
- return null;
534
- }
535
-
536
- const id = uuid();
537
- const schemaRepositoryDef = _.isString(schema.repository) ? { type: schema.repository } : schema.repository;
538
-
539
- // Keep behavioral overrides while omitting mutable runtime state and nested repo instances.
540
- const safeOverrides = _.omit(boundRepository.originalConfig || {}, [
541
- 'id',
542
- 'name',
543
- 'isUnique',
544
- 'local',
545
- 'remote',
546
- 'entities',
547
- 'filters',
548
- 'sorters',
549
- 'page',
550
- 'previousPage',
551
- 'pageTotal',
552
- 'pageStart',
553
- 'pageEnd',
554
- 'totalPages',
555
- 'total',
556
- 'isFiltered',
557
- 'isInitialized',
558
- 'isLoaded',
559
- 'isLoading',
560
- 'lastLoaded',
561
- 'hash',
562
- ]);
563
-
564
- const config = _.merge({}, schemaRepositoryDef, this._repositoryGlobals, safeOverrides, {
565
- schema,
566
- id,
567
- name: boundRepository.name + '-' + id,
568
- isUnique: true,
569
- });
570
-
571
- const repository = this._createRepositorySync(config);
572
-
573
- this.repositories[repository.id] = repository;
574
- this._initializeRepositoryInBackground(repository);
575
-
576
- if (repository.isRegisteredEvent('logout')) { // OneBuild repository emits this
577
- this.relayEventsFrom(repository, ['logout']);
578
- }
579
- if (repository.isRegisteredEvent(CROSS_TAB_EVENT_NAME)) {
580
- this.relayEventsFrom(repository, [CROSS_TAB_EVENT_NAME]);
581
- }
582
-
583
- this.emit('createRepository', repository);
584
- return repository;
585
- }
586
-
587
- /**
588
- * Creates a Repository instance synchronously.
589
- * Used when we must return a repository instance immediately.
590
- * @param {object} config - Repository config object
591
- * @return {Repository} repository
592
- * @private
593
- */
594
- _createRepositorySync = (config) => {
595
- if (this.isDestroyed) {
596
- throw new Error('this._createRepositorySync is no longer valid. OneHatData has been destroyed.');
597
- }
598
-
599
- const workingConfig = _.merge({}, config);
600
-
601
- if (workingConfig.type === 'lfr') {
602
- const generalConfig = _.omit(workingConfig, ['type', 'local', 'remote']);
603
-
604
- let localConfig = workingConfig.local;
605
- let remoteConfig = workingConfig.remote;
606
-
607
- if (_.isString(localConfig)) {
608
- localConfig = {
609
- type: localConfig,
610
- };
611
- }
612
- if (_.isString(remoteConfig)) {
613
- remoteConfig = {
614
- type: remoteConfig,
615
- };
616
- }
617
-
618
- if (workingConfig.mode === MODE_COMMAND_QUEUE) {
619
- generalConfig.isPaginated = false;
620
- remoteConfig.type = 'command';
621
- }
622
-
623
- localConfig = _.merge({}, generalConfig, localConfig);
624
- remoteConfig = _.merge({}, generalConfig, remoteConfig);
625
-
626
- const localRepository = this._createRepositorySync(localConfig);
627
- const remoteRepository = this._createRepositorySync(remoteConfig);
628
-
629
- workingConfig.local = localRepository;
630
- workingConfig.remote = remoteRepository;
631
- }
632
-
633
- const RepositoryType = this._repositoryTypes[workingConfig.type];
634
- if (!RepositoryType) {
635
- throw new Error('Repository type does not exist');
636
- }
637
-
638
- return new RepositoryType(workingConfig, this);
639
- }
640
-
641
- /**
642
- * Initializes repositories in the proper dependency order.
643
- * @param {Repository} repository - Repository instance
644
- * @private
645
- */
646
- _initializeRepositoryInBackground = (repository) => {
647
- repository._initializationError = null;
648
- repository._initializationPromise = Promise.resolve()
649
- .then(async () => {
650
- if (repository && repository.type === 'lfr') {
651
- if (repository.local && _.isFunction(repository.local.initialize)) {
652
- await repository.local.initialize();
653
- }
654
- if (repository.remote && _.isFunction(repository.remote.initialize)) {
655
- await repository.remote.initialize();
656
- }
657
- }
658
- await repository.initialize();
659
- })
660
- .catch((error) => {
661
- repository._initializationError = error;
662
- repository.emit('error', error);
509
+ throw new Error('this.getUniqueRepository: Schema does not have a bound Repository. Name: ' + name);
510
+ }
511
+
512
+ const
513
+ id = uuid(),
514
+ schemaRepositoryDef = _.isString(schema.repository) ? { type: schema.repository } : schema.repository,
515
+ safeOverrides = _.omit(boundRepository.originalConfig || {}, [ // Keep behavioral overrides while omitting mutable runtime state and nested repo instances.
516
+ 'id',
517
+ 'name',
518
+ 'isUnique',
519
+ 'local',
520
+ 'remote',
521
+ 'entities',
522
+ 'filters',
523
+ 'sorters',
524
+ 'page',
525
+ 'previousPage',
526
+ 'pageTotal',
527
+ 'pageStart',
528
+ 'pageEnd',
529
+ 'totalPages',
530
+ 'total',
531
+ 'isFiltered',
532
+ 'isInitialized',
533
+ 'isLoaded',
534
+ 'isLoading',
535
+ 'lastLoaded',
536
+ 'hash',
537
+ ]),
538
+ config = _.merge({}, schemaRepositoryDef, this._repositoryGlobals, safeOverrides, {
539
+ schema,
540
+ id,
541
+ name: boundRepository.name + '-' + id,
542
+ isUnique: true,
663
543
  });
664
- return repository._initializationPromise;
665
- }
666
544
 
667
- /**
668
- * Waits for repository initialization if currently pending.
669
- * @param {Repository} repository - Repository instance
670
- * @param {number} timeout - Max ms to wait for initialization
671
- * @return {Promise<Repository>} repository
672
- * @private
673
- */
674
- _waitForRepositoryInitialization = async (repository, timeout = 10000) => {
675
- if (!repository) {
676
- return null;
677
- }
678
-
679
- if (repository._initializationPromise) {
680
- await repository._initializationPromise;
681
- }
682
-
683
- if (repository._initializationError) {
684
- throw repository._initializationError;
685
- }
686
-
687
- if (repository.isInitialized === true) {
688
- return repository;
689
- }
690
-
691
- if (repository.isInitializing && _.isFunction(repository.on) && _.isFunction(repository.off)) {
692
- await new Promise((resolve, reject) => {
693
- const timeoutId = setTimeout(() => {
694
- repository.off('initialize', handleInitialize);
695
- repository.off('error', handleError);
696
- reject(new Error('Timed out waiting for repository initialization: ' + repository.name));
697
- }, timeout);
698
-
699
- const handleInitialize = () => {
700
- clearTimeout(timeoutId);
701
- repository.off('initialize', handleInitialize);
702
- repository.off('error', handleError);
703
- resolve();
704
- };
705
-
706
- const handleError = (error) => {
707
- clearTimeout(timeoutId);
708
- repository.off('initialize', handleInitialize);
709
- repository.off('error', handleError);
710
- reject(error || new Error('Repository initialization failed: ' + repository.name));
711
- };
712
-
713
- repository.on('initialize', handleInitialize);
714
- repository.on('error', handleError);
715
- });
716
- }
717
-
718
- if (repository._initializationError) {
719
- throw repository._initializationError;
720
- }
721
-
722
- return repository;
545
+ return await this.createRepository(config);
723
546
  }
724
547
 
725
548
  /**
@@ -730,13 +553,17 @@ export class OneHatData extends EventEmitter {
730
553
  */
731
554
  getOrCreateUniqueRepository = async (mapName, schemaName) => {
732
555
  if (this.isDestroyed) {
733
- throw new Error('this.getUniqueRepository is no longer valid. OneHatData has been destroyed.');
556
+ throw new Error('this.getOrCreateUniqueRepository is no longer valid. OneHatData has been destroyed.');
734
557
  }
735
558
 
736
559
  // Try to get it
737
560
  let id = this.uniqueRepositoryIdsMap[mapName];
738
561
  if (id) {
739
- return this.getRepositoryById(id);
562
+ const existingRepository = this.getRepositoryById(id);
563
+ if (existingRepository) {
564
+ return existingRepository;
565
+ }
566
+ delete this.uniqueRepositoryIdsMap[mapName];
740
567
  }
741
568
 
742
569
  // Try to create it
@@ -744,11 +571,8 @@ export class OneHatData extends EventEmitter {
744
571
  if (!schema) {
745
572
  return null;
746
573
  }
747
-
748
- const repository = await this.createRepository(schemaName);
574
+ const repository = await this.getUniqueRepository(schemaName);
749
575
  id = repository.id;
750
- repository.name += '-' + id;
751
- repository.isUnique = true;
752
576
  this.uniqueRepositoryIdsMap[mapName] = repository.id;
753
577
  return repository;
754
578
  }
@@ -141,58 +141,51 @@ class AjaxRepository extends Repository {
141
141
  }
142
142
 
143
143
  async initialize() {
144
- this.isInitializing = true;
145
- try {
146
144
 
147
- this.registerEvents([
148
- 'beforeLoad',
149
- ]);
145
+ this.registerEvents([
146
+ 'beforeLoad',
147
+ ]);
150
148
 
151
- // Respond to Repository events
152
- this.on('beforeSave', this._onBeforeSave);
149
+ // Respond to Repository events
150
+ this.on('beforeSave', this._onBeforeSave);
153
151
 
154
152
 
155
- // Create Reader
156
- let readerConfig;
157
- if (this.reader && this.reader.type) {
158
- readerConfig = this.reader;
159
- } else if (_.isString(this.reader)) {
160
- readerConfig = {
161
- type: this.reader,
162
- };
163
- }
164
- if (readerConfig && ReaderTypes[readerConfig.type]) {
165
- const Reader = ReaderTypes[readerConfig.type];
166
- this.reader = new Reader(readerConfig);
167
- } else {
168
- this.reader = null;
169
- }
153
+ // Create Reader
154
+ let readerConfig;
155
+ if (this.reader && this.reader.type) {
156
+ readerConfig = this.reader;
157
+ } else if (_.isString(this.reader)) {
158
+ readerConfig = {
159
+ type: this.reader,
160
+ };
161
+ }
162
+ if (readerConfig && ReaderTypes[readerConfig.type]) {
163
+ const Reader = ReaderTypes[readerConfig.type];
164
+ this.reader = new Reader(readerConfig);
165
+ } else {
166
+ this.reader = null;
167
+ }
170
168
 
171
- // Create Writer
172
- let writerConfig;
173
- if (this.writer && this.writer.type) {
174
- writerConfig = this.writer;
175
- } else if (_.isString(this.writer)) {
176
- writerConfig = {
177
- type: this.writer,
178
- };
179
- }
180
- if (writerConfig && WriterTypes[writerConfig.type]) {
181
- const Writer = WriterTypes[writerConfig.type];
182
- this.writer = new Writer(writerConfig);
183
- } else {
184
- this.writer = null;
185
- }
169
+ // Create Writer
170
+ let writerConfig;
171
+ if (this.writer && this.writer.type) {
172
+ writerConfig = this.writer;
173
+ } else if (_.isString(this.writer)) {
174
+ writerConfig = {
175
+ type: this.writer,
176
+ };
177
+ }
178
+ if (writerConfig && WriterTypes[writerConfig.type]) {
179
+ const Writer = WriterTypes[writerConfig.type];
180
+ this.writer = new Writer(writerConfig);
181
+ } else {
182
+ this.writer = null;
183
+ }
186
184
 
187
- // Initialize query params
188
- this._setInitialQueryParams();
185
+ // Initialize query params
186
+ this._setInitialQueryParams();
189
187
 
190
- await super.initialize();
191
- } finally {
192
- if (!this.isInitialized) {
193
- this.isInitializing = false;
194
- }
195
- }
188
+ await super.initialize();
196
189
  }
197
190
 
198
191
  /**
@@ -232,9 +225,9 @@ class AjaxRepository extends Repository {
232
225
  * @param {string} name - Param name to set.
233
226
  * @param {any} value - Param value to set.
234
227
  * @param {boolean} isBaseParam - Whether param is a base param (to be sent on every request).
228
+ * @returns {boolean} - Returns true if the param was changed, false otherwise.
235
229
  */
236
230
  setParam(name, value, isBaseParam = false) {
237
- this._assertInitialized('setParam');
238
231
  const
239
232
  re = /^([^\[]+)\[([^\]]+)\](.*)$/,
240
233
  matches = name.match(re),
@@ -243,25 +236,45 @@ class AjaxRepository extends Repository {
243
236
  if (matches) { // name has array notation like 'conditions[username]'
244
237
  const
245
238
  first = matches[1],
246
- second = matches[2];
247
- if (paramsToChange && !paramsToChange.hasOwnProperty(first)) {
248
- paramsToChange[first] = {};
249
- }
250
- if (_.isNil(value) && paramsToChange[first] && paramsToChange[first].hasOwnProperty(second)) {
239
+ second = matches[2],
240
+ hasFirst = paramsToChange && paramsToChange.hasOwnProperty(first),
241
+ hasSecond = hasFirst && paramsToChange[first] && paramsToChange[first].hasOwnProperty(second);
242
+
243
+ if (_.isNil(value)) {
244
+ if (!hasSecond) {
245
+ return false;
246
+ }
251
247
  delete paramsToChange[first][second];
252
248
  if (_.isEmpty(paramsToChange[first])) {
253
249
  delete paramsToChange[first];
254
250
  }
255
- return;
251
+ return true;
252
+ }
253
+
254
+ if (!hasFirst) {
255
+ paramsToChange[first] = {};
256
+ }
257
+
258
+ if (hasSecond && _.isEqual(paramsToChange[first][second], value)) {
259
+ return false;
256
260
  }
257
261
  paramsToChange[first][second] = value;
258
- return;
262
+ return true;
259
263
  }
260
- if (_.isNil(value) && paramsToChange && paramsToChange.hasOwnProperty(name)) {
264
+ if (_.isNil(value)) {
265
+ if (!paramsToChange || !paramsToChange.hasOwnProperty(name)) {
266
+ return false;
267
+ }
261
268
  delete paramsToChange[name];
262
- return;
269
+ return true;
270
+ }
271
+
272
+ if (paramsToChange && paramsToChange.hasOwnProperty(name) && _.isEqual(paramsToChange[name], value)) {
273
+ return false;
263
274
  }
275
+
264
276
  paramsToChange[name] = value;
277
+ return true;
265
278
  }
266
279
 
267
280
  /**
@@ -269,9 +282,9 @@ class AjaxRepository extends Repository {
269
282
  * Sets a single query param.
270
283
  * @param {string} name - Param name to set.
271
284
  * @param {boolean} isBaseParam - Whether param is a base param (to be sent on every request).
285
+ * @returns {boolean} - Returns true if the param was changed, false otherwise.
272
286
  */
273
287
  setValuelessParam(name, isBaseParam = false) {
274
- this._assertInitialized('setValuelessParam');
275
288
  const
276
289
  re = /^([^\[]+)\[([^\]]+)\](.*)$/,
277
290
  matches = name.match(re),
@@ -282,28 +295,37 @@ class AjaxRepository extends Repository {
282
295
  first = matches[1],
283
296
  second = matches[2];
284
297
  if (paramsToChange && !paramsToChange.hasOwnProperty(first)) {
285
- paramsToChange[first] = [];
298
+ paramsToChange[first] = {};
286
299
  }
287
- if (paramsToChange[first] && paramsToChange[first].hasOwnProperty(second)) {
288
- delete paramsToChange[first][second];
289
- return;
300
+ if (paramsToChange[first] && paramsToChange[first].hasOwnProperty(second) && paramsToChange[first][second] === true) {
301
+ return false;
290
302
  }
291
- paramsToChange[first][ paramsToChange[first].length ] = second;
292
- return;
303
+ paramsToChange[first][second] = true;
304
+ return true;
305
+ }
306
+
307
+ if (paramsToChange && paramsToChange.hasOwnProperty(name) && paramsToChange[name] === true) {
308
+ return false;
293
309
  }
294
- paramsToChange[paramsToChange.length] = second;
310
+
311
+ paramsToChange[name] = true;
312
+ return true;
295
313
  }
296
314
 
297
315
  /**
298
316
  * Sets query params
299
317
  * @param {object} params - Params to set. Key is parameter name, value is parameter value
318
+ * @returns {boolean} - Returns true if any param was changed, false otherwise.
300
319
  */
301
320
  setParams(params) {
302
- this._assertInitialized('setParams');
303
321
  const oThis = this;
322
+ let isChanged = false;
304
323
  _.each(params, (value, name) => {
305
- oThis.setParam(name, value);
324
+ if (oThis.setParam(name, value)) {
325
+ isChanged = true;
326
+ }
306
327
  });
328
+ return isChanged;
307
329
  }
308
330
 
309
331
  /**
@@ -311,7 +333,6 @@ class AjaxRepository extends Repository {
311
333
  * @param {string} name - Param name
312
334
  */
313
335
  hasBaseParam(name) {
314
- this._assertInitialized('hasBaseParam');
315
336
  if (this._baseParams.hasOwnProperty(name)) {
316
337
  return true;
317
338
  }
@@ -335,7 +356,6 @@ class AjaxRepository extends Repository {
335
356
  * @param {string} name - Param name
336
357
  */
337
358
  getBaseParam(name) {
338
- this._assertInitialized('getBaseParam');
339
359
  if (!this.hasBaseParam(name)) {
340
360
  return null;
341
361
  }
@@ -363,7 +383,6 @@ class AjaxRepository extends Repository {
363
383
  * @param {object} params - Params to set. Key is parameter name, value is parameter value
364
384
  */
365
385
  getBaseParams() {
366
- this._assertInitialized('getBaseParams');
367
386
  return this._baseParams;
368
387
  }
369
388
 
@@ -371,7 +390,6 @@ class AjaxRepository extends Repository {
371
390
  * Returns current value of any baseParam query conditions
372
391
  */
373
392
  getBaseParamConditions() {
374
- this._assertInitialized('getBaseParamConditions');
375
393
  const
376
394
  existingConditions = this._baseParams.conditions || {},
377
395
  convertedConditions = {};
@@ -385,7 +403,6 @@ class AjaxRepository extends Repository {
385
403
  * Returns current value of any param query conditions
386
404
  */
387
405
  getParamConditions() {
388
- this._assertInitialized('getParamConditions');
389
406
  const
390
407
  existingConditions = this._params.conditions || {},
391
408
  convertedConditions = {};
@@ -400,7 +417,6 @@ class AjaxRepository extends Repository {
400
417
  * @param {string} name - Param name
401
418
  */
402
419
  hasParam(name) {
403
- this._assertInitialized('hasParam');
404
420
  if (this._params.hasOwnProperty(name)) {
405
421
  return true;
406
422
  }
@@ -423,22 +439,26 @@ class AjaxRepository extends Repository {
423
439
  * Sets base query param
424
440
  * @param {string} name - Param name to set.
425
441
  * @param {any} value - Param value to set.
442
+ * @returns {boolean} - Returns true if the base param was changed, false otherwise.
426
443
  */
427
444
  setBaseParam(name, value) {
428
- this._assertInitialized('setBaseParam');
429
- this.setParam(name, value, true);
445
+ return this.setParam(name, value, true);
430
446
  }
431
447
 
432
448
  /**
433
449
  * Sets base query params. These params are sent on every request.
434
450
  * @param {object} params - Base params to set. Key is parameter name, value is parameter value
451
+ * @returns {boolean} - Returns true if any base param was changed, false otherwise.
435
452
  */
436
453
  setBaseParams(params) {
437
- this._assertInitialized('setBaseParams');
438
454
  const oThis = this;
455
+ let isChanged = false;
439
456
  _.each(params, (value, name) => {
440
- oThis.setBaseParam(name, value);
457
+ if (oThis.setBaseParam(name, value)) {
458
+ isChanged = true;
459
+ }
441
460
  });
461
+ return isChanged;
442
462
  }
443
463
 
444
464
  /**
@@ -447,7 +467,6 @@ class AjaxRepository extends Repository {
447
467
  * @param {boolean} reload - Whether to reload repository. Defaults to false.
448
468
  */
449
469
  clearParams(reload = false, clearBase = false) {
450
- this._assertInitialized('clearParams');
451
470
  this._params = {};
452
471
  if (clearBase) {
453
472
  this._baseParams = {};
@@ -461,42 +480,51 @@ class AjaxRepository extends Repository {
461
480
  * Sets sort and direction params.
462
481
  * Only one sorter is allowed with this Repository type.
463
482
  * Refreshes entities.
483
+ * @returns {boolean} - Returns true if the sort params were changed, false otherwise.
464
484
  */
465
485
  _onChangeSorters() {
466
- this._assertInitialized('_onChangeSorters');
467
486
  const sorter = this.sorters[0];
468
- this.setBaseParam(this.paramSort, sorter.name);
469
- this.setBaseParam(this.paramDirection, sorter.direction);
487
+ let isChanged = false;
488
+ isChanged = this.setBaseParam(this.paramSort, sorter.name) || isChanged;
489
+ isChanged = this.setBaseParam(this.paramDirection, sorter.direction) || isChanged;
470
490
 
471
491
  if (this.isLoaded && !this.eventsPaused) {
472
492
  return this.reload();
473
493
  }
494
+
495
+ return isChanged;
474
496
  }
475
497
 
476
498
  /**
477
499
  * Sets filter params.
478
500
  * Refreshes entities.
501
+ * @returns {boolean} - Returns true if the filter params were changed, false otherwise.
479
502
  */
480
503
  _onChangeFilters() {
481
- this._assertInitialized('_onChangeFilters');
482
504
  const oThis = this;
505
+ let isChanged = false;
483
506
  _.each(this.filters, (value, name) => {
484
- oThis.setParam(name, value);
507
+ if (oThis.setParam(name, value)) {
508
+ isChanged = true;
509
+ }
485
510
  });
486
511
 
487
512
  if (this.isLoaded && !this.eventsPaused) {
488
513
  return this.reload();
489
514
  }
515
+
516
+ return isChanged;
490
517
  }
491
518
 
492
519
  /**
493
520
  * Sets pagination params.
494
521
  * Refreshes entities.
522
+ * @returns {boolean} - Returns true if the pagination params were changed, false otherwise.
495
523
  */
496
524
  _onChangePagination() {
497
- this._assertInitialized('_onChangePagination');
498
- this.setBaseParam(this.paramPageNum, this.isPaginated ? this.page : null);
499
- this.setBaseParam(this.paramPageSize, this.isPaginated ? this.pageSize : null);
525
+ let isChanged = false;
526
+ isChanged = this.setBaseParam(this.paramPageNum, this.isPaginated ? this.page : null) || isChanged;
527
+ isChanged = this.setBaseParam(this.paramPageSize, this.isPaginated ? this.pageSize : null) || isChanged;
500
528
 
501
529
  if (this.isLoaded && !this.eventsPaused) {
502
530
  const
@@ -517,6 +545,8 @@ class AjaxRepository extends Repository {
517
545
  return this.reload();
518
546
  }
519
547
  }
548
+
549
+ return isChanged;
520
550
  }
521
551
 
522
552
 
@@ -534,7 +564,6 @@ class AjaxRepository extends Repository {
534
564
  * @fires beforeLoad,changeData,load,error
535
565
  */
536
566
  async load(params, callback = null) {
537
- this._assertInitialized('load');
538
567
  if (this.isTree) {
539
568
  return this.loadRootNodes();
540
569
  }
@@ -707,7 +736,6 @@ class AjaxRepository extends Repository {
707
736
  }
708
737
 
709
738
  showMore(params = {}, callback) {
710
- this._assertInitialized('showMore');
711
739
  params.showMore = true;
712
740
  return this.load(params, callback);
713
741
  }
@@ -720,7 +748,6 @@ class AjaxRepository extends Repository {
720
748
  * @fires reloadEntity,beforeLoad,changeData,load,error
721
749
  */
722
750
  async reloadEntity(entity, callback = null) { // use this notation so we can override it in subclasses
723
- this._assertInitialized('reloadEntity');
724
751
  if (this.isDestroyed) {
725
752
  this.throwError('this.reloadEntity is no longer valid. Repository has been destroyed.');
726
753
  return;
@@ -784,7 +811,6 @@ class AjaxRepository extends Repository {
784
811
  * @private
785
812
  */
786
813
  _getReloadEntityParams(entity) {
787
- this._assertInitialized('_getReloadEntityParams');
788
814
  const params = {
789
815
  id: entity.id,
790
816
  };
@@ -796,7 +822,6 @@ class AjaxRepository extends Repository {
796
822
  * @private
797
823
  */
798
824
  _onBeforeSave() {
799
- this._assertInitialized('_onBeforeSave');
800
825
  this._operations = {
801
826
  add: false,
802
827
  edit: false,
@@ -1198,8 +1223,6 @@ class AjaxRepository extends Repository {
1198
1223
  * @private
1199
1224
  */
1200
1225
  _send(method, url, data, options = {}) {
1201
- this._assertInitialized('_send');
1202
-
1203
1226
  if (!url) {
1204
1227
  this.throwError('No url submitted');
1205
1228
  return;
@@ -1279,7 +1302,6 @@ class AjaxRepository extends Repository {
1279
1302
  * since the server normally sorts, and they haven't yet gone to server.
1280
1303
  */
1281
1304
  sortInMemory() {
1282
- this._assertInitialized('sortInMemory');
1283
1305
  const sorters = this.sorters;
1284
1306
  let sortNames = [],
1285
1307
  sortDirections = [];
@@ -183,6 +183,7 @@ class OneBuildRepository extends AjaxRepository {
183
183
  * Sets "conditions" param.
184
184
  * OneBuild uses a single, multi-dimentional param for filtering.
185
185
  * Refreshes entities.
186
+ * @returns {boolean} - Returns true if the filter params were changed, false otherwise.
186
187
  */
187
188
  _onChangeFilters() {
188
189
  // Clear existing "conditions" params
@@ -193,11 +194,16 @@ class OneBuildRepository extends AjaxRepository {
193
194
  }
194
195
 
195
196
  const oThis = this;
197
+ let isChanged = false;
196
198
  _.each(this.filters, (filter, ix) => {
197
199
  if (_.includes(nonConditionFilters, filter.name)) {
198
- oThis.setParam(filter.name, filter.value);
200
+ if (oThis.setParam(filter.name, filter.value)) {
201
+ isChanged = true;
202
+ }
199
203
  } else {
200
- oThis.setParam('conditions[' + filter.name + ']', filter.value);
204
+ if (oThis.setParam('conditions[' + filter.name + ']', filter.value)) {
205
+ isChanged = true;
206
+ }
201
207
  }
202
208
  });
203
209
 
@@ -208,21 +214,25 @@ class OneBuildRepository extends AjaxRepository {
208
214
  return this.reload();
209
215
  }
210
216
  }
217
+
218
+ return isChanged;
211
219
  }
212
220
 
213
221
  /**
214
222
  * Sets "order" param.
215
223
  * OneBuild uses a single order param, rather than separate name & direction params.
216
224
  * Refreshes entities.
225
+ * @returns {boolean} - Returns true if the sort params were changed, false otherwise.
217
226
  */
218
227
  _onChangeSorters() {
219
228
  let sorterStrings = [];
229
+ let isChanged = false;
220
230
  _.each(this.sorters, (sorter) => {
221
231
  sorterStrings.push(sorter.name + ' ' + sorter.direction);
222
232
  });
223
233
 
224
234
  if (!_.isEmpty(sorterStrings)) {
225
- this.setBaseParam('order', sorterStrings.join(','));
235
+ isChanged = this.setBaseParam('order', sorterStrings.join(',')) || isChanged;
226
236
  }
227
237
 
228
238
  if (!this.eventsPaused) {
@@ -240,6 +250,8 @@ class OneBuildRepository extends AjaxRepository {
240
250
  this.emit('changeSorters');
241
251
  }
242
252
  }
253
+
254
+ return isChanged;
243
255
  }
244
256
 
245
257
  /**
@@ -373,29 +373,6 @@ export default class Repository extends EventEmitter {
373
373
  }
374
374
  }
375
375
 
376
- /**
377
- * Throws when operational methods are used before initialization completes.
378
- * @param {string} methodName - Name of the method being guarded
379
- * @private
380
- */
381
- _assertInitialized(methodName) {
382
- const strictMethods = {
383
- add: true,
384
- addMultiple: true,
385
- save: true,
386
- delete: true,
387
- load: true,
388
- reloadEntity: true,
389
- _send: true,
390
- };
391
- if (!strictMethods[methodName]) {
392
- return;
393
- }
394
- if (!this.isInitialized && !this.isInitializing) {
395
- throw new Error('Repository ' + this.name + ' is not initialized. Cannot call ' + methodName + ' yet.');
396
- }
397
- }
398
-
399
376
  /**
400
377
  * Creates the methods for this Repository, based on Schema.
401
378
  * @private
@@ -622,7 +599,6 @@ export default class Repository extends EventEmitter {
622
599
  this.throwError('this.sort is no longer valid. Repository has been destroyed.');
623
600
  return;
624
601
  }
625
- this._assertInitialized('sort');
626
602
  // Assemble sorting definition objects
627
603
  let sorters = [];
628
604
  if (_.isNil(arg1)) {
@@ -683,7 +659,6 @@ export default class Repository extends EventEmitter {
683
659
  this.throwError('this.setSorters is no longer valid. Repository has been destroyed.');
684
660
  return;
685
661
  }
686
- this._assertInitialized('setSorters');
687
662
  if (!this.allowsMultiSort && sorters.length > 1) {
688
663
  this.throwError('Cannot have more than one sorter at a time.');
689
664
  return;
@@ -793,7 +768,6 @@ export default class Repository extends EventEmitter {
793
768
  this.throwError('this.filter is no longer valid. Repository has been destroyed.');
794
769
  return;
795
770
  }
796
- this._assertInitialized('filter');
797
771
 
798
772
  if (_.isNil(arg1)) {
799
773
  return this.clearFilters();
@@ -870,7 +844,6 @@ export default class Repository extends EventEmitter {
870
844
  * @return this
871
845
  */
872
846
  setFilters(filters, clearFirst = true) {
873
- this._assertInitialized('setFilters');
874
847
  const parsed = _.map(filters, (value, name) => {
875
848
  return {
876
849
  name,
@@ -891,7 +864,6 @@ export default class Repository extends EventEmitter {
891
864
  * - repository.clearFilters(['first_name', 'last_name']); // Clear multiple filters
892
865
  */
893
866
  clearFilters(filtersToClear) {
894
- this._assertInitialized('clearFilters');
895
867
  let filters = [];
896
868
  if (filtersToClear) {
897
869
  if (_.isString(filtersToClear)) {
@@ -914,7 +886,6 @@ export default class Repository extends EventEmitter {
914
886
  this.throwError('this._setFilters is no longer valid. Repository has been destroyed.');
915
887
  return;
916
888
  }
917
- this._assertInitialized('_setFilters');
918
889
  if (!_.isEqual(this.filters, filters)) {
919
890
  this.filters = filters;
920
891
  this.resetPagination();
@@ -1154,7 +1125,6 @@ export default class Repository extends EventEmitter {
1154
1125
  this.throwError('this.add is no longer valid. Repository has been destroyed.');
1155
1126
  return;
1156
1127
  }
1157
- this._assertInitialized('add');
1158
1128
  if (!this.canAdd) {
1159
1129
  this.throwError('Adding has been disabled on this repository.');
1160
1130
  return;
@@ -1242,8 +1212,6 @@ export default class Repository extends EventEmitter {
1242
1212
  * @return {array} entities - new Entity objects
1243
1213
  */
1244
1214
  async addMultiple(allData, isPersisted = false) {
1245
- this._assertInitialized('addMultiple');
1246
-
1247
1215
  if (!this.canAdd) {
1248
1216
  this.throwError('Adding has been disabled on this repository.');
1249
1217
  return;
@@ -1428,7 +1396,6 @@ export default class Repository extends EventEmitter {
1428
1396
  this.throwError('this.getByIx is no longer valid. Repository has been destroyed.');
1429
1397
  return;
1430
1398
  }
1431
- this._assertInitialized('getByIx');
1432
1399
  return this.entities[ix];
1433
1400
  }
1434
1401
 
@@ -1444,7 +1411,6 @@ export default class Repository extends EventEmitter {
1444
1411
  this.throwError('this.getByRange is no longer valid. Repository has been destroyed.');
1445
1412
  return;
1446
1413
  }
1447
- this._assertInitialized('getByRange');
1448
1414
  return _.slice(this.entities, startIx, endIx+1);
1449
1415
  }
1450
1416
 
@@ -1458,7 +1424,6 @@ export default class Repository extends EventEmitter {
1458
1424
  this.throwError('this.getById is no longer valid. Repository has been destroyed.');
1459
1425
  return;
1460
1426
  }
1461
- this._assertInitialized('getById');
1462
1427
  if (_.isNil(id)) {
1463
1428
  return null;
1464
1429
  }
@@ -1475,7 +1440,6 @@ export default class Repository extends EventEmitter {
1475
1440
  this.throwError('this.getIxById is no longer valid. Repository has been destroyed.');
1476
1441
  return;
1477
1442
  }
1478
- this._assertInitialized('getIxById');
1479
1443
  if (_.isNil(id)) {
1480
1444
  return null;
1481
1445
  }
@@ -1497,7 +1461,6 @@ export default class Repository extends EventEmitter {
1497
1461
  this.throwError('this.getBy is no longer valid. Repository has been destroyed.');
1498
1462
  return;
1499
1463
  }
1500
- this._assertInitialized('getBy');
1501
1464
  return _.filter(this.entities, filter);
1502
1465
  }
1503
1466
 
@@ -1515,7 +1478,6 @@ export default class Repository extends EventEmitter {
1515
1478
  this.throwError('this.getFirstBy is no longer valid. Repository has been destroyed.');
1516
1479
  return;
1517
1480
  }
1518
- this._assertInitialized('getFirstBy');
1519
1481
  return _.find(this.entities, filter);
1520
1482
  }
1521
1483
 
@@ -1561,7 +1523,6 @@ export default class Repository extends EventEmitter {
1561
1523
  this.throwError('this.getEntities is no longer valid. Repository has been destroyed.');
1562
1524
  return;
1563
1525
  }
1564
- this._assertInitialized('getEntities');
1565
1526
  return this.entities;
1566
1527
  }
1567
1528
  /* */
@@ -1577,7 +1538,6 @@ export default class Repository extends EventEmitter {
1577
1538
  this.throwError('this.getPagedEntities is no longer valid. Repository has been destroyed.');
1578
1539
  return;
1579
1540
  }
1580
- this._assertInitialized('getEntitiesOnPage');
1581
1541
  const entities = this.getEntities();
1582
1542
  if (!this.isPaginated) {
1583
1543
  return entities;
@@ -1817,7 +1777,6 @@ export default class Repository extends EventEmitter {
1817
1777
  this.throwError('this.save is no longer valid. Repository has been destroyed.');
1818
1778
  return;
1819
1779
  }
1820
- this._assertInitialized('save');
1821
1780
 
1822
1781
  this.emit('beforeSave'); // So subclasses can prep anything needed for saving
1823
1782
 
@@ -2062,7 +2021,6 @@ export default class Repository extends EventEmitter {
2062
2021
  * @fires delete
2063
2022
  */
2064
2023
  async delete(entities, moveSubtreeUp = false) {
2065
- this._assertInitialized('delete');
2066
2024
  if (this.isDestroyed) {
2067
2025
  this.throwError('this.delete is no longer valid. Repository has been destroyed.');
2068
2026
  return;