@ember-data/model 4.5.0-beta.0 → 4.6.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.
@@ -0,0 +1,709 @@
1
+ import { assert } from '@ember/debug';
2
+ import { DEBUG } from '@glimmer/env';
3
+
4
+ import { importSync } from '@embroider/macros';
5
+ import { all, resolve } from 'rsvp';
6
+
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';
13
+ import type { UpgradedMeta } from '@ember-data/record-data/-private/graph/-edge-definition';
14
+ import type { RelationshipState } from '@ember-data/record-data/-private/graph/-state';
15
+ 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';
19
+ import type { DSModel } from '@ember-data/types/q/ds-model';
20
+ import type { ResourceIdentifierObject } from '@ember-data/types/q/ember-data-json-api';
21
+ import type { StableRecordIdentifier } from '@ember-data/types/q/identifier';
22
+ import type { RecordData } from '@ember-data/types/q/record-data';
23
+ 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
+ import type { RecordInstance } from '@ember-data/types/q/record-instance';
26
+ import type { DefaultSingleResourceRelationship } from '@ember-data/types/q/relationship-record-data';
27
+ import type { FindOptions } from '@ember-data/types/q/store';
28
+ import type { Dict } from '@ember-data/types/q/utils';
29
+
30
+ import { _findBelongsTo, _findHasMany } from './legacy-data-fetch';
31
+ import { assertIdentifierHasId } from './legacy-data-utils';
32
+ import type { ManyArrayCreateArgs } from './many-array';
33
+ import ManyArray from './many-array';
34
+ import type { BelongsToProxyCreateArgs, BelongsToProxyMeta } from './promise-belongs-to';
35
+ import PromiseBelongsTo from './promise-belongs-to';
36
+ import type { HasManyProxyCreateArgs } from './promise-many-array';
37
+ import PromiseManyArray from './promise-many-array';
38
+ import BelongsToReference from './references/belongs-to';
39
+ import HasManyReference from './references/has-many';
40
+
41
+ type ManyArrayFactory = { create(args: ManyArrayCreateArgs): ManyArray };
42
+ type PromiseBelongsToFactory = { create(args: BelongsToProxyCreateArgs): PromiseBelongsTo };
43
+
44
+ export class LegacySupport {
45
+ declare record: DSModel;
46
+ declare store: Store;
47
+ declare recordData: DefaultRecordData;
48
+ declare references: Dict<BelongsToReference | HasManyReference>;
49
+ declare identifier: StableRecordIdentifier;
50
+ declare _manyArrayCache: Dict<ManyArray>;
51
+ declare _relationshipPromisesCache: Dict<Promise<ManyArray | RecordInstance>>;
52
+ declare _relationshipProxyCache: Dict<PromiseManyArray | PromiseBelongsTo>;
53
+
54
+ declare isDestroying: boolean;
55
+ declare isDestroyed: boolean;
56
+
57
+ constructor(record: DSModel) {
58
+ this.record = record;
59
+ this.store = storeFor(record)!;
60
+ this.identifier = recordIdentifierFor(record);
61
+ this.recordData = this.store._instanceCache.getRecordData(this.identifier) as DefaultRecordData;
62
+
63
+ this._manyArrayCache = Object.create(null) as Dict<ManyArray>;
64
+ this._relationshipPromisesCache = Object.create(null) as Dict<Promise<ManyArray | RecordInstance>>;
65
+ this._relationshipProxyCache = Object.create(null) as Dict<PromiseManyArray | PromiseBelongsTo>;
66
+ this.references = Object.create(null) as Dict<BelongsToReference>;
67
+ }
68
+
69
+ _findBelongsTo(
70
+ key: string,
71
+ resource: DefaultSingleResourceRelationship,
72
+ relationshipMeta: RelationshipSchema,
73
+ options?: FindOptions
74
+ ): Promise<RecordInstance | null> {
75
+ // TODO @runspired follow up if parent isNew then we should not be attempting load here
76
+ // TODO @runspired follow up on whether this should be in the relationship requests cache
77
+ return this._findBelongsToByJsonApiResource(resource, this.identifier, relationshipMeta, options).then(
78
+ (identifier: StableRecordIdentifier | null) =>
79
+ handleCompletedRelationshipRequest(this, key, resource._relationship, identifier),
80
+ (e: Error) => handleCompletedRelationshipRequest(this, key, resource._relationship, null, e)
81
+ );
82
+ }
83
+
84
+ reloadBelongsTo(key: string, options?: FindOptions): Promise<RecordInstance | null> {
85
+ let loadingPromise = this._relationshipPromisesCache[key] as Promise<RecordInstance | null> | undefined;
86
+ if (loadingPromise) {
87
+ return loadingPromise;
88
+ }
89
+
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);
99
+ if (this._relationshipProxyCache[key]) {
100
+ return this._updatePromiseProxyFor('belongsTo', key, { promise });
101
+ }
102
+ return promise;
103
+ }
104
+
105
+ getBelongsTo(key: string, options?: FindOptions): PromiseBelongsTo | RecordInstance | null {
106
+ 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;
116
+ let _belongsToState: BelongsToProxyMeta = {
117
+ key,
118
+ store,
119
+ legacySupport: this,
120
+ modelName: relationshipMeta.type,
121
+ };
122
+
123
+ if (isAsync) {
124
+ if (resource._relationship.state.hasFailedLoadAttempt) {
125
+ return this._relationshipProxyCache[key] as PromiseBelongsTo;
126
+ }
127
+
128
+ let promise = this._findBelongsTo(key, resource, relationshipMeta, options);
129
+
130
+ return this._updatePromiseProxyFor('belongsTo', key, {
131
+ promise,
132
+ content: relatedIdentifier ? store._instanceCache.getRecord(relatedIdentifier) : null,
133
+ _belongsToState,
134
+ });
135
+ } else {
136
+ if (relatedIdentifier === null) {
137
+ return null;
138
+ } else {
139
+ let toReturn = store._instanceCache.getRecord(relatedIdentifier);
140
+ assert(
141
+ `You looked up the '${key}' relationship on a '${identifier.type}' with id ${
142
+ 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
145
+ );
146
+ return toReturn;
147
+ }
148
+ }
149
+ }
150
+
151
+ setDirtyBelongsTo(key: string, value: RecordInstance | null) {
152
+ return this.recordData.setDirtyBelongsTo(key, extractRecordDataFromRecord(value));
153
+ }
154
+
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;
163
+ }
164
+
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
+ }
179
+
180
+ return manyArray;
181
+ }
182
+
183
+ fetchAsyncHasMany(
184
+ key: string,
185
+ relationship: ManyRelationship,
186
+ manyArray: ManyArray,
187
+ options?: FindOptions
188
+ ): Promise<ManyArray> {
189
+ if (HAS_RECORD_DATA_PACKAGE) {
190
+ let loadingPromise = this._relationshipPromisesCache[key] as Promise<ManyArray> | undefined;
191
+ if (loadingPromise) {
192
+ return loadingPromise;
193
+ }
194
+
195
+ const jsonApi = this.recordData.getHasMany(key);
196
+
197
+ loadingPromise = this._findHasManyByJsonApiResource(jsonApi, this.identifier, relationship, options).then(
198
+ () => handleCompletedRelationshipRequest(this, key, relationship, manyArray),
199
+ (e: Error) => handleCompletedRelationshipRequest(this, key, relationship, manyArray, e)
200
+ );
201
+ this._relationshipPromisesCache[key] = loadingPromise;
202
+ return loadingPromise;
203
+ }
204
+ assert('hasMany only works with the @ember-data/record-data package');
205
+ }
206
+
207
+ reloadHasMany(key: string, options?: FindOptions) {
208
+ if (HAS_RECORD_DATA_PACKAGE) {
209
+ let loadingPromise = this._relationshipPromisesCache[key];
210
+ if (loadingPromise) {
211
+ return loadingPromise;
212
+ }
213
+ const graphFor = (
214
+ importSync('@ember-data/record-data/-private') as typeof import('@ember-data/record-data/-private')
215
+ ).graphFor;
216
+ const relationship = graphFor(this.store).get(this.identifier, key) as ManyRelationship;
217
+ const { definition, state } = relationship;
218
+
219
+ state.hasFailedLoadAttempt = false;
220
+ state.shouldForceReload = true;
221
+ let manyArray = this.getManyArray(key, definition);
222
+ let promise = this.fetchAsyncHasMany(key, relationship, manyArray, options);
223
+
224
+ if (this._relationshipProxyCache[key]) {
225
+ return this._updatePromiseProxyFor('hasMany', key, { promise });
226
+ }
227
+
228
+ return promise;
229
+ }
230
+ assert(`hasMany only works with the @ember-data/record-data package`);
231
+ }
232
+
233
+ getHasMany(key: string, options?: FindOptions): PromiseManyArray | ManyArray {
234
+ if (HAS_RECORD_DATA_PACKAGE) {
235
+ const graphFor = (
236
+ importSync('@ember-data/record-data/-private') as typeof import('@ember-data/record-data/-private')
237
+ ).graphFor;
238
+ const relationship = graphFor(this.store).get(this.identifier, key) as ManyRelationship;
239
+ const { definition, state } = relationship;
240
+ let manyArray = this.getManyArray(key, definition);
241
+
242
+ if (definition.isAsync) {
243
+ if (state.hasFailedLoadAttempt) {
244
+ return this._relationshipProxyCache[key] as PromiseManyArray;
245
+ }
246
+
247
+ let promise = this.fetchAsyncHasMany(key, relationship, manyArray, options);
248
+
249
+ return this._updatePromiseProxyFor('hasMany', key, { promise, content: manyArray });
250
+ } else {
251
+ assert(
252
+ `You looked up the '${key}' relationship on a '${this.identifier.type}' with id ${
253
+ 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 })')`,
255
+ !anyUnloaded(this.store, relationship)
256
+ );
257
+
258
+ return manyArray;
259
+ }
260
+ }
261
+ assert(`hasMany only works with the @ember-data/record-data package`);
262
+ }
263
+
264
+ setDirtyHasMany(key: string, records: RecordInstance[]) {
265
+ assertRecordsPassedToHasMany(records);
266
+ return this.recordData.setDirtyHasMany(key, extractRecordDatasFromRecords(records));
267
+ }
268
+
269
+ _updatePromiseProxyFor(kind: 'hasMany', key: string, args: HasManyProxyCreateArgs): PromiseManyArray;
270
+ _updatePromiseProxyFor(kind: 'belongsTo', key: string, args: BelongsToProxyCreateArgs): PromiseBelongsTo;
271
+ _updatePromiseProxyFor(
272
+ kind: 'belongsTo',
273
+ key: string,
274
+ args: { promise: Promise<RecordInstance | null> }
275
+ ): PromiseBelongsTo;
276
+ _updatePromiseProxyFor(
277
+ kind: 'hasMany' | 'belongsTo',
278
+ key: string,
279
+ args: BelongsToProxyCreateArgs | HasManyProxyCreateArgs | { promise: Promise<RecordInstance | null> }
280
+ ): PromiseBelongsTo | PromiseManyArray {
281
+ let promiseProxy = this._relationshipProxyCache[key];
282
+ if (kind === 'hasMany') {
283
+ const { promise, content } = args as HasManyProxyCreateArgs;
284
+ if (promiseProxy) {
285
+ assert(`Expected a PromiseManyArray`, '_update' in promiseProxy);
286
+ promiseProxy._update(promise, content);
287
+ } else {
288
+ promiseProxy = this._relationshipProxyCache[key] = new PromiseManyArray(promise, content);
289
+ }
290
+ return promiseProxy;
291
+ }
292
+ if (promiseProxy) {
293
+ const { promise, content } = args as BelongsToProxyCreateArgs;
294
+ assert(`Expected a PromiseBelongsTo`, '_belongsToState' in promiseProxy);
295
+
296
+ if (content !== undefined) {
297
+ promiseProxy.set('content', content);
298
+ }
299
+ void promiseProxy.set('promise', promise);
300
+ } else {
301
+ promiseProxy = (PromiseBelongsTo as unknown as PromiseBelongsToFactory).create(args as BelongsToProxyCreateArgs);
302
+ this._relationshipProxyCache[key] = promiseProxy;
303
+ }
304
+
305
+ return promiseProxy;
306
+ }
307
+
308
+ referenceFor(kind: string | null, name: string) {
309
+ let reference = this.references[name];
310
+
311
+ if (!reference) {
312
+ if (!HAS_RECORD_DATA_PACKAGE) {
313
+ // TODO @runspired while this feels odd, it is not a regression in capability because we do
314
+ // not today support references pulling from RecordDatas other than our own
315
+ // because of the intimate API access involved. This is something we will need to redesign.
316
+ assert(`snapshot.belongsTo only supported for @ember-data/record-data`);
317
+ }
318
+ const graphFor = (
319
+ importSync('@ember-data/record-data/-private') as typeof import('@ember-data/record-data/-private')
320
+ ).graphFor;
321
+ const relationship = graphFor(this.store).get(this.identifier, name);
322
+
323
+ if (DEBUG && kind) {
324
+ let modelName = this.identifier.type;
325
+ let actualRelationshipKind = relationship.definition.kind;
326
+ assert(
327
+ `You tried to get the '${name}' relationship on a '${modelName}' via record.${kind}('${name}'), but the relationship is of kind '${actualRelationshipKind}'. Use record.${actualRelationshipKind}('${name}') instead.`,
328
+ actualRelationshipKind === kind
329
+ );
330
+ }
331
+
332
+ let relationshipKind = relationship.definition.kind;
333
+
334
+ if (relationshipKind === 'belongsTo') {
335
+ reference = new BelongsToReference(this.store, this.identifier, relationship as BelongsToRelationship, name);
336
+ } else if (relationshipKind === 'hasMany') {
337
+ reference = new HasManyReference(this.store, this.identifier, relationship as ManyRelationship, name);
338
+ }
339
+
340
+ this.references[name] = reference;
341
+ }
342
+
343
+ return reference;
344
+ }
345
+
346
+ _findHasManyByJsonApiResource(
347
+ resource,
348
+ parentIdentifier: StableRecordIdentifier,
349
+ relationship: ManyRelationship,
350
+ options: FindOptions = {}
351
+ ): Promise<void | unknown[]> {
352
+ if (HAS_RECORD_DATA_PACKAGE) {
353
+ if (!resource) {
354
+ return resolve();
355
+ }
356
+ const { definition, state } = relationship;
357
+ let adapter = this.store.adapterFor(definition.type);
358
+
359
+ let { isStale, hasDematerializedInverse, hasReceivedData, isEmpty, shouldForceReload } = state;
360
+ const allInverseRecordsAreLoaded = areAllInverseRecordsLoaded(this.store, resource);
361
+
362
+ let shouldFindViaLink =
363
+ resource.links &&
364
+ resource.links.related &&
365
+ (typeof adapter.findHasMany === 'function' || typeof resource.data === 'undefined') &&
366
+ (shouldForceReload || hasDematerializedInverse || isStale || (!allInverseRecordsAreLoaded && !isEmpty));
367
+
368
+ // fetch via link
369
+ if (shouldFindViaLink) {
370
+ // findHasMany, although not public, does not need to care about our upgrade relationship definitions
371
+ // and can stick with the public definition API for now.
372
+ const relationshipMeta = this.store._instanceCache._storeWrapper.relationshipsDefinitionFor(
373
+ definition.inverseType
374
+ )[definition.key];
375
+ let adapter = this.store.adapterFor(parentIdentifier.type);
376
+
377
+ /*
378
+ If a relationship was originally populated by the adapter as a link
379
+ (as opposed to a list of IDs), this method is called when the
380
+ relationship is fetched.
381
+
382
+ The link (which is usually a URL) is passed through unchanged, so the
383
+ adapter can make whatever request it wants.
384
+
385
+ The usual use-case is for the server to register a URL as a link, and
386
+ then use that URL in the future to make a request for the relationship.
387
+ */
388
+ assert(
389
+ `You tried to load a hasMany relationship but you have no adapter (for ${parentIdentifier.type})`,
390
+ adapter
391
+ );
392
+ assert(
393
+ `You tried to load a hasMany relationship from a specified 'link' in the original payload but your adapter does not implement 'findHasMany'`,
394
+ typeof adapter.findHasMany === 'function'
395
+ );
396
+
397
+ return _findHasMany(adapter, this.store, parentIdentifier, resource.links.related, relationshipMeta, options);
398
+ }
399
+
400
+ let preferLocalCache = hasReceivedData && !isEmpty;
401
+
402
+ let hasLocalPartialData =
403
+ hasDematerializedInverse || (isEmpty && Array.isArray(resource.data) && resource.data.length > 0);
404
+
405
+ // fetch using data, pulling from local cache if possible
406
+ if (!shouldForceReload && !isStale && (preferLocalCache || hasLocalPartialData)) {
407
+ let finds = new Array(resource.data.length);
408
+ 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);
411
+ }
412
+
413
+ return all(finds);
414
+ }
415
+
416
+ let hasData = hasReceivedData && !isEmpty;
417
+
418
+ // fetch by data
419
+ if (hasData || hasLocalPartialData) {
420
+ let identifiers = resource.data.map((json) => this.store.identifierCache.getOrCreateRecordIdentifier(json));
421
+ let fetches = new Array(identifiers.length);
422
+ const manager = this.store._fetchManager;
423
+
424
+ for (let i = 0; i < identifiers.length; i++) {
425
+ let identifier = identifiers[i];
426
+ assertIdentifierHasId(identifier);
427
+ fetches[i] = manager.scheduleFetch(identifier, options);
428
+ }
429
+
430
+ return all(fetches);
431
+ }
432
+
433
+ // we were explicitly told we have no data and no links.
434
+ // TODO if the relationshipIsStale, should we hit the adapter anyway?
435
+ return resolve();
436
+ }
437
+ assert(`hasMany only works with the @ember-data/record-data package`);
438
+ }
439
+
440
+ _findBelongsToByJsonApiResource(
441
+ resource,
442
+ parentIdentifier: StableRecordIdentifier,
443
+ relationshipMeta,
444
+ options: FindOptions = {}
445
+ ): Promise<StableRecordIdentifier | null> {
446
+ if (!resource) {
447
+ return resolve(null);
448
+ }
449
+
450
+ const internalModel = resource.data ? this.store._instanceCache._internalModelForResource(resource.data) : null;
451
+
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));
460
+
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
+ }
467
+ }
468
+
469
+ // fetch via link
470
+ if (shouldFindViaLink) {
471
+ return _findBelongsTo(this.store, parentIdentifier, resource.links.related, relationshipMeta, options);
472
+ }
473
+
474
+ let preferLocalCache = hasReceivedData && allInverseRecordsAreLoaded && !isEmpty;
475
+ let hasLocalPartialData = hasDematerializedInverse || (isEmpty && resource.data);
476
+ // null is explicit empty, undefined is "we don't know anything"
477
+ let localDataIsEmpty = resource.data === undefined || resource.data === null;
478
+
479
+ // fetch using data, pulling from local cache if possible
480
+ if (!shouldForceReload && !isStale && (preferLocalCache || hasLocalPartialData)) {
481
+ /*
482
+ We have canonical data, but our local state is empty
483
+ */
484
+ if (localDataIsEmpty) {
485
+ return resolve(null);
486
+ }
487
+
488
+ if (!internalModel) {
489
+ assert(`No InternalModel found for ${resource.lid}`, internalModel);
490
+ }
491
+
492
+ return this.store._instanceCache._fetchDataIfNeededForIdentifier(internalModel.identifier, options);
493
+ }
494
+
495
+ let resourceIsLocal = !localDataIsEmpty && resource.data.id === null;
496
+
497
+ if (internalModel && resourceIsLocal) {
498
+ return resolve(internalModel.identifier);
499
+ }
500
+
501
+ // fetch by data
502
+ if (internalModel && !localDataIsEmpty) {
503
+ let identifier = internalModel.identifier;
504
+ assertIdentifierHasId(identifier);
505
+
506
+ return this.store._fetchManager.scheduleFetch(identifier, options);
507
+ }
508
+
509
+ // we were explicitly told we have no data and no links.
510
+ // TODO if the relationshipIsStale, should we hit the adapter anyway?
511
+ return resolve(null);
512
+ }
513
+
514
+ destroy() {
515
+ assert(
516
+ 'Cannot destroy an internalModel while its record is materialized',
517
+ !this.record || this.record.isDestroyed || this.record.isDestroying
518
+ );
519
+ this.isDestroying = true;
520
+
521
+ const cache = this._manyArrayCache;
522
+ Object.keys(cache).forEach((key) => {
523
+ cache[key]!.destroy();
524
+ delete cache[key];
525
+ });
526
+ const keys = Object.keys(this._relationshipProxyCache);
527
+ keys.forEach((key) => {
528
+ const proxy = this._relationshipProxyCache[key]!;
529
+ if (proxy.destroy) {
530
+ proxy.destroy();
531
+ }
532
+ delete this._relationshipProxyCache[key];
533
+ });
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
+ }
541
+ this.isDestroyed = true;
542
+ }
543
+ }
544
+
545
+ function handleCompletedRelationshipRequest(
546
+ recordExt: LegacySupport,
547
+ key: string,
548
+ relationship: BelongsToRelationship,
549
+ value: StableRecordIdentifier | null
550
+ ): RecordInstance | null;
551
+ function handleCompletedRelationshipRequest(
552
+ recordExt: LegacySupport,
553
+ key: string,
554
+ relationship: ManyRelationship,
555
+ value: ManyArray
556
+ ): ManyArray;
557
+ function handleCompletedRelationshipRequest(
558
+ recordExt: LegacySupport,
559
+ key: string,
560
+ relationship: BelongsToRelationship,
561
+ value: null,
562
+ error: Error
563
+ ): never;
564
+ function handleCompletedRelationshipRequest(
565
+ recordExt: LegacySupport,
566
+ key: string,
567
+ relationship: ManyRelationship,
568
+ value: ManyArray,
569
+ error: Error
570
+ ): never;
571
+ function handleCompletedRelationshipRequest(
572
+ recordExt: LegacySupport,
573
+ key: string,
574
+ relationship: BelongsToRelationship | ManyRelationship,
575
+ value: ManyArray | StableRecordIdentifier | null,
576
+ error?: Error
577
+ ): ManyArray | RecordInstance | null {
578
+ delete recordExt._relationshipPromisesCache[key];
579
+ relationship.state.shouldForceReload = false;
580
+ const isHasMany = relationship.definition.kind === 'hasMany';
581
+
582
+ if (isHasMany) {
583
+ // we don't notify the record property here to avoid refetch
584
+ // only the many array
585
+ (value as ManyArray).notify();
586
+ }
587
+
588
+ if (error) {
589
+ relationship.state.hasFailedLoadAttempt = true;
590
+ let proxy = recordExt._relationshipProxyCache[key];
591
+ // belongsTo relationships are sometimes unloaded
592
+ // when a load fails, in this case we need
593
+ // to make sure that we aren't proxying
594
+ // to destroyed content
595
+ // for the sync belongsTo reload case there will be no proxy
596
+ // for the async reload case there will be no proxy if the ui
597
+ // has never been accessed
598
+ if (proxy && !isHasMany) {
599
+ // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access
600
+ if (proxy.content && proxy.content.isDestroying) {
601
+ (proxy as PromiseBelongsTo).set('content', null);
602
+ }
603
+ }
604
+
605
+ throw error;
606
+ }
607
+
608
+ if (isHasMany) {
609
+ (value as ManyArray).set('isLoaded', true);
610
+ }
611
+
612
+ relationship.state.hasFailedLoadAttempt = false;
613
+ // only set to not stale if no error is thrown
614
+ relationship.state.isStale = false;
615
+
616
+ return isHasMany || !value
617
+ ? (value as ManyArray | null)
618
+ : recordExt.store.peekRecord(value as StableRecordIdentifier);
619
+ }
620
+
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 };
638
+
639
+ function extractRecordDataFromRecord(recordOrPromiseRecord: PromiseProxyRecord | RecordInstance | null) {
640
+ if (!recordOrPromiseRecord) {
641
+ return null;
642
+ }
643
+
644
+ if (isPromiseRecord(recordOrPromiseRecord)) {
645
+ let content = recordOrPromiseRecord.get && recordOrPromiseRecord.get('content');
646
+ assert(
647
+ '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
+ content !== undefined
649
+ );
650
+ return content ? recordDataFor(content) : null;
651
+ }
652
+
653
+ return recordDataFor(recordOrPromiseRecord);
654
+ }
655
+
656
+ function isPromiseRecord(record: PromiseProxyRecord | RecordInstance): record is PromiseProxyRecord {
657
+ return !!record.then;
658
+ }
659
+
660
+ function anyUnloaded(store: Store, relationship: ManyRelationship) {
661
+ let state = relationship.currentState;
662
+ const unloaded = state.find((s) => {
663
+ let im = store._instanceCache.getInternalModel(s);
664
+ return im._isDematerializing || !im.isLoaded;
665
+ });
666
+
667
+ return unloaded || false;
668
+ }
669
+
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
+ function areAllInverseRecordsLoaded(store: Store, resource: JsonApiRelationship): boolean {
681
+ const cache = store.identifierCache;
682
+
683
+ if (Array.isArray(resource.data)) {
684
+ // treat as collection
685
+ // 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
+ }
699
+ }
700
+ }
701
+
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);
709
+ }