@ember-data/model 4.8.0-alpha.4 → 4.8.0-beta.0

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.
@@ -9,7 +9,7 @@ import {
9
9
  DEPRECATE_RELATIONSHIPS_WITHOUT_TYPE,
10
10
  } from '@ember-data/private-build-infra/deprecations';
11
11
 
12
- import { LEGACY_SUPPORT } from './model';
12
+ import { lookupLegacySupport } from './model';
13
13
  import { computedMacroWithOptionalParams } from './util';
14
14
 
15
15
  function normalizeType(type) {
@@ -205,7 +205,7 @@ function belongsTo(modelName, options) {
205
205
  if (this.isDestroying || this.isDestroyed) {
206
206
  return null;
207
207
  }
208
- const support = LEGACY_SUPPORT.lookup(this);
208
+ const support = lookupLegacySupport(this);
209
209
 
210
210
  if (DEBUG) {
211
211
  if (['currentState'].indexOf(key) !== -1) {
@@ -237,7 +237,7 @@ function belongsTo(modelName, options) {
237
237
  return support.getBelongsTo(key);
238
238
  },
239
239
  set(key, value) {
240
- const support = LEGACY_SUPPORT.lookup(this);
240
+ const support = lookupLegacySupport(this);
241
241
  if (DEBUG) {
242
242
  if (['currentState'].indexOf(key) !== -1) {
243
243
  throw new Error(
@@ -245,7 +245,7 @@ function belongsTo(modelName, options) {
245
245
  );
246
246
  }
247
247
  }
248
- this.store._backburner.join(() => {
248
+ this.store._join(() => {
249
249
  support.setDirtyBelongsTo(key, value);
250
250
  });
251
251
 
@@ -0,0 +1,54 @@
1
+ import { deprecate } from '@ember/debug';
2
+
3
+ import { resolve } from 'rsvp';
4
+
5
+ import { PromiseObject } from './promise-proxy-base';
6
+
7
+ function promiseObject<T>(promise: Promise<T>): PromiseObject<T> {
8
+ return PromiseObject.create({
9
+ promise: resolve(promise),
10
+ }) as PromiseObject<T>;
11
+ }
12
+
13
+ // constructor is accessed in some internals but not including it in the copyright for the deprecation
14
+ const ALLOWABLE_METHODS = ['constructor', 'then', 'catch', 'finally'];
15
+ const PROXIED_OBJECT_PROPS = ['content', 'isPending', 'isSettled', 'isRejected', 'isFulfilled', 'promise', 'reason'];
16
+
17
+ export function deprecatedPromiseObject<T>(promise: Promise<T>): PromiseObject<T> {
18
+ const promiseObjectProxy: PromiseObject<T> = promiseObject(promise);
19
+ const handler = {
20
+ get(target: object, prop: string, receiver?: object): unknown {
21
+ if (typeof prop === 'symbol') {
22
+ return Reflect.get(target, prop, receiver);
23
+ }
24
+ if (!ALLOWABLE_METHODS.includes(prop)) {
25
+ deprecate(
26
+ `Accessing ${prop} is deprecated. The return type is being changed fomr PromiseObjectProxy to a Promise. The only available methods to access on this promise are .then, .catch and .finally`,
27
+ false,
28
+ {
29
+ id: 'ember-data:model-save-promise',
30
+ until: '5.0',
31
+ for: '@ember-data/store',
32
+ since: {
33
+ available: '4.4',
34
+ enabled: '4.4',
35
+ },
36
+ }
37
+ );
38
+ }
39
+
40
+ const value: unknown = target[prop];
41
+ if (value && typeof value === 'function' && typeof value.bind === 'function') {
42
+ return value.bind(target);
43
+ }
44
+
45
+ if (PROXIED_OBJECT_PROPS.includes(prop)) {
46
+ return value;
47
+ }
48
+
49
+ return undefined;
50
+ },
51
+ };
52
+
53
+ return new Proxy(promiseObjectProxy, handler);
54
+ }
@@ -15,7 +15,7 @@ import {
15
15
  DEPRECATE_RELATIONSHIPS_WITHOUT_TYPE,
16
16
  } from '@ember-data/private-build-infra/deprecations';
17
17
 
18
- import { LEGACY_SUPPORT } from './model';
18
+ import { lookupLegacySupport } from './model';
19
19
  import { computedMacroWithOptionalParams } from './util';
20
20
 
21
21
  function normalizeType(type) {
@@ -249,7 +249,7 @@ function hasMany(type, options) {
249
249
  if (this.isDestroying || this.isDestroyed) {
250
250
  return A();
251
251
  }
252
- return LEGACY_SUPPORT.lookup(this).getHasMany(key);
252
+ return lookupLegacySupport(this).getHasMany(key);
253
253
  },
254
254
  set(key, records) {
255
255
  if (DEBUG) {
@@ -259,9 +259,11 @@ function hasMany(type, options) {
259
259
  );
260
260
  }
261
261
  }
262
- const support = LEGACY_SUPPORT.lookup(this);
263
- this.store._backburner.join(() => {
264
- support.setDirtyHasMany(key, records);
262
+ const support = lookupLegacySupport(this);
263
+ const manyArray = support.getManyArray(key);
264
+ assert(`You must pass an array of records to set a hasMany relationship`, Array.isArray(records));
265
+ this.store._join(() => {
266
+ manyArray.splice(0, manyArray.length, ...records);
265
267
  });
266
268
 
267
269
  return support.getHasMany(key);
@@ -1,22 +1,21 @@
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 { BelongsToRelationship, ManyRelationship } from '@ember-data/record-data/-private';
8
+ import { DEPRECATE_PROMISE_PROXIES } from '@ember-data/private-build-infra/deprecations';
9
9
  import type { UpgradedMeta } from '@ember-data/record-data/-private/graph/-edge-definition';
10
- import ImplicitRelationship from '@ember-data/record-data/-private/relationships/state/implicit';
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';
11
14
  import type Store from '@ember-data/store';
12
- import { recordIdentifierFor, storeFor } from '@ember-data/store/-private';
13
- import { IdentifierCache } from '@ember-data/store/-private/caches/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';
14
17
  import type { DSModel } from '@ember-data/types/q/ds-model';
15
- import {
16
- CollectionResourceRelationship,
17
- ResourceIdentifierObject,
18
- SingleResourceRelationship,
19
- } from '@ember-data/types/q/ember-data-json-api';
18
+ import { CollectionResourceRelationship, SingleResourceRelationship } from '@ember-data/types/q/ember-data-json-api';
20
19
  import type { StableRecordIdentifier } from '@ember-data/types/q/identifier';
21
20
  import type { RecordData } from '@ember-data/types/q/record-data';
22
21
  import type { JsonApiRelationship } from '@ember-data/types/q/record-data-json-api';
@@ -26,8 +25,7 @@ import type { Dict } from '@ember-data/types/q/utils';
26
25
 
27
26
  import { _findBelongsTo, _findHasMany } from './legacy-data-fetch';
28
27
  import { assertIdentifierHasId } from './legacy-data-utils';
29
- import type { ManyArrayCreateArgs } from './many-array';
30
- import ManyArray from './many-array';
28
+ import RelatedCollection from './many-array';
31
29
  import type { BelongsToProxyCreateArgs, BelongsToProxyMeta } from './promise-belongs-to';
32
30
  import PromiseBelongsTo from './promise-belongs-to';
33
31
  import type { HasManyProxyCreateArgs } from './promise-many-array';
@@ -35,7 +33,6 @@ import PromiseManyArray from './promise-many-array';
35
33
  import BelongsToReference from './references/belongs-to';
36
34
  import HasManyReference from './references/has-many';
37
35
 
38
- type ManyArrayFactory = { create(args: ManyArrayCreateArgs): ManyArray };
39
36
  type PromiseBelongsToFactory = { create(args: BelongsToProxyCreateArgs): PromiseBelongsTo };
40
37
 
41
38
  export class LegacySupport {
@@ -44,8 +41,8 @@ export class LegacySupport {
44
41
  declare recordData: RecordData;
45
42
  declare references: Dict<BelongsToReference | HasManyReference>;
46
43
  declare identifier: StableRecordIdentifier;
47
- declare _manyArrayCache: Dict<ManyArray>;
48
- declare _relationshipPromisesCache: Dict<Promise<ManyArray | RecordInstance>>;
44
+ declare _manyArrayCache: Dict<RelatedCollection>;
45
+ declare _relationshipPromisesCache: Dict<Promise<RelatedCollection | RecordInstance>>;
49
46
  declare _relationshipProxyCache: Dict<PromiseManyArray | PromiseBelongsTo>;
50
47
 
51
48
  declare isDestroying: boolean;
@@ -57,12 +54,38 @@ export class LegacySupport {
57
54
  this.identifier = recordIdentifierFor(record);
58
55
  this.recordData = this.store._instanceCache.getRecordData(this.identifier);
59
56
 
60
- this._manyArrayCache = Object.create(null) as Dict<ManyArray>;
61
- 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>>;
62
59
  this._relationshipProxyCache = Object.create(null) as Dict<PromiseManyArray | PromiseBelongsTo>;
63
60
  this.references = Object.create(null) as Dict<BelongsToReference>;
64
61
  }
65
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
+
66
89
  _findBelongsTo(
67
90
  key: string,
68
91
  resource: SingleResourceRelationship,
@@ -103,8 +126,8 @@ export class LegacySupport {
103
126
  getBelongsTo(key: string, options?: FindOptions): PromiseBelongsTo | RecordInstance | null {
104
127
  const { identifier, recordData } = this;
105
128
  let resource = recordData.getRelationship(this.identifier, key) as SingleResourceRelationship;
106
- let relatedIdentifier =
107
- resource && resource.data ? this.store.identifierCache.getOrCreateRecordIdentifier(resource.data) : null;
129
+ let relatedIdentifier = resource && resource.data ? resource.data : null;
130
+ assert(`Expected a stable identifier`, !relatedIdentifier || isStableIdentifier(relatedIdentifier));
108
131
 
109
132
  const store = this.store;
110
133
  const graphFor = (
@@ -127,10 +150,11 @@ export class LegacySupport {
127
150
  }
128
151
 
129
152
  let promise = this._findBelongsTo(key, resource, relationship, options);
153
+ const isLoaded = relatedIdentifier && store._instanceCache.recordIsLoaded(relatedIdentifier);
130
154
 
131
155
  return this._updatePromiseProxyFor('belongsTo', key, {
132
156
  promise,
133
- content: relatedIdentifier ? store._instanceCache.getRecord(relatedIdentifier) : null,
157
+ content: isLoaded ? store._instanceCache.getRecord(relatedIdentifier!) : null,
134
158
  _belongsToState,
135
159
  });
136
160
  } else {
@@ -150,53 +174,100 @@ export class LegacySupport {
150
174
  }
151
175
 
152
176
  setDirtyBelongsTo(key: string, value: RecordInstance | null) {
153
- return this.recordData.setBelongsTo(this.identifier, key, extractIdentifierFromRecord(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
+ );
154
187
  }
155
188
 
156
- getManyArray(key: string, definition?: UpgradedMeta): ManyArray {
157
- assert('hasMany only works with the @ember-data/record-data package', HAS_RECORD_DATA_PACKAGE);
158
- let manyArray: ManyArray | undefined = this._manyArrayCache[key];
159
- if (!definition) {
160
- const graphFor = (
161
- importSync('@ember-data/record-data/-private') as typeof import('@ember-data/record-data/-private')
162
- ).graphFor;
163
- 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
+ }
164
208
  }
165
209
 
166
- if (!manyArray) {
167
- manyArray = (ManyArray as unknown as ManyArrayFactory).create({
168
- store: this.store,
169
- type: this.store.modelFor(definition.type),
170
- identifier: this.identifier,
171
- recordData: this.recordData,
172
- key,
173
- isPolymorphic: definition.isPolymorphic,
174
- isAsync: definition.isAsync,
175
- _inverseIsAsync: definition.inverseIsAsync,
176
- legacySupport: this,
177
- isLoaded: !definition.isAsync,
178
- });
179
- this._manyArrayCache[key] = manyArray;
180
- }
210
+ return [identifiers, jsonApi];
211
+ }
212
+
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
+ }
181
222
 
182
- return manyArray;
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');
183
248
  }
184
249
 
185
250
  fetchAsyncHasMany(
186
251
  key: string,
187
252
  relationship: ManyRelationship,
188
- manyArray: ManyArray,
253
+ manyArray: RelatedCollection,
189
254
  options?: FindOptions
190
- ): Promise<ManyArray> {
255
+ ): Promise<RelatedCollection> {
191
256
  if (HAS_RECORD_DATA_PACKAGE) {
192
- let loadingPromise = this._relationshipPromisesCache[key] as Promise<ManyArray> | undefined;
257
+ let loadingPromise = this._relationshipPromisesCache[key] as Promise<RelatedCollection> | undefined;
193
258
  if (loadingPromise) {
194
259
  return loadingPromise;
195
260
  }
196
261
 
197
262
  const jsonApi = this.recordData.getRelationship(this.identifier, key) as CollectionResourceRelationship;
263
+ const promise = this._findHasManyByJsonApiResource(jsonApi, this.identifier, relationship, options);
198
264
 
199
- loadingPromise = this._findHasManyByJsonApiResource(jsonApi, this.identifier, relationship, options).then(
265
+ if (!promise) {
266
+ manyArray.isLoaded = true;
267
+ return resolve(manyArray);
268
+ }
269
+
270
+ loadingPromise = promise.then(
200
271
  () => handleCompletedRelationshipRequest(this, key, relationship, manyArray),
201
272
  (e: Error) => handleCompletedRelationshipRequest(this, key, relationship, manyArray, e)
202
273
  );
@@ -232,7 +303,7 @@ export class LegacySupport {
232
303
  assert(`hasMany only works with the @ember-data/record-data package`);
233
304
  }
234
305
 
235
- getHasMany(key: string, options?: FindOptions): PromiseManyArray | ManyArray {
306
+ getHasMany(key: string, options?: FindOptions): PromiseManyArray | RelatedCollection {
236
307
  if (HAS_RECORD_DATA_PACKAGE) {
237
308
  const graphFor = (
238
309
  importSync('@ember-data/record-data/-private') as typeof import('@ember-data/record-data/-private')
@@ -263,11 +334,6 @@ export class LegacySupport {
263
334
  assert(`hasMany only works with the @ember-data/record-data package`);
264
335
  }
265
336
 
266
- setDirtyHasMany(key: string, records: RecordInstance[]) {
267
- assertRecordsPassedToHasMany(records);
268
- return this.recordData.setHasMany(this.identifier, key, extractIdentifiersFromRecords(records));
269
- }
270
-
271
337
  _updatePromiseProxyFor(kind: 'hasMany', key: string, args: HasManyProxyCreateArgs): PromiseManyArray;
272
338
  _updatePromiseProxyFor(kind: 'belongsTo', key: string, args: BelongsToProxyCreateArgs): PromiseBelongsTo;
273
339
  _updatePromiseProxyFor(
@@ -320,7 +386,8 @@ export class LegacySupport {
320
386
  const graphFor = (
321
387
  importSync('@ember-data/record-data/-private') as typeof import('@ember-data/record-data/-private')
322
388
  ).graphFor;
323
- const relationship = graphFor(this.store).get(this.identifier, name);
389
+ const graph = graphFor(this.store);
390
+ const relationship = graph.get(this.identifier, name);
324
391
 
325
392
  if (DEBUG && kind) {
326
393
  let modelName = this.identifier.type;
@@ -334,9 +401,15 @@ export class LegacySupport {
334
401
  let relationshipKind = relationship.definition.kind;
335
402
 
336
403
  if (relationshipKind === 'belongsTo') {
337
- 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
+ );
338
411
  } else if (relationshipKind === 'hasMany') {
339
- reference = new HasManyReference(this.store, this.identifier, relationship as ManyRelationship, name);
412
+ reference = new HasManyReference(this.store, graph, this.identifier, relationship as ManyRelationship, name);
340
413
  }
341
414
 
342
415
  this.references[name] = reference;
@@ -350,10 +423,10 @@ export class LegacySupport {
350
423
  parentIdentifier: StableRecordIdentifier,
351
424
  relationship: ManyRelationship,
352
425
  options: FindOptions = {}
353
- ): Promise<void | unknown[]> {
426
+ ): Promise<void | unknown[]> | void {
354
427
  if (HAS_RECORD_DATA_PACKAGE) {
355
428
  if (!resource) {
356
- return resolve();
429
+ return;
357
430
  }
358
431
  const { definition, state } = relationship;
359
432
  const adapter = this.store.adapterFor(definition.type);
@@ -403,11 +476,19 @@ export class LegacySupport {
403
476
 
404
477
  // fetch using data, pulling from local cache if possible
405
478
  if (!shouldForceReload && !isStale && (preferLocalCache || hasLocalPartialData)) {
479
+ if (allInverseRecordsAreLoaded) {
480
+ return;
481
+ }
406
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,8 +498,9 @@ export class LegacySupport {
417
498
 
418
499
  // fetch by data
419
500
  if (hasData || hasLocalPartialData) {
420
- assert(`Expected collection to be an array`, Array.isArray(resource.data));
421
- 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));
422
504
  let fetches = new Array(identifiers.length);
423
505
  const manager = this.store._fetchManager;
424
506
 
@@ -433,7 +515,7 @@ export class LegacySupport {
433
515
 
434
516
  // we were explicitly told we have no data and no links.
435
517
  // TODO if the relationshipIsStale, should we hit the adapter anyway?
436
- return resolve();
518
+ return;
437
519
  }
438
520
  assert(`hasMany only works with the @ember-data/record-data package`);
439
521
  }
@@ -448,7 +530,8 @@ export class LegacySupport {
448
530
  return resolve(null);
449
531
  }
450
532
 
451
- const identifier = resource.data ? this.store.identifierCache.getOrCreateRecordIdentifier(resource.data) : null;
533
+ const identifier = resource.data ? resource.data : null;
534
+ assert(`Expected a stable identifier`, !identifier || isStableIdentifier(identifier));
452
535
 
453
536
  let { isStale, hasDematerializedInverse, hasReceivedData, isEmpty, shouldForceReload } = relationship.state;
454
537
 
@@ -549,8 +632,8 @@ function handleCompletedRelationshipRequest(
549
632
  recordExt: LegacySupport,
550
633
  key: string,
551
634
  relationship: ManyRelationship,
552
- value: ManyArray
553
- ): ManyArray;
635
+ value: RelatedCollection
636
+ ): RelatedCollection;
554
637
  function handleCompletedRelationshipRequest(
555
638
  recordExt: LegacySupport,
556
639
  key: string,
@@ -562,16 +645,16 @@ function handleCompletedRelationshipRequest(
562
645
  recordExt: LegacySupport,
563
646
  key: string,
564
647
  relationship: ManyRelationship,
565
- value: ManyArray,
648
+ value: RelatedCollection,
566
649
  error: Error
567
650
  ): never;
568
651
  function handleCompletedRelationshipRequest(
569
652
  recordExt: LegacySupport,
570
653
  key: string,
571
654
  relationship: BelongsToRelationship | ManyRelationship,
572
- value: ManyArray | StableRecordIdentifier | null,
655
+ value: RelatedCollection | StableRecordIdentifier | null,
573
656
  error?: Error
574
- ): ManyArray | RecordInstance | null {
657
+ ): RelatedCollection | RecordInstance | null {
575
658
  delete recordExt._relationshipPromisesCache[key];
576
659
  relationship.state.shouldForceReload = false;
577
660
  const isHasMany = relationship.definition.kind === 'hasMany';
@@ -579,7 +662,7 @@ function handleCompletedRelationshipRequest(
579
662
  if (isHasMany) {
580
663
  // we don't notify the record property here to avoid refetch
581
664
  // only the many array
582
- (value as ManyArray).notify();
665
+ (value as RelatedCollection).notify();
583
666
  }
584
667
 
585
668
  if (error) {
@@ -603,7 +686,7 @@ function handleCompletedRelationshipRequest(
603
686
  }
604
687
 
605
688
  if (isHasMany) {
606
- (value as ManyArray).set('isLoaded', true);
689
+ (value as RelatedCollection).isLoaded = true;
607
690
  }
608
691
 
609
692
  relationship.state.hasFailedLoadAttempt = false;
@@ -611,33 +694,10 @@ function handleCompletedRelationshipRequest(
611
694
  relationship.state.isStale = false;
612
695
 
613
696
  return isHasMany || !value
614
- ? (value as ManyArray | null)
697
+ ? (value as RelatedCollection | null)
615
698
  : recordExt.store.peekRecord(value as StableRecordIdentifier);
616
699
  }
617
700
 
618
- function assertRecordsPassedToHasMany(records: RecordInstance[]) {
619
- assert(`You must pass an array of records to set a hasMany relationship`, Array.isArray(records));
620
- assert(
621
- `All elements of a hasMany relationship must be instances of Model, you passed ${records
622
- .map((r) => `${typeof r}`)
623
- .join(', ')}`,
624
- (function () {
625
- return records.every((record) => {
626
- try {
627
- recordIdentifierFor(record);
628
- return true;
629
- } catch {
630
- return false;
631
- }
632
- });
633
- })()
634
- );
635
- }
636
-
637
- function extractIdentifiersFromRecords(records: RecordInstance[]): StableRecordIdentifier[] {
638
- return records.map(extractIdentifierFromRecord) as StableRecordIdentifier[];
639
- }
640
-
641
701
  type PromiseProxyRecord = { then(): void; content: RecordInstance | null | undefined };
642
702
 
643
703
  function extractIdentifierFromRecord(recordOrPromiseRecord: PromiseProxyRecord | RecordInstance | null) {
@@ -645,12 +705,25 @@ function extractIdentifierFromRecord(recordOrPromiseRecord: PromiseProxyRecord |
645
705
  return null;
646
706
  }
647
707
 
648
- if (isPromiseRecord(recordOrPromiseRecord)) {
708
+ if (DEPRECATE_PROMISE_PROXIES && isPromiseRecord(recordOrPromiseRecord)) {
649
709
  let content = recordOrPromiseRecord.content;
650
710
  assert(
651
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.',
652
712
  content !== undefined
653
713
  );
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
+ );
654
727
  return content ? recordIdentifierFor(content) : null;
655
728
  }
656
729
 
@@ -662,7 +735,7 @@ function isPromiseRecord(record: PromiseProxyRecord | RecordInstance): record is
662
735
  }
663
736
 
664
737
  function anyUnloaded(store: Store, relationship: ManyRelationship) {
665
- let state = relationship.currentState;
738
+ let state = relationship.localState;
666
739
  const cache = store._instanceCache;
667
740
  const unloaded = state.find((s) => {
668
741
  let isLoaded = cache.recordIsLoaded(s, true);
@@ -673,30 +746,21 @@ function anyUnloaded(store: Store, relationship: ManyRelationship) {
673
746
  }
674
747
 
675
748
  function areAllInverseRecordsLoaded(store: Store, resource: JsonApiRelationship): boolean {
676
- const cache = store.identifierCache;
749
+ const instanceCache = store._instanceCache;
750
+ const identifiers = resource.data;
677
751
 
678
- if (Array.isArray(resource.data)) {
752
+ if (Array.isArray(identifiers)) {
753
+ assert(`Expected stable identifiers`, identifiers.every(isStableIdentifier));
679
754
  // treat as collection
680
755
  // check for unloaded records
681
- let hasEmptyRecords = resource.data.reduce((hasEmptyModel, resourceIdentifier) => {
682
- return hasEmptyModel || isEmpty(store, cache, resourceIdentifier);
683
- }, false);
684
-
685
- return !hasEmptyRecords;
686
- } else {
687
- // treat as single resource
688
- if (!resource.data) {
689
- return true;
690
- } else {
691
- return !isEmpty(store, cache, resource.data);
692
- }
756
+ return identifiers.every((identifier: StableRecordIdentifier) => instanceCache.recordIsLoaded(identifier));
693
757
  }
694
- }
695
758
 
696
- function isEmpty(store: Store, cache: IdentifierCache, resource: ResourceIdentifierObject): boolean {
697
- const identifier = cache.getOrCreateRecordIdentifier(resource);
698
- const recordData = store._instanceCache.peek({ identifier, bucket: 'recordData' });
699
- return !recordData || !!recordData.isEmpty?.(identifier);
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
766
  function isBelongsTo(