@ember-data/model 4.11.0 → 4.12.0-beta.0
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/diff-array.ts → -private.js} +41 -13
- package/addon/-private.js.map +1 -0
- package/addon/has-many-a275fe0d.js +6283 -0
- package/addon/has-many-a275fe0d.js.map +1 -0
- package/addon/index.js +1 -0
- package/addon/index.js.map +1 -0
- package/addon-main.js +90 -0
- package/package.json +43 -15
- package/addon/-private/attr.js +0 -162
- package/addon/-private/belongs-to.js +0 -268
- package/addon/-private/debug/assert-polymorphic-type.js +0 -71
- package/addon/-private/deprecated-promise-proxy.ts +0 -76
- package/addon/-private/errors.ts +0 -425
- package/addon/-private/has-many.js +0 -280
- package/addon/-private/index.ts +0 -14
- package/addon/-private/legacy-data-fetch.js +0 -395
- package/addon/-private/legacy-data-utils.ts +0 -92
- package/addon/-private/legacy-relationships-support.ts +0 -774
- package/addon/-private/many-array.ts +0 -401
- package/addon/-private/model-for-mixin.ts +0 -38
- package/addon/-private/model.js +0 -2516
- package/addon/-private/notify-changes.ts +0 -72
- package/addon/-private/promise-belongs-to.ts +0 -73
- package/addon/-private/promise-many-array.ts +0 -425
- package/addon/-private/promise-proxy-base.js +0 -4
- package/addon/-private/record-state.ts +0 -468
- package/addon/-private/references/belongs-to.ts +0 -624
- package/addon/-private/references/has-many.ts +0 -669
- package/addon/-private/relationship-meta.ts +0 -98
- package/addon/-private/util.ts +0 -31
- package/addon/index.ts +0 -39
- package/blueprints/model/HELP.md +0 -26
- package/blueprints/model/files/__root__/__path__/__name__.js +0 -5
- package/blueprints/model/index.js +0 -158
- package/blueprints/model/native-files/__root__/__path__/__name__.js +0 -5
- package/blueprints/model-test/index.js +0 -33
- package/blueprints/model-test/mocha-files/__root__/__path__/__test__.js +0 -18
- package/blueprints/model-test/mocha-rfc-232-files/__root__/__path__/__test__.js +0 -15
- package/blueprints/model-test/qunit-files/__root__/__path__/__test__.js +0 -14
- package/index.js +0 -46
|
@@ -1,774 +0,0 @@
|
|
|
1
|
-
import { assert, deprecate } from '@ember/debug';
|
|
2
|
-
import { DEBUG } from '@glimmer/env';
|
|
3
|
-
|
|
4
|
-
import { importSync } from '@embroider/macros';
|
|
5
|
-
import { all, resolve } from 'rsvp';
|
|
6
|
-
|
|
7
|
-
import { HAS_RECORD_DATA_PACKAGE } from '@ember-data/private-build-infra';
|
|
8
|
-
import { DEPRECATE_PROMISE_PROXIES } from '@ember-data/private-build-infra/deprecations';
|
|
9
|
-
import type { UpgradedMeta } from '@ember-data/record-data/-private/graph/-edge-definition';
|
|
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';
|
|
14
|
-
import type Store from '@ember-data/store';
|
|
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';
|
|
17
|
-
import type { DSModel } from '@ember-data/types/q/ds-model';
|
|
18
|
-
import { CollectionResourceRelationship, SingleResourceRelationship } from '@ember-data/types/q/ember-data-json-api';
|
|
19
|
-
import type { StableRecordIdentifier } from '@ember-data/types/q/identifier';
|
|
20
|
-
import type { RecordData } from '@ember-data/types/q/record-data';
|
|
21
|
-
import type { JsonApiRelationship } from '@ember-data/types/q/record-data-json-api';
|
|
22
|
-
import type { RecordInstance } from '@ember-data/types/q/record-instance';
|
|
23
|
-
import type { FindOptions } from '@ember-data/types/q/store';
|
|
24
|
-
import type { Dict } from '@ember-data/types/q/utils';
|
|
25
|
-
|
|
26
|
-
import { _findBelongsTo, _findHasMany } from './legacy-data-fetch';
|
|
27
|
-
import { assertIdentifierHasId } from './legacy-data-utils';
|
|
28
|
-
import RelatedCollection from './many-array';
|
|
29
|
-
import type { BelongsToProxyCreateArgs, BelongsToProxyMeta } from './promise-belongs-to';
|
|
30
|
-
import PromiseBelongsTo from './promise-belongs-to';
|
|
31
|
-
import type { HasManyProxyCreateArgs } from './promise-many-array';
|
|
32
|
-
import PromiseManyArray from './promise-many-array';
|
|
33
|
-
import BelongsToReference from './references/belongs-to';
|
|
34
|
-
import HasManyReference from './references/has-many';
|
|
35
|
-
|
|
36
|
-
type PromiseBelongsToFactory = { create(args: BelongsToProxyCreateArgs): PromiseBelongsTo };
|
|
37
|
-
|
|
38
|
-
export class LegacySupport {
|
|
39
|
-
declare record: DSModel;
|
|
40
|
-
declare store: Store;
|
|
41
|
-
declare recordData: RecordData;
|
|
42
|
-
declare references: Dict<BelongsToReference | HasManyReference>;
|
|
43
|
-
declare identifier: StableRecordIdentifier;
|
|
44
|
-
declare _manyArrayCache: Dict<RelatedCollection>;
|
|
45
|
-
declare _relationshipPromisesCache: Dict<Promise<RelatedCollection | RecordInstance>>;
|
|
46
|
-
declare _relationshipProxyCache: Dict<PromiseManyArray | PromiseBelongsTo>;
|
|
47
|
-
|
|
48
|
-
declare isDestroying: boolean;
|
|
49
|
-
declare isDestroyed: boolean;
|
|
50
|
-
|
|
51
|
-
constructor(record: DSModel) {
|
|
52
|
-
this.record = record;
|
|
53
|
-
this.store = storeFor(record)!;
|
|
54
|
-
this.identifier = recordIdentifierFor(record);
|
|
55
|
-
this.recordData = this.store._instanceCache.getRecordData(this.identifier);
|
|
56
|
-
|
|
57
|
-
this._manyArrayCache = Object.create(null) as Dict<RelatedCollection>;
|
|
58
|
-
this._relationshipPromisesCache = Object.create(null) as Dict<Promise<RelatedCollection | RecordInstance>>;
|
|
59
|
-
this._relationshipProxyCache = Object.create(null) as Dict<PromiseManyArray | PromiseBelongsTo>;
|
|
60
|
-
this.references = Object.create(null) as Dict<BelongsToReference>;
|
|
61
|
-
}
|
|
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
|
-
|
|
89
|
-
_findBelongsTo(
|
|
90
|
-
key: string,
|
|
91
|
-
resource: SingleResourceRelationship,
|
|
92
|
-
relationship: BelongsToRelationship,
|
|
93
|
-
options?: FindOptions
|
|
94
|
-
): Promise<RecordInstance | null> {
|
|
95
|
-
// TODO @runspired follow up if parent isNew then we should not be attempting load here
|
|
96
|
-
// TODO @runspired follow up on whether this should be in the relationship requests cache
|
|
97
|
-
return this._findBelongsToByJsonApiResource(resource, this.identifier, relationship, options).then(
|
|
98
|
-
(identifier: StableRecordIdentifier | null) =>
|
|
99
|
-
handleCompletedRelationshipRequest(this, key, relationship, identifier),
|
|
100
|
-
(e: Error) => handleCompletedRelationshipRequest(this, key, relationship, null, e)
|
|
101
|
-
);
|
|
102
|
-
}
|
|
103
|
-
|
|
104
|
-
reloadBelongsTo(key: string, options?: FindOptions): Promise<RecordInstance | null> {
|
|
105
|
-
let loadingPromise = this._relationshipPromisesCache[key] as Promise<RecordInstance | null> | undefined;
|
|
106
|
-
if (loadingPromise) {
|
|
107
|
-
return loadingPromise;
|
|
108
|
-
}
|
|
109
|
-
|
|
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);
|
|
120
|
-
if (this._relationshipProxyCache[key]) {
|
|
121
|
-
return this._updatePromiseProxyFor('belongsTo', key, { promise });
|
|
122
|
-
}
|
|
123
|
-
return promise;
|
|
124
|
-
}
|
|
125
|
-
|
|
126
|
-
getBelongsTo(key: string, options?: FindOptions): PromiseBelongsTo | RecordInstance | null {
|
|
127
|
-
const { identifier, recordData } = this;
|
|
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;
|
|
140
|
-
let _belongsToState: BelongsToProxyMeta = {
|
|
141
|
-
key,
|
|
142
|
-
store,
|
|
143
|
-
legacySupport: this,
|
|
144
|
-
modelName: relationship.definition.type,
|
|
145
|
-
};
|
|
146
|
-
|
|
147
|
-
if (isAsync) {
|
|
148
|
-
if (relationship.state.hasFailedLoadAttempt) {
|
|
149
|
-
return this._relationshipProxyCache[key] as PromiseBelongsTo;
|
|
150
|
-
}
|
|
151
|
-
|
|
152
|
-
let promise = this._findBelongsTo(key, resource, relationship, options);
|
|
153
|
-
const isLoaded = relatedIdentifier && store._instanceCache.recordIsLoaded(relatedIdentifier);
|
|
154
|
-
|
|
155
|
-
return this._updatePromiseProxyFor('belongsTo', key, {
|
|
156
|
-
promise,
|
|
157
|
-
content: isLoaded ? store._instanceCache.getRecord(relatedIdentifier!) : null,
|
|
158
|
-
_belongsToState,
|
|
159
|
-
});
|
|
160
|
-
} else {
|
|
161
|
-
if (relatedIdentifier === null) {
|
|
162
|
-
return null;
|
|
163
|
-
} else {
|
|
164
|
-
let toReturn = store._instanceCache.getRecord(relatedIdentifier);
|
|
165
|
-
assert(
|
|
166
|
-
`You looked up the '${key}' relationship on a '${identifier.type}' with id ${
|
|
167
|
-
identifier.id || 'null'
|
|
168
|
-
} 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> })\`)`,
|
|
169
|
-
toReturn === null || store._instanceCache.recordIsLoaded(relatedIdentifier, true)
|
|
170
|
-
);
|
|
171
|
-
return toReturn;
|
|
172
|
-
}
|
|
173
|
-
}
|
|
174
|
-
}
|
|
175
|
-
|
|
176
|
-
setDirtyBelongsTo(key: string, value: RecordInstance | null) {
|
|
177
|
-
return this.recordData.update(
|
|
178
|
-
{
|
|
179
|
-
op: 'replaceRelatedRecord',
|
|
180
|
-
record: this.identifier,
|
|
181
|
-
field: key,
|
|
182
|
-
value: extractIdentifierFromRecord(value),
|
|
183
|
-
},
|
|
184
|
-
// @ts-expect-error
|
|
185
|
-
true
|
|
186
|
-
);
|
|
187
|
-
}
|
|
188
|
-
|
|
189
|
-
_getCurrentState(
|
|
190
|
-
identifier: StableRecordIdentifier,
|
|
191
|
-
field: string
|
|
192
|
-
): [StableRecordIdentifier[], CollectionResourceRelationship] {
|
|
193
|
-
let jsonApi = (this.recordData as NonSingletonRecordDataManager).getRelationship(
|
|
194
|
-
identifier,
|
|
195
|
-
field,
|
|
196
|
-
true
|
|
197
|
-
) as CollectionResourceRelationship;
|
|
198
|
-
const cache = this.store._instanceCache;
|
|
199
|
-
let identifiers: StableRecordIdentifier[] = [];
|
|
200
|
-
if (jsonApi.data) {
|
|
201
|
-
for (let i = 0; i < jsonApi.data.length; i++) {
|
|
202
|
-
const identifier = jsonApi.data[i];
|
|
203
|
-
assert(`Expected a stable identifier`, isStableIdentifier(identifier));
|
|
204
|
-
if (cache.recordIsLoaded(identifier, true)) {
|
|
205
|
-
identifiers.push(identifier);
|
|
206
|
-
}
|
|
207
|
-
}
|
|
208
|
-
}
|
|
209
|
-
|
|
210
|
-
return [identifiers, jsonApi];
|
|
211
|
-
}
|
|
212
|
-
|
|
213
|
-
getManyArray(key: string, definition?: UpgradedMeta): RelatedCollection {
|
|
214
|
-
if (HAS_RECORD_DATA_PACKAGE) {
|
|
215
|
-
let manyArray: RelatedCollection | undefined = this._manyArrayCache[key];
|
|
216
|
-
if (!definition) {
|
|
217
|
-
const graphFor = (
|
|
218
|
-
importSync('@ember-data/record-data/-private') as typeof import('@ember-data/record-data/-private')
|
|
219
|
-
).graphFor;
|
|
220
|
-
definition = graphFor(this.store).get(this.identifier, key).definition;
|
|
221
|
-
}
|
|
222
|
-
|
|
223
|
-
if (!manyArray) {
|
|
224
|
-
const [identifiers, doc] = this._getCurrentState(this.identifier, key);
|
|
225
|
-
|
|
226
|
-
manyArray = new RelatedCollection({
|
|
227
|
-
store: this.store,
|
|
228
|
-
type: definition.type,
|
|
229
|
-
identifier: this.identifier,
|
|
230
|
-
recordData: this.recordData,
|
|
231
|
-
identifiers,
|
|
232
|
-
key,
|
|
233
|
-
meta: doc.meta || null,
|
|
234
|
-
links: doc.links || null,
|
|
235
|
-
isPolymorphic: definition.isPolymorphic,
|
|
236
|
-
isAsync: definition.isAsync,
|
|
237
|
-
_inverseIsAsync: definition.inverseIsAsync,
|
|
238
|
-
manager: this,
|
|
239
|
-
isLoaded: !definition.isAsync,
|
|
240
|
-
allowMutation: true,
|
|
241
|
-
});
|
|
242
|
-
this._manyArrayCache[key] = manyArray;
|
|
243
|
-
}
|
|
244
|
-
|
|
245
|
-
return manyArray;
|
|
246
|
-
}
|
|
247
|
-
assert('hasMany only works with the @ember-data/record-data package');
|
|
248
|
-
}
|
|
249
|
-
|
|
250
|
-
fetchAsyncHasMany(
|
|
251
|
-
key: string,
|
|
252
|
-
relationship: ManyRelationship,
|
|
253
|
-
manyArray: RelatedCollection,
|
|
254
|
-
options?: FindOptions
|
|
255
|
-
): Promise<RelatedCollection> {
|
|
256
|
-
if (HAS_RECORD_DATA_PACKAGE) {
|
|
257
|
-
let loadingPromise = this._relationshipPromisesCache[key] as Promise<RelatedCollection> | undefined;
|
|
258
|
-
if (loadingPromise) {
|
|
259
|
-
return loadingPromise;
|
|
260
|
-
}
|
|
261
|
-
|
|
262
|
-
const jsonApi = this.recordData.getRelationship(this.identifier, key) as CollectionResourceRelationship;
|
|
263
|
-
const promise = this._findHasManyByJsonApiResource(jsonApi, this.identifier, relationship, options);
|
|
264
|
-
|
|
265
|
-
if (!promise) {
|
|
266
|
-
manyArray.isLoaded = true;
|
|
267
|
-
return resolve(manyArray);
|
|
268
|
-
}
|
|
269
|
-
|
|
270
|
-
loadingPromise = promise.then(
|
|
271
|
-
() => handleCompletedRelationshipRequest(this, key, relationship, manyArray),
|
|
272
|
-
(e: Error) => handleCompletedRelationshipRequest(this, key, relationship, manyArray, e)
|
|
273
|
-
);
|
|
274
|
-
this._relationshipPromisesCache[key] = loadingPromise;
|
|
275
|
-
return loadingPromise;
|
|
276
|
-
}
|
|
277
|
-
assert('hasMany only works with the @ember-data/record-data package');
|
|
278
|
-
}
|
|
279
|
-
|
|
280
|
-
reloadHasMany(key: string, options?: FindOptions) {
|
|
281
|
-
if (HAS_RECORD_DATA_PACKAGE) {
|
|
282
|
-
let loadingPromise = this._relationshipPromisesCache[key];
|
|
283
|
-
if (loadingPromise) {
|
|
284
|
-
return loadingPromise;
|
|
285
|
-
}
|
|
286
|
-
const graphFor = (
|
|
287
|
-
importSync('@ember-data/record-data/-private') as typeof import('@ember-data/record-data/-private')
|
|
288
|
-
).graphFor;
|
|
289
|
-
const relationship = graphFor(this.store).get(this.identifier, key) as ManyRelationship;
|
|
290
|
-
const { definition, state } = relationship;
|
|
291
|
-
|
|
292
|
-
state.hasFailedLoadAttempt = false;
|
|
293
|
-
state.shouldForceReload = true;
|
|
294
|
-
let manyArray = this.getManyArray(key, definition);
|
|
295
|
-
let promise = this.fetchAsyncHasMany(key, relationship, manyArray, options);
|
|
296
|
-
|
|
297
|
-
if (this._relationshipProxyCache[key]) {
|
|
298
|
-
return this._updatePromiseProxyFor('hasMany', key, { promise });
|
|
299
|
-
}
|
|
300
|
-
|
|
301
|
-
return promise;
|
|
302
|
-
}
|
|
303
|
-
assert(`hasMany only works with the @ember-data/record-data package`);
|
|
304
|
-
}
|
|
305
|
-
|
|
306
|
-
getHasMany(key: string, options?: FindOptions): PromiseManyArray | RelatedCollection {
|
|
307
|
-
if (HAS_RECORD_DATA_PACKAGE) {
|
|
308
|
-
const graphFor = (
|
|
309
|
-
importSync('@ember-data/record-data/-private') as typeof import('@ember-data/record-data/-private')
|
|
310
|
-
).graphFor;
|
|
311
|
-
const relationship = graphFor(this.store).get(this.identifier, key) as ManyRelationship;
|
|
312
|
-
const { definition, state } = relationship;
|
|
313
|
-
let manyArray = this.getManyArray(key, definition);
|
|
314
|
-
|
|
315
|
-
if (definition.isAsync) {
|
|
316
|
-
if (state.hasFailedLoadAttempt) {
|
|
317
|
-
return this._relationshipProxyCache[key] as PromiseManyArray;
|
|
318
|
-
}
|
|
319
|
-
|
|
320
|
-
let promise = this.fetchAsyncHasMany(key, relationship, manyArray, options);
|
|
321
|
-
|
|
322
|
-
return this._updatePromiseProxyFor('hasMany', key, { promise, content: manyArray });
|
|
323
|
-
} else {
|
|
324
|
-
assert(
|
|
325
|
-
`You looked up the '${key}' relationship on a '${this.identifier.type}' with id ${
|
|
326
|
-
this.identifier.id || 'null'
|
|
327
|
-
} 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> })')`,
|
|
328
|
-
!anyUnloaded(this.store, relationship)
|
|
329
|
-
);
|
|
330
|
-
|
|
331
|
-
return manyArray;
|
|
332
|
-
}
|
|
333
|
-
}
|
|
334
|
-
assert(`hasMany only works with the @ember-data/record-data package`);
|
|
335
|
-
}
|
|
336
|
-
|
|
337
|
-
_updatePromiseProxyFor(kind: 'hasMany', key: string, args: HasManyProxyCreateArgs): PromiseManyArray;
|
|
338
|
-
_updatePromiseProxyFor(kind: 'belongsTo', key: string, args: BelongsToProxyCreateArgs): PromiseBelongsTo;
|
|
339
|
-
_updatePromiseProxyFor(
|
|
340
|
-
kind: 'belongsTo',
|
|
341
|
-
key: string,
|
|
342
|
-
args: { promise: Promise<RecordInstance | null> }
|
|
343
|
-
): PromiseBelongsTo;
|
|
344
|
-
_updatePromiseProxyFor(
|
|
345
|
-
kind: 'hasMany' | 'belongsTo',
|
|
346
|
-
key: string,
|
|
347
|
-
args: BelongsToProxyCreateArgs | HasManyProxyCreateArgs | { promise: Promise<RecordInstance | null> }
|
|
348
|
-
): PromiseBelongsTo | PromiseManyArray {
|
|
349
|
-
let promiseProxy = this._relationshipProxyCache[key];
|
|
350
|
-
if (kind === 'hasMany') {
|
|
351
|
-
const { promise, content } = args as HasManyProxyCreateArgs;
|
|
352
|
-
if (promiseProxy) {
|
|
353
|
-
assert(`Expected a PromiseManyArray`, '_update' in promiseProxy);
|
|
354
|
-
promiseProxy._update(promise, content);
|
|
355
|
-
} else {
|
|
356
|
-
promiseProxy = this._relationshipProxyCache[key] = new PromiseManyArray(promise, content);
|
|
357
|
-
}
|
|
358
|
-
return promiseProxy;
|
|
359
|
-
}
|
|
360
|
-
if (promiseProxy) {
|
|
361
|
-
const { promise, content } = args as BelongsToProxyCreateArgs;
|
|
362
|
-
assert(`Expected a PromiseBelongsTo`, '_belongsToState' in promiseProxy);
|
|
363
|
-
|
|
364
|
-
if (content !== undefined) {
|
|
365
|
-
promiseProxy.set('content', content);
|
|
366
|
-
}
|
|
367
|
-
void promiseProxy.set('promise', promise);
|
|
368
|
-
} else {
|
|
369
|
-
promiseProxy = (PromiseBelongsTo as unknown as PromiseBelongsToFactory).create(args as BelongsToProxyCreateArgs);
|
|
370
|
-
this._relationshipProxyCache[key] = promiseProxy;
|
|
371
|
-
}
|
|
372
|
-
|
|
373
|
-
return promiseProxy;
|
|
374
|
-
}
|
|
375
|
-
|
|
376
|
-
referenceFor(kind: string | null, name: string) {
|
|
377
|
-
let reference = this.references[name];
|
|
378
|
-
|
|
379
|
-
if (!reference) {
|
|
380
|
-
if (!HAS_RECORD_DATA_PACKAGE) {
|
|
381
|
-
// TODO @runspired while this feels odd, it is not a regression in capability because we do
|
|
382
|
-
// not today support references pulling from RecordDatas other than our own
|
|
383
|
-
// because of the intimate API access involved. This is something we will need to redesign.
|
|
384
|
-
assert(`snapshot.belongsTo only supported for @ember-data/record-data`);
|
|
385
|
-
}
|
|
386
|
-
const graphFor = (
|
|
387
|
-
importSync('@ember-data/record-data/-private') as typeof import('@ember-data/record-data/-private')
|
|
388
|
-
).graphFor;
|
|
389
|
-
const graph = graphFor(this.store);
|
|
390
|
-
const relationship = graph.get(this.identifier, name);
|
|
391
|
-
|
|
392
|
-
if (DEBUG) {
|
|
393
|
-
if (kind) {
|
|
394
|
-
let modelName = this.identifier.type;
|
|
395
|
-
let actualRelationshipKind = relationship.definition.kind;
|
|
396
|
-
assert(
|
|
397
|
-
`You tried to get the '${name}' relationship on a '${modelName}' via record.${kind}('${name}'), but the relationship is of kind '${actualRelationshipKind}'. Use record.${actualRelationshipKind}('${name}') instead.`,
|
|
398
|
-
actualRelationshipKind === kind
|
|
399
|
-
);
|
|
400
|
-
}
|
|
401
|
-
}
|
|
402
|
-
|
|
403
|
-
let relationshipKind = relationship.definition.kind;
|
|
404
|
-
|
|
405
|
-
if (relationshipKind === 'belongsTo') {
|
|
406
|
-
reference = new BelongsToReference(
|
|
407
|
-
this.store,
|
|
408
|
-
graph,
|
|
409
|
-
this.identifier,
|
|
410
|
-
relationship as BelongsToRelationship,
|
|
411
|
-
name
|
|
412
|
-
);
|
|
413
|
-
} else if (relationshipKind === 'hasMany') {
|
|
414
|
-
reference = new HasManyReference(this.store, graph, this.identifier, relationship as ManyRelationship, name);
|
|
415
|
-
}
|
|
416
|
-
|
|
417
|
-
this.references[name] = reference;
|
|
418
|
-
}
|
|
419
|
-
|
|
420
|
-
return reference;
|
|
421
|
-
}
|
|
422
|
-
|
|
423
|
-
_findHasManyByJsonApiResource(
|
|
424
|
-
resource: CollectionResourceRelationship,
|
|
425
|
-
parentIdentifier: StableRecordIdentifier,
|
|
426
|
-
relationship: ManyRelationship,
|
|
427
|
-
options: FindOptions = {}
|
|
428
|
-
): Promise<void | unknown[]> | void {
|
|
429
|
-
if (HAS_RECORD_DATA_PACKAGE) {
|
|
430
|
-
if (!resource) {
|
|
431
|
-
return;
|
|
432
|
-
}
|
|
433
|
-
const { definition, state } = relationship;
|
|
434
|
-
const adapter = this.store.adapterFor(definition.type);
|
|
435
|
-
const { isStale, hasDematerializedInverse, hasReceivedData, isEmpty, shouldForceReload } = state;
|
|
436
|
-
const allInverseRecordsAreLoaded = areAllInverseRecordsLoaded(this.store, resource);
|
|
437
|
-
const shouldFindViaLink =
|
|
438
|
-
resource.links &&
|
|
439
|
-
resource.links.related &&
|
|
440
|
-
(typeof adapter.findHasMany === 'function' || typeof resource.data === 'undefined') &&
|
|
441
|
-
(shouldForceReload || hasDematerializedInverse || isStale || (!allInverseRecordsAreLoaded && !isEmpty));
|
|
442
|
-
|
|
443
|
-
// fetch via link
|
|
444
|
-
if (shouldFindViaLink) {
|
|
445
|
-
// findHasMany, although not public, does not need to care about our upgrade relationship definitions
|
|
446
|
-
// and can stick with the public definition API for now.
|
|
447
|
-
const relationshipMeta = this.store
|
|
448
|
-
.getSchemaDefinitionService()
|
|
449
|
-
.relationshipsDefinitionFor({ type: definition.inverseType })[definition.key];
|
|
450
|
-
let adapter = this.store.adapterFor(parentIdentifier.type);
|
|
451
|
-
|
|
452
|
-
/*
|
|
453
|
-
If a relationship was originally populated by the adapter as a link
|
|
454
|
-
(as opposed to a list of IDs), this method is called when the
|
|
455
|
-
relationship is fetched.
|
|
456
|
-
|
|
457
|
-
The link (which is usually a URL) is passed through unchanged, so the
|
|
458
|
-
adapter can make whatever request it wants.
|
|
459
|
-
|
|
460
|
-
The usual use-case is for the server to register a URL as a link, and
|
|
461
|
-
then use that URL in the future to make a request for the relationship.
|
|
462
|
-
*/
|
|
463
|
-
assert(
|
|
464
|
-
`You tried to load a hasMany relationship but you have no adapter (for ${parentIdentifier.type})`,
|
|
465
|
-
adapter
|
|
466
|
-
);
|
|
467
|
-
assert(
|
|
468
|
-
`You tried to load a hasMany relationship from a specified 'link' in the original payload but your adapter does not implement 'findHasMany'`,
|
|
469
|
-
typeof adapter.findHasMany === 'function'
|
|
470
|
-
);
|
|
471
|
-
|
|
472
|
-
return _findHasMany(adapter, this.store, parentIdentifier, resource.links!.related, relationshipMeta, options);
|
|
473
|
-
}
|
|
474
|
-
|
|
475
|
-
const preferLocalCache = hasReceivedData && !isEmpty;
|
|
476
|
-
const hasLocalPartialData =
|
|
477
|
-
hasDematerializedInverse || (isEmpty && Array.isArray(resource.data) && resource.data.length > 0);
|
|
478
|
-
|
|
479
|
-
// fetch using data, pulling from local cache if possible
|
|
480
|
-
if (!shouldForceReload && !isStale && (preferLocalCache || hasLocalPartialData)) {
|
|
481
|
-
if (allInverseRecordsAreLoaded) {
|
|
482
|
-
return;
|
|
483
|
-
}
|
|
484
|
-
assert(`Expected collection to be an array`, Array.isArray(resource.data));
|
|
485
|
-
if (allInverseRecordsAreLoaded) {
|
|
486
|
-
return;
|
|
487
|
-
}
|
|
488
|
-
let finds = new Array(resource.data.length);
|
|
489
|
-
let cache = this.store._instanceCache;
|
|
490
|
-
for (let i = 0; i < resource.data.length; i++) {
|
|
491
|
-
const identifier = resource.data[i];
|
|
492
|
-
assert(`expected a stable identifier`, isStableIdentifier(identifier));
|
|
493
|
-
finds[i] = cache._fetchDataIfNeededForIdentifier(identifier, options);
|
|
494
|
-
}
|
|
495
|
-
|
|
496
|
-
return all(finds);
|
|
497
|
-
}
|
|
498
|
-
|
|
499
|
-
let hasData = hasReceivedData && !isEmpty;
|
|
500
|
-
|
|
501
|
-
// fetch by data
|
|
502
|
-
if (hasData || hasLocalPartialData) {
|
|
503
|
-
const identifiers = resource.data;
|
|
504
|
-
assert(`Expected collection to be an array`, Array.isArray(identifiers));
|
|
505
|
-
assert(`Expected stable identifiers`, identifiers.every(isStableIdentifier));
|
|
506
|
-
let fetches = new Array(identifiers.length);
|
|
507
|
-
const manager = this.store._fetchManager;
|
|
508
|
-
|
|
509
|
-
for (let i = 0; i < identifiers.length; i++) {
|
|
510
|
-
let identifier = identifiers[i];
|
|
511
|
-
assertIdentifierHasId(identifier);
|
|
512
|
-
fetches[i] = manager.scheduleFetch(identifier, options);
|
|
513
|
-
}
|
|
514
|
-
|
|
515
|
-
return all(fetches);
|
|
516
|
-
}
|
|
517
|
-
|
|
518
|
-
// we were explicitly told we have no data and no links.
|
|
519
|
-
// TODO if the relationshipIsStale, should we hit the adapter anyway?
|
|
520
|
-
return;
|
|
521
|
-
}
|
|
522
|
-
assert(`hasMany only works with the @ember-data/record-data package`);
|
|
523
|
-
}
|
|
524
|
-
|
|
525
|
-
_findBelongsToByJsonApiResource(
|
|
526
|
-
resource: SingleResourceRelationship,
|
|
527
|
-
parentIdentifier: StableRecordIdentifier,
|
|
528
|
-
relationship: BelongsToRelationship,
|
|
529
|
-
options: FindOptions = {}
|
|
530
|
-
): Promise<StableRecordIdentifier | null> {
|
|
531
|
-
if (!resource) {
|
|
532
|
-
return resolve(null);
|
|
533
|
-
}
|
|
534
|
-
|
|
535
|
-
const identifier = resource.data ? resource.data : null;
|
|
536
|
-
assert(`Expected a stable identifier`, !identifier || isStableIdentifier(identifier));
|
|
537
|
-
|
|
538
|
-
let { isStale, hasDematerializedInverse, hasReceivedData, isEmpty, shouldForceReload } = relationship.state;
|
|
539
|
-
|
|
540
|
-
// short circuit if we are already loading
|
|
541
|
-
let pendingRequest = identifier && this.store._fetchManager.getPendingFetch(identifier, options);
|
|
542
|
-
if (pendingRequest) {
|
|
543
|
-
return pendingRequest;
|
|
544
|
-
}
|
|
545
|
-
|
|
546
|
-
const allInverseRecordsAreLoaded = areAllInverseRecordsLoaded(this.store, resource);
|
|
547
|
-
const shouldFindViaLink =
|
|
548
|
-
resource.links?.related &&
|
|
549
|
-
(shouldForceReload || hasDematerializedInverse || isStale || (!allInverseRecordsAreLoaded && !isEmpty));
|
|
550
|
-
|
|
551
|
-
// fetch via link
|
|
552
|
-
if (shouldFindViaLink) {
|
|
553
|
-
const relationshipMeta = this.store.getSchemaDefinitionService().relationshipsDefinitionFor(this.identifier)[
|
|
554
|
-
relationship.definition.key
|
|
555
|
-
];
|
|
556
|
-
assert(`Attempted to access a belongsTo relationship but no definition exists for it`, relationshipMeta);
|
|
557
|
-
|
|
558
|
-
return _findBelongsTo(this.store, parentIdentifier, resource.links!.related, relationshipMeta, options);
|
|
559
|
-
}
|
|
560
|
-
|
|
561
|
-
let preferLocalCache = hasReceivedData && allInverseRecordsAreLoaded && !isEmpty;
|
|
562
|
-
let hasLocalPartialData = hasDematerializedInverse || (isEmpty && resource.data);
|
|
563
|
-
// null is explicit empty, undefined is "we don't know anything"
|
|
564
|
-
const localDataIsEmpty = resource.data === undefined || resource.data === null;
|
|
565
|
-
|
|
566
|
-
// fetch using data, pulling from local cache if possible
|
|
567
|
-
if (!shouldForceReload && !isStale && (preferLocalCache || hasLocalPartialData)) {
|
|
568
|
-
/*
|
|
569
|
-
We have canonical data, but our local state is empty
|
|
570
|
-
*/
|
|
571
|
-
if (localDataIsEmpty) {
|
|
572
|
-
return resolve(null);
|
|
573
|
-
}
|
|
574
|
-
|
|
575
|
-
if (!identifier) {
|
|
576
|
-
assert(`No Information found for ${resource.data!.lid}`, identifier);
|
|
577
|
-
}
|
|
578
|
-
|
|
579
|
-
return this.store._instanceCache._fetchDataIfNeededForIdentifier(identifier, options);
|
|
580
|
-
}
|
|
581
|
-
|
|
582
|
-
let resourceIsLocal = !localDataIsEmpty && resource.data!.id === null;
|
|
583
|
-
|
|
584
|
-
if (identifier && resourceIsLocal) {
|
|
585
|
-
return resolve(identifier);
|
|
586
|
-
}
|
|
587
|
-
|
|
588
|
-
// fetch by data
|
|
589
|
-
if (identifier && !localDataIsEmpty) {
|
|
590
|
-
assertIdentifierHasId(identifier);
|
|
591
|
-
|
|
592
|
-
return this.store._fetchManager.scheduleFetch(identifier, options);
|
|
593
|
-
}
|
|
594
|
-
|
|
595
|
-
// we were explicitly told we have no data and no links.
|
|
596
|
-
// TODO if the relationshipIsStale, should we hit the adapter anyway?
|
|
597
|
-
return resolve(null);
|
|
598
|
-
}
|
|
599
|
-
|
|
600
|
-
destroy() {
|
|
601
|
-
this.isDestroying = true;
|
|
602
|
-
|
|
603
|
-
let cache: Dict<{ destroy(): void }> = this._manyArrayCache;
|
|
604
|
-
this._manyArrayCache = Object.create(null);
|
|
605
|
-
Object.keys(cache).forEach((key) => {
|
|
606
|
-
cache[key]!.destroy();
|
|
607
|
-
});
|
|
608
|
-
|
|
609
|
-
cache = this._relationshipProxyCache;
|
|
610
|
-
this._relationshipProxyCache = Object.create(null);
|
|
611
|
-
Object.keys(cache).forEach((key) => {
|
|
612
|
-
const proxy = cache[key]!;
|
|
613
|
-
if (proxy.destroy) {
|
|
614
|
-
proxy.destroy();
|
|
615
|
-
}
|
|
616
|
-
});
|
|
617
|
-
|
|
618
|
-
cache = this.references;
|
|
619
|
-
this.references = Object.create(null);
|
|
620
|
-
Object.keys(cache).forEach((key) => {
|
|
621
|
-
cache[key]!.destroy();
|
|
622
|
-
});
|
|
623
|
-
this.isDestroyed = true;
|
|
624
|
-
}
|
|
625
|
-
}
|
|
626
|
-
|
|
627
|
-
function handleCompletedRelationshipRequest(
|
|
628
|
-
recordExt: LegacySupport,
|
|
629
|
-
key: string,
|
|
630
|
-
relationship: BelongsToRelationship,
|
|
631
|
-
value: StableRecordIdentifier | null
|
|
632
|
-
): RecordInstance | null;
|
|
633
|
-
function handleCompletedRelationshipRequest(
|
|
634
|
-
recordExt: LegacySupport,
|
|
635
|
-
key: string,
|
|
636
|
-
relationship: ManyRelationship,
|
|
637
|
-
value: RelatedCollection
|
|
638
|
-
): RelatedCollection;
|
|
639
|
-
function handleCompletedRelationshipRequest(
|
|
640
|
-
recordExt: LegacySupport,
|
|
641
|
-
key: string,
|
|
642
|
-
relationship: BelongsToRelationship,
|
|
643
|
-
value: null,
|
|
644
|
-
error: Error
|
|
645
|
-
): never;
|
|
646
|
-
function handleCompletedRelationshipRequest(
|
|
647
|
-
recordExt: LegacySupport,
|
|
648
|
-
key: string,
|
|
649
|
-
relationship: ManyRelationship,
|
|
650
|
-
value: RelatedCollection,
|
|
651
|
-
error: Error
|
|
652
|
-
): never;
|
|
653
|
-
function handleCompletedRelationshipRequest(
|
|
654
|
-
recordExt: LegacySupport,
|
|
655
|
-
key: string,
|
|
656
|
-
relationship: BelongsToRelationship | ManyRelationship,
|
|
657
|
-
value: RelatedCollection | StableRecordIdentifier | null,
|
|
658
|
-
error?: Error
|
|
659
|
-
): RelatedCollection | RecordInstance | null {
|
|
660
|
-
delete recordExt._relationshipPromisesCache[key];
|
|
661
|
-
relationship.state.shouldForceReload = false;
|
|
662
|
-
const isHasMany = relationship.definition.kind === 'hasMany';
|
|
663
|
-
|
|
664
|
-
if (isHasMany) {
|
|
665
|
-
// we don't notify the record property here to avoid refetch
|
|
666
|
-
// only the many array
|
|
667
|
-
(value as RelatedCollection).notify();
|
|
668
|
-
}
|
|
669
|
-
|
|
670
|
-
if (error) {
|
|
671
|
-
relationship.state.hasFailedLoadAttempt = true;
|
|
672
|
-
let proxy = recordExt._relationshipProxyCache[key];
|
|
673
|
-
// belongsTo relationships are sometimes unloaded
|
|
674
|
-
// when a load fails, in this case we need
|
|
675
|
-
// to make sure that we aren't proxying
|
|
676
|
-
// to destroyed content
|
|
677
|
-
// for the sync belongsTo reload case there will be no proxy
|
|
678
|
-
// for the async reload case there will be no proxy if the ui
|
|
679
|
-
// has never been accessed
|
|
680
|
-
if (proxy && !isHasMany) {
|
|
681
|
-
// eslint-disable-next-line @typescript-eslint/no-unsafe-member-access
|
|
682
|
-
if (proxy.content && proxy.content.isDestroying) {
|
|
683
|
-
(proxy as PromiseBelongsTo).set('content', null);
|
|
684
|
-
}
|
|
685
|
-
}
|
|
686
|
-
|
|
687
|
-
throw error;
|
|
688
|
-
}
|
|
689
|
-
|
|
690
|
-
if (isHasMany) {
|
|
691
|
-
(value as RelatedCollection).isLoaded = true;
|
|
692
|
-
}
|
|
693
|
-
|
|
694
|
-
relationship.state.hasFailedLoadAttempt = false;
|
|
695
|
-
// only set to not stale if no error is thrown
|
|
696
|
-
relationship.state.isStale = false;
|
|
697
|
-
|
|
698
|
-
return isHasMany || !value
|
|
699
|
-
? (value as RelatedCollection | null)
|
|
700
|
-
: recordExt.store.peekRecord(value as StableRecordIdentifier);
|
|
701
|
-
}
|
|
702
|
-
|
|
703
|
-
type PromiseProxyRecord = { then(): void; content: RecordInstance | null | undefined };
|
|
704
|
-
|
|
705
|
-
function extractIdentifierFromRecord(recordOrPromiseRecord: PromiseProxyRecord | RecordInstance | null) {
|
|
706
|
-
if (!recordOrPromiseRecord) {
|
|
707
|
-
return null;
|
|
708
|
-
}
|
|
709
|
-
|
|
710
|
-
if (DEPRECATE_PROMISE_PROXIES) {
|
|
711
|
-
if (isPromiseRecord(recordOrPromiseRecord)) {
|
|
712
|
-
let content = recordOrPromiseRecord.content;
|
|
713
|
-
assert(
|
|
714
|
-
'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.',
|
|
715
|
-
content !== undefined
|
|
716
|
-
);
|
|
717
|
-
deprecate(
|
|
718
|
-
`You passed in a PromiseProxy to a Relationship API that now expects a resolved value. await the value before setting it.`,
|
|
719
|
-
false,
|
|
720
|
-
{
|
|
721
|
-
id: 'ember-data:deprecate-promise-proxies',
|
|
722
|
-
until: '5.0',
|
|
723
|
-
since: {
|
|
724
|
-
enabled: '4.7',
|
|
725
|
-
available: '4.7',
|
|
726
|
-
},
|
|
727
|
-
for: 'ember-data',
|
|
728
|
-
}
|
|
729
|
-
);
|
|
730
|
-
return content ? recordIdentifierFor(content) : null;
|
|
731
|
-
}
|
|
732
|
-
}
|
|
733
|
-
|
|
734
|
-
return recordIdentifierFor(recordOrPromiseRecord);
|
|
735
|
-
}
|
|
736
|
-
|
|
737
|
-
function isPromiseRecord(record: PromiseProxyRecord | RecordInstance): record is PromiseProxyRecord {
|
|
738
|
-
return !!record.then;
|
|
739
|
-
}
|
|
740
|
-
|
|
741
|
-
function anyUnloaded(store: Store, relationship: ManyRelationship) {
|
|
742
|
-
let state = relationship.localState;
|
|
743
|
-
const cache = store._instanceCache;
|
|
744
|
-
const unloaded = state.find((s) => {
|
|
745
|
-
let isLoaded = cache.recordIsLoaded(s, true);
|
|
746
|
-
return !isLoaded;
|
|
747
|
-
});
|
|
748
|
-
|
|
749
|
-
return unloaded || false;
|
|
750
|
-
}
|
|
751
|
-
|
|
752
|
-
function areAllInverseRecordsLoaded(store: Store, resource: JsonApiRelationship): boolean {
|
|
753
|
-
const instanceCache = store._instanceCache;
|
|
754
|
-
const identifiers = resource.data;
|
|
755
|
-
|
|
756
|
-
if (Array.isArray(identifiers)) {
|
|
757
|
-
assert(`Expected stable identifiers`, identifiers.every(isStableIdentifier));
|
|
758
|
-
// treat as collection
|
|
759
|
-
// check for unloaded records
|
|
760
|
-
return identifiers.every((identifier: StableRecordIdentifier) => instanceCache.recordIsLoaded(identifier));
|
|
761
|
-
}
|
|
762
|
-
|
|
763
|
-
// treat as single resource
|
|
764
|
-
if (!identifiers) return true;
|
|
765
|
-
|
|
766
|
-
assert(`Expected stable identifiers`, isStableIdentifier(identifiers));
|
|
767
|
-
return instanceCache.recordIsLoaded(identifiers);
|
|
768
|
-
}
|
|
769
|
-
|
|
770
|
-
function isBelongsTo(
|
|
771
|
-
relationship: BelongsToRelationship | ImplicitRelationship | ManyRelationship
|
|
772
|
-
): relationship is BelongsToRelationship {
|
|
773
|
-
return relationship.definition.kind === 'belongsTo';
|
|
774
|
-
}
|