@ember-data/model 4.8.0-alpha.3 → 4.8.0-alpha.4

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.
@@ -5,24 +5,22 @@ import { importSync } from '@embroider/macros';
5
5
  import { all, resolve } from 'rsvp';
6
6
 
7
7
  import { HAS_RECORD_DATA_PACKAGE } from '@ember-data/private-build-infra';
8
- import type {
9
- BelongsToRelationship,
10
- ManyRelationship,
11
- RecordData as DefaultRecordData,
12
- } from '@ember-data/record-data/-private';
8
+ import type { BelongsToRelationship, ManyRelationship } from '@ember-data/record-data/-private';
13
9
  import type { UpgradedMeta } from '@ember-data/record-data/-private/graph/-edge-definition';
14
- import type { RelationshipState } from '@ember-data/record-data/-private/graph/-state';
10
+ import ImplicitRelationship from '@ember-data/record-data/-private/relationships/state/implicit';
15
11
  import type Store from '@ember-data/store';
16
- import { recordDataFor, recordIdentifierFor, storeFor } from '@ember-data/store/-private';
12
+ import { recordIdentifierFor, storeFor } from '@ember-data/store/-private';
17
13
  import { IdentifierCache } from '@ember-data/store/-private/caches/identifier-cache';
18
14
  import type { DSModel } from '@ember-data/types/q/ds-model';
19
- import { ResourceIdentifierObject } from '@ember-data/types/q/ember-data-json-api';
15
+ import {
16
+ CollectionResourceRelationship,
17
+ ResourceIdentifierObject,
18
+ SingleResourceRelationship,
19
+ } from '@ember-data/types/q/ember-data-json-api';
20
20
  import type { StableRecordIdentifier } from '@ember-data/types/q/identifier';
21
21
  import type { RecordData } from '@ember-data/types/q/record-data';
22
22
  import type { JsonApiRelationship } from '@ember-data/types/q/record-data-json-api';
23
- import type { RelationshipSchema } from '@ember-data/types/q/record-data-schemas';
24
23
  import type { RecordInstance } from '@ember-data/types/q/record-instance';
25
- import type { DefaultSingleResourceRelationship } from '@ember-data/types/q/relationship-record-data';
26
24
  import type { FindOptions } from '@ember-data/types/q/store';
27
25
  import type { Dict } from '@ember-data/types/q/utils';
28
26
 
@@ -43,7 +41,7 @@ type PromiseBelongsToFactory = { create(args: BelongsToProxyCreateArgs): Promise
43
41
  export class LegacySupport {
44
42
  declare record: DSModel;
45
43
  declare store: Store;
46
- declare recordData: DefaultRecordData;
44
+ declare recordData: RecordData;
47
45
  declare references: Dict<BelongsToReference | HasManyReference>;
48
46
  declare identifier: StableRecordIdentifier;
49
47
  declare _manyArrayCache: Dict<ManyArray>;
@@ -57,7 +55,7 @@ export class LegacySupport {
57
55
  this.record = record;
58
56
  this.store = storeFor(record)!;
59
57
  this.identifier = recordIdentifierFor(record);
60
- this.recordData = this.store._instanceCache.getRecordData(this.identifier) as DefaultRecordData;
58
+ this.recordData = this.store._instanceCache.getRecordData(this.identifier);
61
59
 
62
60
  this._manyArrayCache = Object.create(null) as Dict<ManyArray>;
63
61
  this._relationshipPromisesCache = Object.create(null) as Dict<Promise<ManyArray | RecordInstance>>;
@@ -67,16 +65,16 @@ export class LegacySupport {
67
65
 
68
66
  _findBelongsTo(
69
67
  key: string,
70
- resource: DefaultSingleResourceRelationship,
71
- relationshipMeta: RelationshipSchema,
68
+ resource: SingleResourceRelationship,
69
+ relationship: BelongsToRelationship,
72
70
  options?: FindOptions
73
71
  ): Promise<RecordInstance | null> {
74
72
  // TODO @runspired follow up if parent isNew then we should not be attempting load here
75
73
  // TODO @runspired follow up on whether this should be in the relationship requests cache
76
- return this._findBelongsToByJsonApiResource(resource, this.identifier, relationshipMeta, options).then(
74
+ return this._findBelongsToByJsonApiResource(resource, this.identifier, relationship, options).then(
77
75
  (identifier: StableRecordIdentifier | null) =>
78
- handleCompletedRelationshipRequest(this, key, resource._relationship, identifier),
79
- (e: Error) => handleCompletedRelationshipRequest(this, key, resource._relationship, null, e)
76
+ handleCompletedRelationshipRequest(this, key, relationship, identifier),
77
+ (e: Error) => handleCompletedRelationshipRequest(this, key, relationship, null, e)
80
78
  );
81
79
  }
82
80
 
@@ -86,15 +84,16 @@ export class LegacySupport {
86
84
  return loadingPromise;
87
85
  }
88
86
 
89
- let resource = this.recordData.getBelongsTo(key);
90
- // TODO move this to a public api
91
- if (resource._relationship) {
92
- resource._relationship.state.hasFailedLoadAttempt = false;
93
- resource._relationship.state.shouldForceReload = true;
94
- }
95
- let relationshipMeta = this.store.getSchemaDefinitionService().relationshipsDefinitionFor(this.identifier)[key];
96
- assert(`Attempted to reload a belongsTo relationship but no definition exists for it`, relationshipMeta);
97
- let promise = this._findBelongsTo(key, resource, relationshipMeta, options);
87
+ const graphFor = (
88
+ importSync('@ember-data/record-data/-private') as typeof import('@ember-data/record-data/-private')
89
+ ).graphFor;
90
+ const relationship = graphFor(this.store).get(this.identifier, key);
91
+ assert(`Expected ${key} to be a belongs-to relationship`, isBelongsTo(relationship));
92
+
93
+ let resource = this.recordData.getRelationship(this.identifier, key) as SingleResourceRelationship;
94
+ relationship.state.hasFailedLoadAttempt = false;
95
+ relationship.state.shouldForceReload = true;
96
+ let promise = this._findBelongsTo(key, resource, relationship, options);
98
97
  if (this._relationshipProxyCache[key]) {
99
98
  return this._updatePromiseProxyFor('belongsTo', key, { promise });
100
99
  }
@@ -103,28 +102,31 @@ export class LegacySupport {
103
102
 
104
103
  getBelongsTo(key: string, options?: FindOptions): PromiseBelongsTo | RecordInstance | null {
105
104
  const { identifier, recordData } = this;
106
- let resource = recordData.getBelongsTo(key);
105
+ let resource = recordData.getRelationship(this.identifier, key) as SingleResourceRelationship;
107
106
  let relatedIdentifier =
108
107
  resource && resource.data ? this.store.identifierCache.getOrCreateRecordIdentifier(resource.data) : null;
109
- let relationshipMeta = this.store.getSchemaDefinitionService().relationshipsDefinitionFor(identifier)[key];
110
- assert(`Attempted to access a belongsTo relationship but no definition exists for it`, relationshipMeta);
111
108
 
112
- let store = this.store;
113
- let async = relationshipMeta.options.async;
114
- let isAsync = typeof async === 'undefined' ? true : async;
109
+ const store = this.store;
110
+ const graphFor = (
111
+ importSync('@ember-data/record-data/-private') as typeof import('@ember-data/record-data/-private')
112
+ ).graphFor;
113
+ const relationship = graphFor(store).get(this.identifier, key);
114
+ assert(`Expected ${key} to be a belongs-to relationship`, isBelongsTo(relationship));
115
+
116
+ let isAsync = relationship.definition.isAsync;
115
117
  let _belongsToState: BelongsToProxyMeta = {
116
118
  key,
117
119
  store,
118
120
  legacySupport: this,
119
- modelName: relationshipMeta.type,
121
+ modelName: relationship.definition.type,
120
122
  };
121
123
 
122
124
  if (isAsync) {
123
- if (resource._relationship.state.hasFailedLoadAttempt) {
125
+ if (relationship.state.hasFailedLoadAttempt) {
124
126
  return this._relationshipProxyCache[key] as PromiseBelongsTo;
125
127
  }
126
128
 
127
- let promise = this._findBelongsTo(key, resource, relationshipMeta, options);
129
+ let promise = this._findBelongsTo(key, resource, relationship, options);
128
130
 
129
131
  return this._updatePromiseProxyFor('belongsTo', key, {
130
132
  promise,
@@ -139,7 +141,7 @@ export class LegacySupport {
139
141
  assert(
140
142
  `You looked up the '${key}' relationship on a '${identifier.type}' with id ${
141
143
  identifier.id || 'null'
142
- } but some of the associated records were not loaded. Either make sure they are all loaded together with the parent record, or specify that the relationship is async (\`belongsTo({ async: true })\`)`,
144
+ } but some of the associated records were not loaded. Either make sure they are all loaded together with the parent record, or specify that the relationship is async (\`belongsTo(<type>, { async: true, inverse: <inverse> })\`)`,
143
145
  toReturn === null || store._instanceCache.recordIsLoaded(relatedIdentifier, true)
144
146
  );
145
147
  return toReturn;
@@ -148,7 +150,7 @@ export class LegacySupport {
148
150
  }
149
151
 
150
152
  setDirtyBelongsTo(key: string, value: RecordInstance | null) {
151
- return this.recordData.setDirtyBelongsTo(key, extractRecordDataFromRecord(value));
153
+ return this.recordData.setBelongsTo(this.identifier, key, extractIdentifierFromRecord(value));
152
154
  }
153
155
 
154
156
  getManyArray(key: string, definition?: UpgradedMeta): ManyArray {
@@ -165,6 +167,7 @@ export class LegacySupport {
165
167
  manyArray = (ManyArray as unknown as ManyArrayFactory).create({
166
168
  store: this.store,
167
169
  type: this.store.modelFor(definition.type),
170
+ identifier: this.identifier,
168
171
  recordData: this.recordData,
169
172
  key,
170
173
  isPolymorphic: definition.isPolymorphic,
@@ -191,7 +194,7 @@ export class LegacySupport {
191
194
  return loadingPromise;
192
195
  }
193
196
 
194
- const jsonApi = this.recordData.getHasMany(key);
197
+ const jsonApi = this.recordData.getRelationship(this.identifier, key) as CollectionResourceRelationship;
195
198
 
196
199
  loadingPromise = this._findHasManyByJsonApiResource(jsonApi, this.identifier, relationship, options).then(
197
200
  () => handleCompletedRelationshipRequest(this, key, relationship, manyArray),
@@ -250,7 +253,7 @@ export class LegacySupport {
250
253
  assert(
251
254
  `You looked up the '${key}' relationship on a '${this.identifier.type}' with id ${
252
255
  this.identifier.id || 'null'
253
- } but some of the associated records were not loaded. Either make sure they are all loaded together with the parent record, or specify that the relationship is async ('hasMany({ async: true })')`,
256
+ } but some of the associated records were not loaded. Either make sure they are all loaded together with the parent record, or specify that the relationship is async ('hasMany(<type>, { async: true, inverse: <inverse> })')`,
254
257
  !anyUnloaded(this.store, relationship)
255
258
  );
256
259
 
@@ -262,7 +265,7 @@ export class LegacySupport {
262
265
 
263
266
  setDirtyHasMany(key: string, records: RecordInstance[]) {
264
267
  assertRecordsPassedToHasMany(records);
265
- return this.recordData.setDirtyHasMany(key, extractRecordDatasFromRecords(records));
268
+ return this.recordData.setHasMany(this.identifier, key, extractIdentifiersFromRecords(records));
266
269
  }
267
270
 
268
271
  _updatePromiseProxyFor(kind: 'hasMany', key: string, args: HasManyProxyCreateArgs): PromiseManyArray;
@@ -343,7 +346,7 @@ export class LegacySupport {
343
346
  }
344
347
 
345
348
  _findHasManyByJsonApiResource(
346
- resource,
349
+ resource: CollectionResourceRelationship,
347
350
  parentIdentifier: StableRecordIdentifier,
348
351
  relationship: ManyRelationship,
349
352
  options: FindOptions = {}
@@ -353,12 +356,10 @@ export class LegacySupport {
353
356
  return resolve();
354
357
  }
355
358
  const { definition, state } = relationship;
356
- let adapter = this.store.adapterFor(definition.type);
357
-
358
- let { isStale, hasDematerializedInverse, hasReceivedData, isEmpty, shouldForceReload } = state;
359
+ const adapter = this.store.adapterFor(definition.type);
360
+ const { isStale, hasDematerializedInverse, hasReceivedData, isEmpty, shouldForceReload } = state;
359
361
  const allInverseRecordsAreLoaded = areAllInverseRecordsLoaded(this.store, resource);
360
-
361
- let shouldFindViaLink =
362
+ const shouldFindViaLink =
362
363
  resource.links &&
363
364
  resource.links.related &&
364
365
  (typeof adapter.findHasMany === 'function' || typeof resource.data === 'undefined') &&
@@ -368,9 +369,9 @@ export class LegacySupport {
368
369
  if (shouldFindViaLink) {
369
370
  // findHasMany, although not public, does not need to care about our upgrade relationship definitions
370
371
  // and can stick with the public definition API for now.
371
- const relationshipMeta = this.store._instanceCache._storeWrapper.relationshipsDefinitionFor(
372
- definition.inverseType
373
- )[definition.key];
372
+ const relationshipMeta = this.store
373
+ .getSchemaDefinitionService()
374
+ .relationshipsDefinitionFor({ type: definition.inverseType })[definition.key];
374
375
  let adapter = this.store.adapterFor(parentIdentifier.type);
375
376
 
376
377
  /*
@@ -393,16 +394,16 @@ export class LegacySupport {
393
394
  typeof adapter.findHasMany === 'function'
394
395
  );
395
396
 
396
- return _findHasMany(adapter, this.store, parentIdentifier, resource.links.related, relationshipMeta, options);
397
+ return _findHasMany(adapter, this.store, parentIdentifier, resource.links!.related, relationshipMeta, options);
397
398
  }
398
399
 
399
- let preferLocalCache = hasReceivedData && !isEmpty;
400
-
401
- let hasLocalPartialData =
400
+ const preferLocalCache = hasReceivedData && !isEmpty;
401
+ const hasLocalPartialData =
402
402
  hasDematerializedInverse || (isEmpty && Array.isArray(resource.data) && resource.data.length > 0);
403
403
 
404
404
  // fetch using data, pulling from local cache if possible
405
405
  if (!shouldForceReload && !isStale && (preferLocalCache || hasLocalPartialData)) {
406
+ assert(`Expected collection to be an array`, Array.isArray(resource.data));
406
407
  let finds = new Array(resource.data.length);
407
408
  for (let i = 0; i < resource.data.length; i++) {
408
409
  let identifier = this.store.identifierCache.getOrCreateRecordIdentifier(resource.data[i]);
@@ -416,6 +417,7 @@ export class LegacySupport {
416
417
 
417
418
  // fetch by data
418
419
  if (hasData || hasLocalPartialData) {
420
+ assert(`Expected collection to be an array`, Array.isArray(resource.data));
419
421
  let identifiers = resource.data.map((json) => this.store.identifierCache.getOrCreateRecordIdentifier(json));
420
422
  let fetches = new Array(identifiers.length);
421
423
  const manager = this.store._fetchManager;
@@ -437,9 +439,9 @@ export class LegacySupport {
437
439
  }
438
440
 
439
441
  _findBelongsToByJsonApiResource(
440
- resource,
442
+ resource: SingleResourceRelationship,
441
443
  parentIdentifier: StableRecordIdentifier,
442
- relationshipMeta,
444
+ relationship: BelongsToRelationship,
443
445
  options: FindOptions = {}
444
446
  ): Promise<StableRecordIdentifier | null> {
445
447
  if (!resource) {
@@ -448,32 +450,33 @@ export class LegacySupport {
448
450
 
449
451
  const identifier = resource.data ? this.store.identifierCache.getOrCreateRecordIdentifier(resource.data) : null;
450
452
 
451
- let { isStale, hasDematerializedInverse, hasReceivedData, isEmpty, shouldForceReload } = resource._relationship
452
- .state as RelationshipState;
453
- const allInverseRecordsAreLoaded = areAllInverseRecordsLoaded(this.store, resource);
454
-
455
- let shouldFindViaLink =
456
- resource.links &&
457
- resource.links.related &&
458
- (shouldForceReload || hasDematerializedInverse || isStale || (!allInverseRecordsAreLoaded && !isEmpty));
453
+ let { isStale, hasDematerializedInverse, hasReceivedData, isEmpty, shouldForceReload } = relationship.state;
459
454
 
460
- if (identifier) {
461
- // short circuit if we are already loading
462
- let pendingRequest = this.store._fetchManager.getPendingFetch(identifier, options);
463
- if (pendingRequest) {
464
- return pendingRequest;
465
- }
455
+ // short circuit if we are already loading
456
+ let pendingRequest = identifier && this.store._fetchManager.getPendingFetch(identifier, options);
457
+ if (pendingRequest) {
458
+ return pendingRequest;
466
459
  }
467
460
 
461
+ const allInverseRecordsAreLoaded = areAllInverseRecordsLoaded(this.store, resource);
462
+ const shouldFindViaLink =
463
+ resource.links?.related &&
464
+ (shouldForceReload || hasDematerializedInverse || isStale || (!allInverseRecordsAreLoaded && !isEmpty));
465
+
468
466
  // fetch via link
469
467
  if (shouldFindViaLink) {
470
- return _findBelongsTo(this.store, parentIdentifier, resource.links.related, relationshipMeta, options);
468
+ const relationshipMeta = this.store.getSchemaDefinitionService().relationshipsDefinitionFor(this.identifier)[
469
+ relationship.definition.key
470
+ ];
471
+ assert(`Attempted to access a belongsTo relationship but no definition exists for it`, relationshipMeta);
472
+
473
+ return _findBelongsTo(this.store, parentIdentifier, resource.links!.related, relationshipMeta, options);
471
474
  }
472
475
 
473
476
  let preferLocalCache = hasReceivedData && allInverseRecordsAreLoaded && !isEmpty;
474
477
  let hasLocalPartialData = hasDematerializedInverse || (isEmpty && resource.data);
475
478
  // null is explicit empty, undefined is "we don't know anything"
476
- let localDataIsEmpty = resource.data === undefined || resource.data === null;
479
+ const localDataIsEmpty = resource.data === undefined || resource.data === null;
477
480
 
478
481
  // fetch using data, pulling from local cache if possible
479
482
  if (!shouldForceReload && !isStale && (preferLocalCache || hasLocalPartialData)) {
@@ -485,13 +488,13 @@ export class LegacySupport {
485
488
  }
486
489
 
487
490
  if (!identifier) {
488
- assert(`No Information found for ${resource.lid}`, identifier);
491
+ assert(`No Information found for ${resource.data!.lid}`, identifier);
489
492
  }
490
493
 
491
494
  return this.store._instanceCache._fetchDataIfNeededForIdentifier(identifier, options);
492
495
  }
493
496
 
494
- let resourceIsLocal = !localDataIsEmpty && resource.data.id === null;
497
+ let resourceIsLocal = !localDataIsEmpty && resource.data!.id === null;
495
498
 
496
499
  if (identifier && resourceIsLocal) {
497
500
  return resolve(identifier);
@@ -512,26 +515,26 @@ export class LegacySupport {
512
515
  destroy() {
513
516
  this.isDestroying = true;
514
517
 
515
- const cache = this._manyArrayCache;
518
+ let cache: Dict<{ destroy(): void }> = this._manyArrayCache;
519
+ this._manyArrayCache = Object.create(null);
516
520
  Object.keys(cache).forEach((key) => {
517
521
  cache[key]!.destroy();
518
- delete cache[key];
519
522
  });
520
- const keys = Object.keys(this._relationshipProxyCache);
521
- keys.forEach((key) => {
522
- const proxy = this._relationshipProxyCache[key]!;
523
+
524
+ cache = this._relationshipProxyCache;
525
+ this._relationshipProxyCache = Object.create(null);
526
+ Object.keys(cache).forEach((key) => {
527
+ const proxy = cache[key]!;
523
528
  if (proxy.destroy) {
524
529
  proxy.destroy();
525
530
  }
526
- delete this._relationshipProxyCache[key];
527
531
  });
528
- if (this.references) {
529
- const refs = this.references;
530
- Object.keys(refs).forEach((key) => {
531
- refs[key]!.destroy();
532
- delete refs[key];
533
- });
534
- }
532
+
533
+ cache = this.references;
534
+ this.references = Object.create(null);
535
+ Object.keys(cache).forEach((key) => {
536
+ cache[key]!.destroy();
537
+ });
535
538
  this.isDestroyed = true;
536
539
  }
537
540
  }
@@ -631,16 +634,13 @@ function assertRecordsPassedToHasMany(records: RecordInstance[]) {
631
634
  );
632
635
  }
633
636
 
634
- function extractRecordDatasFromRecords(records: RecordInstance[]): RecordData[] {
635
- return records.map(extractRecordDataFromRecord) as RecordData[];
637
+ function extractIdentifiersFromRecords(records: RecordInstance[]): StableRecordIdentifier[] {
638
+ return records.map(extractIdentifierFromRecord) as StableRecordIdentifier[];
636
639
  }
637
640
 
638
- type PromiseProxyRecord = {
639
- then(): void;
640
- content: RecordInstance | null | undefined;
641
- };
641
+ type PromiseProxyRecord = { then(): void; content: RecordInstance | null | undefined };
642
642
 
643
- function extractRecordDataFromRecord(recordOrPromiseRecord: PromiseProxyRecord | RecordInstance | null) {
643
+ function extractIdentifierFromRecord(recordOrPromiseRecord: PromiseProxyRecord | RecordInstance | null) {
644
644
  if (!recordOrPromiseRecord) {
645
645
  return null;
646
646
  }
@@ -651,10 +651,10 @@ function extractRecordDataFromRecord(recordOrPromiseRecord: PromiseProxyRecord |
651
651
  'You passed in a promise that did not originate from an EmberData relationship. You can only pass promises that come from a belongsTo or hasMany relationship to the get call.',
652
652
  content !== undefined
653
653
  );
654
- return content ? recordDataFor(content) : null;
654
+ return content ? recordIdentifierFor(content) : null;
655
655
  }
656
656
 
657
- return recordDataFor(recordOrPromiseRecord);
657
+ return recordIdentifierFor(recordOrPromiseRecord);
658
658
  }
659
659
 
660
660
  function isPromiseRecord(record: PromiseProxyRecord | RecordInstance): record is PromiseProxyRecord {
@@ -696,5 +696,11 @@ function areAllInverseRecordsLoaded(store: Store, resource: JsonApiRelationship)
696
696
  function isEmpty(store: Store, cache: IdentifierCache, resource: ResourceIdentifierObject): boolean {
697
697
  const identifier = cache.getOrCreateRecordIdentifier(resource);
698
698
  const recordData = store._instanceCache.peek({ identifier, bucket: 'recordData' });
699
- return !recordData || !!recordData.isEmpty?.();
699
+ return !recordData || !!recordData.isEmpty?.(identifier);
700
+ }
701
+
702
+ function isBelongsTo(
703
+ relationship: BelongsToRelationship | ImplicitRelationship | ManyRelationship
704
+ ): relationship is BelongsToRelationship {
705
+ return relationship.definition.kind === 'belongsTo';
700
706
  }
@@ -9,14 +9,15 @@ import EmberObject, { get } from '@ember/object';
9
9
  import { all } from 'rsvp';
10
10
 
11
11
  import type Store from '@ember-data/store';
12
- import { PromiseArray, recordDataFor } from '@ember-data/store/-private';
12
+ import { PromiseArray, recordIdentifierFor } from '@ember-data/store/-private';
13
13
  import type ShimModelClass from '@ember-data/store/-private/legacy-model-support/shim-model-class';
14
+ import type { NonSingletonRecordDataManager } from '@ember-data/store/-private/managers/record-data-manager';
14
15
  import type { CreateRecordProperties } from '@ember-data/store/-private/store-service';
15
16
  import type { DSModelSchema } from '@ember-data/types/q/ds-model';
16
- import type { Links, PaginationLinks } from '@ember-data/types/q/ember-data-json-api';
17
+ import type { CollectionResourceRelationship, Links, PaginationLinks } from '@ember-data/types/q/ember-data-json-api';
17
18
  import type { StableRecordIdentifier } from '@ember-data/types/q/identifier';
19
+ import type { RecordData } from '@ember-data/types/q/record-data';
18
20
  import type { RecordInstance } from '@ember-data/types/q/record-instance';
19
- import type { RelationshipRecordData } from '@ember-data/types/q/relationship-record-data';
20
21
  import type { FindOptions } from '@ember-data/types/q/store';
21
22
  import type { Dict } from '@ember-data/types/q/utils';
22
23
 
@@ -32,7 +33,8 @@ const MutableArrayWithObject = EmberObject.extend(MutableArray) as unknown as ne
32
33
  export interface ManyArrayCreateArgs {
33
34
  store: Store;
34
35
  type: ShimModelClass;
35
- recordData: RelationshipRecordData;
36
+ identifier: StableRecordIdentifier;
37
+ recordData: RecordData;
36
38
  key: string;
37
39
  isPolymorphic: boolean;
38
40
  isAsync: boolean;
@@ -98,7 +100,8 @@ export default class ManyArray extends MutableArrayWithObject<StableRecordIdenti
98
100
  declare _meta: Dict<unknown> | null;
99
101
  declare _links: Links | PaginationLinks | null;
100
102
  declare currentState: StableRecordIdentifier[];
101
- declare recordData: RelationshipRecordData;
103
+ declare identifier: StableRecordIdentifier;
104
+ declare recordData: RecordData;
102
105
  declare legacySupport: LegacySupport;
103
106
  declare store: Store;
104
107
  declare key: string;
@@ -272,25 +275,22 @@ export default class ManyArray extends MutableArrayWithObject<StableRecordIdenti
272
275
 
273
276
  replace(idx: number, amt: number, objects?: RecordInstance[]) {
274
277
  assert(`Cannot push mutations to the cache while updating the relationship from cache`, !this._isUpdating);
275
- const { store } = this;
278
+ assert(
279
+ 'The third argument to replace needs to be an array.',
280
+ !objects || Array.isArray(objects) || EmberArray.detect(objects)
281
+ );
282
+ const { store, identifier } = this;
276
283
  store._backburner.join(() => {
277
284
  let identifiers: StableRecordIdentifier[];
278
285
  if (amt > 0) {
279
286
  identifiers = this.currentState.slice(idx, idx + amt);
280
- this.recordData.removeFromHasMany(
281
- this.key,
282
- // TODO RecordData V2: recordData should take identifiers not RecordDatas
283
- identifiers.map((identifier) => store._instanceCache.getRecordData(identifier))
284
- );
287
+ this.recordData.removeFromHasMany(identifier, this.key, identifiers);
285
288
  }
286
- if (objects) {
287
- assert(
288
- 'The third argument to replace needs to be an array.',
289
- Array.isArray(objects) || EmberArray.detect(objects)
290
- );
289
+ if (objects && objects.length > 0) {
291
290
  this.recordData.addToHasMany(
291
+ identifier,
292
292
  this.key,
293
- objects.map((obj: RecordInstance) => recordDataFor(obj)),
293
+ objects.map((obj: RecordInstance) => recordIdentifierFor(obj)),
294
294
  idx
295
295
  );
296
296
  }
@@ -305,7 +305,13 @@ export default class ManyArray extends MutableArrayWithObject<StableRecordIdenti
305
305
  }
306
306
  this._isDirty = false;
307
307
  this._isUpdating = true;
308
- let jsonApi = this.recordData.getHasMany(this.key);
308
+ const identifier = this.identifier;
309
+
310
+ let jsonApi = (this.recordData as NonSingletonRecordDataManager).getRelationship(
311
+ identifier,
312
+ this.key,
313
+ true
314
+ ) as CollectionResourceRelationship;
309
315
  const cache = this.store._instanceCache;
310
316
  const idCache = this.store.identifierCache;
311
317
 
@@ -19,6 +19,7 @@ import { HAS_DEBUG_PACKAGE } from '@ember-data/private-build-infra';
19
19
  import {
20
20
  DEPRECATE_EARLY_STATIC,
21
21
  DEPRECATE_MODEL_REOPEN,
22
+ DEPRECATE_RELATIONSHIPS_WITHOUT_INVERSE,
22
23
  DEPRECATE_SAVE_PROMISE_ACCESS,
23
24
  } from '@ember-data/private-build-infra/deprecations';
24
25
  import { recordIdentifierFor, storeFor } from '@ember-data/store';
@@ -119,7 +120,7 @@ function computeOnce(target, key, desc) {
119
120
  */
120
121
  class Model extends EmberObject {
121
122
  @service store;
122
- #notifications;
123
+ ___private_notifications;
123
124
 
124
125
  init(options = {}) {
125
126
  if (DEBUG && !options._secretInit && !options._createProps) {
@@ -142,7 +143,7 @@ class Model extends EmberObject {
142
143
  let notifications = store._notificationManager;
143
144
  let identity = recordIdentifierFor(this);
144
145
 
145
- this.#notifications = notifications.subscribe(identity, (identifier, type, key) => {
146
+ this.___private_notifications = notifications.subscribe(identity, (identifier, type, key) => {
146
147
  notifyChanges(identifier, type, key, this, store);
147
148
  });
148
149
  }
@@ -152,7 +153,7 @@ class Model extends EmberObject {
152
153
  this.___recordState?.destroy();
153
154
  const store = storeFor(this);
154
155
  const identifier = recordIdentifierFor(this);
155
- store._notificationManager.unsubscribe(this.#notifications);
156
+ store._notificationManager.unsubscribe(this.___private_notifications);
156
157
  // Legacy behavior is to notify the relationships on destroy
157
158
  // such that they "clear". It's uncertain this behavior would
158
159
  // be good for a new model paradigm, likely cheaper and safer
@@ -487,7 +488,7 @@ class Model extends EmberObject {
487
488
  );
488
489
 
489
490
  if (normalizedId !== null && didChange) {
490
- this.store._instanceCache.setRecordId(identifier.type, normalizedId, identifier.lid);
491
+ this.store._instanceCache.setRecordId(identifier, normalizedId);
491
492
  this.store._notificationManager.notify(identifier, 'identity');
492
493
  }
493
494
  }
@@ -813,7 +814,7 @@ class Model extends EmberObject {
813
814
  and value is an [oldProp, newProp] array.
814
815
  */
815
816
  changedAttributes() {
816
- return recordDataFor(this).changedAttributes();
817
+ return recordDataFor(this).changedAttrs(recordIdentifierFor(this));
817
818
  }
818
819
 
819
820
  /**
@@ -837,7 +838,7 @@ class Model extends EmberObject {
837
838
  rollbackAttributes() {
838
839
  const { currentState } = this;
839
840
  const { isNew } = currentState;
840
- recordDataFor(this).rollbackAttributes();
841
+ recordDataFor(this).rollbackAttrs(recordIdentifierFor(this));
841
842
  this.errors.clear();
842
843
  currentState.cleanErrorRequests();
843
844
  if (isNew) {
@@ -980,7 +981,7 @@ class Model extends EmberObject {
980
981
  import Model, { belongsTo } from '@ember-data/model';
981
982
 
982
983
  export default class BlogModel extends Model {
983
- @belongsTo({ async: true }) user;
984
+ @belongsTo('user', { async: true, inverse: null }) user;
984
985
  }
985
986
  ```
986
987
 
@@ -1048,7 +1049,7 @@ class Model extends EmberObject {
1048
1049
  import Model, { hasMany } from '@ember-data/model';
1049
1050
 
1050
1051
  export default class BlogModel extends Model {
1051
- @hasMany({ async: true }) comments;
1052
+ @hasMany('comment', { async: true, inverse: null }) comments;
1052
1053
  }
1053
1054
 
1054
1055
  let blog = store.push({
@@ -1201,7 +1202,7 @@ class Model extends EmberObject {
1201
1202
 
1202
1203
  ```javascript
1203
1204
  import RESTSerializer from '@ember-data/serializer/rest';
1204
- import { underscore } from '@ember/string';
1205
+ import { underscore } from '<app-name>/utils/string-utils';
1205
1206
 
1206
1207
  export default const PostSerializer = RESTSerializer.extend({
1207
1208
  payloadKeyFromModelName(modelName) {
@@ -1795,7 +1796,7 @@ class Model extends EmberObject {
1795
1796
  meta.key = name;
1796
1797
  meta.name = name;
1797
1798
  meta.parentModelName = modelName;
1798
- relationships[name] = relationshipFromMeta(meta);
1799
+ relationships[name] = DEPRECATE_RELATIONSHIPS_WITHOUT_INVERSE ? relationshipFromMeta(meta) : meta;
1799
1800
  }
1800
1801
  });
1801
1802
  return relationships;
@@ -1865,6 +1866,7 @@ class Model extends EmberObject {
1865
1866
  let map = new Map();
1866
1867
 
1867
1868
  this.eachComputedProperty((name, meta) => {
1869
+ // TODO end reliance on these booleans and stop leaking them in the spec
1868
1870
  if (meta.isRelationship) {
1869
1871
  map.set(name, meta.kind);
1870
1872
  } else if (meta.isAttribute) {
@@ -2411,7 +2413,7 @@ if (DEBUG) {
2411
2413
  until: '5.0',
2412
2414
  since: { available: '4.8', enabled: '4.8' },
2413
2415
  });
2414
- return originalReopen.call(this, arguments);
2416
+ return originalReopen.call(this, ...arguments);
2415
2417
  };
2416
2418
 
2417
2419
  Model.reopenClass = function deprecatedReopenClass() {
@@ -2425,7 +2427,7 @@ if (DEBUG) {
2425
2427
  since: { available: '4.8', enabled: '4.8' },
2426
2428
  }
2427
2429
  );
2428
- return originalReopenClass.call(this, arguments);
2430
+ return originalReopenClass.call(this, ...arguments);
2429
2431
  };
2430
2432
  }
2431
2433
  }
@@ -66,7 +66,7 @@ function notifyRelationship(identifier: StableRecordIdentifier, key: string, rec
66
66
  function notifyAttribute(store: Store, identifier: StableRecordIdentifier, key: string, record: Model) {
67
67
  let currentValue = cacheFor(record, key);
68
68
 
69
- if (currentValue !== store._instanceCache.getRecordData(identifier).getAttr(key)) {
69
+ if (currentValue !== store._instanceCache.getRecordData(identifier).getAttr(identifier, key)) {
70
70
  record.notifyPropertyChange(key);
71
71
  }
72
72
  }