@ember-data/model 4.8.0-alpha.3 → 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 +5 -31
- package/addon/-private/belongs-to.js +74 -18
- package/addon/-private/has-many.js +70 -14
- package/addon/-private/legacy-data-fetch.js +28 -8
- package/addon/-private/legacy-relationships-support.ts +102 -96
- package/addon/-private/many-array.ts +24 -18
- package/addon/-private/model.js +14 -12
- package/addon/-private/notify-changes.ts +1 -1
- package/addon/-private/record-state.ts +6 -12
- package/addon/-private/references/belongs-to.ts +29 -24
- package/addon/-private/references/has-many.ts +45 -35
- package/addon/-private/relationship-meta.ts +3 -23
- package/package.json +11 -7
|
@@ -5,24 +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 {
|
|
12
|
+
import { recordIdentifierFor, storeFor } from '@ember-data/store/-private';
|
|
17
13
|
import { IdentifierCache } from '@ember-data/store/-private/caches/identifier-cache';
|
|
18
14
|
import type { DSModel } from '@ember-data/types/q/ds-model';
|
|
19
|
-
import {
|
|
15
|
+
import {
|
|
16
|
+
CollectionResourceRelationship,
|
|
17
|
+
ResourceIdentifierObject,
|
|
18
|
+
SingleResourceRelationship,
|
|
19
|
+
} from '@ember-data/types/q/ember-data-json-api';
|
|
20
20
|
import type { StableRecordIdentifier } from '@ember-data/types/q/identifier';
|
|
21
21
|
import type { RecordData } from '@ember-data/types/q/record-data';
|
|
22
22
|
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
23
|
import type { RecordInstance } from '@ember-data/types/q/record-instance';
|
|
25
|
-
import type { DefaultSingleResourceRelationship } from '@ember-data/types/q/relationship-record-data';
|
|
26
24
|
import type { FindOptions } from '@ember-data/types/q/store';
|
|
27
25
|
import type { Dict } from '@ember-data/types/q/utils';
|
|
28
26
|
|
|
@@ -43,7 +41,7 @@ type PromiseBelongsToFactory = { create(args: BelongsToProxyCreateArgs): Promise
|
|
|
43
41
|
export class LegacySupport {
|
|
44
42
|
declare record: DSModel;
|
|
45
43
|
declare store: Store;
|
|
46
|
-
declare recordData:
|
|
44
|
+
declare recordData: RecordData;
|
|
47
45
|
declare references: Dict<BelongsToReference | HasManyReference>;
|
|
48
46
|
declare identifier: StableRecordIdentifier;
|
|
49
47
|
declare _manyArrayCache: Dict<ManyArray>;
|
|
@@ -57,7 +55,7 @@ export class LegacySupport {
|
|
|
57
55
|
this.record = record;
|
|
58
56
|
this.store = storeFor(record)!;
|
|
59
57
|
this.identifier = recordIdentifierFor(record);
|
|
60
|
-
this.recordData = this.store._instanceCache.getRecordData(this.identifier)
|
|
58
|
+
this.recordData = this.store._instanceCache.getRecordData(this.identifier);
|
|
61
59
|
|
|
62
60
|
this._manyArrayCache = Object.create(null) as Dict<ManyArray>;
|
|
63
61
|
this._relationshipPromisesCache = Object.create(null) as Dict<Promise<ManyArray | RecordInstance>>;
|
|
@@ -67,16 +65,16 @@ export class LegacySupport {
|
|
|
67
65
|
|
|
68
66
|
_findBelongsTo(
|
|
69
67
|
key: string,
|
|
70
|
-
resource:
|
|
71
|
-
|
|
68
|
+
resource: SingleResourceRelationship,
|
|
69
|
+
relationship: BelongsToRelationship,
|
|
72
70
|
options?: FindOptions
|
|
73
71
|
): Promise<RecordInstance | null> {
|
|
74
72
|
// TODO @runspired follow up if parent isNew then we should not be attempting load here
|
|
75
73
|
// TODO @runspired follow up on whether this should be in the relationship requests cache
|
|
76
|
-
return this._findBelongsToByJsonApiResource(resource, this.identifier,
|
|
74
|
+
return this._findBelongsToByJsonApiResource(resource, this.identifier, relationship, options).then(
|
|
77
75
|
(identifier: StableRecordIdentifier | null) =>
|
|
78
|
-
handleCompletedRelationshipRequest(this, key,
|
|
79
|
-
(e: Error) => handleCompletedRelationshipRequest(this, key,
|
|
76
|
+
handleCompletedRelationshipRequest(this, key, relationship, identifier),
|
|
77
|
+
(e: Error) => handleCompletedRelationshipRequest(this, key, relationship, null, e)
|
|
80
78
|
);
|
|
81
79
|
}
|
|
82
80
|
|
|
@@ -86,15 +84,16 @@ export class LegacySupport {
|
|
|
86
84
|
return loadingPromise;
|
|
87
85
|
}
|
|
88
86
|
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
let
|
|
96
|
-
|
|
97
|
-
|
|
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);
|
|
98
97
|
if (this._relationshipProxyCache[key]) {
|
|
99
98
|
return this._updatePromiseProxyFor('belongsTo', key, { promise });
|
|
100
99
|
}
|
|
@@ -103,28 +102,31 @@ export class LegacySupport {
|
|
|
103
102
|
|
|
104
103
|
getBelongsTo(key: string, options?: FindOptions): PromiseBelongsTo | RecordInstance | null {
|
|
105
104
|
const { identifier, recordData } = this;
|
|
106
|
-
let resource = recordData.
|
|
105
|
+
let resource = recordData.getRelationship(this.identifier, key) as SingleResourceRelationship;
|
|
107
106
|
let relatedIdentifier =
|
|
108
107
|
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
108
|
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
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;
|
|
115
117
|
let _belongsToState: BelongsToProxyMeta = {
|
|
116
118
|
key,
|
|
117
119
|
store,
|
|
118
120
|
legacySupport: this,
|
|
119
|
-
modelName:
|
|
121
|
+
modelName: relationship.definition.type,
|
|
120
122
|
};
|
|
121
123
|
|
|
122
124
|
if (isAsync) {
|
|
123
|
-
if (
|
|
125
|
+
if (relationship.state.hasFailedLoadAttempt) {
|
|
124
126
|
return this._relationshipProxyCache[key] as PromiseBelongsTo;
|
|
125
127
|
}
|
|
126
128
|
|
|
127
|
-
let promise = this._findBelongsTo(key, resource,
|
|
129
|
+
let promise = this._findBelongsTo(key, resource, relationship, options);
|
|
128
130
|
|
|
129
131
|
return this._updatePromiseProxyFor('belongsTo', key, {
|
|
130
132
|
promise,
|
|
@@ -139,7 +141,7 @@ export class LegacySupport {
|
|
|
139
141
|
assert(
|
|
140
142
|
`You looked up the '${key}' relationship on a '${identifier.type}' with id ${
|
|
141
143
|
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 })\`)`,
|
|
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> })\`)`,
|
|
143
145
|
toReturn === null || store._instanceCache.recordIsLoaded(relatedIdentifier, true)
|
|
144
146
|
);
|
|
145
147
|
return toReturn;
|
|
@@ -148,7 +150,7 @@ export class LegacySupport {
|
|
|
148
150
|
}
|
|
149
151
|
|
|
150
152
|
setDirtyBelongsTo(key: string, value: RecordInstance | null) {
|
|
151
|
-
return this.recordData.
|
|
153
|
+
return this.recordData.setBelongsTo(this.identifier, key, extractIdentifierFromRecord(value));
|
|
152
154
|
}
|
|
153
155
|
|
|
154
156
|
getManyArray(key: string, definition?: UpgradedMeta): ManyArray {
|
|
@@ -165,6 +167,7 @@ export class LegacySupport {
|
|
|
165
167
|
manyArray = (ManyArray as unknown as ManyArrayFactory).create({
|
|
166
168
|
store: this.store,
|
|
167
169
|
type: this.store.modelFor(definition.type),
|
|
170
|
+
identifier: this.identifier,
|
|
168
171
|
recordData: this.recordData,
|
|
169
172
|
key,
|
|
170
173
|
isPolymorphic: definition.isPolymorphic,
|
|
@@ -191,7 +194,7 @@ export class LegacySupport {
|
|
|
191
194
|
return loadingPromise;
|
|
192
195
|
}
|
|
193
196
|
|
|
194
|
-
const jsonApi = this.recordData.
|
|
197
|
+
const jsonApi = this.recordData.getRelationship(this.identifier, key) as CollectionResourceRelationship;
|
|
195
198
|
|
|
196
199
|
loadingPromise = this._findHasManyByJsonApiResource(jsonApi, this.identifier, relationship, options).then(
|
|
197
200
|
() => handleCompletedRelationshipRequest(this, key, relationship, manyArray),
|
|
@@ -250,7 +253,7 @@ export class LegacySupport {
|
|
|
250
253
|
assert(
|
|
251
254
|
`You looked up the '${key}' relationship on a '${this.identifier.type}' with id ${
|
|
252
255
|
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 })')`,
|
|
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> })')`,
|
|
254
257
|
!anyUnloaded(this.store, relationship)
|
|
255
258
|
);
|
|
256
259
|
|
|
@@ -262,7 +265,7 @@ export class LegacySupport {
|
|
|
262
265
|
|
|
263
266
|
setDirtyHasMany(key: string, records: RecordInstance[]) {
|
|
264
267
|
assertRecordsPassedToHasMany(records);
|
|
265
|
-
return this.recordData.
|
|
268
|
+
return this.recordData.setHasMany(this.identifier, key, extractIdentifiersFromRecords(records));
|
|
266
269
|
}
|
|
267
270
|
|
|
268
271
|
_updatePromiseProxyFor(kind: 'hasMany', key: string, args: HasManyProxyCreateArgs): PromiseManyArray;
|
|
@@ -343,7 +346,7 @@ export class LegacySupport {
|
|
|
343
346
|
}
|
|
344
347
|
|
|
345
348
|
_findHasManyByJsonApiResource(
|
|
346
|
-
resource,
|
|
349
|
+
resource: CollectionResourceRelationship,
|
|
347
350
|
parentIdentifier: StableRecordIdentifier,
|
|
348
351
|
relationship: ManyRelationship,
|
|
349
352
|
options: FindOptions = {}
|
|
@@ -353,12 +356,10 @@ export class LegacySupport {
|
|
|
353
356
|
return resolve();
|
|
354
357
|
}
|
|
355
358
|
const { definition, state } = relationship;
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
let { isStale, hasDematerializedInverse, hasReceivedData, isEmpty, shouldForceReload } = state;
|
|
359
|
+
const adapter = this.store.adapterFor(definition.type);
|
|
360
|
+
const { isStale, hasDematerializedInverse, hasReceivedData, isEmpty, shouldForceReload } = state;
|
|
359
361
|
const allInverseRecordsAreLoaded = areAllInverseRecordsLoaded(this.store, resource);
|
|
360
|
-
|
|
361
|
-
let shouldFindViaLink =
|
|
362
|
+
const shouldFindViaLink =
|
|
362
363
|
resource.links &&
|
|
363
364
|
resource.links.related &&
|
|
364
365
|
(typeof adapter.findHasMany === 'function' || typeof resource.data === 'undefined') &&
|
|
@@ -368,9 +369,9 @@ export class LegacySupport {
|
|
|
368
369
|
if (shouldFindViaLink) {
|
|
369
370
|
// findHasMany, although not public, does not need to care about our upgrade relationship definitions
|
|
370
371
|
// and can stick with the public definition API for now.
|
|
371
|
-
const relationshipMeta = this.store
|
|
372
|
-
|
|
373
|
-
|
|
372
|
+
const relationshipMeta = this.store
|
|
373
|
+
.getSchemaDefinitionService()
|
|
374
|
+
.relationshipsDefinitionFor({ type: definition.inverseType })[definition.key];
|
|
374
375
|
let adapter = this.store.adapterFor(parentIdentifier.type);
|
|
375
376
|
|
|
376
377
|
/*
|
|
@@ -393,16 +394,16 @@ export class LegacySupport {
|
|
|
393
394
|
typeof adapter.findHasMany === 'function'
|
|
394
395
|
);
|
|
395
396
|
|
|
396
|
-
return _findHasMany(adapter, this.store, parentIdentifier, resource.links
|
|
397
|
+
return _findHasMany(adapter, this.store, parentIdentifier, resource.links!.related, relationshipMeta, options);
|
|
397
398
|
}
|
|
398
399
|
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
let hasLocalPartialData =
|
|
400
|
+
const preferLocalCache = hasReceivedData && !isEmpty;
|
|
401
|
+
const hasLocalPartialData =
|
|
402
402
|
hasDematerializedInverse || (isEmpty && Array.isArray(resource.data) && resource.data.length > 0);
|
|
403
403
|
|
|
404
404
|
// fetch using data, pulling from local cache if possible
|
|
405
405
|
if (!shouldForceReload && !isStale && (preferLocalCache || hasLocalPartialData)) {
|
|
406
|
+
assert(`Expected collection to be an array`, Array.isArray(resource.data));
|
|
406
407
|
let finds = new Array(resource.data.length);
|
|
407
408
|
for (let i = 0; i < resource.data.length; i++) {
|
|
408
409
|
let identifier = this.store.identifierCache.getOrCreateRecordIdentifier(resource.data[i]);
|
|
@@ -416,6 +417,7 @@ export class LegacySupport {
|
|
|
416
417
|
|
|
417
418
|
// fetch by data
|
|
418
419
|
if (hasData || hasLocalPartialData) {
|
|
420
|
+
assert(`Expected collection to be an array`, Array.isArray(resource.data));
|
|
419
421
|
let identifiers = resource.data.map((json) => this.store.identifierCache.getOrCreateRecordIdentifier(json));
|
|
420
422
|
let fetches = new Array(identifiers.length);
|
|
421
423
|
const manager = this.store._fetchManager;
|
|
@@ -437,9 +439,9 @@ export class LegacySupport {
|
|
|
437
439
|
}
|
|
438
440
|
|
|
439
441
|
_findBelongsToByJsonApiResource(
|
|
440
|
-
resource,
|
|
442
|
+
resource: SingleResourceRelationship,
|
|
441
443
|
parentIdentifier: StableRecordIdentifier,
|
|
442
|
-
|
|
444
|
+
relationship: BelongsToRelationship,
|
|
443
445
|
options: FindOptions = {}
|
|
444
446
|
): Promise<StableRecordIdentifier | null> {
|
|
445
447
|
if (!resource) {
|
|
@@ -448,32 +450,33 @@ export class LegacySupport {
|
|
|
448
450
|
|
|
449
451
|
const identifier = resource.data ? this.store.identifierCache.getOrCreateRecordIdentifier(resource.data) : null;
|
|
450
452
|
|
|
451
|
-
let { isStale, hasDematerializedInverse, hasReceivedData, isEmpty, shouldForceReload } =
|
|
452
|
-
.state as RelationshipState;
|
|
453
|
-
const allInverseRecordsAreLoaded = areAllInverseRecordsLoaded(this.store, resource);
|
|
454
|
-
|
|
455
|
-
let shouldFindViaLink =
|
|
456
|
-
resource.links &&
|
|
457
|
-
resource.links.related &&
|
|
458
|
-
(shouldForceReload || hasDematerializedInverse || isStale || (!allInverseRecordsAreLoaded && !isEmpty));
|
|
453
|
+
let { isStale, hasDematerializedInverse, hasReceivedData, isEmpty, shouldForceReload } = relationship.state;
|
|
459
454
|
|
|
460
|
-
if
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
return pendingRequest;
|
|
465
|
-
}
|
|
455
|
+
// short circuit if we are already loading
|
|
456
|
+
let pendingRequest = identifier && this.store._fetchManager.getPendingFetch(identifier, options);
|
|
457
|
+
if (pendingRequest) {
|
|
458
|
+
return pendingRequest;
|
|
466
459
|
}
|
|
467
460
|
|
|
461
|
+
const allInverseRecordsAreLoaded = areAllInverseRecordsLoaded(this.store, resource);
|
|
462
|
+
const shouldFindViaLink =
|
|
463
|
+
resource.links?.related &&
|
|
464
|
+
(shouldForceReload || hasDematerializedInverse || isStale || (!allInverseRecordsAreLoaded && !isEmpty));
|
|
465
|
+
|
|
468
466
|
// fetch via link
|
|
469
467
|
if (shouldFindViaLink) {
|
|
470
|
-
|
|
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);
|
|
471
474
|
}
|
|
472
475
|
|
|
473
476
|
let preferLocalCache = hasReceivedData && allInverseRecordsAreLoaded && !isEmpty;
|
|
474
477
|
let hasLocalPartialData = hasDematerializedInverse || (isEmpty && resource.data);
|
|
475
478
|
// null is explicit empty, undefined is "we don't know anything"
|
|
476
|
-
|
|
479
|
+
const localDataIsEmpty = resource.data === undefined || resource.data === null;
|
|
477
480
|
|
|
478
481
|
// fetch using data, pulling from local cache if possible
|
|
479
482
|
if (!shouldForceReload && !isStale && (preferLocalCache || hasLocalPartialData)) {
|
|
@@ -485,13 +488,13 @@ export class LegacySupport {
|
|
|
485
488
|
}
|
|
486
489
|
|
|
487
490
|
if (!identifier) {
|
|
488
|
-
assert(`No Information found for ${resource.lid}`, identifier);
|
|
491
|
+
assert(`No Information found for ${resource.data!.lid}`, identifier);
|
|
489
492
|
}
|
|
490
493
|
|
|
491
494
|
return this.store._instanceCache._fetchDataIfNeededForIdentifier(identifier, options);
|
|
492
495
|
}
|
|
493
496
|
|
|
494
|
-
let resourceIsLocal = !localDataIsEmpty && resource.data
|
|
497
|
+
let resourceIsLocal = !localDataIsEmpty && resource.data!.id === null;
|
|
495
498
|
|
|
496
499
|
if (identifier && resourceIsLocal) {
|
|
497
500
|
return resolve(identifier);
|
|
@@ -512,26 +515,26 @@ export class LegacySupport {
|
|
|
512
515
|
destroy() {
|
|
513
516
|
this.isDestroying = true;
|
|
514
517
|
|
|
515
|
-
|
|
518
|
+
let cache: Dict<{ destroy(): void }> = this._manyArrayCache;
|
|
519
|
+
this._manyArrayCache = Object.create(null);
|
|
516
520
|
Object.keys(cache).forEach((key) => {
|
|
517
521
|
cache[key]!.destroy();
|
|
518
|
-
delete cache[key];
|
|
519
522
|
});
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
+
|
|
524
|
+
cache = this._relationshipProxyCache;
|
|
525
|
+
this._relationshipProxyCache = Object.create(null);
|
|
526
|
+
Object.keys(cache).forEach((key) => {
|
|
527
|
+
const proxy = cache[key]!;
|
|
523
528
|
if (proxy.destroy) {
|
|
524
529
|
proxy.destroy();
|
|
525
530
|
}
|
|
526
|
-
delete this._relationshipProxyCache[key];
|
|
527
531
|
});
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
}
|
|
532
|
+
|
|
533
|
+
cache = this.references;
|
|
534
|
+
this.references = Object.create(null);
|
|
535
|
+
Object.keys(cache).forEach((key) => {
|
|
536
|
+
cache[key]!.destroy();
|
|
537
|
+
});
|
|
535
538
|
this.isDestroyed = true;
|
|
536
539
|
}
|
|
537
540
|
}
|
|
@@ -631,16 +634,13 @@ function assertRecordsPassedToHasMany(records: RecordInstance[]) {
|
|
|
631
634
|
);
|
|
632
635
|
}
|
|
633
636
|
|
|
634
|
-
function
|
|
635
|
-
return records.map(
|
|
637
|
+
function extractIdentifiersFromRecords(records: RecordInstance[]): StableRecordIdentifier[] {
|
|
638
|
+
return records.map(extractIdentifierFromRecord) as StableRecordIdentifier[];
|
|
636
639
|
}
|
|
637
640
|
|
|
638
|
-
type PromiseProxyRecord = {
|
|
639
|
-
then(): void;
|
|
640
|
-
content: RecordInstance | null | undefined;
|
|
641
|
-
};
|
|
641
|
+
type PromiseProxyRecord = { then(): void; content: RecordInstance | null | undefined };
|
|
642
642
|
|
|
643
|
-
function
|
|
643
|
+
function extractIdentifierFromRecord(recordOrPromiseRecord: PromiseProxyRecord | RecordInstance | null) {
|
|
644
644
|
if (!recordOrPromiseRecord) {
|
|
645
645
|
return null;
|
|
646
646
|
}
|
|
@@ -651,10 +651,10 @@ function extractRecordDataFromRecord(recordOrPromiseRecord: PromiseProxyRecord |
|
|
|
651
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.',
|
|
652
652
|
content !== undefined
|
|
653
653
|
);
|
|
654
|
-
return content ?
|
|
654
|
+
return content ? recordIdentifierFor(content) : null;
|
|
655
655
|
}
|
|
656
656
|
|
|
657
|
-
return
|
|
657
|
+
return recordIdentifierFor(recordOrPromiseRecord);
|
|
658
658
|
}
|
|
659
659
|
|
|
660
660
|
function isPromiseRecord(record: PromiseProxyRecord | RecordInstance): record is PromiseProxyRecord {
|
|
@@ -696,5 +696,11 @@ function areAllInverseRecordsLoaded(store: Store, resource: JsonApiRelationship)
|
|
|
696
696
|
function isEmpty(store: Store, cache: IdentifierCache, resource: ResourceIdentifierObject): boolean {
|
|
697
697
|
const identifier = cache.getOrCreateRecordIdentifier(resource);
|
|
698
698
|
const recordData = store._instanceCache.peek({ identifier, bucket: 'recordData' });
|
|
699
|
-
return !recordData || !!recordData.isEmpty?.();
|
|
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';
|
|
700
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,
|
|
12
|
+
import { PromiseArray, recordIdentifierFor } from '@ember-data/store/-private';
|
|
13
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';
|
|
14
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,7 +305,13 @@ 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;
|
|
309
315
|
const cache = this.store._instanceCache;
|
|
310
316
|
const idCache = this.store.identifierCache;
|
|
311
317
|
|
package/addon/-private/model.js
CHANGED
|
@@ -19,6 +19,7 @@ import { HAS_DEBUG_PACKAGE } from '@ember-data/private-build-infra';
|
|
|
19
19
|
import {
|
|
20
20
|
DEPRECATE_EARLY_STATIC,
|
|
21
21
|
DEPRECATE_MODEL_REOPEN,
|
|
22
|
+
DEPRECATE_RELATIONSHIPS_WITHOUT_INVERSE,
|
|
22
23
|
DEPRECATE_SAVE_PROMISE_ACCESS,
|
|
23
24
|
} from '@ember-data/private-build-infra/deprecations';
|
|
24
25
|
import { recordIdentifierFor, storeFor } from '@ember-data/store';
|
|
@@ -119,7 +120,7 @@ function computeOnce(target, key, desc) {
|
|
|
119
120
|
*/
|
|
120
121
|
class Model extends EmberObject {
|
|
121
122
|
@service store;
|
|
122
|
-
|
|
123
|
+
___private_notifications;
|
|
123
124
|
|
|
124
125
|
init(options = {}) {
|
|
125
126
|
if (DEBUG && !options._secretInit && !options._createProps) {
|
|
@@ -142,7 +143,7 @@ class Model extends EmberObject {
|
|
|
142
143
|
let notifications = store._notificationManager;
|
|
143
144
|
let identity = recordIdentifierFor(this);
|
|
144
145
|
|
|
145
|
-
this
|
|
146
|
+
this.___private_notifications = notifications.subscribe(identity, (identifier, type, key) => {
|
|
146
147
|
notifyChanges(identifier, type, key, this, store);
|
|
147
148
|
});
|
|
148
149
|
}
|
|
@@ -152,7 +153,7 @@ class Model extends EmberObject {
|
|
|
152
153
|
this.___recordState?.destroy();
|
|
153
154
|
const store = storeFor(this);
|
|
154
155
|
const identifier = recordIdentifierFor(this);
|
|
155
|
-
store._notificationManager.unsubscribe(this
|
|
156
|
+
store._notificationManager.unsubscribe(this.___private_notifications);
|
|
156
157
|
// Legacy behavior is to notify the relationships on destroy
|
|
157
158
|
// such that they "clear". It's uncertain this behavior would
|
|
158
159
|
// be good for a new model paradigm, likely cheaper and safer
|
|
@@ -487,7 +488,7 @@ class Model extends EmberObject {
|
|
|
487
488
|
);
|
|
488
489
|
|
|
489
490
|
if (normalizedId !== null && didChange) {
|
|
490
|
-
this.store._instanceCache.setRecordId(identifier
|
|
491
|
+
this.store._instanceCache.setRecordId(identifier, normalizedId);
|
|
491
492
|
this.store._notificationManager.notify(identifier, 'identity');
|
|
492
493
|
}
|
|
493
494
|
}
|
|
@@ -813,7 +814,7 @@ class Model extends EmberObject {
|
|
|
813
814
|
and value is an [oldProp, newProp] array.
|
|
814
815
|
*/
|
|
815
816
|
changedAttributes() {
|
|
816
|
-
return recordDataFor(this).
|
|
817
|
+
return recordDataFor(this).changedAttrs(recordIdentifierFor(this));
|
|
817
818
|
}
|
|
818
819
|
|
|
819
820
|
/**
|
|
@@ -837,7 +838,7 @@ class Model extends EmberObject {
|
|
|
837
838
|
rollbackAttributes() {
|
|
838
839
|
const { currentState } = this;
|
|
839
840
|
const { isNew } = currentState;
|
|
840
|
-
recordDataFor(this).
|
|
841
|
+
recordDataFor(this).rollbackAttrs(recordIdentifierFor(this));
|
|
841
842
|
this.errors.clear();
|
|
842
843
|
currentState.cleanErrorRequests();
|
|
843
844
|
if (isNew) {
|
|
@@ -980,7 +981,7 @@ class Model extends EmberObject {
|
|
|
980
981
|
import Model, { belongsTo } from '@ember-data/model';
|
|
981
982
|
|
|
982
983
|
export default class BlogModel extends Model {
|
|
983
|
-
@belongsTo({ async: true }) user;
|
|
984
|
+
@belongsTo('user', { async: true, inverse: null }) user;
|
|
984
985
|
}
|
|
985
986
|
```
|
|
986
987
|
|
|
@@ -1048,7 +1049,7 @@ class Model extends EmberObject {
|
|
|
1048
1049
|
import Model, { hasMany } from '@ember-data/model';
|
|
1049
1050
|
|
|
1050
1051
|
export default class BlogModel extends Model {
|
|
1051
|
-
@hasMany({ async: true }) comments;
|
|
1052
|
+
@hasMany('comment', { async: true, inverse: null }) comments;
|
|
1052
1053
|
}
|
|
1053
1054
|
|
|
1054
1055
|
let blog = store.push({
|
|
@@ -1201,7 +1202,7 @@ class Model extends EmberObject {
|
|
|
1201
1202
|
|
|
1202
1203
|
```javascript
|
|
1203
1204
|
import RESTSerializer from '@ember-data/serializer/rest';
|
|
1204
|
-
import { underscore } from '
|
|
1205
|
+
import { underscore } from '<app-name>/utils/string-utils';
|
|
1205
1206
|
|
|
1206
1207
|
export default const PostSerializer = RESTSerializer.extend({
|
|
1207
1208
|
payloadKeyFromModelName(modelName) {
|
|
@@ -1795,7 +1796,7 @@ class Model extends EmberObject {
|
|
|
1795
1796
|
meta.key = name;
|
|
1796
1797
|
meta.name = name;
|
|
1797
1798
|
meta.parentModelName = modelName;
|
|
1798
|
-
relationships[name] = relationshipFromMeta(meta);
|
|
1799
|
+
relationships[name] = DEPRECATE_RELATIONSHIPS_WITHOUT_INVERSE ? relationshipFromMeta(meta) : meta;
|
|
1799
1800
|
}
|
|
1800
1801
|
});
|
|
1801
1802
|
return relationships;
|
|
@@ -1865,6 +1866,7 @@ class Model extends EmberObject {
|
|
|
1865
1866
|
let map = new Map();
|
|
1866
1867
|
|
|
1867
1868
|
this.eachComputedProperty((name, meta) => {
|
|
1869
|
+
// TODO end reliance on these booleans and stop leaking them in the spec
|
|
1868
1870
|
if (meta.isRelationship) {
|
|
1869
1871
|
map.set(name, meta.kind);
|
|
1870
1872
|
} else if (meta.isAttribute) {
|
|
@@ -2411,7 +2413,7 @@ if (DEBUG) {
|
|
|
2411
2413
|
until: '5.0',
|
|
2412
2414
|
since: { available: '4.8', enabled: '4.8' },
|
|
2413
2415
|
});
|
|
2414
|
-
return originalReopen.call(this, arguments);
|
|
2416
|
+
return originalReopen.call(this, ...arguments);
|
|
2415
2417
|
};
|
|
2416
2418
|
|
|
2417
2419
|
Model.reopenClass = function deprecatedReopenClass() {
|
|
@@ -2425,7 +2427,7 @@ if (DEBUG) {
|
|
|
2425
2427
|
since: { available: '4.8', enabled: '4.8' },
|
|
2426
2428
|
}
|
|
2427
2429
|
);
|
|
2428
|
-
return originalReopenClass.call(this, arguments);
|
|
2430
|
+
return originalReopenClass.call(this, ...arguments);
|
|
2429
2431
|
};
|
|
2430
2432
|
}
|
|
2431
2433
|
}
|
|
@@ -66,7 +66,7 @@ function notifyRelationship(identifier: StableRecordIdentifier, key: string, rec
|
|
|
66
66
|
function notifyAttribute(store: Store, identifier: StableRecordIdentifier, key: string, record: Model) {
|
|
67
67
|
let currentValue = cacheFor(record, key);
|
|
68
68
|
|
|
69
|
-
if (currentValue !== store._instanceCache.getRecordData(identifier).getAttr(key)) {
|
|
69
|
+
if (currentValue !== store._instanceCache.getRecordData(identifier).getAttr(identifier, key)) {
|
|
70
70
|
record.notifyPropertyChange(key);
|
|
71
71
|
}
|
|
72
72
|
}
|