@ember-data/model 4.8.0-alpha.1 → 4.8.0-alpha.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/addon/-private/attr.js +9 -24
- package/addon/-private/belongs-to.js +83 -21
- package/addon/-private/errors.ts +6 -6
- package/addon/-private/has-many.js +77 -17
- package/addon/-private/legacy-data-fetch.js +35 -13
- package/addon/-private/legacy-data-utils.ts +1 -1
- package/addon/-private/legacy-relationships-support.ts +126 -129
- package/addon/-private/many-array.ts +37 -25
- package/addon/-private/model-for-mixin.ts +2 -4
- package/addon/-private/model.js +431 -112
- package/addon/-private/notify-changes.ts +2 -2
- package/addon/-private/promise-many-array.ts +170 -112
- package/addon/-private/record-state.ts +63 -46
- package/addon/-private/references/belongs-to.ts +33 -28
- package/addon/-private/references/has-many.ts +50 -42
- package/addon/-private/relationship-meta.ts +5 -25
- package/index.js +1 -0
- package/package.json +11 -7
|
@@ -5,25 +5,22 @@ import { importSync } from '@embroider/macros';
|
|
|
5
5
|
import { all, resolve } from 'rsvp';
|
|
6
6
|
|
|
7
7
|
import { HAS_RECORD_DATA_PACKAGE } from '@ember-data/private-build-infra';
|
|
8
|
-
import type {
|
|
9
|
-
BelongsToRelationship,
|
|
10
|
-
ManyRelationship,
|
|
11
|
-
RecordData as DefaultRecordData,
|
|
12
|
-
} from '@ember-data/record-data/-private';
|
|
8
|
+
import type { BelongsToRelationship, ManyRelationship } from '@ember-data/record-data/-private';
|
|
13
9
|
import type { UpgradedMeta } from '@ember-data/record-data/-private/graph/-edge-definition';
|
|
14
|
-
import
|
|
10
|
+
import ImplicitRelationship from '@ember-data/record-data/-private/relationships/state/implicit';
|
|
15
11
|
import type Store from '@ember-data/store';
|
|
16
|
-
import
|
|
17
|
-
import {
|
|
18
|
-
import type { IdentifierCache } from '@ember-data/store/-private/identifier-cache';
|
|
12
|
+
import { recordIdentifierFor, storeFor } from '@ember-data/store/-private';
|
|
13
|
+
import { IdentifierCache } from '@ember-data/store/-private/caches/identifier-cache';
|
|
19
14
|
import type { DSModel } from '@ember-data/types/q/ds-model';
|
|
20
|
-
import
|
|
15
|
+
import {
|
|
16
|
+
CollectionResourceRelationship,
|
|
17
|
+
ResourceIdentifierObject,
|
|
18
|
+
SingleResourceRelationship,
|
|
19
|
+
} from '@ember-data/types/q/ember-data-json-api';
|
|
21
20
|
import type { StableRecordIdentifier } from '@ember-data/types/q/identifier';
|
|
22
21
|
import type { RecordData } from '@ember-data/types/q/record-data';
|
|
23
22
|
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
23
|
import type { RecordInstance } from '@ember-data/types/q/record-instance';
|
|
26
|
-
import type { DefaultSingleResourceRelationship } from '@ember-data/types/q/relationship-record-data';
|
|
27
24
|
import type { FindOptions } from '@ember-data/types/q/store';
|
|
28
25
|
import type { Dict } from '@ember-data/types/q/utils';
|
|
29
26
|
|
|
@@ -44,7 +41,7 @@ type PromiseBelongsToFactory = { create(args: BelongsToProxyCreateArgs): Promise
|
|
|
44
41
|
export class LegacySupport {
|
|
45
42
|
declare record: DSModel;
|
|
46
43
|
declare store: Store;
|
|
47
|
-
declare recordData:
|
|
44
|
+
declare recordData: RecordData;
|
|
48
45
|
declare references: Dict<BelongsToReference | HasManyReference>;
|
|
49
46
|
declare identifier: StableRecordIdentifier;
|
|
50
47
|
declare _manyArrayCache: Dict<ManyArray>;
|
|
@@ -58,7 +55,7 @@ export class LegacySupport {
|
|
|
58
55
|
this.record = record;
|
|
59
56
|
this.store = storeFor(record)!;
|
|
60
57
|
this.identifier = recordIdentifierFor(record);
|
|
61
|
-
this.recordData = this.store._instanceCache.getRecordData(this.identifier)
|
|
58
|
+
this.recordData = this.store._instanceCache.getRecordData(this.identifier);
|
|
62
59
|
|
|
63
60
|
this._manyArrayCache = Object.create(null) as Dict<ManyArray>;
|
|
64
61
|
this._relationshipPromisesCache = Object.create(null) as Dict<Promise<ManyArray | RecordInstance>>;
|
|
@@ -68,16 +65,16 @@ export class LegacySupport {
|
|
|
68
65
|
|
|
69
66
|
_findBelongsTo(
|
|
70
67
|
key: string,
|
|
71
|
-
resource:
|
|
72
|
-
|
|
68
|
+
resource: SingleResourceRelationship,
|
|
69
|
+
relationship: BelongsToRelationship,
|
|
73
70
|
options?: FindOptions
|
|
74
71
|
): Promise<RecordInstance | null> {
|
|
75
72
|
// TODO @runspired follow up if parent isNew then we should not be attempting load here
|
|
76
73
|
// TODO @runspired follow up on whether this should be in the relationship requests cache
|
|
77
|
-
return this._findBelongsToByJsonApiResource(resource, this.identifier,
|
|
74
|
+
return this._findBelongsToByJsonApiResource(resource, this.identifier, relationship, options).then(
|
|
78
75
|
(identifier: StableRecordIdentifier | null) =>
|
|
79
|
-
handleCompletedRelationshipRequest(this, key,
|
|
80
|
-
(e: Error) => handleCompletedRelationshipRequest(this, key,
|
|
76
|
+
handleCompletedRelationshipRequest(this, key, relationship, identifier),
|
|
77
|
+
(e: Error) => handleCompletedRelationshipRequest(this, key, relationship, null, e)
|
|
81
78
|
);
|
|
82
79
|
}
|
|
83
80
|
|
|
@@ -87,15 +84,16 @@ export class LegacySupport {
|
|
|
87
84
|
return loadingPromise;
|
|
88
85
|
}
|
|
89
86
|
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
let
|
|
97
|
-
|
|
98
|
-
|
|
87
|
+
const graphFor = (
|
|
88
|
+
importSync('@ember-data/record-data/-private') as typeof import('@ember-data/record-data/-private')
|
|
89
|
+
).graphFor;
|
|
90
|
+
const relationship = graphFor(this.store).get(this.identifier, key);
|
|
91
|
+
assert(`Expected ${key} to be a belongs-to relationship`, isBelongsTo(relationship));
|
|
92
|
+
|
|
93
|
+
let resource = this.recordData.getRelationship(this.identifier, key) as SingleResourceRelationship;
|
|
94
|
+
relationship.state.hasFailedLoadAttempt = false;
|
|
95
|
+
relationship.state.shouldForceReload = true;
|
|
96
|
+
let promise = this._findBelongsTo(key, resource, relationship, options);
|
|
99
97
|
if (this._relationshipProxyCache[key]) {
|
|
100
98
|
return this._updatePromiseProxyFor('belongsTo', key, { promise });
|
|
101
99
|
}
|
|
@@ -104,28 +102,31 @@ export class LegacySupport {
|
|
|
104
102
|
|
|
105
103
|
getBelongsTo(key: string, options?: FindOptions): PromiseBelongsTo | RecordInstance | null {
|
|
106
104
|
const { identifier, recordData } = this;
|
|
107
|
-
let resource = recordData.
|
|
105
|
+
let resource = recordData.getRelationship(this.identifier, key) as SingleResourceRelationship;
|
|
108
106
|
let relatedIdentifier =
|
|
109
107
|
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
108
|
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
109
|
+
const store = this.store;
|
|
110
|
+
const graphFor = (
|
|
111
|
+
importSync('@ember-data/record-data/-private') as typeof import('@ember-data/record-data/-private')
|
|
112
|
+
).graphFor;
|
|
113
|
+
const relationship = graphFor(store).get(this.identifier, key);
|
|
114
|
+
assert(`Expected ${key} to be a belongs-to relationship`, isBelongsTo(relationship));
|
|
115
|
+
|
|
116
|
+
let isAsync = relationship.definition.isAsync;
|
|
116
117
|
let _belongsToState: BelongsToProxyMeta = {
|
|
117
118
|
key,
|
|
118
119
|
store,
|
|
119
120
|
legacySupport: this,
|
|
120
|
-
modelName:
|
|
121
|
+
modelName: relationship.definition.type,
|
|
121
122
|
};
|
|
122
123
|
|
|
123
124
|
if (isAsync) {
|
|
124
|
-
if (
|
|
125
|
+
if (relationship.state.hasFailedLoadAttempt) {
|
|
125
126
|
return this._relationshipProxyCache[key] as PromiseBelongsTo;
|
|
126
127
|
}
|
|
127
128
|
|
|
128
|
-
let promise = this._findBelongsTo(key, resource,
|
|
129
|
+
let promise = this._findBelongsTo(key, resource, relationship, options);
|
|
129
130
|
|
|
130
131
|
return this._updatePromiseProxyFor('belongsTo', key, {
|
|
131
132
|
promise,
|
|
@@ -140,8 +141,8 @@ export class LegacySupport {
|
|
|
140
141
|
assert(
|
|
141
142
|
`You looked up the '${key}' relationship on a '${identifier.type}' with id ${
|
|
142
143
|
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 ||
|
|
144
|
+
} but some of the associated records were not loaded. Either make sure they are all loaded together with the parent record, or specify that the relationship is async (\`belongsTo(<type>, { async: true, inverse: <inverse> })\`)`,
|
|
145
|
+
toReturn === null || store._instanceCache.recordIsLoaded(relatedIdentifier, true)
|
|
145
146
|
);
|
|
146
147
|
return toReturn;
|
|
147
148
|
}
|
|
@@ -149,7 +150,7 @@ export class LegacySupport {
|
|
|
149
150
|
}
|
|
150
151
|
|
|
151
152
|
setDirtyBelongsTo(key: string, value: RecordInstance | null) {
|
|
152
|
-
return this.recordData.
|
|
153
|
+
return this.recordData.setBelongsTo(this.identifier, key, extractIdentifierFromRecord(value));
|
|
153
154
|
}
|
|
154
155
|
|
|
155
156
|
getManyArray(key: string, definition?: UpgradedMeta): ManyArray {
|
|
@@ -166,6 +167,7 @@ export class LegacySupport {
|
|
|
166
167
|
manyArray = (ManyArray as unknown as ManyArrayFactory).create({
|
|
167
168
|
store: this.store,
|
|
168
169
|
type: this.store.modelFor(definition.type),
|
|
170
|
+
identifier: this.identifier,
|
|
169
171
|
recordData: this.recordData,
|
|
170
172
|
key,
|
|
171
173
|
isPolymorphic: definition.isPolymorphic,
|
|
@@ -192,7 +194,7 @@ export class LegacySupport {
|
|
|
192
194
|
return loadingPromise;
|
|
193
195
|
}
|
|
194
196
|
|
|
195
|
-
const jsonApi = this.recordData.
|
|
197
|
+
const jsonApi = this.recordData.getRelationship(this.identifier, key) as CollectionResourceRelationship;
|
|
196
198
|
|
|
197
199
|
loadingPromise = this._findHasManyByJsonApiResource(jsonApi, this.identifier, relationship, options).then(
|
|
198
200
|
() => handleCompletedRelationshipRequest(this, key, relationship, manyArray),
|
|
@@ -251,7 +253,7 @@ export class LegacySupport {
|
|
|
251
253
|
assert(
|
|
252
254
|
`You looked up the '${key}' relationship on a '${this.identifier.type}' with id ${
|
|
253
255
|
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 })')`,
|
|
256
|
+
} but some of the associated records were not loaded. Either make sure they are all loaded together with the parent record, or specify that the relationship is async ('hasMany(<type>, { async: true, inverse: <inverse> })')`,
|
|
255
257
|
!anyUnloaded(this.store, relationship)
|
|
256
258
|
);
|
|
257
259
|
|
|
@@ -263,7 +265,7 @@ export class LegacySupport {
|
|
|
263
265
|
|
|
264
266
|
setDirtyHasMany(key: string, records: RecordInstance[]) {
|
|
265
267
|
assertRecordsPassedToHasMany(records);
|
|
266
|
-
return this.recordData.
|
|
268
|
+
return this.recordData.setHasMany(this.identifier, key, extractIdentifiersFromRecords(records));
|
|
267
269
|
}
|
|
268
270
|
|
|
269
271
|
_updatePromiseProxyFor(kind: 'hasMany', key: string, args: HasManyProxyCreateArgs): PromiseManyArray;
|
|
@@ -344,7 +346,7 @@ export class LegacySupport {
|
|
|
344
346
|
}
|
|
345
347
|
|
|
346
348
|
_findHasManyByJsonApiResource(
|
|
347
|
-
resource,
|
|
349
|
+
resource: CollectionResourceRelationship,
|
|
348
350
|
parentIdentifier: StableRecordIdentifier,
|
|
349
351
|
relationship: ManyRelationship,
|
|
350
352
|
options: FindOptions = {}
|
|
@@ -354,12 +356,10 @@ export class LegacySupport {
|
|
|
354
356
|
return resolve();
|
|
355
357
|
}
|
|
356
358
|
const { definition, state } = relationship;
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
let { isStale, hasDematerializedInverse, hasReceivedData, isEmpty, shouldForceReload } = state;
|
|
359
|
+
const adapter = this.store.adapterFor(definition.type);
|
|
360
|
+
const { isStale, hasDematerializedInverse, hasReceivedData, isEmpty, shouldForceReload } = state;
|
|
360
361
|
const allInverseRecordsAreLoaded = areAllInverseRecordsLoaded(this.store, resource);
|
|
361
|
-
|
|
362
|
-
let shouldFindViaLink =
|
|
362
|
+
const shouldFindViaLink =
|
|
363
363
|
resource.links &&
|
|
364
364
|
resource.links.related &&
|
|
365
365
|
(typeof adapter.findHasMany === 'function' || typeof resource.data === 'undefined') &&
|
|
@@ -369,9 +369,9 @@ export class LegacySupport {
|
|
|
369
369
|
if (shouldFindViaLink) {
|
|
370
370
|
// findHasMany, although not public, does not need to care about our upgrade relationship definitions
|
|
371
371
|
// and can stick with the public definition API for now.
|
|
372
|
-
const relationshipMeta = this.store
|
|
373
|
-
|
|
374
|
-
|
|
372
|
+
const relationshipMeta = this.store
|
|
373
|
+
.getSchemaDefinitionService()
|
|
374
|
+
.relationshipsDefinitionFor({ type: definition.inverseType })[definition.key];
|
|
375
375
|
let adapter = this.store.adapterFor(parentIdentifier.type);
|
|
376
376
|
|
|
377
377
|
/*
|
|
@@ -394,16 +394,16 @@ export class LegacySupport {
|
|
|
394
394
|
typeof adapter.findHasMany === 'function'
|
|
395
395
|
);
|
|
396
396
|
|
|
397
|
-
return _findHasMany(adapter, this.store, parentIdentifier, resource.links
|
|
397
|
+
return _findHasMany(adapter, this.store, parentIdentifier, resource.links!.related, relationshipMeta, options);
|
|
398
398
|
}
|
|
399
399
|
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
let hasLocalPartialData =
|
|
400
|
+
const preferLocalCache = hasReceivedData && !isEmpty;
|
|
401
|
+
const hasLocalPartialData =
|
|
403
402
|
hasDematerializedInverse || (isEmpty && Array.isArray(resource.data) && resource.data.length > 0);
|
|
404
403
|
|
|
405
404
|
// fetch using data, pulling from local cache if possible
|
|
406
405
|
if (!shouldForceReload && !isStale && (preferLocalCache || hasLocalPartialData)) {
|
|
406
|
+
assert(`Expected collection to be an array`, Array.isArray(resource.data));
|
|
407
407
|
let finds = new Array(resource.data.length);
|
|
408
408
|
for (let i = 0; i < resource.data.length; i++) {
|
|
409
409
|
let identifier = this.store.identifierCache.getOrCreateRecordIdentifier(resource.data[i]);
|
|
@@ -417,6 +417,7 @@ export class LegacySupport {
|
|
|
417
417
|
|
|
418
418
|
// fetch by data
|
|
419
419
|
if (hasData || hasLocalPartialData) {
|
|
420
|
+
assert(`Expected collection to be an array`, Array.isArray(resource.data));
|
|
420
421
|
let identifiers = resource.data.map((json) => this.store.identifierCache.getOrCreateRecordIdentifier(json));
|
|
421
422
|
let fetches = new Array(identifiers.length);
|
|
422
423
|
const manager = this.store._fetchManager;
|
|
@@ -438,43 +439,44 @@ export class LegacySupport {
|
|
|
438
439
|
}
|
|
439
440
|
|
|
440
441
|
_findBelongsToByJsonApiResource(
|
|
441
|
-
resource,
|
|
442
|
+
resource: SingleResourceRelationship,
|
|
442
443
|
parentIdentifier: StableRecordIdentifier,
|
|
443
|
-
|
|
444
|
+
relationship: BelongsToRelationship,
|
|
444
445
|
options: FindOptions = {}
|
|
445
446
|
): Promise<StableRecordIdentifier | null> {
|
|
446
447
|
if (!resource) {
|
|
447
448
|
return resolve(null);
|
|
448
449
|
}
|
|
449
450
|
|
|
450
|
-
const
|
|
451
|
-
|
|
452
|
-
let { isStale, hasDematerializedInverse, hasReceivedData, isEmpty, shouldForceReload } = resource._relationship
|
|
453
|
-
.state as RelationshipState;
|
|
454
|
-
const allInverseRecordsAreLoaded = areAllInverseRecordsLoaded(this.store, resource);
|
|
451
|
+
const identifier = resource.data ? this.store.identifierCache.getOrCreateRecordIdentifier(resource.data) : null;
|
|
455
452
|
|
|
456
|
-
let
|
|
457
|
-
resource.links &&
|
|
458
|
-
resource.links.related &&
|
|
459
|
-
(shouldForceReload || hasDematerializedInverse || isStale || (!allInverseRecordsAreLoaded && !isEmpty));
|
|
453
|
+
let { isStale, hasDematerializedInverse, hasReceivedData, isEmpty, shouldForceReload } = relationship.state;
|
|
460
454
|
|
|
461
|
-
if
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
return pendingRequest;
|
|
466
|
-
}
|
|
455
|
+
// short circuit if we are already loading
|
|
456
|
+
let pendingRequest = identifier && this.store._fetchManager.getPendingFetch(identifier, options);
|
|
457
|
+
if (pendingRequest) {
|
|
458
|
+
return pendingRequest;
|
|
467
459
|
}
|
|
468
460
|
|
|
461
|
+
const allInverseRecordsAreLoaded = areAllInverseRecordsLoaded(this.store, resource);
|
|
462
|
+
const shouldFindViaLink =
|
|
463
|
+
resource.links?.related &&
|
|
464
|
+
(shouldForceReload || hasDematerializedInverse || isStale || (!allInverseRecordsAreLoaded && !isEmpty));
|
|
465
|
+
|
|
469
466
|
// fetch via link
|
|
470
467
|
if (shouldFindViaLink) {
|
|
471
|
-
|
|
468
|
+
const relationshipMeta = this.store.getSchemaDefinitionService().relationshipsDefinitionFor(this.identifier)[
|
|
469
|
+
relationship.definition.key
|
|
470
|
+
];
|
|
471
|
+
assert(`Attempted to access a belongsTo relationship but no definition exists for it`, relationshipMeta);
|
|
472
|
+
|
|
473
|
+
return _findBelongsTo(this.store, parentIdentifier, resource.links!.related, relationshipMeta, options);
|
|
472
474
|
}
|
|
473
475
|
|
|
474
476
|
let preferLocalCache = hasReceivedData && allInverseRecordsAreLoaded && !isEmpty;
|
|
475
477
|
let hasLocalPartialData = hasDematerializedInverse || (isEmpty && resource.data);
|
|
476
478
|
// null is explicit empty, undefined is "we don't know anything"
|
|
477
|
-
|
|
479
|
+
const localDataIsEmpty = resource.data === undefined || resource.data === null;
|
|
478
480
|
|
|
479
481
|
// fetch using data, pulling from local cache if possible
|
|
480
482
|
if (!shouldForceReload && !isStale && (preferLocalCache || hasLocalPartialData)) {
|
|
@@ -485,22 +487,21 @@ export class LegacySupport {
|
|
|
485
487
|
return resolve(null);
|
|
486
488
|
}
|
|
487
489
|
|
|
488
|
-
if (!
|
|
489
|
-
assert(`No
|
|
490
|
+
if (!identifier) {
|
|
491
|
+
assert(`No Information found for ${resource.data!.lid}`, identifier);
|
|
490
492
|
}
|
|
491
493
|
|
|
492
|
-
return this.store._instanceCache._fetchDataIfNeededForIdentifier(
|
|
494
|
+
return this.store._instanceCache._fetchDataIfNeededForIdentifier(identifier, options);
|
|
493
495
|
}
|
|
494
496
|
|
|
495
|
-
let resourceIsLocal = !localDataIsEmpty && resource.data
|
|
497
|
+
let resourceIsLocal = !localDataIsEmpty && resource.data!.id === null;
|
|
496
498
|
|
|
497
|
-
if (
|
|
498
|
-
return resolve(
|
|
499
|
+
if (identifier && resourceIsLocal) {
|
|
500
|
+
return resolve(identifier);
|
|
499
501
|
}
|
|
500
502
|
|
|
501
503
|
// fetch by data
|
|
502
|
-
if (
|
|
503
|
-
let identifier = internalModel.identifier;
|
|
504
|
+
if (identifier && !localDataIsEmpty) {
|
|
504
505
|
assertIdentifierHasId(identifier);
|
|
505
506
|
|
|
506
507
|
return this.store._fetchManager.scheduleFetch(identifier, options);
|
|
@@ -512,32 +513,28 @@ export class LegacySupport {
|
|
|
512
513
|
}
|
|
513
514
|
|
|
514
515
|
destroy() {
|
|
515
|
-
assert(
|
|
516
|
-
'Cannot destroy an internalModel while its record is materialized',
|
|
517
|
-
!this.record || this.record.isDestroyed || this.record.isDestroying
|
|
518
|
-
);
|
|
519
516
|
this.isDestroying = true;
|
|
520
517
|
|
|
521
|
-
|
|
518
|
+
let cache: Dict<{ destroy(): void }> = this._manyArrayCache;
|
|
519
|
+
this._manyArrayCache = Object.create(null);
|
|
522
520
|
Object.keys(cache).forEach((key) => {
|
|
523
521
|
cache[key]!.destroy();
|
|
524
|
-
delete cache[key];
|
|
525
522
|
});
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
523
|
+
|
|
524
|
+
cache = this._relationshipProxyCache;
|
|
525
|
+
this._relationshipProxyCache = Object.create(null);
|
|
526
|
+
Object.keys(cache).forEach((key) => {
|
|
527
|
+
const proxy = cache[key]!;
|
|
529
528
|
if (proxy.destroy) {
|
|
530
529
|
proxy.destroy();
|
|
531
530
|
}
|
|
532
|
-
delete this._relationshipProxyCache[key];
|
|
533
531
|
});
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
}
|
|
532
|
+
|
|
533
|
+
cache = this.references;
|
|
534
|
+
this.references = Object.create(null);
|
|
535
|
+
Object.keys(cache).forEach((key) => {
|
|
536
|
+
cache[key]!.destroy();
|
|
537
|
+
});
|
|
541
538
|
this.isDestroyed = true;
|
|
542
539
|
}
|
|
543
540
|
}
|
|
@@ -625,32 +622,39 @@ function assertRecordsPassedToHasMany(records: RecordInstance[]) {
|
|
|
625
622
|
.map((r) => `${typeof r}`)
|
|
626
623
|
.join(', ')}`,
|
|
627
624
|
(function () {
|
|
628
|
-
return records.every((record) =>
|
|
625
|
+
return records.every((record) => {
|
|
626
|
+
try {
|
|
627
|
+
recordIdentifierFor(record);
|
|
628
|
+
return true;
|
|
629
|
+
} catch {
|
|
630
|
+
return false;
|
|
631
|
+
}
|
|
632
|
+
});
|
|
629
633
|
})()
|
|
630
634
|
);
|
|
631
635
|
}
|
|
632
636
|
|
|
633
|
-
function
|
|
634
|
-
return records.map(
|
|
637
|
+
function extractIdentifiersFromRecords(records: RecordInstance[]): StableRecordIdentifier[] {
|
|
638
|
+
return records.map(extractIdentifierFromRecord) as StableRecordIdentifier[];
|
|
635
639
|
}
|
|
636
640
|
|
|
637
|
-
type PromiseProxyRecord = { then(): void;
|
|
641
|
+
type PromiseProxyRecord = { then(): void; content: RecordInstance | null | undefined };
|
|
638
642
|
|
|
639
|
-
function
|
|
643
|
+
function extractIdentifierFromRecord(recordOrPromiseRecord: PromiseProxyRecord | RecordInstance | null) {
|
|
640
644
|
if (!recordOrPromiseRecord) {
|
|
641
645
|
return null;
|
|
642
646
|
}
|
|
643
647
|
|
|
644
648
|
if (isPromiseRecord(recordOrPromiseRecord)) {
|
|
645
|
-
let content = recordOrPromiseRecord.
|
|
649
|
+
let content = recordOrPromiseRecord.content;
|
|
646
650
|
assert(
|
|
647
651
|
'You passed in a promise that did not originate from an EmberData relationship. You can only pass promises that come from a belongsTo or hasMany relationship to the get call.',
|
|
648
652
|
content !== undefined
|
|
649
653
|
);
|
|
650
|
-
return content ?
|
|
654
|
+
return content ? recordIdentifierFor(content) : null;
|
|
651
655
|
}
|
|
652
656
|
|
|
653
|
-
return
|
|
657
|
+
return recordIdentifierFor(recordOrPromiseRecord);
|
|
654
658
|
}
|
|
655
659
|
|
|
656
660
|
function isPromiseRecord(record: PromiseProxyRecord | RecordInstance): record is PromiseProxyRecord {
|
|
@@ -659,24 +663,15 @@ function isPromiseRecord(record: PromiseProxyRecord | RecordInstance): record is
|
|
|
659
663
|
|
|
660
664
|
function anyUnloaded(store: Store, relationship: ManyRelationship) {
|
|
661
665
|
let state = relationship.currentState;
|
|
666
|
+
const cache = store._instanceCache;
|
|
662
667
|
const unloaded = state.find((s) => {
|
|
663
|
-
let
|
|
664
|
-
return
|
|
668
|
+
let isLoaded = cache.recordIsLoaded(s, true);
|
|
669
|
+
return !isLoaded;
|
|
665
670
|
});
|
|
666
671
|
|
|
667
672
|
return unloaded || false;
|
|
668
673
|
}
|
|
669
674
|
|
|
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
675
|
function areAllInverseRecordsLoaded(store: Store, resource: JsonApiRelationship): boolean {
|
|
681
676
|
const cache = store.identifierCache;
|
|
682
677
|
|
|
@@ -684,7 +679,7 @@ function areAllInverseRecordsLoaded(store: Store, resource: JsonApiRelationship)
|
|
|
684
679
|
// treat as collection
|
|
685
680
|
// check for unloaded records
|
|
686
681
|
let hasEmptyRecords = resource.data.reduce((hasEmptyModel, resourceIdentifier) => {
|
|
687
|
-
return hasEmptyModel ||
|
|
682
|
+
return hasEmptyModel || isEmpty(store, cache, resourceIdentifier);
|
|
688
683
|
}, false);
|
|
689
684
|
|
|
690
685
|
return !hasEmptyRecords;
|
|
@@ -693,17 +688,19 @@ function areAllInverseRecordsLoaded(store: Store, resource: JsonApiRelationship)
|
|
|
693
688
|
if (!resource.data) {
|
|
694
689
|
return true;
|
|
695
690
|
} else {
|
|
696
|
-
|
|
697
|
-
return !internalModel.isEmpty;
|
|
691
|
+
return !isEmpty(store, cache, resource.data);
|
|
698
692
|
}
|
|
699
693
|
}
|
|
700
694
|
}
|
|
701
695
|
|
|
702
|
-
function
|
|
703
|
-
store: Store,
|
|
704
|
-
cache: IdentifierCache,
|
|
705
|
-
resource: ResourceIdentifierObject
|
|
706
|
-
): InternalModel {
|
|
696
|
+
function isEmpty(store: Store, cache: IdentifierCache, resource: ResourceIdentifierObject): boolean {
|
|
707
697
|
const identifier = cache.getOrCreateRecordIdentifier(resource);
|
|
708
|
-
|
|
698
|
+
const recordData = store._instanceCache.peek({ identifier, bucket: 'recordData' });
|
|
699
|
+
return !recordData || !!recordData.isEmpty?.(identifier);
|
|
700
|
+
}
|
|
701
|
+
|
|
702
|
+
function isBelongsTo(
|
|
703
|
+
relationship: BelongsToRelationship | ImplicitRelationship | ManyRelationship
|
|
704
|
+
): relationship is BelongsToRelationship {
|
|
705
|
+
return relationship.definition.kind === 'belongsTo';
|
|
709
706
|
}
|
|
@@ -9,14 +9,15 @@ import EmberObject, { get } from '@ember/object';
|
|
|
9
9
|
import { all } from 'rsvp';
|
|
10
10
|
|
|
11
11
|
import type Store from '@ember-data/store';
|
|
12
|
-
import { PromiseArray,
|
|
13
|
-
import type
|
|
14
|
-
import type
|
|
12
|
+
import { PromiseArray, recordIdentifierFor } from '@ember-data/store/-private';
|
|
13
|
+
import type ShimModelClass from '@ember-data/store/-private/legacy-model-support/shim-model-class';
|
|
14
|
+
import type { NonSingletonRecordDataManager } from '@ember-data/store/-private/managers/record-data-manager';
|
|
15
|
+
import type { CreateRecordProperties } from '@ember-data/store/-private/store-service';
|
|
15
16
|
import type { DSModelSchema } from '@ember-data/types/q/ds-model';
|
|
16
|
-
import type { Links, PaginationLinks } from '@ember-data/types/q/ember-data-json-api';
|
|
17
|
+
import type { CollectionResourceRelationship, Links, PaginationLinks } from '@ember-data/types/q/ember-data-json-api';
|
|
17
18
|
import type { StableRecordIdentifier } from '@ember-data/types/q/identifier';
|
|
19
|
+
import type { RecordData } from '@ember-data/types/q/record-data';
|
|
18
20
|
import type { RecordInstance } from '@ember-data/types/q/record-instance';
|
|
19
|
-
import type { RelationshipRecordData } from '@ember-data/types/q/relationship-record-data';
|
|
20
21
|
import type { FindOptions } from '@ember-data/types/q/store';
|
|
21
22
|
import type { Dict } from '@ember-data/types/q/utils';
|
|
22
23
|
|
|
@@ -32,7 +33,8 @@ const MutableArrayWithObject = EmberObject.extend(MutableArray) as unknown as ne
|
|
|
32
33
|
export interface ManyArrayCreateArgs {
|
|
33
34
|
store: Store;
|
|
34
35
|
type: ShimModelClass;
|
|
35
|
-
|
|
36
|
+
identifier: StableRecordIdentifier;
|
|
37
|
+
recordData: RecordData;
|
|
36
38
|
key: string;
|
|
37
39
|
isPolymorphic: boolean;
|
|
38
40
|
isAsync: boolean;
|
|
@@ -98,7 +100,8 @@ export default class ManyArray extends MutableArrayWithObject<StableRecordIdenti
|
|
|
98
100
|
declare _meta: Dict<unknown> | null;
|
|
99
101
|
declare _links: Links | PaginationLinks | null;
|
|
100
102
|
declare currentState: StableRecordIdentifier[];
|
|
101
|
-
declare
|
|
103
|
+
declare identifier: StableRecordIdentifier;
|
|
104
|
+
declare recordData: RecordData;
|
|
102
105
|
declare legacySupport: LegacySupport;
|
|
103
106
|
declare store: Store;
|
|
104
107
|
declare key: string;
|
|
@@ -272,25 +275,22 @@ export default class ManyArray extends MutableArrayWithObject<StableRecordIdenti
|
|
|
272
275
|
|
|
273
276
|
replace(idx: number, amt: number, objects?: RecordInstance[]) {
|
|
274
277
|
assert(`Cannot push mutations to the cache while updating the relationship from cache`, !this._isUpdating);
|
|
275
|
-
|
|
278
|
+
assert(
|
|
279
|
+
'The third argument to replace needs to be an array.',
|
|
280
|
+
!objects || Array.isArray(objects) || EmberArray.detect(objects)
|
|
281
|
+
);
|
|
282
|
+
const { store, identifier } = this;
|
|
276
283
|
store._backburner.join(() => {
|
|
277
284
|
let identifiers: StableRecordIdentifier[];
|
|
278
285
|
if (amt > 0) {
|
|
279
286
|
identifiers = this.currentState.slice(idx, idx + amt);
|
|
280
|
-
this.recordData.removeFromHasMany(
|
|
281
|
-
this.key,
|
|
282
|
-
// TODO RecordData V2: recordData should take identifiers not RecordDatas
|
|
283
|
-
identifiers.map((identifier) => store._instanceCache.getRecordData(identifier))
|
|
284
|
-
);
|
|
287
|
+
this.recordData.removeFromHasMany(identifier, this.key, identifiers);
|
|
285
288
|
}
|
|
286
|
-
if (objects) {
|
|
287
|
-
assert(
|
|
288
|
-
'The third argument to replace needs to be an array.',
|
|
289
|
-
Array.isArray(objects) || EmberArray.detect(objects)
|
|
290
|
-
);
|
|
289
|
+
if (objects && objects.length > 0) {
|
|
291
290
|
this.recordData.addToHasMany(
|
|
291
|
+
identifier,
|
|
292
292
|
this.key,
|
|
293
|
-
objects.map((obj: RecordInstance) =>
|
|
293
|
+
objects.map((obj: RecordInstance) => recordIdentifierFor(obj)),
|
|
294
294
|
idx
|
|
295
295
|
);
|
|
296
296
|
}
|
|
@@ -305,17 +305,23 @@ export default class ManyArray extends MutableArrayWithObject<StableRecordIdenti
|
|
|
305
305
|
}
|
|
306
306
|
this._isDirty = false;
|
|
307
307
|
this._isUpdating = true;
|
|
308
|
-
|
|
308
|
+
const identifier = this.identifier;
|
|
309
|
+
|
|
310
|
+
let jsonApi = (this.recordData as NonSingletonRecordDataManager).getRelationship(
|
|
311
|
+
identifier,
|
|
312
|
+
this.key,
|
|
313
|
+
true
|
|
314
|
+
) as CollectionResourceRelationship;
|
|
315
|
+
const cache = this.store._instanceCache;
|
|
316
|
+
const idCache = this.store.identifierCache;
|
|
309
317
|
|
|
310
318
|
let identifiers: StableRecordIdentifier[] = [];
|
|
311
319
|
if (jsonApi.data) {
|
|
312
320
|
for (let i = 0; i < jsonApi.data.length; i++) {
|
|
313
|
-
|
|
314
|
-
let im = this.store._instanceCache._internalModelForResource(jsonApi.data[i]);
|
|
315
|
-
let shouldRemove = im._isDematerializing || im.isEmpty || !im.isLoaded;
|
|
321
|
+
const identifier = idCache.getOrCreateRecordIdentifier(jsonApi.data[i]);
|
|
316
322
|
|
|
317
|
-
if (
|
|
318
|
-
identifiers.push(
|
|
323
|
+
if (cache.recordIsLoaded(identifier, true)) {
|
|
324
|
+
identifiers.push(identifier);
|
|
319
325
|
}
|
|
320
326
|
}
|
|
321
327
|
}
|
|
@@ -348,6 +354,12 @@ export default class ManyArray extends MutableArrayWithObject<StableRecordIdenti
|
|
|
348
354
|
this._isUpdating = false;
|
|
349
355
|
}
|
|
350
356
|
|
|
357
|
+
destroy() {
|
|
358
|
+
this._length = 0;
|
|
359
|
+
this.currentState = [];
|
|
360
|
+
return super.destroy();
|
|
361
|
+
}
|
|
362
|
+
|
|
351
363
|
/**
|
|
352
364
|
Reloads all of the records in the manyArray. If the manyArray
|
|
353
365
|
holds a relationship that was originally fetched using a links url
|
|
@@ -29,10 +29,8 @@ export default function modelForMixin(store: Store, normalizedModelName: string)
|
|
|
29
29
|
let mixin = MaybeMixin && MaybeMixin.class;
|
|
30
30
|
if (mixin) {
|
|
31
31
|
let ModelForMixin = Model.extend(mixin);
|
|
32
|
-
ModelForMixin.
|
|
33
|
-
|
|
34
|
-
__mixin: mixin,
|
|
35
|
-
});
|
|
32
|
+
ModelForMixin.__isMixin = true;
|
|
33
|
+
ModelForMixin.__mixin = mixin;
|
|
36
34
|
//Cache the class as a model
|
|
37
35
|
owner.register('model:' + normalizedModelName, ModelForMixin);
|
|
38
36
|
}
|