@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.
Files changed (40) hide show
  1. package/addon/{-private/diff-array.ts → -private.js} +41 -13
  2. package/addon/-private.js.map +1 -0
  3. package/addon/has-many-a275fe0d.js +6283 -0
  4. package/addon/has-many-a275fe0d.js.map +1 -0
  5. package/addon/index.js +1 -0
  6. package/addon/index.js.map +1 -0
  7. package/addon-main.js +90 -0
  8. package/package.json +43 -15
  9. package/addon/-private/attr.js +0 -162
  10. package/addon/-private/belongs-to.js +0 -268
  11. package/addon/-private/debug/assert-polymorphic-type.js +0 -71
  12. package/addon/-private/deprecated-promise-proxy.ts +0 -76
  13. package/addon/-private/errors.ts +0 -425
  14. package/addon/-private/has-many.js +0 -280
  15. package/addon/-private/index.ts +0 -14
  16. package/addon/-private/legacy-data-fetch.js +0 -395
  17. package/addon/-private/legacy-data-utils.ts +0 -92
  18. package/addon/-private/legacy-relationships-support.ts +0 -774
  19. package/addon/-private/many-array.ts +0 -401
  20. package/addon/-private/model-for-mixin.ts +0 -38
  21. package/addon/-private/model.js +0 -2516
  22. package/addon/-private/notify-changes.ts +0 -72
  23. package/addon/-private/promise-belongs-to.ts +0 -73
  24. package/addon/-private/promise-many-array.ts +0 -425
  25. package/addon/-private/promise-proxy-base.js +0 -4
  26. package/addon/-private/record-state.ts +0 -468
  27. package/addon/-private/references/belongs-to.ts +0 -624
  28. package/addon/-private/references/has-many.ts +0 -669
  29. package/addon/-private/relationship-meta.ts +0 -98
  30. package/addon/-private/util.ts +0 -31
  31. package/addon/index.ts +0 -39
  32. package/blueprints/model/HELP.md +0 -26
  33. package/blueprints/model/files/__root__/__path__/__name__.js +0 -5
  34. package/blueprints/model/index.js +0 -158
  35. package/blueprints/model/native-files/__root__/__path__/__name__.js +0 -5
  36. package/blueprints/model-test/index.js +0 -33
  37. package/blueprints/model-test/mocha-files/__root__/__path__/__test__.js +0 -18
  38. package/blueprints/model-test/mocha-rfc-232-files/__root__/__path__/__test__.js +0 -15
  39. package/blueprints/model-test/qunit-files/__root__/__path__/__test__.js +0 -14
  40. 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
- }