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