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

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