@ember-data/model 4.7.0-beta.0 → 4.7.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,36 +1,31 @@
1
- import { assert } from '@ember/debug';
1
+ import { assert, deprecate } from '@ember/debug';
2
2
  import { DEBUG } from '@glimmer/env';
3
3
 
4
4
  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 { DEPRECATE_PROMISE_PROXIES } from '@ember-data/private-build-infra/deprecations';
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 type { LocalRelationshipOperation } from '@ember-data/record-data/-private/graph/-operations';
11
+ import type { ImplicitRelationship } from '@ember-data/record-data/-private/graph/index';
12
+ import type BelongsToRelationship from '@ember-data/record-data/-private/relationships/state/belongs-to';
13
+ import type ManyRelationship from '@ember-data/record-data/-private/relationships/state/has-many';
15
14
  import type Store from '@ember-data/store';
16
- import type { InternalModel } from '@ember-data/store/-private';
17
- import { recordDataFor, recordIdentifierFor, storeFor } from '@ember-data/store/-private';
18
- import type { IdentifierCache } from '@ember-data/store/-private/identifier-cache';
15
+ import { fastPush, isStableIdentifier, recordIdentifierFor, SOURCE, storeFor } from '@ember-data/store/-private';
16
+ import type { NonSingletonRecordDataManager } from '@ember-data/store/-private/managers/record-data-manager';
19
17
  import type { DSModel } from '@ember-data/types/q/ds-model';
20
- import type { ResourceIdentifierObject } from '@ember-data/types/q/ember-data-json-api';
18
+ import { CollectionResourceRelationship, SingleResourceRelationship } from '@ember-data/types/q/ember-data-json-api';
21
19
  import type { StableRecordIdentifier } from '@ember-data/types/q/identifier';
22
20
  import type { RecordData } from '@ember-data/types/q/record-data';
23
21
  import type { JsonApiRelationship } from '@ember-data/types/q/record-data-json-api';
24
- import type { RelationshipSchema } from '@ember-data/types/q/record-data-schemas';
25
22
  import type { RecordInstance } from '@ember-data/types/q/record-instance';
26
- import type { DefaultSingleResourceRelationship } from '@ember-data/types/q/relationship-record-data';
27
23
  import type { FindOptions } from '@ember-data/types/q/store';
28
24
  import type { Dict } from '@ember-data/types/q/utils';
29
25
 
30
26
  import { _findBelongsTo, _findHasMany } from './legacy-data-fetch';
31
27
  import { assertIdentifierHasId } from './legacy-data-utils';
32
- import type { ManyArrayCreateArgs } from './many-array';
33
- import ManyArray from './many-array';
28
+ import RelatedCollection from './many-array';
34
29
  import type { BelongsToProxyCreateArgs, BelongsToProxyMeta } from './promise-belongs-to';
35
30
  import PromiseBelongsTo from './promise-belongs-to';
36
31
  import type { HasManyProxyCreateArgs } from './promise-many-array';
@@ -38,17 +33,16 @@ import PromiseManyArray from './promise-many-array';
38
33
  import BelongsToReference from './references/belongs-to';
39
34
  import HasManyReference from './references/has-many';
40
35
 
41
- type ManyArrayFactory = { create(args: ManyArrayCreateArgs): ManyArray };
42
36
  type PromiseBelongsToFactory = { create(args: BelongsToProxyCreateArgs): PromiseBelongsTo };
43
37
 
44
38
  export class LegacySupport {
45
39
  declare record: DSModel;
46
40
  declare store: Store;
47
- declare recordData: DefaultRecordData;
41
+ declare recordData: RecordData;
48
42
  declare references: Dict<BelongsToReference | HasManyReference>;
49
43
  declare identifier: StableRecordIdentifier;
50
- declare _manyArrayCache: Dict<ManyArray>;
51
- declare _relationshipPromisesCache: Dict<Promise<ManyArray | RecordInstance>>;
44
+ declare _manyArrayCache: Dict<RelatedCollection>;
45
+ declare _relationshipPromisesCache: Dict<Promise<RelatedCollection | RecordInstance>>;
52
46
  declare _relationshipProxyCache: Dict<PromiseManyArray | PromiseBelongsTo>;
53
47
 
54
48
  declare isDestroying: boolean;
@@ -58,26 +52,52 @@ export class LegacySupport {
58
52
  this.record = record;
59
53
  this.store = storeFor(record)!;
60
54
  this.identifier = recordIdentifierFor(record);
61
- this.recordData = this.store._instanceCache.getRecordData(this.identifier) as DefaultRecordData;
55
+ this.recordData = this.store._instanceCache.getRecordData(this.identifier);
62
56
 
63
- this._manyArrayCache = Object.create(null) as Dict<ManyArray>;
64
- this._relationshipPromisesCache = Object.create(null) as Dict<Promise<ManyArray | RecordInstance>>;
57
+ this._manyArrayCache = Object.create(null) as Dict<RelatedCollection>;
58
+ this._relationshipPromisesCache = Object.create(null) as Dict<Promise<RelatedCollection | RecordInstance>>;
65
59
  this._relationshipProxyCache = Object.create(null) as Dict<PromiseManyArray | PromiseBelongsTo>;
66
60
  this.references = Object.create(null) as Dict<BelongsToReference>;
67
61
  }
68
62
 
63
+ _syncArray(array: RelatedCollection) {
64
+ // It’s possible the parent side of the relationship may have been destroyed by this point
65
+ if (this.isDestroyed || this.isDestroying) {
66
+ return;
67
+ }
68
+ const currentState = array[SOURCE];
69
+ const identifier = this.identifier;
70
+
71
+ let [identifiers, jsonApi] = this._getCurrentState(identifier, array.key);
72
+
73
+ if (jsonApi.meta) {
74
+ array.meta = jsonApi.meta;
75
+ }
76
+
77
+ if (jsonApi.links) {
78
+ array.links = jsonApi.links;
79
+ }
80
+
81
+ currentState.length = 0;
82
+ fastPush(currentState, identifiers);
83
+ }
84
+
85
+ updateCache(operation: LocalRelationshipOperation): void {
86
+ this.recordData.update(operation);
87
+ }
88
+
69
89
  _findBelongsTo(
70
90
  key: string,
71
- resource: DefaultSingleResourceRelationship,
72
- relationshipMeta: RelationshipSchema,
91
+ resource: SingleResourceRelationship,
92
+ relationship: BelongsToRelationship,
73
93
  options?: FindOptions
74
94
  ): Promise<RecordInstance | null> {
75
95
  // TODO @runspired follow up if parent isNew then we should not be attempting load here
76
96
  // TODO @runspired follow up on whether this should be in the relationship requests cache
77
- return this._findBelongsToByJsonApiResource(resource, this.identifier, relationshipMeta, options).then(
97
+ return this._findBelongsToByJsonApiResource(resource, this.identifier, relationship, options).then(
78
98
  (identifier: StableRecordIdentifier | null) =>
79
- handleCompletedRelationshipRequest(this, key, resource._relationship, identifier),
80
- (e: Error) => handleCompletedRelationshipRequest(this, key, resource._relationship, null, e)
99
+ handleCompletedRelationshipRequest(this, key, relationship, identifier),
100
+ (e: Error) => handleCompletedRelationshipRequest(this, key, relationship, null, e)
81
101
  );
82
102
  }
83
103
 
@@ -87,15 +107,16 @@ export class LegacySupport {
87
107
  return loadingPromise;
88
108
  }
89
109
 
90
- let resource = this.recordData.getBelongsTo(key);
91
- // TODO move this to a public api
92
- if (resource._relationship) {
93
- resource._relationship.state.hasFailedLoadAttempt = false;
94
- resource._relationship.state.shouldForceReload = true;
95
- }
96
- let relationshipMeta = this.store.getSchemaDefinitionService().relationshipsDefinitionFor(this.identifier)[key];
97
- assert(`Attempted to reload a belongsTo relationship but no definition exists for it`, relationshipMeta);
98
- let promise = this._findBelongsTo(key, resource, relationshipMeta, options);
110
+ const graphFor = (
111
+ importSync('@ember-data/record-data/-private') as typeof import('@ember-data/record-data/-private')
112
+ ).graphFor;
113
+ const relationship = graphFor(this.store).get(this.identifier, key);
114
+ assert(`Expected ${key} to be a belongs-to relationship`, isBelongsTo(relationship));
115
+
116
+ let resource = this.recordData.getRelationship(this.identifier, key) as SingleResourceRelationship;
117
+ relationship.state.hasFailedLoadAttempt = false;
118
+ relationship.state.shouldForceReload = true;
119
+ let promise = this._findBelongsTo(key, resource, relationship, options);
99
120
  if (this._relationshipProxyCache[key]) {
100
121
  return this._updatePromiseProxyFor('belongsTo', key, { promise });
101
122
  }
@@ -104,32 +125,36 @@ export class LegacySupport {
104
125
 
105
126
  getBelongsTo(key: string, options?: FindOptions): PromiseBelongsTo | RecordInstance | null {
106
127
  const { identifier, recordData } = this;
107
- let resource = recordData.getBelongsTo(key);
108
- let relatedIdentifier =
109
- resource && resource.data ? this.store.identifierCache.getOrCreateRecordIdentifier(resource.data) : null;
110
- let relationshipMeta = this.store.getSchemaDefinitionService().relationshipsDefinitionFor(identifier)[key];
111
- assert(`Attempted to access a belongsTo relationship but no definition exists for it`, relationshipMeta);
112
-
113
- let store = this.store;
114
- let async = relationshipMeta.options.async;
115
- let isAsync = typeof async === 'undefined' ? true : async;
128
+ let resource = recordData.getRelationship(this.identifier, key) as SingleResourceRelationship;
129
+ let relatedIdentifier = resource && resource.data ? resource.data : null;
130
+ assert(`Expected a stable identifier`, !relatedIdentifier || isStableIdentifier(relatedIdentifier));
131
+
132
+ const store = this.store;
133
+ const graphFor = (
134
+ importSync('@ember-data/record-data/-private') as typeof import('@ember-data/record-data/-private')
135
+ ).graphFor;
136
+ const relationship = graphFor(store).get(this.identifier, key);
137
+ assert(`Expected ${key} to be a belongs-to relationship`, isBelongsTo(relationship));
138
+
139
+ let isAsync = relationship.definition.isAsync;
116
140
  let _belongsToState: BelongsToProxyMeta = {
117
141
  key,
118
142
  store,
119
143
  legacySupport: this,
120
- modelName: relationshipMeta.type,
144
+ modelName: relationship.definition.type,
121
145
  };
122
146
 
123
147
  if (isAsync) {
124
- if (resource._relationship.state.hasFailedLoadAttempt) {
148
+ if (relationship.state.hasFailedLoadAttempt) {
125
149
  return this._relationshipProxyCache[key] as PromiseBelongsTo;
126
150
  }
127
151
 
128
- let promise = this._findBelongsTo(key, resource, relationshipMeta, options);
152
+ let promise = this._findBelongsTo(key, resource, relationship, options);
153
+ const isLoaded = relatedIdentifier && store._instanceCache.recordIsLoaded(relatedIdentifier);
129
154
 
130
155
  return this._updatePromiseProxyFor('belongsTo', key, {
131
156
  promise,
132
- content: relatedIdentifier ? store._instanceCache.getRecord(relatedIdentifier) : null,
157
+ content: isLoaded ? store._instanceCache.getRecord(relatedIdentifier!) : null,
133
158
  _belongsToState,
134
159
  });
135
160
  } else {
@@ -140,8 +165,8 @@ export class LegacySupport {
140
165
  assert(
141
166
  `You looked up the '${key}' relationship on a '${identifier.type}' with id ${
142
167
  identifier.id || 'null'
143
- } 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
- toReturn === null || !store._instanceCache.getInternalModel(relatedIdentifier).isEmpty
168
+ } 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> })\`)`,
169
+ toReturn === null || store._instanceCache.recordIsLoaded(relatedIdentifier, true)
145
170
  );
146
171
  return toReturn;
147
172
  }
@@ -149,52 +174,100 @@ export class LegacySupport {
149
174
  }
150
175
 
151
176
  setDirtyBelongsTo(key: string, value: RecordInstance | null) {
152
- return this.recordData.setDirtyBelongsTo(key, extractRecordDataFromRecord(value));
177
+ return this.recordData.update(
178
+ {
179
+ op: 'replaceRelatedRecord',
180
+ record: this.identifier,
181
+ field: key,
182
+ value: extractIdentifierFromRecord(value),
183
+ },
184
+ // @ts-expect-error
185
+ true
186
+ );
153
187
  }
154
188
 
155
- getManyArray(key: string, definition?: UpgradedMeta): ManyArray {
156
- assert('hasMany only works with the @ember-data/record-data package', HAS_RECORD_DATA_PACKAGE);
157
- let manyArray: ManyArray | undefined = this._manyArrayCache[key];
158
- if (!definition) {
159
- const graphFor = (
160
- importSync('@ember-data/record-data/-private') as typeof import('@ember-data/record-data/-private')
161
- ).graphFor;
162
- definition = graphFor(this.store).get(this.identifier, key).definition;
189
+ _getCurrentState(
190
+ identifier: StableRecordIdentifier,
191
+ field: string
192
+ ): [StableRecordIdentifier[], CollectionResourceRelationship] {
193
+ let jsonApi = (this.recordData as NonSingletonRecordDataManager).getRelationship(
194
+ identifier,
195
+ field,
196
+ true
197
+ ) as CollectionResourceRelationship;
198
+ const cache = this.store._instanceCache;
199
+ let identifiers: StableRecordIdentifier[] = [];
200
+ if (jsonApi.data) {
201
+ for (let i = 0; i < jsonApi.data.length; i++) {
202
+ const identifier = jsonApi.data[i];
203
+ assert(`Expected a stable identifier`, isStableIdentifier(identifier));
204
+ if (cache.recordIsLoaded(identifier, true)) {
205
+ identifiers.push(identifier);
206
+ }
207
+ }
163
208
  }
164
209
 
165
- if (!manyArray) {
166
- manyArray = (ManyArray as unknown as ManyArrayFactory).create({
167
- store: this.store,
168
- type: this.store.modelFor(definition.type),
169
- recordData: this.recordData,
170
- key,
171
- isPolymorphic: definition.isPolymorphic,
172
- isAsync: definition.isAsync,
173
- _inverseIsAsync: definition.inverseIsAsync,
174
- legacySupport: this,
175
- isLoaded: !definition.isAsync,
176
- });
177
- this._manyArrayCache[key] = manyArray;
178
- }
210
+ return [identifiers, jsonApi];
211
+ }
179
212
 
180
- return manyArray;
213
+ getManyArray(key: string, definition?: UpgradedMeta): RelatedCollection {
214
+ if (HAS_RECORD_DATA_PACKAGE) {
215
+ let manyArray: RelatedCollection | undefined = this._manyArrayCache[key];
216
+ if (!definition) {
217
+ const graphFor = (
218
+ importSync('@ember-data/record-data/-private') as typeof import('@ember-data/record-data/-private')
219
+ ).graphFor;
220
+ definition = graphFor(this.store).get(this.identifier, key).definition;
221
+ }
222
+
223
+ if (!manyArray) {
224
+ const [identifiers, doc] = this._getCurrentState(this.identifier, key);
225
+
226
+ manyArray = new RelatedCollection({
227
+ store: this.store,
228
+ type: definition.type,
229
+ identifier: this.identifier,
230
+ recordData: this.recordData,
231
+ identifiers,
232
+ key,
233
+ meta: doc.meta || null,
234
+ links: doc.links || null,
235
+ isPolymorphic: definition.isPolymorphic,
236
+ isAsync: definition.isAsync,
237
+ _inverseIsAsync: definition.inverseIsAsync,
238
+ manager: this,
239
+ isLoaded: !definition.isAsync,
240
+ allowMutation: true,
241
+ });
242
+ this._manyArrayCache[key] = manyArray;
243
+ }
244
+
245
+ return manyArray;
246
+ }
247
+ assert('hasMany only works with the @ember-data/record-data package');
181
248
  }
182
249
 
183
250
  fetchAsyncHasMany(
184
251
  key: string,
185
252
  relationship: ManyRelationship,
186
- manyArray: ManyArray,
253
+ manyArray: RelatedCollection,
187
254
  options?: FindOptions
188
- ): Promise<ManyArray> {
255
+ ): Promise<RelatedCollection> {
189
256
  if (HAS_RECORD_DATA_PACKAGE) {
190
- let loadingPromise = this._relationshipPromisesCache[key] as Promise<ManyArray> | undefined;
257
+ let loadingPromise = this._relationshipPromisesCache[key] as Promise<RelatedCollection> | undefined;
191
258
  if (loadingPromise) {
192
259
  return loadingPromise;
193
260
  }
194
261
 
195
- const jsonApi = this.recordData.getHasMany(key);
262
+ const jsonApi = this.recordData.getRelationship(this.identifier, key) as CollectionResourceRelationship;
263
+ const promise = this._findHasManyByJsonApiResource(jsonApi, this.identifier, relationship, options);
264
+
265
+ if (!promise) {
266
+ manyArray.isLoaded = true;
267
+ return resolve(manyArray);
268
+ }
196
269
 
197
- loadingPromise = this._findHasManyByJsonApiResource(jsonApi, this.identifier, relationship, options).then(
270
+ loadingPromise = promise.then(
198
271
  () => handleCompletedRelationshipRequest(this, key, relationship, manyArray),
199
272
  (e: Error) => handleCompletedRelationshipRequest(this, key, relationship, manyArray, e)
200
273
  );
@@ -230,7 +303,7 @@ export class LegacySupport {
230
303
  assert(`hasMany only works with the @ember-data/record-data package`);
231
304
  }
232
305
 
233
- getHasMany(key: string, options?: FindOptions): PromiseManyArray | ManyArray {
306
+ getHasMany(key: string, options?: FindOptions): PromiseManyArray | RelatedCollection {
234
307
  if (HAS_RECORD_DATA_PACKAGE) {
235
308
  const graphFor = (
236
309
  importSync('@ember-data/record-data/-private') as typeof import('@ember-data/record-data/-private')
@@ -251,7 +324,7 @@ export class LegacySupport {
251
324
  assert(
252
325
  `You looked up the '${key}' relationship on a '${this.identifier.type}' with id ${
253
326
  this.identifier.id || 'null'
254
- } 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 })')`,
327
+ } 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> })')`,
255
328
  !anyUnloaded(this.store, relationship)
256
329
  );
257
330
 
@@ -261,11 +334,6 @@ export class LegacySupport {
261
334
  assert(`hasMany only works with the @ember-data/record-data package`);
262
335
  }
263
336
 
264
- setDirtyHasMany(key: string, records: RecordInstance[]) {
265
- assertRecordsPassedToHasMany(records);
266
- return this.recordData.setDirtyHasMany(key, extractRecordDatasFromRecords(records));
267
- }
268
-
269
337
  _updatePromiseProxyFor(kind: 'hasMany', key: string, args: HasManyProxyCreateArgs): PromiseManyArray;
270
338
  _updatePromiseProxyFor(kind: 'belongsTo', key: string, args: BelongsToProxyCreateArgs): PromiseBelongsTo;
271
339
  _updatePromiseProxyFor(
@@ -318,7 +386,8 @@ export class LegacySupport {
318
386
  const graphFor = (
319
387
  importSync('@ember-data/record-data/-private') as typeof import('@ember-data/record-data/-private')
320
388
  ).graphFor;
321
- const relationship = graphFor(this.store).get(this.identifier, name);
389
+ const graph = graphFor(this.store);
390
+ const relationship = graph.get(this.identifier, name);
322
391
 
323
392
  if (DEBUG && kind) {
324
393
  let modelName = this.identifier.type;
@@ -332,9 +401,15 @@ export class LegacySupport {
332
401
  let relationshipKind = relationship.definition.kind;
333
402
 
334
403
  if (relationshipKind === 'belongsTo') {
335
- reference = new BelongsToReference(this.store, this.identifier, relationship as BelongsToRelationship, name);
404
+ reference = new BelongsToReference(
405
+ this.store,
406
+ graph,
407
+ this.identifier,
408
+ relationship as BelongsToRelationship,
409
+ name
410
+ );
336
411
  } else if (relationshipKind === 'hasMany') {
337
- reference = new HasManyReference(this.store, this.identifier, relationship as ManyRelationship, name);
412
+ reference = new HasManyReference(this.store, graph, this.identifier, relationship as ManyRelationship, name);
338
413
  }
339
414
 
340
415
  this.references[name] = reference;
@@ -344,22 +419,20 @@ export class LegacySupport {
344
419
  }
345
420
 
346
421
  _findHasManyByJsonApiResource(
347
- resource,
422
+ resource: CollectionResourceRelationship,
348
423
  parentIdentifier: StableRecordIdentifier,
349
424
  relationship: ManyRelationship,
350
425
  options: FindOptions = {}
351
- ): Promise<void | unknown[]> {
426
+ ): Promise<void | unknown[]> | void {
352
427
  if (HAS_RECORD_DATA_PACKAGE) {
353
428
  if (!resource) {
354
- return resolve();
429
+ return;
355
430
  }
356
431
  const { definition, state } = relationship;
357
- let adapter = this.store.adapterFor(definition.type);
358
-
359
- let { isStale, hasDematerializedInverse, hasReceivedData, isEmpty, shouldForceReload } = state;
432
+ const adapter = this.store.adapterFor(definition.type);
433
+ const { isStale, hasDematerializedInverse, hasReceivedData, isEmpty, shouldForceReload } = state;
360
434
  const allInverseRecordsAreLoaded = areAllInverseRecordsLoaded(this.store, resource);
361
-
362
- let shouldFindViaLink =
435
+ const shouldFindViaLink =
363
436
  resource.links &&
364
437
  resource.links.related &&
365
438
  (typeof adapter.findHasMany === 'function' || typeof resource.data === 'undefined') &&
@@ -369,9 +442,9 @@ export class LegacySupport {
369
442
  if (shouldFindViaLink) {
370
443
  // findHasMany, although not public, does not need to care about our upgrade relationship definitions
371
444
  // and can stick with the public definition API for now.
372
- const relationshipMeta = this.store._instanceCache._storeWrapper.relationshipsDefinitionFor(
373
- definition.inverseType
374
- )[definition.key];
445
+ const relationshipMeta = this.store
446
+ .getSchemaDefinitionService()
447
+ .relationshipsDefinitionFor({ type: definition.inverseType })[definition.key];
375
448
  let adapter = this.store.adapterFor(parentIdentifier.type);
376
449
 
377
450
  /*
@@ -394,20 +467,28 @@ export class LegacySupport {
394
467
  typeof adapter.findHasMany === 'function'
395
468
  );
396
469
 
397
- return _findHasMany(adapter, this.store, parentIdentifier, resource.links.related, relationshipMeta, options);
470
+ return _findHasMany(adapter, this.store, parentIdentifier, resource.links!.related, relationshipMeta, options);
398
471
  }
399
472
 
400
- let preferLocalCache = hasReceivedData && !isEmpty;
401
-
402
- let hasLocalPartialData =
473
+ const preferLocalCache = hasReceivedData && !isEmpty;
474
+ const hasLocalPartialData =
403
475
  hasDematerializedInverse || (isEmpty && Array.isArray(resource.data) && resource.data.length > 0);
404
476
 
405
477
  // fetch using data, pulling from local cache if possible
406
478
  if (!shouldForceReload && !isStale && (preferLocalCache || hasLocalPartialData)) {
479
+ if (allInverseRecordsAreLoaded) {
480
+ return;
481
+ }
482
+ assert(`Expected collection to be an array`, Array.isArray(resource.data));
483
+ if (allInverseRecordsAreLoaded) {
484
+ return;
485
+ }
407
486
  let finds = new Array(resource.data.length);
487
+ let cache = this.store._instanceCache;
408
488
  for (let i = 0; i < resource.data.length; i++) {
409
- let identifier = this.store.identifierCache.getOrCreateRecordIdentifier(resource.data[i]);
410
- finds[i] = this.store._instanceCache._fetchDataIfNeededForIdentifier(identifier, options);
489
+ const identifier = resource.data[i];
490
+ assert(`expected a stable identifier`, isStableIdentifier(identifier));
491
+ finds[i] = cache._fetchDataIfNeededForIdentifier(identifier, options);
411
492
  }
412
493
 
413
494
  return all(finds);
@@ -417,7 +498,9 @@ export class LegacySupport {
417
498
 
418
499
  // fetch by data
419
500
  if (hasData || hasLocalPartialData) {
420
- let identifiers = resource.data.map((json) => this.store.identifierCache.getOrCreateRecordIdentifier(json));
501
+ const identifiers = resource.data;
502
+ assert(`Expected collection to be an array`, Array.isArray(identifiers));
503
+ assert(`Expected stable identifiers`, identifiers.every(isStableIdentifier));
421
504
  let fetches = new Array(identifiers.length);
422
505
  const manager = this.store._fetchManager;
423
506
 
@@ -432,49 +515,51 @@ export class LegacySupport {
432
515
 
433
516
  // we were explicitly told we have no data and no links.
434
517
  // TODO if the relationshipIsStale, should we hit the adapter anyway?
435
- return resolve();
518
+ return;
436
519
  }
437
520
  assert(`hasMany only works with the @ember-data/record-data package`);
438
521
  }
439
522
 
440
523
  _findBelongsToByJsonApiResource(
441
- resource,
524
+ resource: SingleResourceRelationship,
442
525
  parentIdentifier: StableRecordIdentifier,
443
- relationshipMeta,
526
+ relationship: BelongsToRelationship,
444
527
  options: FindOptions = {}
445
528
  ): Promise<StableRecordIdentifier | null> {
446
529
  if (!resource) {
447
530
  return resolve(null);
448
531
  }
449
532
 
450
- const internalModel = resource.data ? this.store._instanceCache._internalModelForResource(resource.data) : null;
533
+ const identifier = resource.data ? resource.data : null;
534
+ assert(`Expected a stable identifier`, !identifier || isStableIdentifier(identifier));
451
535
 
452
- let { isStale, hasDematerializedInverse, hasReceivedData, isEmpty, shouldForceReload } = resource._relationship
453
- .state as RelationshipState;
454
- const allInverseRecordsAreLoaded = areAllInverseRecordsLoaded(this.store, resource);
455
-
456
- let shouldFindViaLink =
457
- resource.links &&
458
- resource.links.related &&
459
- (shouldForceReload || hasDematerializedInverse || isStale || (!allInverseRecordsAreLoaded && !isEmpty));
536
+ let { isStale, hasDematerializedInverse, hasReceivedData, isEmpty, shouldForceReload } = relationship.state;
460
537
 
461
- if (internalModel) {
462
- // short circuit if we are already loading
463
- let pendingRequest = this.store._fetchManager.getPendingFetch(internalModel.identifier, options);
464
- if (pendingRequest) {
465
- return pendingRequest;
466
- }
538
+ // short circuit if we are already loading
539
+ let pendingRequest = identifier && this.store._fetchManager.getPendingFetch(identifier, options);
540
+ if (pendingRequest) {
541
+ return pendingRequest;
467
542
  }
468
543
 
544
+ const allInverseRecordsAreLoaded = areAllInverseRecordsLoaded(this.store, resource);
545
+ const shouldFindViaLink =
546
+ resource.links?.related &&
547
+ (shouldForceReload || hasDematerializedInverse || isStale || (!allInverseRecordsAreLoaded && !isEmpty));
548
+
469
549
  // fetch via link
470
550
  if (shouldFindViaLink) {
471
- return _findBelongsTo(this.store, parentIdentifier, resource.links.related, relationshipMeta, options);
551
+ const relationshipMeta = this.store.getSchemaDefinitionService().relationshipsDefinitionFor(this.identifier)[
552
+ relationship.definition.key
553
+ ];
554
+ assert(`Attempted to access a belongsTo relationship but no definition exists for it`, relationshipMeta);
555
+
556
+ return _findBelongsTo(this.store, parentIdentifier, resource.links!.related, relationshipMeta, options);
472
557
  }
473
558
 
474
559
  let preferLocalCache = hasReceivedData && allInverseRecordsAreLoaded && !isEmpty;
475
560
  let hasLocalPartialData = hasDematerializedInverse || (isEmpty && resource.data);
476
561
  // null is explicit empty, undefined is "we don't know anything"
477
- let localDataIsEmpty = resource.data === undefined || resource.data === null;
562
+ const localDataIsEmpty = resource.data === undefined || resource.data === null;
478
563
 
479
564
  // fetch using data, pulling from local cache if possible
480
565
  if (!shouldForceReload && !isStale && (preferLocalCache || hasLocalPartialData)) {
@@ -485,22 +570,21 @@ export class LegacySupport {
485
570
  return resolve(null);
486
571
  }
487
572
 
488
- if (!internalModel) {
489
- assert(`No InternalModel found for ${resource.lid}`, internalModel);
573
+ if (!identifier) {
574
+ assert(`No Information found for ${resource.data!.lid}`, identifier);
490
575
  }
491
576
 
492
- return this.store._instanceCache._fetchDataIfNeededForIdentifier(internalModel.identifier, options);
577
+ return this.store._instanceCache._fetchDataIfNeededForIdentifier(identifier, options);
493
578
  }
494
579
 
495
- let resourceIsLocal = !localDataIsEmpty && resource.data.id === null;
580
+ let resourceIsLocal = !localDataIsEmpty && resource.data!.id === null;
496
581
 
497
- if (internalModel && resourceIsLocal) {
498
- return resolve(internalModel.identifier);
582
+ if (identifier && resourceIsLocal) {
583
+ return resolve(identifier);
499
584
  }
500
585
 
501
586
  // fetch by data
502
- if (internalModel && !localDataIsEmpty) {
503
- let identifier = internalModel.identifier;
587
+ if (identifier && !localDataIsEmpty) {
504
588
  assertIdentifierHasId(identifier);
505
589
 
506
590
  return this.store._fetchManager.scheduleFetch(identifier, options);
@@ -512,32 +596,28 @@ export class LegacySupport {
512
596
  }
513
597
 
514
598
  destroy() {
515
- assert(
516
- 'Cannot destroy an internalModel while its record is materialized',
517
- !this.record || this.record.isDestroyed || this.record.isDestroying
518
- );
519
599
  this.isDestroying = true;
520
600
 
521
- const cache = this._manyArrayCache;
601
+ let cache: Dict<{ destroy(): void }> = this._manyArrayCache;
602
+ this._manyArrayCache = Object.create(null);
522
603
  Object.keys(cache).forEach((key) => {
523
604
  cache[key]!.destroy();
524
- delete cache[key];
525
605
  });
526
- const keys = Object.keys(this._relationshipProxyCache);
527
- keys.forEach((key) => {
528
- const proxy = this._relationshipProxyCache[key]!;
606
+
607
+ cache = this._relationshipProxyCache;
608
+ this._relationshipProxyCache = Object.create(null);
609
+ Object.keys(cache).forEach((key) => {
610
+ const proxy = cache[key]!;
529
611
  if (proxy.destroy) {
530
612
  proxy.destroy();
531
613
  }
532
- delete this._relationshipProxyCache[key];
533
614
  });
534
- if (this.references) {
535
- const refs = this.references;
536
- Object.keys(refs).forEach((key) => {
537
- refs[key]!.destroy();
538
- delete refs[key];
539
- });
540
- }
615
+
616
+ cache = this.references;
617
+ this.references = Object.create(null);
618
+ Object.keys(cache).forEach((key) => {
619
+ cache[key]!.destroy();
620
+ });
541
621
  this.isDestroyed = true;
542
622
  }
543
623
  }
@@ -552,8 +632,8 @@ function handleCompletedRelationshipRequest(
552
632
  recordExt: LegacySupport,
553
633
  key: string,
554
634
  relationship: ManyRelationship,
555
- value: ManyArray
556
- ): ManyArray;
635
+ value: RelatedCollection
636
+ ): RelatedCollection;
557
637
  function handleCompletedRelationshipRequest(
558
638
  recordExt: LegacySupport,
559
639
  key: string,
@@ -565,16 +645,16 @@ function handleCompletedRelationshipRequest(
565
645
  recordExt: LegacySupport,
566
646
  key: string,
567
647
  relationship: ManyRelationship,
568
- value: ManyArray,
648
+ value: RelatedCollection,
569
649
  error: Error
570
650
  ): never;
571
651
  function handleCompletedRelationshipRequest(
572
652
  recordExt: LegacySupport,
573
653
  key: string,
574
654
  relationship: BelongsToRelationship | ManyRelationship,
575
- value: ManyArray | StableRecordIdentifier | null,
655
+ value: RelatedCollection | StableRecordIdentifier | null,
576
656
  error?: Error
577
- ): ManyArray | RecordInstance | null {
657
+ ): RelatedCollection | RecordInstance | null {
578
658
  delete recordExt._relationshipPromisesCache[key];
579
659
  relationship.state.shouldForceReload = false;
580
660
  const isHasMany = relationship.definition.kind === 'hasMany';
@@ -582,7 +662,7 @@ function handleCompletedRelationshipRequest(
582
662
  if (isHasMany) {
583
663
  // we don't notify the record property here to avoid refetch
584
664
  // only the many array
585
- (value as ManyArray).notify();
665
+ (value as RelatedCollection).notify();
586
666
  }
587
667
 
588
668
  if (error) {
@@ -606,7 +686,7 @@ function handleCompletedRelationshipRequest(
606
686
  }
607
687
 
608
688
  if (isHasMany) {
609
- (value as ManyArray).set('isLoaded', true);
689
+ (value as RelatedCollection).isLoaded = true;
610
690
  }
611
691
 
612
692
  relationship.state.hasFailedLoadAttempt = false;
@@ -614,43 +694,40 @@ function handleCompletedRelationshipRequest(
614
694
  relationship.state.isStale = false;
615
695
 
616
696
  return isHasMany || !value
617
- ? (value as ManyArray | null)
697
+ ? (value as RelatedCollection | null)
618
698
  : recordExt.store.peekRecord(value as StableRecordIdentifier);
619
699
  }
620
700
 
621
- function assertRecordsPassedToHasMany(records: RecordInstance[]) {
622
- assert(`You must pass an array of records to set a hasMany relationship`, Array.isArray(records));
623
- assert(
624
- `All elements of a hasMany relationship must be instances of Model, you passed ${records
625
- .map((r) => `${typeof r}`)
626
- .join(', ')}`,
627
- (function () {
628
- return records.every((record) => Object.prototype.hasOwnProperty.call(record, '_internalModel') === true);
629
- })()
630
- );
631
- }
632
-
633
- function extractRecordDatasFromRecords(records: RecordInstance[]): RecordData[] {
634
- return records.map(extractRecordDataFromRecord) as RecordData[];
635
- }
636
-
637
- type PromiseProxyRecord = { then(): void; get(str: 'content'): RecordInstance | null | undefined };
701
+ type PromiseProxyRecord = { then(): void; content: RecordInstance | null | undefined };
638
702
 
639
- function extractRecordDataFromRecord(recordOrPromiseRecord: PromiseProxyRecord | RecordInstance | null) {
703
+ function extractIdentifierFromRecord(recordOrPromiseRecord: PromiseProxyRecord | RecordInstance | null) {
640
704
  if (!recordOrPromiseRecord) {
641
705
  return null;
642
706
  }
643
707
 
644
- if (isPromiseRecord(recordOrPromiseRecord)) {
645
- let content = recordOrPromiseRecord.get && recordOrPromiseRecord.get('content');
708
+ if (DEPRECATE_PROMISE_PROXIES && isPromiseRecord(recordOrPromiseRecord)) {
709
+ let content = recordOrPromiseRecord.content;
646
710
  assert(
647
711
  '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.',
648
712
  content !== undefined
649
713
  );
650
- return content ? recordDataFor(content) : null;
714
+ deprecate(
715
+ `You passed in a PromiseProxy to a Relationship API that now expects a resolved value. await the value before setting it.`,
716
+ false,
717
+ {
718
+ id: 'ember-data:deprecate-promise-proxies',
719
+ until: '5.0',
720
+ since: {
721
+ enabled: '4.8',
722
+ available: '4.8',
723
+ },
724
+ for: 'ember-data',
725
+ }
726
+ );
727
+ return content ? recordIdentifierFor(content) : null;
651
728
  }
652
729
 
653
- return recordDataFor(recordOrPromiseRecord);
730
+ return recordIdentifierFor(recordOrPromiseRecord);
654
731
  }
655
732
 
656
733
  function isPromiseRecord(record: PromiseProxyRecord | RecordInstance): record is PromiseProxyRecord {
@@ -658,52 +735,36 @@ function isPromiseRecord(record: PromiseProxyRecord | RecordInstance): record is
658
735
  }
659
736
 
660
737
  function anyUnloaded(store: Store, relationship: ManyRelationship) {
661
- let state = relationship.currentState;
738
+ let state = relationship.localState;
739
+ const cache = store._instanceCache;
662
740
  const unloaded = state.find((s) => {
663
- let im = store._instanceCache.getInternalModel(s);
664
- return im._isDematerializing || !im.isLoaded;
741
+ let isLoaded = cache.recordIsLoaded(s, true);
742
+ return !isLoaded;
665
743
  });
666
744
 
667
745
  return unloaded || false;
668
746
  }
669
747
 
670
- /**
671
- * Flag indicating whether all inverse records are available
672
- *
673
- * true if the inverse exists and is loaded (not empty)
674
- * true if there is no inverse
675
- * false if the inverse exists and is not loaded (empty)
676
- *
677
- * @internal
678
- * @return {boolean}
679
- */
680
748
  function areAllInverseRecordsLoaded(store: Store, resource: JsonApiRelationship): boolean {
681
- const cache = store.identifierCache;
749
+ const instanceCache = store._instanceCache;
750
+ const identifiers = resource.data;
682
751
 
683
- if (Array.isArray(resource.data)) {
752
+ if (Array.isArray(identifiers)) {
753
+ assert(`Expected stable identifiers`, identifiers.every(isStableIdentifier));
684
754
  // treat as collection
685
755
  // check for unloaded records
686
- let hasEmptyRecords = resource.data.reduce((hasEmptyModel, resourceIdentifier) => {
687
- return hasEmptyModel || internalModelForRelatedResource(store, cache, resourceIdentifier).isEmpty;
688
- }, false);
689
-
690
- return !hasEmptyRecords;
691
- } else {
692
- // treat as single resource
693
- if (!resource.data) {
694
- return true;
695
- } else {
696
- const internalModel = internalModelForRelatedResource(store, cache, resource.data);
697
- return !internalModel.isEmpty;
698
- }
756
+ return identifiers.every((identifier: StableRecordIdentifier) => instanceCache.recordIsLoaded(identifier));
699
757
  }
758
+
759
+ // treat as single resource
760
+ if (!identifiers) return true;
761
+
762
+ assert(`Expected stable identifiers`, isStableIdentifier(identifiers));
763
+ return instanceCache.recordIsLoaded(identifiers);
700
764
  }
701
765
 
702
- function internalModelForRelatedResource(
703
- store: Store,
704
- cache: IdentifierCache,
705
- resource: ResourceIdentifierObject
706
- ): InternalModel {
707
- const identifier = cache.getOrCreateRecordIdentifier(resource);
708
- return store._instanceCache._internalModelForResource(identifier);
766
+ function isBelongsTo(
767
+ relationship: BelongsToRelationship | ImplicitRelationship | ManyRelationship
768
+ ): relationship is BelongsToRelationship {
769
+ return relationship.definition.kind === 'belongsTo';
709
770
  }