@ember-data/store 4.5.0-beta.0 → 4.5.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 (64) hide show
  1. package/addon/-private/{system/backburner.js → backburner.js} +0 -0
  2. package/addon/-private/{system/coerce-id.ts → coerce-id.ts} +0 -0
  3. package/addon/-private/{system/store/common.js → common.js} +0 -0
  4. package/addon/-private/{system/core-store.ts → core-store.ts} +467 -1253
  5. package/addon/-private/{system/errors-utils.js → errors-utils.js} +7 -6
  6. package/addon/-private/{system/fetch-manager.ts → fetch-manager.ts} +72 -42
  7. package/addon/-private/finders.js +107 -0
  8. package/addon/-private/identifer-debug-consts.ts +3 -0
  9. package/addon/-private/{identifiers/cache.ts → identifier-cache.ts} +26 -14
  10. package/addon/-private/{system/identity-map.ts → identity-map.ts} +2 -1
  11. package/addon/-private/index.ts +17 -17
  12. package/addon/-private/instance-cache.ts +387 -0
  13. package/addon/-private/{system/store/internal-model-factory.ts → internal-model-factory.ts} +25 -19
  14. package/addon/-private/{system/internal-model-map.ts → internal-model-map.ts} +9 -5
  15. package/addon/-private/model/internal-model.ts +602 -0
  16. package/addon/-private/{system/references/record.ts → model/record-reference.ts} +23 -36
  17. package/addon/-private/{system/model → model}/shim-model-class.ts +19 -14
  18. package/addon/-private/{system/normalize-model-name.ts → normalize-model-name.ts} +0 -0
  19. package/addon/-private/{system/promise-proxies.ts → promise-proxies.ts} +12 -5
  20. package/addon/-private/{system/promise-proxy-base.js → promise-proxy-base.js} +0 -0
  21. package/addon/-private/{system/record-array-manager.ts → record-array-manager.ts} +19 -18
  22. package/addon/-private/{system/record-arrays → record-arrays}/adapter-populated-record-array.ts +11 -10
  23. package/addon/-private/{system/record-arrays → record-arrays}/record-array.ts +37 -19
  24. package/addon/-private/record-data-for.ts +39 -0
  25. package/addon/-private/{system/store/record-data-store-wrapper.ts → record-data-store-wrapper.ts} +21 -26
  26. package/addon/-private/{system/record-notification-manager.ts → record-notification-manager.ts} +8 -3
  27. package/addon/-private/{system/request-cache.ts → request-cache.ts} +5 -6
  28. package/addon/-private/{system/schema-definition-service.ts → schema-definition-service.ts} +30 -14
  29. package/addon/-private/{system/store/serializer-response.ts → serializer-response.ts} +7 -6
  30. package/addon/-private/{system/snapshot-record-array.ts → snapshot-record-array.ts} +27 -8
  31. package/addon/-private/{system/snapshot.ts → snapshot.ts} +54 -39
  32. package/addon/-private/utils/construct-resource.ts +7 -3
  33. package/addon/-private/utils/promise-record.ts +9 -18
  34. package/addon/-private/{system/weak-cache.ts → weak-cache.ts} +2 -2
  35. package/addon/index.ts +1 -0
  36. package/package.json +21 -20
  37. package/addon/-private/identifiers/is-stable-identifier.ts +0 -18
  38. package/addon/-private/identifiers/utils/uuid-v4.ts +0 -80
  39. package/addon/-private/system/ds-model-store.ts +0 -136
  40. package/addon/-private/system/model/internal-model.ts +0 -1303
  41. package/addon/-private/system/model/states.js +0 -736
  42. package/addon/-private/system/record-arrays.ts +0 -8
  43. package/addon/-private/system/record-data-for.ts +0 -54
  44. package/addon/-private/system/references/belongs-to.ts +0 -406
  45. package/addon/-private/system/references/has-many.ts +0 -487
  46. package/addon/-private/system/references/reference.ts +0 -205
  47. package/addon/-private/system/references.js +0 -9
  48. package/addon/-private/system/store/finders.js +0 -412
  49. package/addon/-private/ts-interfaces/ds-model.ts +0 -50
  50. package/addon/-private/ts-interfaces/ember-data-json-api.ts +0 -145
  51. package/addon/-private/ts-interfaces/fetch-manager.ts +0 -44
  52. package/addon/-private/ts-interfaces/identifier.ts +0 -246
  53. package/addon/-private/ts-interfaces/minimum-adapter-interface.ts +0 -584
  54. package/addon/-private/ts-interfaces/minimum-serializer-interface.ts +0 -257
  55. package/addon/-private/ts-interfaces/promise-proxies.ts +0 -3
  56. package/addon/-private/ts-interfaces/record-data-json-api.ts +0 -29
  57. package/addon/-private/ts-interfaces/record-data-record-wrapper.ts +0 -46
  58. package/addon/-private/ts-interfaces/record-data-schemas.ts +0 -45
  59. package/addon/-private/ts-interfaces/record-data-store-wrapper.ts +0 -56
  60. package/addon/-private/ts-interfaces/record-data.ts +0 -72
  61. package/addon/-private/ts-interfaces/record-instance.ts +0 -18
  62. package/addon/-private/ts-interfaces/schema-definition-service.ts +0 -12
  63. package/addon/-private/ts-interfaces/store.ts +0 -10
  64. package/addon/-private/ts-interfaces/utils.ts +0 -6
@@ -1,1303 +0,0 @@
1
- import { A, default as EmberArray } from '@ember/array';
2
- import { assert, inspect } from '@ember/debug';
3
- import EmberError from '@ember/error';
4
- import { get } from '@ember/object';
5
- import { _backburner as emberBackburner, cancel, run } from '@ember/runloop';
6
- import { DEBUG } from '@glimmer/env';
7
-
8
- import { importSync } from '@embroider/macros';
9
- import RSVP, { resolve } from 'rsvp';
10
-
11
- import type { ManyArray } from '@ember-data/model/-private';
12
- import RecordState from '@ember-data/model/-private/record-state';
13
- import type { ManyArrayCreateArgs } from '@ember-data/model/-private/system/many-array';
14
- import type {
15
- BelongsToProxyCreateArgs,
16
- BelongsToProxyMeta,
17
- } from '@ember-data/model/-private/system/promise-belongs-to';
18
- import type PromiseBelongsTo from '@ember-data/model/-private/system/promise-belongs-to';
19
- import type { HasManyProxyCreateArgs } from '@ember-data/model/-private/system/promise-many-array';
20
- import type PromiseManyArray from '@ember-data/model/-private/system/promise-many-array';
21
- import { HAS_MODEL_PACKAGE, HAS_RECORD_DATA_PACKAGE } from '@ember-data/private-build-infra';
22
- import type {
23
- BelongsToRelationship,
24
- ManyRelationship,
25
- RecordData as DefaultRecordData,
26
- } from '@ember-data/record-data/-private';
27
- import type { UpgradedMeta } from '@ember-data/record-data/-private/graph/-edge-definition';
28
- import type {
29
- DefaultSingleResourceRelationship,
30
- RelationshipRecordData,
31
- } from '@ember-data/record-data/-private/ts-interfaces/relationship-record-data';
32
-
33
- import type { DSModel } from '../../ts-interfaces/ds-model';
34
- import type { StableRecordIdentifier } from '../../ts-interfaces/identifier';
35
- import type { ChangedAttributesHash, RecordData } from '../../ts-interfaces/record-data';
36
- import type { JsonApiResource, JsonApiValidationError } from '../../ts-interfaces/record-data-json-api';
37
- import type { RelationshipSchema } from '../../ts-interfaces/record-data-schemas';
38
- import type { RecordInstance } from '../../ts-interfaces/record-instance';
39
- import type { FindOptions } from '../../ts-interfaces/store';
40
- import type { Dict } from '../../ts-interfaces/utils';
41
- import type CoreStore from '../core-store';
42
- import type { CreateRecordProperties } from '../core-store';
43
- import { errorsHashToArray } from '../errors-utils';
44
- import recordDataFor from '../record-data-for';
45
- import { BelongsToReference, HasManyReference, RecordReference } from '../references';
46
- import Snapshot from '../snapshot';
47
- import { internalModelFactoryFor } from '../store/internal-model-factory';
48
- import RootState from './states';
49
-
50
- type PrivateModelModule = {
51
- ManyArray: { create(args: ManyArrayCreateArgs): ManyArray };
52
- PromiseBelongsTo: { create(args: BelongsToProxyCreateArgs): PromiseBelongsTo };
53
- PromiseManyArray: new (...args: unknown[]) => PromiseManyArray;
54
- };
55
-
56
- /**
57
- @module @ember-data/store
58
- */
59
-
60
- const { hasOwnProperty } = Object.prototype;
61
-
62
- let _ManyArray: PrivateModelModule['ManyArray'];
63
- let _PromiseBelongsTo: PrivateModelModule['PromiseBelongsTo'];
64
- let _PromiseManyArray: PrivateModelModule['PromiseManyArray'];
65
-
66
- let _found = false;
67
- let _getModelPackage: () => boolean;
68
- if (HAS_MODEL_PACKAGE) {
69
- _getModelPackage = function () {
70
- if (!_found) {
71
- let modelPackage = importSync('@ember-data/model/-private') as PrivateModelModule;
72
- ({
73
- ManyArray: _ManyArray,
74
- PromiseBelongsTo: _PromiseBelongsTo,
75
- PromiseManyArray: _PromiseManyArray,
76
- } = modelPackage);
77
- if (_ManyArray && _PromiseBelongsTo && _PromiseManyArray) {
78
- _found = true;
79
- }
80
- }
81
- return _found;
82
- };
83
- }
84
-
85
- /*
86
- The TransitionChainMap caches the `state.enters`, `state.setups`, and final state reached
87
- when transitioning from one state to another, so that future transitions can replay the
88
- transition without needing to walk the state tree, collect these hook calls and determine
89
- the state to transition into.
90
-
91
- A future optimization would be to build a single chained method out of the collected enters
92
- and setups. It may also be faster to do a two level cache (from: { to }) instead of caching based
93
- on a key that adds the two together.
94
- */
95
- // TODO before deleting the state machine we should
96
- // ensure all things in this map were properly accounted for.
97
- // in the RecordState class.
98
- const TransitionChainMap = Object.create(null);
99
-
100
- const _extractPivotNameCache = Object.create(null);
101
- const _splitOnDotCache = Object.create(null);
102
-
103
- function splitOnDot(name: string): string[] {
104
- return _splitOnDotCache[name] || (_splitOnDotCache[name] = name.split('.'));
105
- }
106
-
107
- function extractPivotName(name: string): string {
108
- return _extractPivotNameCache[name] || (_extractPivotNameCache[name] = splitOnDot(name)[0]);
109
- }
110
-
111
- function isDSModel(record: RecordInstance | null): record is DSModel {
112
- return (
113
- HAS_MODEL_PACKAGE &&
114
- !!record &&
115
- 'constructor' in record &&
116
- 'isModel' in record.constructor &&
117
- record.constructor.isModel === true
118
- );
119
- }
120
-
121
- export default class InternalModel {
122
- declare _id: string | null;
123
- declare modelName: string;
124
- declare clientId: string;
125
- declare __recordData: RecordData | null;
126
- declare _isDestroyed: boolean;
127
- declare isError: boolean;
128
- declare _pendingRecordArrayManagerFlush: boolean;
129
- declare _isDematerializing: boolean;
130
- declare _doNotDestroy: boolean;
131
- declare isDestroying: boolean;
132
- declare _isUpdatingId: boolean;
133
- declare _deletedRecordWasNew: boolean;
134
-
135
- // Not typed yet
136
- declare _promiseProxy: any;
137
- declare _record: RecordInstance | null;
138
- declare _scheduledDestroy: any;
139
- declare _modelClass: any;
140
- declare __recordArrays: any;
141
- declare references: any;
142
- declare _recordReference: RecordReference;
143
- declare _manyArrayCache: Dict<ManyArray>;
144
-
145
- declare _relationshipPromisesCache: Dict<Promise<ManyArray | RecordInstance>>;
146
- declare _relationshipProxyCache: Dict<PromiseManyArray | PromiseBelongsTo>;
147
- declare error: any;
148
- declare currentState: RecordState;
149
- declare _previousState: any;
150
- declare store: CoreStore;
151
- declare identifier: StableRecordIdentifier;
152
-
153
- constructor(store: CoreStore, identifier: StableRecordIdentifier) {
154
- if (HAS_MODEL_PACKAGE) {
155
- _getModelPackage();
156
- }
157
- this.store = store;
158
- this.identifier = identifier;
159
- this._id = identifier.id;
160
- this._isUpdatingId = false;
161
- this.modelName = identifier.type;
162
- this.clientId = identifier.lid;
163
-
164
- this.__recordData = null;
165
-
166
- this._promiseProxy = null;
167
- this._isDestroyed = false;
168
- this._doNotDestroy = false;
169
- this.isError = false;
170
- this._pendingRecordArrayManagerFlush = false; // used by the recordArrayManager
171
-
172
- // During dematerialization we don't want to rematerialize the record. The
173
- // reason this might happen is that dematerialization removes records from
174
- // record arrays, and Ember arrays will always `objectAt(0)` and
175
- // `objectAt(len - 1)` to test whether or not `firstObject` or `lastObject`
176
- // have changed.
177
- this._isDematerializing = false;
178
- this._scheduledDestroy = null;
179
-
180
- this._record = null;
181
- this.error = null;
182
-
183
- // caches for lazy getters
184
- this._modelClass = null;
185
- this.__recordArrays = null;
186
- this._recordReference = null;
187
- this.__recordData = null;
188
-
189
- this.error = null;
190
-
191
- // other caches
192
- // class fields have [[DEFINE]] semantics which are significantly slower than [[SET]] semantics here
193
- this._manyArrayCache = Object.create(null);
194
- this._relationshipPromisesCache = Object.create(null);
195
- this._relationshipProxyCache = Object.create(null);
196
- this.references = Object.create(null);
197
- this.currentState = RootState.empty;
198
- }
199
-
200
- get id(): string | null {
201
- return this.identifier.id;
202
- }
203
- set id(value: string | null) {
204
- if (value !== this._id) {
205
- let newIdentifier = { type: this.identifier.type, lid: this.identifier.lid, id: value };
206
- this.store.identifierCache.updateRecordIdentifier(this.identifier, newIdentifier);
207
- this.notifyPropertyChange('id');
208
- }
209
- }
210
-
211
- get modelClass() {
212
- if (this.store.modelFor) {
213
- return this._modelClass || (this._modelClass = this.store.modelFor(this.modelName));
214
- }
215
- }
216
-
217
- get recordReference(): RecordReference {
218
- if (this._recordReference === null) {
219
- this._recordReference = new RecordReference(this.store, this.identifier);
220
- }
221
- return this._recordReference;
222
- }
223
-
224
- get _recordData(): RecordData {
225
- if (this.__recordData === null) {
226
- let recordData = this.store._createRecordData(this.identifier);
227
- this.__recordData = recordData;
228
- return recordData;
229
- }
230
- return this.__recordData;
231
- }
232
-
233
- set _recordData(newValue) {
234
- this.__recordData = newValue;
235
- }
236
-
237
- isHiddenFromRecordArrays() {
238
- // During dematerialization we don't want to rematerialize the record.
239
- // recordWasDeleted can cause other records to rematerialize because it
240
- // removes the internal model from the array and Ember arrays will always
241
- // `objectAt(0)` and `objectAt(len -1)` to check whether `firstObject` or
242
- // `lastObject` have changed. When this happens we don't want those
243
- // models to rematerialize their records.
244
-
245
- // eager checks to avoid instantiating record data if we are empty or loading
246
- if (this.currentState.isEmpty) {
247
- return true;
248
- }
249
-
250
- if (this.currentState.isLoading) {
251
- return false;
252
- }
253
-
254
- let isRecordFullyDeleted = this._isRecordFullyDeleted();
255
- return this._isDematerializing || this.hasScheduledDestroy() || this.isDestroyed || isRecordFullyDeleted;
256
- }
257
-
258
- _isRecordFullyDeleted(): boolean {
259
- if (this._recordData.isDeletionCommitted && this._recordData.isDeletionCommitted()) {
260
- return true;
261
- } else if (
262
- this._recordData.isNew &&
263
- this._recordData.isDeleted &&
264
- this._recordData.isNew() &&
265
- this._recordData.isDeleted()
266
- ) {
267
- return true;
268
- } else {
269
- return this.currentState.stateName === 'root.deleted.saved';
270
- }
271
- }
272
-
273
- isDeleted() {
274
- if (this._recordData.isDeleted) {
275
- return this._recordData.isDeleted();
276
- } else {
277
- return this.currentState.isDeleted;
278
- }
279
- }
280
-
281
- isNew() {
282
- if (this._recordData.isNew) {
283
- return this._recordData.isNew();
284
- } else {
285
- return this.currentState.isNew;
286
- }
287
- }
288
-
289
- getRecord(properties?: CreateRecordProperties): RecordInstance {
290
- let record = this._record;
291
-
292
- if (this._isDematerializing) {
293
- // TODO we should assert here instead of this return.
294
- return null as unknown as RecordInstance;
295
- }
296
-
297
- if (!record) {
298
- let { store } = this;
299
-
300
- record = this._record = store._instantiateRecord(
301
- this,
302
- this.modelName,
303
- this._recordData,
304
- this.identifier,
305
- properties
306
- );
307
- }
308
-
309
- return record;
310
- }
311
-
312
- dematerializeRecord() {
313
- this._isDematerializing = true;
314
-
315
- // TODO IGOR add a test that fails when this is missing, something that involves canceling a destroy
316
- // and the destroy not happening, and then later on trying to destroy
317
- this._doNotDestroy = false;
318
- // this has to occur before the internal model is removed
319
- // for legacy compat.
320
- if (this._record) {
321
- this.store.teardownRecord(this._record);
322
- }
323
-
324
- // move to an empty never-loaded state
325
- // ensure any record notifications happen prior to us
326
- // unseting the record but after we've triggered
327
- // destroy
328
- this.store._backburner.join(() => {
329
- this._recordData.unloadRecord();
330
- });
331
-
332
- if (this._record) {
333
- let keys = Object.keys(this._relationshipProxyCache);
334
- keys.forEach((key) => {
335
- let proxy = this._relationshipProxyCache[key]!;
336
- if (proxy.destroy) {
337
- proxy.destroy();
338
- }
339
- delete this._relationshipProxyCache[key];
340
- });
341
- }
342
-
343
- this._record = null;
344
- this.error = null;
345
- this._previousState = this.currentState;
346
- this.currentState = RootState.empty;
347
- this.store.recordArrayManager.recordDidChange(this.identifier);
348
- }
349
-
350
- deleteRecord() {
351
- run(() => {
352
- const backburner = this.store._backburner;
353
- backburner.run(() => {
354
- if (this._recordData.setIsDeleted) {
355
- this._recordData.setIsDeleted(true);
356
- }
357
-
358
- if (this.isNew()) {
359
- // destroyRecord follows up deleteRecord with save(). This prevents an unecessary save for a new record
360
- this._deletedRecordWasNew = true;
361
- this.send('deleteRecord');
362
- this.unloadRecord();
363
- } else {
364
- this.send('deleteRecord');
365
- }
366
- });
367
- });
368
- }
369
-
370
- save(options: FindOptions = {}): Promise<void> {
371
- if (this._deletedRecordWasNew) {
372
- return resolve();
373
- }
374
- let promiseLabel = 'DS: Model#save ' + this;
375
- let resolver = RSVP.defer<void>(promiseLabel);
376
-
377
- // Casting to promise to narrow due to the feature flag paths inside scheduleSave
378
- return this.store.scheduleSave(this, resolver, options) as Promise<void>;
379
- }
380
-
381
- reload(options: Dict<unknown> = {}): Promise<InternalModel> {
382
- return this.store._reloadRecord(this, options);
383
- }
384
-
385
- /*
386
- Unload the record for this internal model. This will cause the record to be
387
- destroyed and freed up for garbage collection. It will also do a check
388
- for cleaning up internal models.
389
-
390
- This check is performed by first computing the set of related internal
391
- models. If all records in this set are unloaded, then the entire set is
392
- destroyed. Otherwise, nothing in the set is destroyed.
393
-
394
- This means that this internal model will be freed up for garbage collection
395
- once all models that refer to it via some relationship are also unloaded.
396
- */
397
- unloadRecord() {
398
- if (this.isDestroyed) {
399
- return;
400
- }
401
- this.send('unloadRecord');
402
- this.dematerializeRecord();
403
- if (this._scheduledDestroy === null) {
404
- this._scheduledDestroy = emberBackburner.schedule('destroy', this, '_checkForOrphanedInternalModels');
405
- }
406
- }
407
-
408
- hasScheduledDestroy() {
409
- return !!this._scheduledDestroy;
410
- }
411
-
412
- cancelDestroy() {
413
- assert(
414
- `You cannot cancel the destruction of an InternalModel once it has already been destroyed`,
415
- !this.isDestroyed
416
- );
417
-
418
- this._doNotDestroy = true;
419
- this._isDematerializing = false;
420
- cancel(this._scheduledDestroy);
421
- this._scheduledDestroy = null;
422
- }
423
-
424
- // typically, we prefer to async destroy this lets us batch cleanup work.
425
- // Unfortunately, some scenarios where that is not possible. Such as:
426
- //
427
- // ```js
428
- // const record = store.findRecord(‘record’, 1);
429
- // record.unloadRecord();
430
- // store.createRecord(‘record’, 1);
431
- // ```
432
- //
433
- // In those scenarios, we make that model's cleanup work, sync.
434
- //
435
- destroySync() {
436
- if (this._isDematerializing) {
437
- this.cancelDestroy();
438
- }
439
- this._checkForOrphanedInternalModels();
440
- if (this.isDestroyed || this.isDestroying) {
441
- return;
442
- }
443
-
444
- // just in-case we are not one of the orphaned, we should still
445
- // still destroy ourselves
446
- this.destroy();
447
- }
448
-
449
- _checkForOrphanedInternalModels() {
450
- this._isDematerializing = false;
451
- this._scheduledDestroy = null;
452
- if (this.isDestroyed) {
453
- return;
454
- }
455
- }
456
-
457
- _findBelongsTo(
458
- key: string,
459
- resource: DefaultSingleResourceRelationship,
460
- relationshipMeta: RelationshipSchema,
461
- options?: Dict<unknown>
462
- ): Promise<RecordInstance | null> {
463
- // TODO @runspired follow up if parent isNew then we should not be attempting load here
464
- // TODO @runspired follow up on whether this should be in the relationship requests cache
465
- return this.store._findBelongsToByJsonApiResource(resource, this, relationshipMeta, options).then(
466
- (internalModel) => handleCompletedRelationshipRequest(this, key, resource._relationship, internalModel),
467
- (e) => handleCompletedRelationshipRequest(this, key, resource._relationship, null, e)
468
- );
469
- }
470
-
471
- getBelongsTo(key: string, options?: Dict<unknown>): PromiseBelongsTo | RecordInstance | null {
472
- let resource = (this._recordData as DefaultRecordData).getBelongsTo(key);
473
- let identifier =
474
- resource && resource.data ? this.store.identifierCache.getOrCreateRecordIdentifier(resource.data) : null;
475
- let relationshipMeta = this.store._relationshipMetaFor(this.modelName, null, key);
476
- assert(`Attempted to access a belongsTo relationship but no definition exists for it`, relationshipMeta);
477
-
478
- let store = this.store;
479
- let parentInternalModel = this;
480
- let async = relationshipMeta.options.async;
481
- let isAsync = typeof async === 'undefined' ? true : async;
482
- let _belongsToState: BelongsToProxyMeta = {
483
- key,
484
- store,
485
- originatingInternalModel: this,
486
- modelName: relationshipMeta.type,
487
- };
488
-
489
- if (isAsync) {
490
- let internalModel = identifier !== null ? store._internalModelForResource(identifier) : null;
491
-
492
- if (resource._relationship.state.hasFailedLoadAttempt) {
493
- return this._relationshipProxyCache[key] as PromiseBelongsTo;
494
- }
495
-
496
- let promise = this._findBelongsTo(key, resource, relationshipMeta, options);
497
-
498
- return this._updatePromiseProxyFor('belongsTo', key, {
499
- promise,
500
- content: internalModel ? internalModel.getRecord() : null,
501
- _belongsToState,
502
- });
503
- } else {
504
- if (identifier === null) {
505
- return null;
506
- } else {
507
- let internalModel = store._internalModelForResource(identifier);
508
- let toReturn = internalModel.getRecord();
509
- assert(
510
- "You looked up the '" +
511
- key +
512
- "' relationship on a '" +
513
- parentInternalModel.modelName +
514
- "' with id " +
515
- parentInternalModel.id +
516
- ' 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 })`)',
517
- toReturn === null || !internalModel.currentState.isEmpty
518
- );
519
- return toReturn;
520
- }
521
- }
522
- }
523
-
524
- getManyArray(key: string, definition?: UpgradedMeta): ManyArray {
525
- assert('hasMany only works with the @ember-data/record-data package', HAS_RECORD_DATA_PACKAGE);
526
- let manyArray: ManyArray | undefined = this._manyArrayCache[key];
527
- if (!definition) {
528
- const graphFor = (
529
- importSync('@ember-data/record-data/-private') as typeof import('@ember-data/record-data/-private')
530
- ).graphFor;
531
- definition = graphFor(this.store).get(this.identifier, key).definition as UpgradedMeta;
532
- }
533
-
534
- if (!manyArray) {
535
- manyArray = _ManyArray.create({
536
- store: this.store,
537
- type: this.store.modelFor(definition.type),
538
- recordData: this._recordData as RelationshipRecordData,
539
- key,
540
- isPolymorphic: definition.isPolymorphic,
541
- isAsync: definition.isAsync,
542
- _inverseIsAsync: definition.inverseIsAsync,
543
- internalModel: this,
544
- isLoaded: !definition.isAsync,
545
- });
546
- this._manyArrayCache[key] = manyArray;
547
- }
548
-
549
- return manyArray;
550
- }
551
-
552
- fetchAsyncHasMany(
553
- key: string,
554
- relationship: ManyRelationship,
555
- manyArray: ManyArray,
556
- options?: Dict<unknown>
557
- ): Promise<ManyArray> {
558
- if (HAS_RECORD_DATA_PACKAGE) {
559
- let loadingPromise = this._relationshipPromisesCache[key] as Promise<ManyArray> | undefined;
560
- if (loadingPromise) {
561
- return loadingPromise;
562
- }
563
-
564
- const jsonApi = this._recordData.getHasMany(key);
565
-
566
- loadingPromise = this.store._findHasManyByJsonApiResource(jsonApi, this, relationship, options).then(
567
- () => handleCompletedRelationshipRequest(this, key, relationship, manyArray),
568
- (e) => handleCompletedRelationshipRequest(this, key, relationship, manyArray, e)
569
- );
570
- this._relationshipPromisesCache[key] = loadingPromise;
571
- return loadingPromise;
572
- }
573
- assert('hasMany only works with the @ember-data/record-data package');
574
- }
575
-
576
- getHasMany(key: string, options?): PromiseManyArray | ManyArray {
577
- if (HAS_RECORD_DATA_PACKAGE) {
578
- const graphFor = (
579
- importSync('@ember-data/record-data/-private') as typeof import('@ember-data/record-data/-private')
580
- ).graphFor;
581
- const relationship = graphFor(this.store).get(this.identifier, key) as ManyRelationship;
582
- const { definition, state } = relationship;
583
- let manyArray = this.getManyArray(key, definition);
584
-
585
- if (definition.isAsync) {
586
- if (state.hasFailedLoadAttempt) {
587
- return this._relationshipProxyCache[key] as PromiseManyArray;
588
- }
589
-
590
- let promise = this.fetchAsyncHasMany(key, relationship, manyArray, options);
591
-
592
- return this._updatePromiseProxyFor('hasMany', key, { promise, content: manyArray });
593
- } else {
594
- assert(
595
- `You looked up the '${key}' relationship on a '${this.modelName}' with id ${this.id} 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 })')`,
596
- !anyUnloaded(this.store, relationship)
597
- );
598
-
599
- return manyArray;
600
- }
601
- }
602
- assert(`hasMany only works with the @ember-data/record-data package`);
603
- }
604
-
605
- _updatePromiseProxyFor(kind: 'hasMany', key: string, args: HasManyProxyCreateArgs): PromiseManyArray;
606
- _updatePromiseProxyFor(kind: 'belongsTo', key: string, args: BelongsToProxyCreateArgs): PromiseBelongsTo;
607
- _updatePromiseProxyFor(
608
- kind: 'belongsTo',
609
- key: string,
610
- args: { promise: Promise<RecordInstance | null> }
611
- ): PromiseBelongsTo;
612
- _updatePromiseProxyFor(
613
- kind: 'hasMany' | 'belongsTo',
614
- key: string,
615
- args: BelongsToProxyCreateArgs | HasManyProxyCreateArgs | { promise: Promise<RecordInstance | null> }
616
- ): PromiseBelongsTo | PromiseManyArray {
617
- let promiseProxy = this._relationshipProxyCache[key];
618
- if (kind === 'hasMany') {
619
- const { promise, content } = args as HasManyProxyCreateArgs;
620
- if (promiseProxy) {
621
- assert(`Expected a PromiseManyArray`, '_update' in promiseProxy);
622
- promiseProxy._update(promise, content);
623
- } else {
624
- promiseProxy = this._relationshipProxyCache[key] = new _PromiseManyArray(promise, content);
625
- }
626
- return promiseProxy;
627
- }
628
- if (promiseProxy) {
629
- const { promise, content } = args as BelongsToProxyCreateArgs;
630
- assert(`Expected a PromiseBelongsTo`, '_belongsToState' in promiseProxy);
631
-
632
- if (content !== undefined) {
633
- promiseProxy.set('content', content);
634
- }
635
- promiseProxy.set('promise', promise);
636
- } else {
637
- // this usage of `any` can be removed when `@types/ember_object` proxy allows `null` for content
638
- this._relationshipProxyCache[key] = promiseProxy = _PromiseBelongsTo.create(args as any);
639
- }
640
-
641
- return promiseProxy;
642
- }
643
-
644
- reloadHasMany(key: string, options) {
645
- if (HAS_RECORD_DATA_PACKAGE) {
646
- let loadingPromise = this._relationshipPromisesCache[key];
647
- if (loadingPromise) {
648
- return loadingPromise;
649
- }
650
- const graphFor = (
651
- importSync('@ember-data/record-data/-private') as typeof import('@ember-data/record-data/-private')
652
- ).graphFor;
653
- const relationship = graphFor(this.store).get(this.identifier, key) as ManyRelationship;
654
- const { definition, state } = relationship;
655
-
656
- state.hasFailedLoadAttempt = false;
657
- state.shouldForceReload = true;
658
- let manyArray = this.getManyArray(key, definition);
659
- let promise = this.fetchAsyncHasMany(key, relationship, manyArray, options);
660
-
661
- if (this._relationshipProxyCache[key]) {
662
- return this._updatePromiseProxyFor('hasMany', key, { promise });
663
- }
664
-
665
- return promise;
666
- }
667
- assert(`hasMany only works with the @ember-data/record-data package`);
668
- }
669
-
670
- reloadBelongsTo(key: string, options?: Dict<unknown>): Promise<RecordInstance | null> {
671
- let loadingPromise = this._relationshipPromisesCache[key] as Promise<RecordInstance | null> | undefined;
672
- if (loadingPromise) {
673
- return loadingPromise;
674
- }
675
-
676
- let resource = (this._recordData as DefaultRecordData).getBelongsTo(key);
677
- // TODO move this to a public api
678
- if (resource._relationship) {
679
- resource._relationship.state.hasFailedLoadAttempt = false;
680
- resource._relationship.state.shouldForceReload = true;
681
- }
682
- let relationshipMeta = this.store._relationshipMetaFor(this.modelName, null, key);
683
- assert(`Attempted to reload a belongsTo relationship but no definition exists for it`, relationshipMeta);
684
- let promise = this._findBelongsTo(key, resource, relationshipMeta, options);
685
- if (this._relationshipProxyCache[key]) {
686
- return this._updatePromiseProxyFor('belongsTo', key, { promise });
687
- }
688
- return promise;
689
- }
690
-
691
- destroyFromRecordData() {
692
- if (this._doNotDestroy) {
693
- this._doNotDestroy = false;
694
- return;
695
- }
696
- this.destroy();
697
- }
698
-
699
- destroy() {
700
- assert(
701
- 'Cannot destroy an internalModel while its record is materialized',
702
- !this._record || this._record.isDestroyed || this._record.isDestroying
703
- );
704
- this.isDestroying = true;
705
- if (this._recordReference) {
706
- this._recordReference.destroy();
707
- }
708
- this._recordReference = null;
709
- let cache = this._manyArrayCache;
710
- Object.keys(cache).forEach((key) => {
711
- cache[key]!.destroy();
712
- delete cache[key];
713
- });
714
- if (this.references) {
715
- cache = this.references;
716
- Object.keys(cache).forEach((key) => {
717
- cache[key]!.destroy();
718
- delete cache[key];
719
- });
720
- }
721
-
722
- internalModelFactoryFor(this.store).remove(this);
723
- this._isDestroyed = true;
724
- }
725
-
726
- setupData(data) {
727
- const hasRecord = this.hasRecord;
728
- if (hasRecord) {
729
- let changedKeys = this._recordData.pushData(data, true);
730
- this.notifyAttributes(changedKeys);
731
- } else {
732
- this._recordData.pushData(data);
733
- }
734
- this.send('pushedData');
735
- }
736
-
737
- notifyAttributes(keys: string[]): void {
738
- let manager = this.store._notificationManager;
739
- let { identifier } = this;
740
-
741
- for (let i = 0; i < keys.length; i++) {
742
- manager.notify(identifier, 'attributes', keys[i]);
743
- }
744
- }
745
-
746
- setDirtyHasMany(key: string, records) {
747
- assertRecordsPassedToHasMany(records);
748
- return this._recordData.setDirtyHasMany(key, extractRecordDatasFromRecords(records));
749
- }
750
-
751
- setDirtyBelongsTo(key: string, value) {
752
- return this._recordData.setDirtyBelongsTo(key, extractRecordDataFromRecord(value));
753
- }
754
-
755
- setDirtyAttribute<T>(key: string, value: T): T {
756
- if (this.isDeleted()) {
757
- if (DEBUG) {
758
- throw new EmberError(`Attempted to set '${key}' to '${value}' on the deleted record ${this}`);
759
- } else {
760
- throw new EmberError(`Attempted to set '${key}' on the deleted record ${this}`);
761
- }
762
- }
763
-
764
- let currentValue = this._recordData.getAttr(key);
765
- if (currentValue !== value) {
766
- this._recordData.setDirtyAttribute(key, value);
767
- let isDirty = this._recordData.isAttrDirty(key);
768
- this.send('didSetProperty', {
769
- name: key,
770
- isDirty: isDirty,
771
- });
772
- }
773
-
774
- return value;
775
- }
776
-
777
- get isDestroyed(): boolean {
778
- return this._isDestroyed;
779
- }
780
-
781
- get hasRecord(): boolean {
782
- return !!this._record;
783
- }
784
-
785
- createSnapshot(options: FindOptions = {}): Snapshot {
786
- return new Snapshot(options, this.identifier, this.store);
787
- }
788
-
789
- hasChangedAttributes(): boolean {
790
- if (!this.__recordData) {
791
- // no need to calculate changed attributes when calling `findRecord`
792
- return false;
793
- }
794
- return this._recordData.hasChangedAttributes();
795
- }
796
-
797
- changedAttributes(): ChangedAttributesHash {
798
- if (!this.__recordData) {
799
- // no need to calculate changed attributes when calling `findRecord`
800
- return {};
801
- }
802
- return this._recordData.changedAttributes();
803
- }
804
-
805
- adapterWillCommit(): void {
806
- this._recordData.willCommit();
807
- this.send('willCommit');
808
- }
809
-
810
- adapterDidDirty(): void {
811
- this.send('becomeDirty');
812
- }
813
-
814
- send(name: string, context?) {
815
- let currentState = this.currentState;
816
-
817
- if (!currentState[name]) {
818
- this._unhandledEvent(currentState, name, context);
819
- }
820
-
821
- return currentState[name](this, context);
822
- }
823
-
824
- notifyHasManyChange(key: string) {
825
- if (this.hasRecord) {
826
- let manyArray = this._manyArrayCache[key];
827
- let hasPromise = !!this._relationshipPromisesCache[key];
828
-
829
- if (manyArray && hasPromise) {
830
- // do nothing, we will notify the ManyArray directly
831
- // once the fetch has completed.
832
- return;
833
- }
834
-
835
- this.store._notificationManager.notify(this.identifier, 'relationships', key);
836
- }
837
- }
838
-
839
- notifyBelongsToChange(key: string) {
840
- if (this.hasRecord) {
841
- this.store._notificationManager.notify(this.identifier, 'relationships', key);
842
- }
843
- }
844
-
845
- notifyPropertyChange(key: string) {
846
- if (this.hasRecord) {
847
- // TODO this should likely *mostly* be the `attributes` bucket
848
- // but it seems for local mutations we rely on computed updating
849
- // iteself when set. As we design our own thing we may need to change
850
- // that.
851
- this.store._notificationManager.notify(this.identifier, 'property', key);
852
- }
853
- }
854
-
855
- notifyStateChange(key?: string) {
856
- if (this.hasRecord) {
857
- this.store._notificationManager.notify(this.identifier, 'state');
858
- }
859
- if (!key || key === 'isDeletionCommitted') {
860
- this.store.recordArrayManager.recordDidChange(this.identifier);
861
- }
862
- }
863
-
864
- didCreateRecord() {
865
- this._recordData.clientDidCreate();
866
- }
867
-
868
- rollbackAttributes() {
869
- this.store._backburner.join(() => {
870
- let dirtyKeys = this._recordData.rollbackAttributes();
871
- if (this.isError) {
872
- this.didCleanError();
873
- }
874
-
875
- this.send('rolledBack');
876
-
877
- if (this.hasRecord && dirtyKeys && dirtyKeys.length > 0) {
878
- this.notifyAttributes(dirtyKeys);
879
- }
880
- });
881
- }
882
-
883
- transitionTo(name: string) {
884
- // POSSIBLE TODO: Remove this code and replace with
885
- // always having direct reference to state objects
886
-
887
- let pivotName = extractPivotName(name);
888
- let state: any = this.currentState;
889
- let transitionMapId = `${state.stateName}->${name}`;
890
-
891
- do {
892
- if (state.exit) {
893
- state.exit(this);
894
- }
895
- state = state.parentState;
896
- } while (!state[pivotName]);
897
-
898
- let setups;
899
- let enters;
900
- let i;
901
- let l;
902
- let map = TransitionChainMap[transitionMapId];
903
-
904
- if (map) {
905
- setups = map.setups;
906
- enters = map.enters;
907
- state = map.state;
908
- } else {
909
- setups = [];
910
- enters = [];
911
-
912
- let path = splitOnDot(name);
913
-
914
- for (i = 0, l = path.length; i < l; i++) {
915
- state = state[path[i]];
916
-
917
- if (state.enter) {
918
- enters.push(state);
919
- }
920
- if (state.setup) {
921
- setups.push(state);
922
- }
923
- }
924
-
925
- TransitionChainMap[transitionMapId] = { setups, enters, state };
926
- }
927
-
928
- for (i = 0, l = enters.length; i < l; i++) {
929
- enters[i].enter(this);
930
- }
931
-
932
- this.currentState = state;
933
-
934
- // isDSModel is the guard we want, but may be too restrictive if
935
- // ember-m3 / ember-data-model-fragments were relying on this still.
936
- if (this.hasRecord && isDSModel(this._record)) {
937
- // TODO eliminate this.
938
- this.notifyStateChange('currentState');
939
- }
940
-
941
- for (i = 0, l = setups.length; i < l; i++) {
942
- setups[i].setup(this);
943
- }
944
- }
945
-
946
- _unhandledEvent(state, name: string, context) {
947
- let errorMessage = 'Attempted to handle event `' + name + '` ';
948
- errorMessage += 'on ' + String(this) + ' while in state ';
949
- errorMessage += state.stateName + '. ';
950
-
951
- if (context !== undefined) {
952
- errorMessage += 'Called with ' + inspect(context) + '.';
953
- }
954
-
955
- throw new EmberError(errorMessage);
956
- }
957
-
958
- removeFromInverseRelationships() {
959
- if (this.__recordData) {
960
- this.store._backburner.join(() => {
961
- this._recordData.removeFromInverseRelationships();
962
- });
963
- }
964
- }
965
-
966
- /*
967
- When a find request is triggered on the store, the user can optionally pass in
968
- attributes and relationships to be preloaded. These are meant to behave as if they
969
- came back from the server, except the user obtained them out of band and is informing
970
- the store of their existence. The most common use case is for supporting client side
971
- nested URLs, such as `/posts/1/comments/2` so the user can do
972
- `store.findRecord('comment', 2, { preload: { post: 1 } })` without having to fetch the post.
973
-
974
- Preloaded data can be attributes and relationships passed in either as IDs or as actual
975
- models.
976
- */
977
- preloadData(preload) {
978
- let jsonPayload: JsonApiResource = {};
979
- //TODO(Igor) consider the polymorphic case
980
- Object.keys(preload).forEach((key) => {
981
- let preloadValue = get(preload, key);
982
- let relationshipMeta = this.modelClass.metaForProperty(key);
983
- if (relationshipMeta.isRelationship) {
984
- if (!jsonPayload.relationships) {
985
- jsonPayload.relationships = {};
986
- }
987
- jsonPayload.relationships[key] = this._preloadRelationship(key, preloadValue);
988
- } else {
989
- if (!jsonPayload.attributes) {
990
- jsonPayload.attributes = {};
991
- }
992
- jsonPayload.attributes[key] = preloadValue;
993
- }
994
- });
995
- this._recordData.pushData(jsonPayload);
996
- }
997
-
998
- _preloadRelationship(key, preloadValue) {
999
- let relationshipMeta = this.modelClass.metaForProperty(key);
1000
- let modelClass = relationshipMeta.type;
1001
- let data;
1002
- if (relationshipMeta.kind === 'hasMany') {
1003
- assert('You need to pass in an array to set a hasMany property on a record', Array.isArray(preloadValue));
1004
- data = preloadValue.map((value) => this._convertPreloadRelationshipToJSON(value, modelClass));
1005
- } else {
1006
- data = this._convertPreloadRelationshipToJSON(preloadValue, modelClass);
1007
- }
1008
- return { data };
1009
- }
1010
-
1011
- _convertPreloadRelationshipToJSON(value, modelClass) {
1012
- if (typeof value === 'string' || typeof value === 'number') {
1013
- return { type: modelClass, id: value };
1014
- }
1015
- let internalModel;
1016
- if (value._internalModel) {
1017
- internalModel = value._internalModel;
1018
- } else {
1019
- internalModel = value;
1020
- }
1021
- // TODO IGOR DAVID assert if no id is present
1022
- return { type: internalModel.modelName, id: internalModel.id };
1023
- }
1024
-
1025
- /*
1026
- * calling `store.setRecordId` is necessary to update
1027
- * the cache index for this record if we have changed.
1028
- *
1029
- * However, since the store is not aware of whether the update
1030
- * is from us (via user set) or from a push of new data
1031
- * it will also call us so that we can notify and update state.
1032
- *
1033
- * When it does so it calls with `fromCache` so that we can
1034
- * short-circuit instead of cycling back.
1035
- *
1036
- * This differs from the short-circuit in the `_isUpdatingId`
1037
- * case in that the the cache can originate the call to setId,
1038
- * so on first entry we will still need to do our own update.
1039
- */
1040
- setId(id: string, fromCache: boolean = false) {
1041
- if (this._isUpdatingId === true) {
1042
- return;
1043
- }
1044
- this._isUpdatingId = true;
1045
- let didChange = id !== this._id;
1046
- this._id = id;
1047
-
1048
- if (didChange && id !== null) {
1049
- if (!fromCache) {
1050
- this.store.setRecordId(this.modelName, id, this.clientId);
1051
- }
1052
- // internal set of ID to get it to RecordData from DS.Model
1053
- // if we are within create we may not have a recordData yet.
1054
- if (this.__recordData && this._recordData.__setId) {
1055
- this._recordData.__setId(id);
1056
- }
1057
- }
1058
-
1059
- if (didChange && this.hasRecord) {
1060
- this.store._notificationManager.notify(this.identifier, 'identity');
1061
- }
1062
- this._isUpdatingId = false;
1063
- }
1064
-
1065
- didError() {}
1066
-
1067
- didCleanError() {}
1068
-
1069
- /*
1070
- If the adapter did not return a hash in response to a commit,
1071
- merge the changed attributes and relationships into the existing
1072
- saved data.
1073
- */
1074
- adapterDidCommit(data) {
1075
- this.didCleanError();
1076
-
1077
- this._recordData.didCommit(data);
1078
- this.send('didCommit');
1079
- this.store.recordArrayManager.recordDidChange(this.identifier);
1080
-
1081
- if (!data) {
1082
- return;
1083
- }
1084
- this.store._notificationManager.notify(this.identifier, 'attributes');
1085
- }
1086
-
1087
- hasErrors(): boolean {
1088
- // TODO add assertion forcing consuming RecordData's to implement getErrors
1089
- if (this._recordData.getErrors) {
1090
- return this._recordData.getErrors(this.identifier).length > 0;
1091
- } else {
1092
- // we can't have errors if we never tried loading
1093
- if (!this._record) {
1094
- return false;
1095
- }
1096
- let errors = (this._record as DSModel).errors;
1097
- return errors.length > 0;
1098
- }
1099
- }
1100
-
1101
- // FOR USE DURING COMMIT PROCESS
1102
- adapterDidInvalidate(parsedErrors, error?) {
1103
- // TODO @runspired this should be handled by RecordState
1104
- // and errors should be dirtied but lazily fetch if at
1105
- // all possible. We should only notify errors here.
1106
- let attribute;
1107
- if (error && parsedErrors) {
1108
- // TODO add assertion forcing consuming RecordData's to implement getErrors
1109
- if (!this._recordData.getErrors) {
1110
- let record = this.getRecord() as DSModel;
1111
- let errors = record.errors;
1112
- for (attribute in parsedErrors) {
1113
- if (hasOwnProperty.call(parsedErrors, attribute)) {
1114
- errors._add(attribute, parsedErrors[attribute]);
1115
- }
1116
- }
1117
- }
1118
-
1119
- let jsonApiErrors: JsonApiValidationError[] = errorsHashToArray(parsedErrors);
1120
- this.send('becameInvalid');
1121
- if (jsonApiErrors.length === 0) {
1122
- jsonApiErrors = [{ title: 'Invalid Error', detail: '', source: { pointer: '/data' } }];
1123
- }
1124
- this._recordData.commitWasRejected(this.identifier, jsonApiErrors);
1125
- } else {
1126
- this.send('becameError');
1127
- this._recordData.commitWasRejected(this.identifier);
1128
- }
1129
- }
1130
-
1131
- notifyErrorsChange() {
1132
- this.store._notificationManager.notify(this.identifier, 'errors');
1133
- }
1134
-
1135
- adapterDidError() {
1136
- this.send('becameError');
1137
-
1138
- this._recordData.commitWasRejected();
1139
- }
1140
-
1141
- toString() {
1142
- return `<${this.modelName}:${this.id}>`;
1143
- }
1144
-
1145
- referenceFor(kind: string | null, name: string) {
1146
- let reference = this.references[name];
1147
-
1148
- if (!reference) {
1149
- if (!HAS_RECORD_DATA_PACKAGE) {
1150
- // TODO @runspired while this feels odd, it is not a regression in capability because we do
1151
- // not today support references pulling from RecordDatas other than our own
1152
- // because of the intimate API access involved. This is something we will need to redesign.
1153
- assert(`snapshot.belongsTo only supported for @ember-data/record-data`);
1154
- }
1155
- const graphFor = (
1156
- importSync('@ember-data/record-data/-private') as typeof import('@ember-data/record-data/-private')
1157
- ).graphFor;
1158
- const relationship = graphFor(this.store._storeWrapper).get(this.identifier, name);
1159
-
1160
- if (DEBUG && kind) {
1161
- let modelName = this.modelName;
1162
- let actualRelationshipKind = relationship.definition.kind;
1163
- assert(
1164
- `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.`,
1165
- actualRelationshipKind === kind
1166
- );
1167
- }
1168
-
1169
- let relationshipKind = relationship.definition.kind;
1170
- let identifierOrInternalModel = this.identifier;
1171
-
1172
- if (relationshipKind === 'belongsTo') {
1173
- reference = new BelongsToReference(this.store, identifierOrInternalModel, relationship, name);
1174
- } else if (relationshipKind === 'hasMany') {
1175
- reference = new HasManyReference(this.store, identifierOrInternalModel, relationship, name);
1176
- }
1177
-
1178
- this.references[name] = reference;
1179
- }
1180
-
1181
- return reference;
1182
- }
1183
- }
1184
-
1185
- function handleCompletedRelationshipRequest(
1186
- internalModel: InternalModel,
1187
- key: string,
1188
- relationship: BelongsToRelationship,
1189
- value: InternalModel | null
1190
- ): RecordInstance | null;
1191
- function handleCompletedRelationshipRequest(
1192
- internalModel: InternalModel,
1193
- key: string,
1194
- relationship: ManyRelationship,
1195
- value: ManyArray
1196
- ): ManyArray;
1197
- function handleCompletedRelationshipRequest(
1198
- internalModel: InternalModel,
1199
- key: string,
1200
- relationship: BelongsToRelationship,
1201
- value: null,
1202
- error: Error
1203
- ): never;
1204
- function handleCompletedRelationshipRequest(
1205
- internalModel: InternalModel,
1206
- key: string,
1207
- relationship: ManyRelationship,
1208
- value: ManyArray,
1209
- error: Error
1210
- ): never;
1211
- function handleCompletedRelationshipRequest(
1212
- internalModel: InternalModel,
1213
- key: string,
1214
- relationship: BelongsToRelationship | ManyRelationship,
1215
- value: ManyArray | InternalModel | null,
1216
- error?: Error
1217
- ): ManyArray | RecordInstance | null {
1218
- delete internalModel._relationshipPromisesCache[key];
1219
- relationship.state.shouldForceReload = false;
1220
- const isHasMany = relationship.definition.kind === 'hasMany';
1221
-
1222
- if (isHasMany) {
1223
- // we don't notify the record property here to avoid refetch
1224
- // only the many array
1225
- (value as ManyArray).notify();
1226
- }
1227
-
1228
- if (error) {
1229
- relationship.state.hasFailedLoadAttempt = true;
1230
- let proxy = internalModel._relationshipProxyCache[key];
1231
- // belongsTo relationships are sometimes unloaded
1232
- // when a load fails, in this case we need
1233
- // to make sure that we aren't proxying
1234
- // to destroyed content
1235
- // for the sync belongsTo reload case there will be no proxy
1236
- // for the async reload case there will be no proxy if the ui
1237
- // has never been accessed
1238
- if (proxy && !isHasMany) {
1239
- if (proxy.content && proxy.content.isDestroying) {
1240
- // TODO @types/ember__object incorrectly disallows `null`, we should either
1241
- // override or fix upstream
1242
- (proxy as PromiseBelongsTo).set('content', null as unknown as undefined);
1243
- }
1244
- }
1245
-
1246
- throw error;
1247
- }
1248
-
1249
- if (isHasMany) {
1250
- (value as ManyArray).set('isLoaded', true);
1251
- }
1252
-
1253
- relationship.state.hasFailedLoadAttempt = false;
1254
- // only set to not stale if no error is thrown
1255
- relationship.state.isStale = false;
1256
-
1257
- return isHasMany || !value ? (value as ManyArray | null) : (value as InternalModel).getRecord();
1258
- }
1259
-
1260
- export function assertRecordsPassedToHasMany(records) {
1261
- // TODO only allow native arrays
1262
- assert(
1263
- `You must pass an array of records to set a hasMany relationship`,
1264
- Array.isArray(records) || EmberArray.detect(records)
1265
- );
1266
- assert(
1267
- `All elements of a hasMany relationship must be instances of Model, you passed ${inspect(records)}`,
1268
- (function () {
1269
- return A(records).every((record) => hasOwnProperty.call(record, '_internalModel') === true);
1270
- })()
1271
- );
1272
- }
1273
-
1274
- export function extractRecordDatasFromRecords(records) {
1275
- return records.map(extractRecordDataFromRecord);
1276
- }
1277
-
1278
- export function extractRecordDataFromRecord(recordOrPromiseRecord) {
1279
- if (!recordOrPromiseRecord) {
1280
- return null;
1281
- }
1282
-
1283
- if (recordOrPromiseRecord.then) {
1284
- let content = recordOrPromiseRecord.get && recordOrPromiseRecord.get('content');
1285
- assert(
1286
- '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.',
1287
- content !== undefined
1288
- );
1289
- return content ? recordDataFor(content) : null;
1290
- }
1291
-
1292
- return recordDataFor(recordOrPromiseRecord);
1293
- }
1294
-
1295
- function anyUnloaded(store: CoreStore, relationship: ManyRelationship) {
1296
- let state = relationship.currentState;
1297
- const unloaded = state.find((s) => {
1298
- let im = store._internalModelForResource(s);
1299
- return im._isDematerializing || !im.currentState.isLoaded;
1300
- });
1301
-
1302
- return unloaded || false;
1303
- }