@ember-data/store 5.3.0-alpha.9 → 5.3.0-beta.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.
@@ -1,6 +1,6 @@
1
1
  import { macroCondition, getOwnConfig } from '@embroider/macros';
2
2
  import { getOwner } from '@ember/application';
3
- import { assert, warn } from '@ember/debug';
3
+ import { assert, deprecate, warn } from '@ember/debug';
4
4
  import EmberObject from '@ember/object';
5
5
  import { _backburner } from '@ember/runloop';
6
6
  import { tracked } from '@glimmer/tracking';
@@ -354,50 +354,67 @@ function copyDocumentProperties(target, source) {
354
354
  }
355
355
  }
356
356
 
357
- /**
358
- @module @ember-data/store
359
- */
360
-
361
357
  // Used by the store to normalize IDs entering the store. Despite the fact
362
358
  // that developers may provide IDs as numbers (e.g., `store.findRecord('person', 1)`),
363
359
  // it is important that internally we use strings, since IDs may be serialized
364
360
  // and lose type information. For example, Ember's router may put a record's
365
361
  // ID into the URL, and if we later try to deserialize that URL and find the
366
362
  // corresponding record, we will not know if it is a string or a number.
367
-
368
363
  function coerceId(id) {
369
- if (id === null || id === undefined || id === '') {
370
- return null;
371
- }
372
- if (typeof id === 'string') {
373
- return id;
374
- }
375
- if (typeof id === 'symbol') {
376
- return id.toString();
364
+ if (macroCondition(getOwnConfig().deprecations.DEPRECATE_NON_STRICT_ID)) {
365
+ let normalized;
366
+ if (id === null || id === undefined || id === '') {
367
+ normalized = null;
368
+ } else {
369
+ normalized = String(id);
370
+ }
371
+ deprecate(`The resource id '<${typeof id}> ${String(id)} ' is not normalized. Update your application code to use '${JSON.stringify(normalized)}' instead.`, normalized === id, {
372
+ id: 'ember-data:deprecate-non-strict-id',
373
+ until: '6.0',
374
+ for: 'ember-data',
375
+ since: {
376
+ available: '5.3',
377
+ enabled: '5.3'
378
+ }
379
+ });
380
+ return normalized;
377
381
  }
378
- return '' + id;
382
+ assert(`Resource IDs must be a non-empty string or null. Received '${String(id)}'.`, id === null || typeof id === 'string' && id.length > 0);
383
+ return id;
379
384
  }
380
385
  function ensureStringId(id) {
381
386
  let normalized = null;
382
387
  if (typeof id === 'string') {
383
388
  normalized = id.length > 0 ? id : null;
384
389
  } else if (typeof id === 'number' && !isNaN(id)) {
385
- normalized = '' + id;
386
- }
387
- if (normalized === null) {
388
- throw new Error(`Expected id to be a string or number, received ${String(id)}`);
390
+ normalized = String(id);
389
391
  }
392
+ assert(`Expected id to be a string or number, received ${String(id)}`, normalized !== null);
390
393
  return normalized;
391
394
  }
392
395
 
393
396
  // provided for additional debuggability
394
397
  const DEBUG_CLIENT_ORIGINATED = Symbol('record-originated-on-client');
395
398
  const DEBUG_IDENTIFIER_BUCKET = Symbol('identifier-bucket');
396
- function isNonEmptyString(str) {
397
- return str && typeof str === 'string';
398
- }
399
- function normalizeModelName(modelName) {
400
- return dasherize(modelName);
399
+ const DEBUG_STALE_CACHE_OWNER = Symbol('warpDriveStaleCache');
400
+
401
+ // also present in production
402
+ const CACHE_OWNER = Symbol('warpDriveCache');
403
+ function normalizeModelName(type) {
404
+ if (macroCondition(getOwnConfig().deprecations.DEPRECATE_NON_STRICT_TYPES)) {
405
+ const result = dasherize(type);
406
+ deprecate(`The resource type '${type}' is not normalized. Update your application code to use '${result}' instead of '${type}'.`, result === type, {
407
+ id: 'ember-data:deprecate-non-strict-types',
408
+ until: '6.0',
409
+ for: 'ember-data',
410
+ since: {
411
+ available: '5.3',
412
+ enabled: '5.3'
413
+ }
414
+ });
415
+ return result;
416
+ }
417
+ return type;
401
418
  }
402
419
 
403
420
  /**
@@ -441,6 +458,21 @@ function installPolyfill() {
441
458
  };
442
459
  }
443
460
  }
461
+ function isResource(resource) {
462
+ return Boolean(resource && typeof resource === 'object');
463
+ }
464
+ function hasProp(resource, prop) {
465
+ return Boolean(isResource(resource) && prop in resource && typeof resource[prop] === 'string' && resource[prop].length);
466
+ }
467
+ function hasLid(resource) {
468
+ return hasProp(resource, 'lid');
469
+ }
470
+ function hasId(resource) {
471
+ return hasProp(resource, 'id') || Boolean(isResource(resource) && 'id' in resource && typeof resource.id === 'number');
472
+ }
473
+ function hasType(resource) {
474
+ return hasProp(resource, 'type');
475
+ }
444
476
 
445
477
  /**
446
478
  @module @ember-data/store
@@ -448,7 +480,7 @@ function installPolyfill() {
448
480
  const IDENTIFIERS = new Set();
449
481
  const DOCUMENTS = new Set();
450
482
  function isStableIdentifier(identifier) {
451
- return IDENTIFIERS.has(identifier);
483
+ return identifier[CACHE_OWNER] !== undefined || IDENTIFIERS.has(identifier);
452
484
  }
453
485
  function isDocumentIdentifier(identifier) {
454
486
  return DOCUMENTS.has(identifier);
@@ -459,7 +491,7 @@ if (macroCondition(getOwnConfig().polyfillUUID)) {
459
491
  installPolyfill();
460
492
  }
461
493
  function uuidv4() {
462
- assert('crypto.randomUUID needs to be avaliable. Some browsers incorrectly disallow it in insecure contexts. You maybe want to enable the polyfill: https://github.com/emberjs/data#randomuuid-polyfill', _crypto.randomUUID);
494
+ assert('crypto.randomUUID needs to be avaliable. Some browsers incorrectly disallow it in insecure contexts. You maybe want to enable the polyfill: https://github.com/emberjs/data#randomuuid-polyfill', typeof _crypto.randomUUID === 'function');
463
495
  return _crypto.randomUUID();
464
496
  }
465
497
  function freeze(obj) {
@@ -468,6 +500,9 @@ function freeze(obj) {
468
500
  }
469
501
  return obj;
470
502
  }
503
+
504
+ // type IdentifierTypeLookup = { all: Set<StableRecordIdentifier>; id: Map<string, StableRecordIdentifier> };
505
+ // type IdentifiersByType = Map<string, IdentifierTypeLookup>;
471
506
  let configuredForgetMethod;
472
507
  let configuredGenerationMethod;
473
508
  let configuredResetMethod;
@@ -484,20 +519,47 @@ function setIdentifierForgetMethod(method) {
484
519
  function setIdentifierResetMethod(method) {
485
520
  configuredResetMethod = method;
486
521
  }
522
+
523
+ // Map<type, Map<id, lid>>
524
+
525
+ const NEW_IDENTIFIERS = new Map();
526
+ let IDENTIFIER_CACHE_ID = 0;
527
+ function updateTypeIdMapping(typeMap, identifier, id) {
528
+ let idMap = typeMap.get(identifier.type);
529
+ if (!idMap) {
530
+ idMap = new Map();
531
+ typeMap.set(identifier.type, idMap);
532
+ }
533
+ idMap.set(id, identifier.lid);
534
+ }
535
+ function defaultUpdateMethod(identifier, data, bucket) {
536
+ if (bucket === 'record') {
537
+ assert(`Expected identifier to be a StableRecordIdentifier`, isStableIdentifier(identifier));
538
+ if (!identifier.id && hasId(data)) {
539
+ updateTypeIdMapping(NEW_IDENTIFIERS, identifier, data.id);
540
+ }
541
+ }
542
+ }
543
+ function defaultKeyInfoMethod(resource, known) {
544
+ // TODO RFC something to make this configurable
545
+ const id = hasId(resource) ? coerceId(resource.id) : null;
546
+ const type = hasType(resource) ? normalizeModelName(resource.type) : known ? known.type : null;
547
+ assert(`Expected keyInfoForResource to provide a type for the resource`, type);
548
+ return {
549
+ type,
550
+ id
551
+ };
552
+ }
487
553
  function defaultGenerationMethod(data, bucket) {
488
554
  if (bucket === 'record') {
489
- if (isNonEmptyString(data.lid)) {
555
+ if (hasLid(data)) {
490
556
  return data.lid;
491
557
  }
492
- if (data.id !== undefined) {
493
- let {
494
- type,
495
- id
496
- } = data;
497
- // TODO: add test for id not a string
498
- if (isNonEmptyString(coerceId(id))) {
499
- return `@lid:${normalizeModelName(type)}-${id}`;
500
- }
558
+ assert(`Cannot generate an identifier for a resource without a type`, hasType(data));
559
+ if (hasId(data)) {
560
+ const type = normalizeModelName(data.type);
561
+ const lid = NEW_IDENTIFIERS.get(type)?.get(data.id);
562
+ return lid || `@lid:${type}-${data.id}`;
501
563
  }
502
564
  return uuidv4();
503
565
  } else if (bucket === 'document') {
@@ -512,6 +574,9 @@ function defaultGenerationMethod(data, bucket) {
512
574
  assert(`Unknown bucket ${bucket}`, false);
513
575
  }
514
576
  function defaultEmptyCallback(...args) {}
577
+ function defaultMergeMethod(a, _b, _c) {
578
+ return a;
579
+ }
515
580
  let DEBUG_MAP;
516
581
  if (macroCondition(getOwnConfig().env.DEBUG)) {
517
582
  DEBUG_MAP = new WeakMap();
@@ -532,19 +597,21 @@ if (macroCondition(getOwnConfig().env.DEBUG)) {
532
597
  */
533
598
  class IdentifierCache {
534
599
  constructor() {
535
- this._cache = {
536
- lids: new Map(),
537
- types: Object.create(null),
538
- documents: new Map()
539
- };
540
600
  // we cache the user configuredGenerationMethod at init because it must
541
601
  // be configured prior and is not allowed to be changed
542
602
  this._generate = configuredGenerationMethod || defaultGenerationMethod;
543
- this._update = configuredUpdateMethod || defaultEmptyCallback;
603
+ this._update = configuredUpdateMethod || defaultUpdateMethod;
544
604
  this._forget = configuredForgetMethod || defaultEmptyCallback;
545
605
  this._reset = configuredResetMethod || defaultEmptyCallback;
546
- this._merge = defaultEmptyCallback;
606
+ this._merge = defaultMergeMethod;
607
+ this._keyInfoForResource = defaultKeyInfoMethod;
547
608
  this._isDefaultConfig = !configuredGenerationMethod;
609
+ this._id = IDENTIFIER_CACHE_ID++;
610
+ this._cache = {
611
+ resources: new Map(),
612
+ resourcesByType: Object.create(null),
613
+ documents: new Map()
614
+ };
548
615
  }
549
616
 
550
617
  /**
@@ -557,7 +624,10 @@ class IdentifierCache {
557
624
  * @private
558
625
  */
559
626
  __configureMerge(method) {
560
- this._merge = method || defaultEmptyCallback;
627
+ this._merge = method || defaultMergeMethod;
628
+ }
629
+ upgradeIdentifier(resource) {
630
+ return this._getRecordIdentifier(resource, 2);
561
631
  }
562
632
 
563
633
  /**
@@ -565,130 +635,64 @@ class IdentifierCache {
565
635
  * @private
566
636
  */
567
637
 
568
- _getRecordIdentifier(resource, shouldGenerate = false) {
638
+ _getRecordIdentifier(resource, shouldGenerate) {
639
+ if (macroCondition(getOwnConfig().debug.LOG_IDENTIFIERS)) {
640
+ // eslint-disable-next-line no-console
641
+ console.groupCollapsed(`Identifiers: ${shouldGenerate ? 'Generating' : 'Peeking'} Identifier`, resource);
642
+ }
569
643
  // short circuit if we're already the stable version
570
644
  if (isStableIdentifier(resource)) {
571
645
  if (macroCondition(getOwnConfig().env.DEBUG)) {
572
646
  // TODO should we instead just treat this case as a new generation skipping the short circuit?
573
- if (!this._cache.lids.has(resource.lid) || this._cache.lids.get(resource.lid) !== resource) {
574
- throw new Error(`The supplied identifier ${resource} does not belong to this store instance`);
647
+ if (!this._cache.resources.has(resource.lid) || this._cache.resources.get(resource.lid) !== resource) {
648
+ throw new Error(`The supplied identifier ${JSON.stringify(resource)} does not belong to this store instance`);
575
649
  }
576
650
  }
577
651
  if (macroCondition(getOwnConfig().debug.LOG_IDENTIFIERS)) {
578
652
  // eslint-disable-next-line no-console
579
- console.log(`Identifiers: Peeked Identifier was already Stable ${String(resource)}`);
580
- }
581
- return resource;
582
- }
583
- let lid = coerceId(resource.lid);
584
- let identifier = lid !== null ? this._cache.lids.get(lid) : undefined;
585
- if (identifier !== undefined) {
586
- if (macroCondition(getOwnConfig().debug.LOG_IDENTIFIERS)) {
653
+ console.log(`Identifiers: cache HIT - Stable ${resource.lid}`);
587
654
  // eslint-disable-next-line no-console
588
- console.log(`Identifiers: cache HIT ${identifier}`, resource);
655
+ console.groupEnd();
589
656
  }
590
- return identifier;
657
+ return resource;
591
658
  }
659
+
660
+ // the resource is unknown, ask the application to identify this data for us
661
+ const lid = this._generate(resource, 'record');
592
662
  if (macroCondition(getOwnConfig().debug.LOG_IDENTIFIERS)) {
593
663
  // eslint-disable-next-line no-console
594
- console.groupCollapsed(`Identifiers: ${shouldGenerate ? 'Generating' : 'Peeking'} Identifier`, resource);
664
+ console.log(`Identifiers: ${lid ? 'no ' : ''}lid ${lid ? lid + ' ' : ''}determined for resource`, resource);
595
665
  }
596
- if (shouldGenerate === false) {
597
- if (!resource.type || !resource.id) {
598
- return;
666
+ let identifier = /*#__NOINLINE__*/getIdentifierFromLid(this._cache, lid, resource);
667
+ if (identifier !== null) {
668
+ if (macroCondition(getOwnConfig().debug.LOG_IDENTIFIERS)) {
669
+ // eslint-disable-next-line no-console
670
+ console.groupEnd();
599
671
  }
672
+ return identifier;
600
673
  }
601
-
602
- // `type` must always be present
603
- assert('resource.type needs to be a string', 'type' in resource && isNonEmptyString(resource.type));
604
- let type = resource.type && normalizeModelName(resource.type);
605
- let id = coerceId(resource.id);
606
- let keyOptions = getTypeIndex(this._cache.types, type);
607
-
608
- // go straight for the stable RecordIdentifier key'd to `lid`
609
- if (lid !== null) {
610
- identifier = keyOptions.lid.get(lid);
611
- }
612
-
613
- // we may have not seen this resource before
614
- // but just in case we check our own secondary lookup (`id`)
615
- if (identifier === undefined && id !== null) {
616
- identifier = keyOptions.id.get(id);
617
- }
618
- if (identifier === undefined) {
619
- // we have definitely not seen this resource before
620
- // so we allow the user configured `GenerationMethod` to tell us
621
- let newLid = this._generate(resource, 'record');
674
+ if (shouldGenerate === 0) {
622
675
  if (macroCondition(getOwnConfig().debug.LOG_IDENTIFIERS)) {
623
676
  // eslint-disable-next-line no-console
624
- console.log(`Identifiers: lid ${newLid} determined for resource`, resource);
625
- }
626
-
627
- // we do this _even_ when `lid` is present because secondary lookups
628
- // may need to be populated, but we enforce not giving us something
629
- // different than expected
630
- if (lid !== null && newLid !== lid) {
631
- throw new Error(`You should not change the <lid> of a RecordIdentifier`);
632
- } else if (lid === null && !this._isDefaultConfig) {
633
- // allow configuration to tell us that we have
634
- // seen this `lid` before. E.g. a secondary lookup
635
- // connects this resource to a previously seen
636
- // resource.
637
- identifier = keyOptions.lid.get(newLid);
638
- }
639
- if (shouldGenerate === true) {
640
- if (identifier === undefined) {
641
- // if we still don't have an identifier, time to generate one
642
- identifier = makeStableRecordIdentifier(id, type, newLid, 'record', false);
643
-
644
- // populate our unique table
645
- if (macroCondition(getOwnConfig().env.DEBUG)) {
646
- // realistically if you hit this it means you changed `type` :/
647
- // TODO consider how to handle type change assertions more gracefully
648
- if (this._cache.lids.has(identifier.lid)) {
649
- throw new Error(`You should not change the <type> of a RecordIdentifier`);
650
- }
651
- }
652
- this._cache.lids.set(identifier.lid, identifier);
653
-
654
- // populate our primary lookup table
655
- // TODO consider having the `lid` cache be
656
- // one level up
657
- keyOptions.lid.set(identifier.lid, identifier);
658
- if (macroCondition(getOwnConfig().debug.LOG_IDENTIFIERS)) {
659
- if (shouldGenerate) {
660
- // eslint-disable-next-line no-console
661
- console.log(`Identifiers: generated ${String(identifier)} for`, resource);
662
- if (resource[DEBUG_IDENTIFIER_BUCKET]) {
663
- // eslint-disable-next-line no-console
664
- console.trace(`[WARNING] Identifiers: generated a new identifier from a previously used identifier. This is likely a bug.`);
665
- }
666
- }
667
- }
668
- }
669
-
670
- // populate our own secondary lookup table
671
- // even for the "successful" secondary lookup
672
- // by `_generate()`, since we missed the cache
673
- // previously
674
- // we use identifier.id instead of id here
675
- // because they may not match and we prefer
676
- // what we've set via resource data
677
- if (identifier.id !== null) {
678
- keyOptions.id.set(identifier.id, identifier);
679
-
680
- // TODO allow filling out of `id` here
681
- // for the `username` non-client created
682
- // case.
683
- }
677
+ console.groupEnd();
684
678
  }
679
+ return;
685
680
  }
686
681
 
682
+ // if we still don't have an identifier, time to generate one
683
+ if (shouldGenerate === 2) {
684
+ resource.lid = lid;
685
+ resource[CACHE_OWNER] = this._id;
686
+ identifier = /*#__NOINLINE__*/makeStableRecordIdentifier(resource, 'record', false);
687
+ } else {
688
+ // we lie a bit here as a memory optimization
689
+ const keyInfo = this._keyInfoForResource(resource, null);
690
+ keyInfo.lid = lid;
691
+ keyInfo[CACHE_OWNER] = this._id;
692
+ identifier = /*#__NOINLINE__*/makeStableRecordIdentifier(keyInfo, 'record', false);
693
+ }
694
+ addResourceToCache(this._cache, identifier);
687
695
  if (macroCondition(getOwnConfig().debug.LOG_IDENTIFIERS)) {
688
- if (!identifier && !shouldGenerate) {
689
- // eslint-disable-next-line no-console
690
- console.log(`Identifiers: cache MISS`, resource);
691
- }
692
696
  // eslint-disable-next-line no-console
693
697
  console.groupEnd();
694
698
  }
@@ -706,7 +710,7 @@ class IdentifierCache {
706
710
  * @private
707
711
  */
708
712
  peekRecordIdentifier(resource) {
709
- return this._getRecordIdentifier(resource, false);
713
+ return this._getRecordIdentifier(resource, 0);
710
714
  }
711
715
 
712
716
  /**
@@ -751,9 +755,8 @@ class IdentifierCache {
751
755
  @returns {StableRecordIdentifier}
752
756
  @public
753
757
  */
754
-
755
758
  getOrCreateRecordIdentifier(resource) {
756
- return this._getRecordIdentifier(resource, true);
759
+ return this._getRecordIdentifier(resource, 1);
757
760
  }
758
761
 
759
762
  /**
@@ -769,22 +772,22 @@ class IdentifierCache {
769
772
  */
770
773
  createIdentifierForNewRecord(data) {
771
774
  let newLid = this._generate(data, 'record');
772
- let identifier = makeStableRecordIdentifier(data.id || null, data.type, newLid, 'record', true);
773
- let keyOptions = getTypeIndex(this._cache.types, data.type);
775
+ let identifier = /*#__NOINLINE__*/makeStableRecordIdentifier({
776
+ id: data.id || null,
777
+ type: data.type,
778
+ lid: newLid,
779
+ [CACHE_OWNER]: this._id
780
+ }, 'record', true);
774
781
 
775
782
  // populate our unique table
776
783
  if (macroCondition(getOwnConfig().env.DEBUG)) {
777
- if (this._cache.lids.has(identifier.lid)) {
784
+ if (this._cache.resources.has(identifier.lid)) {
778
785
  throw new Error(`The lid generated for the new record is not unique as it matches an existing identifier`);
779
786
  }
780
787
  }
781
- this._cache.lids.set(identifier.lid, identifier);
782
788
 
783
- // populate the type+lid cache
784
- keyOptions.lid.set(newLid, identifier);
785
- if (data.id) {
786
- keyOptions.id.set(data.id, identifier);
787
- }
789
+ /*#__NOINLINE__*/
790
+ addResourceToCache(this._cache, identifier);
788
791
  if (macroCondition(getOwnConfig().debug.LOG_IDENTIFIERS)) {
789
792
  // eslint-disable-next-line no-console
790
793
  console.log(`Identifiers: created identifier ${String(identifier)} for newly generated resource`, data);
@@ -812,43 +815,49 @@ class IdentifierCache {
812
815
  */
813
816
  updateRecordIdentifier(identifierObject, data) {
814
817
  let identifier = this.getOrCreateRecordIdentifier(identifierObject);
815
- let newId = data.id !== undefined ? coerceId(data.id) : null;
816
- let existingIdentifier = detectMerge(this._cache.types, identifier, data, newId, this._cache.lids);
818
+ const keyInfo = this._keyInfoForResource(data, identifier);
819
+ let existingIdentifier = /*#__NOINLINE__*/detectMerge(this._cache, keyInfo, identifier, data);
820
+ const hadLid = hasLid(data);
817
821
  if (!existingIdentifier) {
818
822
  // If the incoming type does not match the identifier type, we need to create an identifier for the incoming
819
823
  // data so we can merge the incoming data with the existing identifier, see #7325 and #7363
820
- if (data.type && identifier.type !== normalizeModelName(data.type)) {
821
- let incomingDataResource = {
822
- ...data
823
- };
824
- // Need to strip the lid from the incomingData in order force a new identifier creation
825
- delete incomingDataResource.lid;
826
- existingIdentifier = this.getOrCreateRecordIdentifier(incomingDataResource);
824
+ if (identifier.type !== keyInfo.type) {
825
+ if (hadLid) {
826
+ // Strip the lid to ensure we force a new identifier creation
827
+ delete data.lid;
828
+ }
829
+ existingIdentifier = this.getOrCreateRecordIdentifier(data);
827
830
  }
828
831
  }
829
832
  if (existingIdentifier) {
830
- let keyOptions = getTypeIndex(this._cache.types, identifier.type);
831
833
  let generatedIdentifier = identifier;
832
- identifier = this._mergeRecordIdentifiers(keyOptions, generatedIdentifier, existingIdentifier, data, newId);
834
+ identifier = this._mergeRecordIdentifiers(keyInfo, generatedIdentifier, existingIdentifier, data);
835
+
836
+ // make sure that the `lid` on the data we are processing matches the lid we kept
837
+ if (hadLid) {
838
+ data.lid = identifier.lid;
839
+ }
833
840
  if (macroCondition(getOwnConfig().debug.LOG_IDENTIFIERS)) {
834
841
  // eslint-disable-next-line no-console
835
842
  console.log(`Identifiers: merged identifiers ${generatedIdentifier.lid} and ${existingIdentifier.lid} for resource into ${identifier.lid}`, data);
836
843
  }
837
844
  }
838
845
  let id = identifier.id;
839
- performRecordIdentifierUpdate(identifier, data, this._update);
840
- newId = identifier.id;
846
+ /*#__NOINLINE__*/
847
+ performRecordIdentifierUpdate(identifier, keyInfo, data, this._update);
848
+ const newId = identifier.id;
841
849
 
842
850
  // add to our own secondary lookup table
843
851
  if (id !== newId && newId !== null) {
844
852
  if (macroCondition(getOwnConfig().debug.LOG_IDENTIFIERS)) {
845
853
  // eslint-disable-next-line no-console
846
- console.log(`Identifiers: updated id for identifier ${identifier.lid} from '${id}' to '${newId}' for resource`, data);
854
+ console.log(`Identifiers: updated id for identifier ${identifier.lid} from '${String(id)}' to '${String(newId)}' for resource`, data);
847
855
  }
848
- let keyOptions = getTypeIndex(this._cache.types, identifier.type);
849
- keyOptions.id.set(newId, identifier);
856
+ const typeSet = this._cache.resourcesByType[identifier.type];
857
+ assert(`Expected to find a typeSet for ${identifier.type}`, typeSet);
858
+ typeSet.id.set(newId, identifier);
850
859
  if (id !== null) {
851
- keyOptions.id.delete(id);
860
+ typeSet.id.delete(id);
852
861
  }
853
862
  } else if (macroCondition(getOwnConfig().debug.LOG_IDENTIFIERS)) {
854
863
  // eslint-disable-next-line no-console
@@ -861,22 +870,22 @@ class IdentifierCache {
861
870
  * @method _mergeRecordIdentifiers
862
871
  * @private
863
872
  */
864
- _mergeRecordIdentifiers(keyOptions, identifier, existingIdentifier, data, newId) {
873
+ _mergeRecordIdentifiers(keyInfo, identifier, existingIdentifier, data) {
874
+ assert(`Expected keyInfo to contain an id`, hasId(keyInfo));
865
875
  // delegate determining which identifier to keep to the configured MergeMethod
866
- let kept = this._merge(identifier, existingIdentifier, data);
867
- let abandoned = kept === identifier ? existingIdentifier : identifier;
876
+ const kept = this._merge(identifier, existingIdentifier, data);
877
+ const abandoned = kept === identifier ? existingIdentifier : identifier;
868
878
 
869
879
  // cleanup the identifier we no longer need
870
880
  this.forgetRecordIdentifier(abandoned);
871
881
 
872
882
  // ensure a secondary cache entry for this id for the identifier we do keep
873
- keyOptions.id.set(newId, kept);
883
+ // keyOptions.id.set(newId, kept);
884
+
874
885
  // ensure a secondary cache entry for this id for the abandoned identifier's type we do keep
875
- let baseKeyOptions = getTypeIndex(this._cache.types, existingIdentifier.type);
876
- baseKeyOptions.id.set(newId, kept);
886
+ // let baseKeyOptions = getTypeIndex(this._cache.resourcesByType, existingIdentifier.type);
887
+ // baseKeyOptions.id.set(newId, kept);
877
888
 
878
- // make sure that the `lid` on the data we are processing matches the lid we kept
879
- data.lid = kept.lid;
880
889
  return kept;
881
890
  }
882
891
 
@@ -891,14 +900,19 @@ class IdentifierCache {
891
900
  @public
892
901
  */
893
902
  forgetRecordIdentifier(identifierObject) {
894
- let identifier = this.getOrCreateRecordIdentifier(identifierObject);
895
- let keyOptions = getTypeIndex(this._cache.types, identifier.type);
903
+ const identifier = this.getOrCreateRecordIdentifier(identifierObject);
904
+ const typeSet = this._cache.resourcesByType[identifier.type];
905
+ assert(`Expected to find a typeSet for ${identifier.type}`, typeSet);
896
906
  if (identifier.id !== null) {
897
- keyOptions.id.delete(identifier.id);
907
+ typeSet.id.delete(identifier.id);
908
+ }
909
+ this._cache.resources.delete(identifier.lid);
910
+ typeSet.lid.delete(identifier.lid);
911
+ if (macroCondition(getOwnConfig().env.DEBUG)) {
912
+ identifier[DEBUG_STALE_CACHE_OWNER] = identifier[CACHE_OWNER];
898
913
  }
899
- this._cache.lids.delete(identifier.lid);
900
- keyOptions.lid.delete(identifier.lid);
901
- IDENTIFIERS.delete(identifierObject);
914
+ identifier[CACHE_OWNER] = undefined;
915
+ IDENTIFIERS.delete(identifier);
902
916
  this._forget(identifier, 'record');
903
917
  if (macroCondition(getOwnConfig().debug.LOG_IDENTIFIERS)) {
904
918
  // eslint-disable-next-line no-console
@@ -906,29 +920,14 @@ class IdentifierCache {
906
920
  }
907
921
  }
908
922
  destroy() {
923
+ NEW_IDENTIFIERS.clear();
909
924
  this._cache.documents.forEach(identifier => {
910
925
  DOCUMENTS.delete(identifier);
911
926
  });
912
927
  this._reset();
913
928
  }
914
929
  }
915
- function getTypeIndex(typeMap, type) {
916
- let typeIndex = typeMap[type];
917
- if (typeIndex === undefined) {
918
- typeIndex = {
919
- lid: new Map(),
920
- id: new Map()
921
- };
922
- typeMap[type] = typeIndex;
923
- }
924
- return typeIndex;
925
- }
926
- function makeStableRecordIdentifier(id, type, lid, bucket, clientOriginated = false) {
927
- let recordIdentifier = {
928
- lid,
929
- id,
930
- type
931
- };
930
+ function makeStableRecordIdentifier(recordIdentifier, bucket, clientOriginated) {
932
931
  IDENTIFIERS.add(recordIdentifier);
933
932
  if (macroCondition(getOwnConfig().env.DEBUG)) {
934
933
  // we enforce immutability in dev
@@ -943,16 +942,29 @@ function makeStableRecordIdentifier(id, type, lid, bucket, clientOriginated = fa
943
942
  get type() {
944
943
  return recordIdentifier.type;
945
944
  },
945
+ get [CACHE_OWNER]() {
946
+ return recordIdentifier[CACHE_OWNER];
947
+ },
948
+ set [CACHE_OWNER](value) {
949
+ recordIdentifier[CACHE_OWNER] = value;
950
+ },
951
+ get [DEBUG_STALE_CACHE_OWNER]() {
952
+ return recordIdentifier[DEBUG_STALE_CACHE_OWNER];
953
+ },
954
+ set [DEBUG_STALE_CACHE_OWNER](value) {
955
+ recordIdentifier[DEBUG_STALE_CACHE_OWNER] = value;
956
+ },
957
+ // @ts-expect-error debug only
946
958
  toString() {
947
- let {
959
+ const {
948
960
  type,
949
961
  id,
950
962
  lid
951
963
  } = recordIdentifier;
952
- return `${clientOriginated ? '[CLIENT_ORIGINATED] ' : ''}${type}:${id} (${lid})`;
964
+ return `${clientOriginated ? '[CLIENT_ORIGINATED] ' : ''}${String(type)}:${String(id)} (${lid})`;
953
965
  },
954
966
  toJSON() {
955
- let {
967
+ const {
956
968
  type,
957
969
  id,
958
970
  lid
@@ -973,37 +985,33 @@ function makeStableRecordIdentifier(id, type, lid, bucket, clientOriginated = fa
973
985
  }
974
986
  return recordIdentifier;
975
987
  }
976
- function performRecordIdentifierUpdate(identifier, data, updateFn) {
988
+ function performRecordIdentifierUpdate(identifier, keyInfo, data, updateFn) {
977
989
  if (macroCondition(getOwnConfig().env.DEBUG)) {
978
- let {
979
- lid
980
- } = data;
981
- let id = 'id' in data ? data.id : undefined;
982
- let type = 'type' in data && data.type && normalizeModelName(data.type);
990
+ const {
991
+ id,
992
+ type
993
+ } = keyInfo;
983
994
 
984
995
  // get the mutable instance behind our proxy wrapper
985
996
  let wrapper = identifier;
986
997
  identifier = DEBUG_MAP.get(wrapper);
987
- if (lid !== undefined) {
988
- let newLid = coerceId(lid);
989
- if (newLid !== identifier.lid) {
990
- throw new Error(`The 'lid' for a RecordIdentifier cannot be updated once it has been created. Attempted to set lid for '${wrapper}' to '${lid}'.`);
998
+ if (hasLid(data)) {
999
+ const lid = data.lid;
1000
+ if (lid !== identifier.lid) {
1001
+ throw new Error(`The 'lid' for a RecordIdentifier cannot be updated once it has been created. Attempted to set lid for '${wrapper.lid}' to '${lid}'.`);
991
1002
  }
992
1003
  }
993
- if (id !== undefined) {
994
- let newId = coerceId(id);
995
- if (identifier.id !== null && identifier.id !== newId) {
996
- // here we warn and ignore, as this may be a mistake, but we allow the user
997
- // to have multiple cache-keys pointing at a single lid so we cannot error
998
- warn(`The 'id' for a RecordIdentifier should not be updated once it has been set. Attempted to set id for '${wrapper}' to '${newId}'.`, false, {
999
- id: 'ember-data:multiple-ids-for-identifier'
1000
- });
1001
- }
1004
+ if (id && identifier.id !== null && identifier.id !== id) {
1005
+ // here we warn and ignore, as this may be a mistake, but we allow the user
1006
+ // to have multiple cache-keys pointing at a single lid so we cannot error
1007
+ warn(`The 'id' for a RecordIdentifier should not be updated once it has been set. Attempted to set id for '${wrapper.lid}' to '${id}'.`, false, {
1008
+ id: 'ember-data:multiple-ids-for-identifier'
1009
+ });
1002
1010
  }
1003
1011
 
1004
1012
  // TODO consider just ignoring here to allow flexible polymorphic support
1005
1013
  if (type && type !== identifier.type) {
1006
- throw new Error(`The 'type' for a RecordIdentifier cannot be updated once it has been set. Attempted to set type for '${wrapper}' to '${type}'.`);
1014
+ throw new Error(`The 'type' for a RecordIdentifier cannot be updated once it has been set. Attempted to set type for '${wrapper.lid}' to '${type}'.`);
1007
1015
  }
1008
1016
  updateFn(wrapper, data, 'record');
1009
1017
  } else {
@@ -1018,32 +1026,62 @@ function performRecordIdentifierUpdate(identifier, data, updateFn) {
1018
1026
  identifier.id = coerceId(data.id);
1019
1027
  }
1020
1028
  }
1021
- function detectMerge(typesCache, identifier, data, newId, lids) {
1029
+ function detectMerge(cache, keyInfo, identifier, data) {
1030
+ const newId = keyInfo.id;
1022
1031
  const {
1023
1032
  id,
1024
1033
  type,
1025
1034
  lid
1026
1035
  } = identifier;
1036
+ const typeSet = cache.resourcesByType[identifier.type];
1037
+
1038
+ // if the IDs are present but do not match
1039
+ // then check if we have an existing identifier
1040
+ // for the newer ID.
1027
1041
  if (id !== null && id !== newId && newId !== null) {
1028
- let keyOptions = getTypeIndex(typesCache, identifier.type);
1029
- let existingIdentifier = keyOptions.id.get(newId);
1042
+ const existingIdentifier = typeSet && typeSet.id.get(newId);
1030
1043
  return existingIdentifier !== undefined ? existingIdentifier : false;
1031
1044
  } else {
1032
- let newType = data.type && normalizeModelName(data.type);
1045
+ const newType = keyInfo.type;
1033
1046
 
1034
1047
  // If the ids and type are the same but lid is not the same, we should trigger a merge of the identifiers
1035
- if (id !== null && id === newId && newType === type && data.lid && data.lid !== lid) {
1036
- let existingIdentifier = lids.get(data.lid);
1037
- return existingIdentifier !== undefined ? existingIdentifier : false;
1048
+ // we trigger a merge of the identifiers
1049
+ // though probably we should just throw an error here
1050
+ if (id !== null && id === newId && newType === type && hasLid(data) && data.lid !== lid) {
1051
+ return getIdentifierFromLid(cache, data.lid, data) || false;
1052
+
1038
1053
  // If the lids are the same, and ids are the same, but types are different we should trigger a merge of the identifiers
1039
- } else if (id !== null && id === newId && newType && newType !== type && data.lid && data.lid === lid) {
1040
- let keyOptions = getTypeIndex(typesCache, newType);
1041
- let existingIdentifier = keyOptions.id.get(id);
1054
+ } else if (id !== null && id === newId && newType && newType !== type && hasLid(data) && data.lid === lid) {
1055
+ const newTypeSet = cache.resourcesByType[newType];
1056
+ const existingIdentifier = newTypeSet && newTypeSet.id.get(newId);
1042
1057
  return existingIdentifier !== undefined ? existingIdentifier : false;
1043
1058
  }
1044
1059
  }
1045
1060
  return false;
1046
1061
  }
1062
+ function getIdentifierFromLid(cache, lid, resource) {
1063
+ const identifier = cache.resources.get(lid);
1064
+ if (macroCondition(getOwnConfig().debug.LOG_IDENTIFIERS)) {
1065
+ // eslint-disable-next-line no-console
1066
+ console.log(`Identifiers: cache ${identifier ? 'HIT' : 'MISS'} - Non-Stable ${lid}`, resource);
1067
+ }
1068
+ return identifier || null;
1069
+ }
1070
+ function addResourceToCache(cache, identifier) {
1071
+ cache.resources.set(identifier.lid, identifier);
1072
+ let typeSet = cache.resourcesByType[identifier.type];
1073
+ if (!typeSet) {
1074
+ typeSet = {
1075
+ lid: new Map(),
1076
+ id: new Map()
1077
+ };
1078
+ cache.resourcesByType[identifier.type] = typeSet;
1079
+ }
1080
+ typeSet.lid.set(identifier.lid, identifier);
1081
+ if (identifier.id) {
1082
+ typeSet.id.set(identifier.id, identifier);
1083
+ }
1084
+ }
1047
1085
  var _class$1, _descriptor$1;
1048
1086
 
1049
1087
  /**
@@ -1402,9 +1440,12 @@ class InstanceCache {
1402
1440
  store.identifierCache.__configureMerge((identifier, matchedIdentifier, resourceData) => {
1403
1441
  let keptIdentifier = identifier;
1404
1442
  if (identifier.id !== matchedIdentifier.id) {
1443
+ // @ts-expect-error TODO this needs to be fixed
1405
1444
  keptIdentifier = 'id' in resourceData && identifier.id === resourceData.id ? identifier : matchedIdentifier;
1406
1445
  } else if (identifier.type !== matchedIdentifier.type) {
1407
- keptIdentifier = 'type' in resourceData && identifier.type === resourceData.type ? identifier : matchedIdentifier;
1446
+ keptIdentifier =
1447
+ // @ts-expect-error TODO this needs to be fixed
1448
+ 'type' in resourceData && identifier.type === resourceData.type ? identifier : matchedIdentifier;
1408
1449
  }
1409
1450
  let staleIdentifier = identifier === keptIdentifier ? matchedIdentifier : identifier;
1410
1451
 
@@ -1419,6 +1460,7 @@ class InstanceCache {
1419
1460
  // we can probably just "swap" what data source the abandoned
1420
1461
  // record points at so long as
1421
1462
  // it itself is not retained by the store in any way.
1463
+ // @ts-expect-error TODO this needs to be fixed
1422
1464
  if ('id' in resourceData) {
1423
1465
  throw new Error(`Failed to update the 'id' for the RecordIdentifier '${identifier.type}:${String(identifier.id)} (${identifier.lid})' to '${String(resourceData.id)}', because that id is already in use by '${matchedIdentifier.type}:${String(matchedIdentifier.id)} (${matchedIdentifier.lid})'`);
1424
1466
  }
@@ -1557,11 +1599,11 @@ class InstanceCache {
1557
1599
  if (type === undefined) {
1558
1600
  // it would be cool if we could just de-ref cache here
1559
1601
  // but probably would require WeakRef models to do so.
1560
- cache.lids.forEach(identifier => {
1602
+ cache.resources.forEach(identifier => {
1561
1603
  this.unloadRecord(identifier);
1562
1604
  });
1563
1605
  } else {
1564
- const typeCache = cache.types;
1606
+ const typeCache = cache.resourcesByType;
1565
1607
  let identifiers = typeCache[type]?.lid;
1566
1608
  if (identifiers) {
1567
1609
  identifiers.forEach(identifier => {
@@ -1683,7 +1725,7 @@ function _convertPreloadRelationshipToJSON(value, type) {
1683
1725
  if (typeof value === 'string' || typeof value === 'number') {
1684
1726
  return {
1685
1727
  type,
1686
- id: value
1728
+ id: ensureStringId(value)
1687
1729
  };
1688
1730
  }
1689
1731
  // TODO if not a record instance assert it's an identifier
@@ -1712,7 +1754,7 @@ function getShimClass(store, modelName) {
1712
1754
  return shim;
1713
1755
  }
1714
1756
  function mapFromHash(hash) {
1715
- let map = new Map();
1757
+ const map = new Map();
1716
1758
  for (let i in hash) {
1717
1759
  if (Object.prototype.hasOwnProperty.call(hash, i)) {
1718
1760
  map.set(i, hash[i]);
@@ -1721,7 +1763,7 @@ function mapFromHash(hash) {
1721
1763
  return map;
1722
1764
  }
1723
1765
 
1724
- // Mimics the static apis of DSModel
1766
+ // Mimics the static apis of @ember-data/model
1725
1767
  class ShimModelClass {
1726
1768
  constructor(store, modelName) {
1727
1769
  this.__store = store;
@@ -2561,13 +2603,13 @@ let IdentifierArray = (_class3 = class IdentifierArray {
2561
2603
  @type Store
2562
2604
  */
2563
2605
 
2564
- destroy() {
2565
- this.isDestroying = true;
2606
+ destroy(clear) {
2607
+ this.isDestroying = !clear;
2566
2608
  // changing the reference breaks the Proxy
2567
2609
  // this[SOURCE] = [];
2568
2610
  this[SOURCE].length = 0;
2569
2611
  this[NOTIFY]();
2570
- this.isDestroyed = true;
2612
+ this.isDestroyed = !clear;
2571
2613
  }
2572
2614
 
2573
2615
  // length must be on self for proxied methods to work properly
@@ -2802,7 +2844,7 @@ let IdentifierArray = (_class3 = class IdentifierArray {
2802
2844
  }
2803
2845
  this.isUpdating = true;
2804
2846
  let updatingPromise = this._update();
2805
- updatingPromise.finally(() => {
2847
+ void updatingPromise.finally(() => {
2806
2848
  this._updatingPromise = null;
2807
2849
  if (this.isDestroying || this.isDestroyed) {
2808
2850
  return;
@@ -2872,8 +2914,8 @@ class Collection extends IdentifierArray {
2872
2914
  });
2873
2915
  return promise;
2874
2916
  }
2875
- destroy() {
2876
- super.destroy();
2917
+ destroy(clear) {
2918
+ super.destroy(clear);
2877
2919
  this._manager._managed.delete(this);
2878
2920
  this._manager._pending.delete(this);
2879
2921
  }
@@ -2971,10 +3013,14 @@ class RecordArrayManager {
2971
3013
  this._staged = new Map();
2972
3014
  this._keyedArrays = new Map();
2973
3015
  this._identifiers = new Map();
3016
+ this._set = new Map();
3017
+ this._visibilitySet = new Map();
2974
3018
  this._subscription = this.store.notifications.subscribe('resource', (identifier, type) => {
2975
3019
  if (type === 'added') {
3020
+ this._visibilitySet.set(identifier, true);
2976
3021
  this.identifierAdded(identifier);
2977
3022
  } else if (type === 'removed') {
3023
+ this._visibilitySet.set(identifier, false);
2978
3024
  this.identifierRemoved(identifier);
2979
3025
  } else if (type === 'state') {
2980
3026
  this.identifierChanged(identifier);
@@ -2986,7 +3032,7 @@ class RecordArrayManager {
2986
3032
  if (!pending || this.isDestroying || this.isDestroyed) {
2987
3033
  return;
2988
3034
  }
2989
- sync(array, pending);
3035
+ sync(array, pending, this._set.get(array));
2990
3036
  this._pending.delete(array);
2991
3037
  }
2992
3038
 
@@ -3019,6 +3065,7 @@ class RecordArrayManager {
3019
3065
  manager: this
3020
3066
  });
3021
3067
  this._live.set(type, array);
3068
+ this._set.set(array, new Set(identifiers));
3022
3069
  }
3023
3070
  return array;
3024
3071
  }
@@ -3036,6 +3083,7 @@ class RecordArrayManager {
3036
3083
  };
3037
3084
  let array = new Collection(options);
3038
3085
  this._managed.add(array);
3086
+ this._set.set(array, new Set(options.identifiers || []));
3039
3087
  if (config.identifiers) {
3040
3088
  associate(this._identifiers, array, config.identifiers);
3041
3089
  }
@@ -3107,6 +3155,7 @@ class RecordArrayManager {
3107
3155
  const old = source.slice();
3108
3156
  source.length = 0;
3109
3157
  fastPush(source, identifiers);
3158
+ this._set.set(array, new Set(identifiers));
3110
3159
  notifyArray(array);
3111
3160
  array.meta = payload.meta || null;
3112
3161
  array.links = payload.links || null;
@@ -3144,21 +3193,30 @@ class RecordArrayManager {
3144
3193
  }
3145
3194
  identifierChanged(identifier) {
3146
3195
  let newState = this.store._instanceCache.recordIsLoaded(identifier, true);
3196
+
3197
+ // if the change matches the most recent direct added/removed
3198
+ // state, then we can ignore it
3199
+ if (this._visibilitySet.get(identifier) === newState) {
3200
+ return;
3201
+ }
3147
3202
  if (newState) {
3148
3203
  this.identifierAdded(identifier);
3149
3204
  } else {
3150
3205
  this.identifierRemoved(identifier);
3151
3206
  }
3152
3207
  }
3153
- clear() {
3154
- this._live.forEach(array => array.destroy());
3155
- this._managed.forEach(array => array.destroy());
3208
+ clear(isClear = true) {
3209
+ this._live.forEach(array => array.destroy(isClear));
3210
+ this._managed.forEach(array => array.destroy(isClear));
3156
3211
  this._managed.clear();
3157
3212
  this._identifiers.clear();
3213
+ this._pending.clear();
3214
+ this._set.forEach(set => set.clear());
3215
+ this._visibilitySet.clear();
3158
3216
  }
3159
3217
  destroy() {
3160
3218
  this.isDestroying = true;
3161
- this.clear();
3219
+ this.clear(false);
3162
3220
  this._live.clear();
3163
3221
  this.isDestroyed = true;
3164
3222
  // eslint-disable-next-line @typescript-eslint/no-unsafe-argument
@@ -3187,19 +3245,20 @@ function disassociateIdentifier(ArraysCache, array, identifier) {
3187
3245
  cache.delete(array);
3188
3246
  }
3189
3247
  }
3190
- function sync(array, changes) {
3248
+ function sync(array, changes, arraySet) {
3191
3249
  let state = array[SOURCE];
3192
3250
  const adds = [];
3193
3251
  const removes = [];
3194
3252
  changes.forEach((value, key) => {
3195
3253
  if (value === 'add') {
3196
3254
  // likely we want to keep a Set along-side
3197
- if (state.includes(key)) {
3255
+ if (arraySet.has(key)) {
3198
3256
  return;
3199
3257
  }
3200
3258
  adds.push(key);
3259
+ arraySet.add(key);
3201
3260
  } else {
3202
- if (state.includes(key)) {
3261
+ if (arraySet.has(key)) {
3203
3262
  removes.push(key);
3204
3263
  }
3205
3264
  }
@@ -3207,6 +3266,7 @@ function sync(array, changes) {
3207
3266
  if (removes.length) {
3208
3267
  if (removes.length === state.length) {
3209
3268
  state.length = 0;
3269
+ arraySet.clear();
3210
3270
  // changing the reference breaks the Proxy
3211
3271
  // state = array[SOURCE] = [];
3212
3272
  } else {
@@ -3214,6 +3274,7 @@ function sync(array, changes) {
3214
3274
  const index = state.indexOf(i);
3215
3275
  if (index !== -1) {
3216
3276
  state.splice(index, 1);
3277
+ arraySet.delete(i);
3217
3278
  }
3218
3279
  });
3219
3280
  }
@@ -3232,11 +3293,9 @@ function sync(array, changes) {
3232
3293
  }
3233
3294
  }
3234
3295
 
3235
- /**
3236
- * @module @ember-data/store
3237
- */
3238
3296
  const Touching = Symbol('touching');
3239
3297
  const RequestPromise = Symbol('promise');
3298
+ const EMPTY_ARR = macroCondition(getOwnConfig().env.DEBUG) ? Object.freeze([]) : [];
3240
3299
  function hasRecordIdentifier(op) {
3241
3300
  return 'recordIdentifier' in op;
3242
3301
  }
@@ -3250,9 +3309,9 @@ function hasRecordIdentifier(op) {
3250
3309
  */
3251
3310
  class RequestStateService {
3252
3311
  constructor(store) {
3253
- this._pending = Object.create(null);
3312
+ this._pending = new Map();
3254
3313
  this._done = new Map();
3255
- this._subscriptions = Object.create(null);
3314
+ this._subscriptions = new Map();
3256
3315
  this._toFlush = [];
3257
3316
  this._store = void 0;
3258
3317
  this._store = store;
@@ -3263,10 +3322,10 @@ class RequestStateService {
3263
3322
  _enqueue(promise, queryRequest) {
3264
3323
  let query = queryRequest.data[0];
3265
3324
  if (hasRecordIdentifier(query)) {
3266
- let lid = query.recordIdentifier.lid;
3325
+ const identifier = query.recordIdentifier;
3267
3326
  let type = query.op === 'saveRecord' ? 'mutation' : 'query';
3268
- if (!this._pending[lid]) {
3269
- this._pending[lid] = [];
3327
+ if (!this._pending.has(identifier)) {
3328
+ this._pending.set(identifier, []);
3270
3329
  }
3271
3330
  let request = {
3272
3331
  state: 'pending',
@@ -3275,10 +3334,10 @@ class RequestStateService {
3275
3334
  };
3276
3335
  request[Touching] = [query.recordIdentifier];
3277
3336
  request[RequestPromise] = promise;
3278
- this._pending[lid].push(request);
3337
+ this._pending.get(identifier).push(request);
3279
3338
  this._triggerSubscriptions(request);
3280
3339
  return promise.then(result => {
3281
- this._dequeue(lid, request);
3340
+ this._dequeue(identifier, request);
3282
3341
  let finalizedRequest = {
3283
3342
  state: 'fulfilled',
3284
3343
  request: queryRequest,
@@ -3292,7 +3351,7 @@ class RequestStateService {
3292
3351
  this._triggerSubscriptions(finalizedRequest);
3293
3352
  return result;
3294
3353
  }, error => {
3295
- this._dequeue(lid, request);
3354
+ this._dequeue(identifier, request);
3296
3355
  let finalizedRequest = {
3297
3356
  state: 'rejected',
3298
3357
  request: queryRequest,
@@ -3329,13 +3388,15 @@ class RequestStateService {
3329
3388
  }
3330
3389
  _flushRequest(req) {
3331
3390
  req[Touching].forEach(identifier => {
3332
- if (this._subscriptions[identifier.lid]) {
3333
- this._subscriptions[identifier.lid].forEach(callback => callback(req));
3391
+ const subscriptions = this._subscriptions.get(identifier);
3392
+ if (subscriptions) {
3393
+ subscriptions.forEach(callback => callback(req));
3334
3394
  }
3335
3395
  });
3336
3396
  }
3337
- _dequeue(lid, request) {
3338
- this._pending[lid] = this._pending[lid].filter(req => req !== request);
3397
+ _dequeue(identifier, request) {
3398
+ const pending = this._pending.get(identifier);
3399
+ this._pending.set(identifier, pending.filter(req => req !== request));
3339
3400
  }
3340
3401
  _addDone(request) {
3341
3402
  request[Touching].forEach(identifier => {
@@ -3346,7 +3407,7 @@ class RequestStateService {
3346
3407
  requests = requests.filter(req => {
3347
3408
  // TODO add support for multiple
3348
3409
  let data;
3349
- if (req.request.data instanceof Array) {
3410
+ if (Array.isArray(req.request.data)) {
3350
3411
  data = req.request.data[0];
3351
3412
  } else {
3352
3413
  data = req.request.data;
@@ -3390,10 +3451,12 @@ class RequestStateService {
3390
3451
  * @param {(state: RequestState) => void} callback
3391
3452
  */
3392
3453
  subscribeForRecord(identifier, callback) {
3393
- if (!this._subscriptions[identifier.lid]) {
3394
- this._subscriptions[identifier.lid] = [];
3454
+ let subscriptions = this._subscriptions.get(identifier);
3455
+ if (!subscriptions) {
3456
+ subscriptions = [];
3457
+ this._subscriptions.set(identifier, subscriptions);
3395
3458
  }
3396
- this._subscriptions[identifier.lid].push(callback);
3459
+ subscriptions.push(callback);
3397
3460
  }
3398
3461
 
3399
3462
  /**
@@ -3405,10 +3468,7 @@ class RequestStateService {
3405
3468
  * @returns {RequestState[]} an array of request states for any pending requests for the given identifier
3406
3469
  */
3407
3470
  getPendingRequestsForRecord(identifier) {
3408
- if (this._pending[identifier.lid]) {
3409
- return this._pending[identifier.lid];
3410
- }
3411
- return [];
3471
+ return this._pending.get(identifier) || EMPTY_ARR;
3412
3472
  }
3413
3473
 
3414
3474
  /**
@@ -3427,6 +3487,9 @@ class RequestStateService {
3427
3487
  return null;
3428
3488
  }
3429
3489
  }
3490
+ function isNonEmptyString(str) {
3491
+ return Boolean(str && typeof str === 'string');
3492
+ }
3430
3493
  function constructResource(type, id, lid) {
3431
3494
  if (typeof type === 'object' && type !== null) {
3432
3495
  let resource = type;
@@ -3623,17 +3686,34 @@ class Store extends EmberObject {
3623
3686
  _run(cb) {
3624
3687
  assert(`EmberData should never encounter a nested run`, !this._cbs);
3625
3688
  const _cbs = this._cbs = {};
3626
- cb();
3627
- if (_cbs.coalesce) {
3628
- _cbs.coalesce();
3629
- }
3630
- if (_cbs.sync) {
3631
- _cbs.sync();
3632
- }
3633
- if (_cbs.notify) {
3634
- _cbs.notify();
3689
+ if (macroCondition(getOwnConfig().env.DEBUG)) {
3690
+ try {
3691
+ cb();
3692
+ if (_cbs.coalesce) {
3693
+ _cbs.coalesce();
3694
+ }
3695
+ if (_cbs.sync) {
3696
+ _cbs.sync();
3697
+ }
3698
+ if (_cbs.notify) {
3699
+ _cbs.notify();
3700
+ }
3701
+ } finally {
3702
+ this._cbs = null;
3703
+ }
3704
+ } else {
3705
+ cb();
3706
+ if (_cbs.coalesce) {
3707
+ _cbs.coalesce();
3708
+ }
3709
+ if (_cbs.sync) {
3710
+ _cbs.sync();
3711
+ }
3712
+ if (_cbs.notify) {
3713
+ _cbs.notify();
3714
+ }
3715
+ this._cbs = null;
3635
3716
  }
3636
- this._cbs = null;
3637
3717
  }
3638
3718
  _join(cb) {
3639
3719
  if (this._cbs) {
@@ -3666,9 +3746,8 @@ class Store extends EmberObject {
3666
3746
  if (macroCondition(getOwnConfig().env.TESTING)) {
3667
3747
  const all = [];
3668
3748
  const pending = this._requestCache._pending;
3669
- const lids = Object.keys(pending);
3670
- lids.forEach(lid => {
3671
- all.push(...pending[lid].map(v => v[RequestPromise]));
3749
+ pending.forEach(requests => {
3750
+ all.push(...requests.map(v => v[RequestPromise]));
3672
3751
  });
3673
3752
  this.requestManager._pending.forEach(v => all.push(v));
3674
3753
  const promise = Promise.allSettled(all);
@@ -3994,7 +4073,7 @@ class Store extends EmberObject {
3994
4073
  };
3995
4074
  if (resource.id) {
3996
4075
  const identifier = this.identifierCache.peekRecordIdentifier(resource);
3997
- assert(`The id ${properties.id} has already been used with another '${normalizedModelName}' record.`, !identifier);
4076
+ assert(`The id ${String(properties.id)} has already been used with another '${normalizedModelName}' record.`, !identifier);
3998
4077
  }
3999
4078
  const identifier = this.identifierCache.createIdentifierForNewRecord(resource);
4000
4079
  const cache = this.cache;
@@ -4476,7 +4555,7 @@ class Store extends EmberObject {
4476
4555
  assertDestroyingStore(this, 'peekRecord');
4477
4556
  }
4478
4557
  assert(`You need to pass a model name to the store's peekRecord method`, identifier);
4479
- assert(`Passing classes to store methods has been removed. Please pass a dasherized string instead of ${identifier}`, typeof identifier === 'string');
4558
+ assert(`Passing classes to store methods has been removed. Please pass a dasherized string instead of ${String(identifier)}`, typeof identifier === 'string');
4480
4559
  const type = normalizeModelName(identifier);
4481
4560
  const normalizedId = ensureStringId(id);
4482
4561
  const resource = {
@@ -4864,7 +4943,7 @@ class Store extends EmberObject {
4864
4943
  if (macroCondition(getOwnConfig().env.DEBUG)) {
4865
4944
  assertDestroyedStoreOnly(this, 'unloadAll');
4866
4945
  }
4867
- assert(`Passing classes to store methods has been removed. Please pass a dasherized string instead of ${modelName}`, !modelName || typeof modelName === 'string');
4946
+ assert(`Passing classes to store methods has been removed. Please pass a dasherized string instead of ${String(modelName)}`, !modelName || typeof modelName === 'string');
4868
4947
  this._join(() => {
4869
4948
  if (modelName === undefined) {
4870
4949
  // destroy the graph before unloadAll
@@ -5032,7 +5111,7 @@ class Store extends EmberObject {
5032
5111
  @method _push
5033
5112
  @private
5034
5113
  @param {Object} jsonApiDoc
5035
- @return {StableRecordIdentifier|Array<StableRecordIdentifier>} identifiers for the primary records that had data loaded
5114
+ @return {StableRecordIdentifier|Array<StableRecordIdentifier>|null} identifiers for the primary records that had data loaded
5036
5115
  */
5037
5116
  _push(jsonApiDoc, asyncFlush) {
5038
5117
  if (macroCondition(getOwnConfig().env.DEBUG)) {
@@ -5058,7 +5137,7 @@ class Store extends EmberObject {
5058
5137
  });
5059
5138
  });
5060
5139
  this._enableAsyncFlush = null;
5061
- return ret.data;
5140
+ return 'data' in ret ? ret.data : null;
5062
5141
  }
5063
5142
 
5064
5143
  /**
@@ -5109,19 +5188,10 @@ class Store extends EmberObject {
5109
5188
  if (macroCondition(getOwnConfig().env.DEBUG)) {
5110
5189
  assertDestroyingStore(this, 'pushPayload');
5111
5190
  }
5112
- let serializer;
5113
- let payload;
5114
- if (!inputPayload) {
5115
- payload = modelName;
5116
- serializer = this.serializerFor('application');
5117
- assert(`You cannot use 'store#pushPayload' without a modelName unless your default serializer defines 'pushPayload'`, typeof serializer.pushPayload === 'function');
5118
- } else {
5119
- payload = inputPayload;
5120
- assert(`Passing classes to store methods has been removed. Please pass a dasherized string instead of ${modelName}`, typeof modelName === 'string');
5121
- let normalizedModelName = normalizeModelName(modelName);
5122
- serializer = this.serializerFor(normalizedModelName);
5123
- }
5124
- assert(`You must define a pushPayload method in your serializer in order to call store.pushPayload`, serializer.pushPayload);
5191
+ const payload = inputPayload || modelName;
5192
+ const normalizedModelName = inputPayload ? normalizeModelName(modelName) : 'application';
5193
+ const serializer = this.serializerFor(normalizedModelName);
5194
+ assert(`You cannot use 'store.pushPayload(<type>, <payload>)' unless the serializer for '${normalizedModelName}' defines 'pushPayload'`, serializer && typeof serializer.pushPayload === 'function');
5125
5195
  serializer.pushPayload(this, payload);
5126
5196
  }
5127
5197
 
@@ -5147,7 +5217,7 @@ class Store extends EmberObject {
5147
5217
  return Promise.reject(`Record Is Disconnected`);
5148
5218
  }
5149
5219
  // TODO we used to check if the record was destroyed here
5150
- assert(`Cannot initiate a save request for an unloaded record: ${identifier}`, this._instanceCache.recordIsLoaded(identifier));
5220
+ assert(`Cannot initiate a save request for an unloaded record: ${identifier.lid}`, this._instanceCache.recordIsLoaded(identifier));
5151
5221
  if (resourceIsFullyDeleted(this._instanceCache, identifier)) {
5152
5222
  return Promise.resolve(record);
5153
5223
  }
@@ -5235,11 +5305,11 @@ class Store extends EmberObject {
5235
5305
  }
5236
5306
  assert(`You need to pass a model name to the store's normalize method`, modelName);
5237
5307
  assert(`Passing classes to store methods has been removed. Please pass a dasherized string instead of ${typeof modelName}`, typeof modelName === 'string');
5238
- let normalizedModelName = normalizeModelName(modelName);
5239
- let serializer = this.serializerFor(normalizedModelName);
5240
- let model = this.modelFor(normalizedModelName);
5241
- assert(`You must define a normalize method in your serializer in order to call store.normalize`, serializer?.normalize);
5242
- return serializer.normalize(model, payload);
5308
+ const normalizedModelName = normalizeModelName(modelName);
5309
+ const serializer = this.serializerFor(normalizedModelName);
5310
+ const schema = this.modelFor(normalizedModelName);
5311
+ assert(`You must define a normalize method in your serializer in order to call store.normalize`, typeof serializer?.normalize === 'function');
5312
+ return serializer.normalize(schema, payload);
5243
5313
  }
5244
5314
 
5245
5315
  /**
@@ -5268,7 +5338,7 @@ class Store extends EmberObject {
5268
5338
  if (adapter) {
5269
5339
  return adapter;
5270
5340
  }
5271
- let owner = getOwner(this);
5341
+ const owner = getOwner(this);
5272
5342
 
5273
5343
  // name specific adapter
5274
5344
  adapter = owner.lookup(`adapter:${normalizedModelName}`);
@@ -5315,9 +5385,9 @@ class Store extends EmberObject {
5315
5385
  if (serializer) {
5316
5386
  return serializer;
5317
5387
  }
5318
- let owner = getOwner(this);
5319
5388
 
5320
5389
  // by name
5390
+ const owner = getOwner(this);
5321
5391
  serializer = owner.lookup(`serializer:${normalizedModelName}`);
5322
5392
  if (serializer !== undefined) {
5323
5393
  _serializerCache[normalizedModelName] = serializer;
@@ -5369,9 +5439,11 @@ class Store extends EmberObject {
5369
5439
  let assertDestroyingStore;
5370
5440
  let assertDestroyedStoreOnly;
5371
5441
  if (macroCondition(getOwnConfig().env.DEBUG)) {
5372
- assertDestroyingStore = function assertDestroyedStore(store, method) {
5442
+ // eslint-disable-next-line @typescript-eslint/no-shadow
5443
+ assertDestroyingStore = function assertDestroyingStore(store, method) {
5373
5444
  assert(`Attempted to call store.${method}(), but the store instance has already been destroyed.`, !(store.isDestroying || store.isDestroyed));
5374
5445
  };
5446
+ // eslint-disable-next-line @typescript-eslint/no-shadow
5375
5447
  assertDestroyedStoreOnly = function assertDestroyedStoreOnly(store, method) {
5376
5448
  assert(`Attempted to call store.${method}(), but the store instance has already been destroyed.`, !store.isDestroyed);
5377
5449
  };