@ember-data/store 4.2.0-alpha.0 → 4.2.0-alpha.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -3,7 +3,6 @@
3
3
  */
4
4
  import { assert } from '@ember/debug';
5
5
 
6
- import { REQUEST_SERVICE } from '@ember-data/canary-features';
7
6
  /*
8
7
  This file encapsulates the various states that a record can transition
9
8
  through during its lifecycle.
@@ -431,6 +430,8 @@ createdState.uncommitted.rollback = function (internalModel) {
431
430
  };
432
431
 
433
432
  createdState.uncommitted.pushedData = function (internalModel) {
433
+ // TODO @runspired consider where to do this once we kill off state machine
434
+ internalModel.store._notificationManager.notify(internalModel.identifier, 'identity');
434
435
  internalModel.transitionTo('loaded.updated.uncommitted');
435
436
  internalModel.triggerLater('didLoad');
436
437
  };
@@ -494,9 +495,6 @@ const RootState = {
494
495
 
495
496
  // EVENTS
496
497
  loadingData(internalModel, promise) {
497
- if (!REQUEST_SERVICE) {
498
- internalModel._promiseProxy = promise;
499
- }
500
498
  internalModel.transitionTo('loading');
501
499
  },
502
500
 
@@ -587,11 +585,7 @@ const RootState = {
587
585
  internalModel.transitionTo('updated.inFlight');
588
586
  },
589
587
 
590
- reloadRecord(internalModel, { resolve, options }) {
591
- if (!REQUEST_SERVICE) {
592
- resolve(internalModel.store._reloadRecord(internalModel, options));
593
- }
594
- },
588
+ reloadRecord() {},
595
589
 
596
590
  deleteRecord(internalModel) {
597
591
  internalModel.transitionTo('deleted.uncommitted');
@@ -7,8 +7,6 @@ import { assert } from '@ember/debug';
7
7
  import { get, set } from '@ember/object';
8
8
  import { _backburner as emberBackburner } from '@ember/runloop';
9
9
 
10
- import { REMOVE_RECORD_ARRAY_MANAGER_LEGACY_COMPAT } from '@ember-data/canary-features';
11
-
12
10
  import isStableIdentifier from '../identifiers/is-stable-identifier';
13
11
  import { AdapterPopulatedRecordArray, RecordArray } from './record-arrays';
14
12
  import { internalModelFactoryFor } from './store/internal-model-factory';
@@ -27,11 +25,10 @@ export function recordArraysForIdentifier(identifierOrInternalModel) {
27
25
  }
28
26
 
29
27
  const pendingForIdentifier = new Set([]);
30
- const IMDematerializing = new WeakMap();
31
28
 
32
29
  const getIdentifier = function getIdentifier(identifierOrInternalModel) {
33
30
  let i = identifierOrInternalModel;
34
- if (!REMOVE_RECORD_ARRAY_MANAGER_LEGACY_COMPAT && !isStableIdentifier(identifierOrInternalModel)) {
31
+ if (!isStableIdentifier(identifierOrInternalModel)) {
35
32
  // identifier may actually be an internalModel
36
33
  // but during materialization we will get an identifier that
37
34
  // has already been removed from the identifiers cache yet
@@ -42,18 +39,7 @@ const getIdentifier = function getIdentifier(identifierOrInternalModel) {
42
39
  return i;
43
40
  };
44
41
 
45
- // REMOVE_RECORD_ARRAY_MANAGER_LEGACY_COMPAT only
46
42
  const peekIMCache = function peekIMCache(cache, identifier) {
47
- if (!REMOVE_RECORD_ARRAY_MANAGER_LEGACY_COMPAT) {
48
- let im = IMDematerializing.get(identifier);
49
- if (im === undefined) {
50
- // if not im._isDematerializing
51
- im = cache.peek(identifier);
52
- }
53
-
54
- return im;
55
- }
56
-
57
43
  return cache.peek(identifier);
58
44
  };
59
45
 
@@ -351,14 +337,6 @@ class RecordArrayManager {
351
337
  let modelName = identifier.type;
352
338
  identifier = getIdentifier(identifier);
353
339
 
354
- if (!REMOVE_RECORD_ARRAY_MANAGER_LEGACY_COMPAT) {
355
- const cache = internalModelFactoryFor(this.store);
356
- const im = peekIMCache(cache, identifier);
357
- if (im && im._isDematerializing) {
358
- IMDematerializing.set(identifier, im);
359
- }
360
- }
361
-
362
340
  if (pendingForIdentifier.has(identifier)) {
363
341
  return;
364
342
  }
@@ -428,15 +406,10 @@ const updateLiveRecordArray = function updateLiveRecordArray(store, recordArray,
428
406
  };
429
407
 
430
408
  const pushIdentifiers = function pushIdentifiers(recordArray, identifiers, cache) {
431
- if (!REMOVE_RECORD_ARRAY_MANAGER_LEGACY_COMPAT && !recordArray._pushIdentifiers) {
432
- // deprecate('not allowed to use this intimate api any more');
433
- recordArray._pushInternalModels(identifiers.map((i) => peekIMCache(cache, i)));
434
- } else {
435
- recordArray._pushIdentifiers(identifiers);
436
- }
409
+ recordArray._pushIdentifiers(identifiers);
437
410
  };
438
411
  const removeIdentifiers = function removeIdentifiers(recordArray, identifiers, cache) {
439
- if (!REMOVE_RECORD_ARRAY_MANAGER_LEGACY_COMPAT && !recordArray._removeIdentifiers) {
412
+ if (!recordArray._removeIdentifiers) {
440
413
  // deprecate('not allowed to use this intimate api any more');
441
414
  recordArray._removeInternalModels(identifiers.map((i) => peekIMCache(cache, i)));
442
415
  } else {
@@ -4,7 +4,7 @@ import type CoreStore from './core-store';
4
4
 
5
5
  type UnsubscribeToken = Object;
6
6
 
7
- const Cache = new WeakMap<StableRecordIdentifier, NotificationCallback>();
7
+ const Cache = new WeakMap<StableRecordIdentifier, Map<UnsubscribeToken, NotificationCallback>>();
8
8
  const Tokens = new WeakMap<UnsubscribeToken, StableRecordIdentifier>();
9
9
 
10
10
  export type NotificationType =
@@ -29,7 +29,8 @@ export function unsubscribe(token: UnsubscribeToken) {
29
29
  throw new Error('Passed unknown unsubscribe token to unsubscribe');
30
30
  }
31
31
  Tokens.delete(token);
32
- Cache.delete(identifier);
32
+ const map = Cache.get(identifier);
33
+ map?.delete(token);
33
34
  }
34
35
  /*
35
36
  Currently only support a single callback per identifier
@@ -39,8 +40,13 @@ export default class NotificationManager {
39
40
 
40
41
  subscribe(identifier: RecordIdentifier, callback: NotificationCallback): UnsubscribeToken {
41
42
  let stableIdentifier = identifierCacheFor(this.store).getOrCreateRecordIdentifier(identifier);
42
- Cache.set(stableIdentifier, callback);
43
+ let map = Cache.get(stableIdentifier);
44
+ if (map === undefined) {
45
+ map = new Map();
46
+ Cache.set(stableIdentifier, map);
47
+ }
43
48
  let unsubToken = {};
49
+ map.set(unsubToken, callback);
44
50
  Tokens.set(unsubToken, stableIdentifier);
45
51
  return unsubToken;
46
52
  }
@@ -49,11 +55,13 @@ export default class NotificationManager {
49
55
  notify(identifier: RecordIdentifier, value: 'errors' | 'meta' | 'identity' | 'unload' | 'state'): boolean;
50
56
  notify(identifier: RecordIdentifier, value: NotificationType, key?: string): boolean {
51
57
  let stableIdentifier = identifierCacheFor(this.store).getOrCreateRecordIdentifier(identifier);
52
- let callback = Cache.get(stableIdentifier);
53
- if (!callback) {
58
+ let callbackMap = Cache.get(stableIdentifier);
59
+ if (!callbackMap || !callbackMap.size) {
54
60
  return false;
55
61
  }
56
- callback(stableIdentifier, value, key);
62
+ callbackMap.forEach((cb) => {
63
+ cb(stableIdentifier, value, key);
64
+ });
57
65
  return true;
58
66
  }
59
67
  }
@@ -1,11 +1,19 @@
1
1
  import { deprecate } from '@ember/debug';
2
+ import { dependentKeyCompat } from '@ember/object/compat';
3
+ import { cached, tracked } from '@glimmer/tracking';
2
4
 
3
5
  import { resolve } from 'rsvp';
4
6
 
5
7
  import { DEPRECATE_BELONGS_TO_REFERENCE_PUSH } from '@ember-data/private-build-infra/deprecations';
8
+ import type { BelongsToRelationship } from '@ember-data/record-data/-private';
6
9
  import { assertPolymorphicType } from '@ember-data/store/-debug';
7
10
 
11
+ import { SingleResourceDocument } from '../../ts-interfaces/ember-data-json-api';
12
+ import { StableRecordIdentifier } from '../../ts-interfaces/identifier';
13
+ import CoreStore from '../core-store';
14
+ import { NotificationType, unsubscribe } from '../record-notification-manager';
8
15
  import { internalModelFactoryFor, peekRecordIdentifier, recordIdentifierFor } from '../store/internal-model-factory';
16
+ import RecordReference from './record';
9
17
  import Reference from './reference';
10
18
 
11
19
  /**
@@ -22,17 +30,77 @@ import Reference from './reference';
22
30
  @extends Reference
23
31
  */
24
32
  export default class BelongsToReference extends Reference {
25
- constructor(store, parentIMOrIdentifier, belongsToRelationship, key) {
26
- super(store, parentIMOrIdentifier);
33
+ declare key: string;
34
+ declare belongsToRelationship: BelongsToRelationship;
35
+ declare type: string;
36
+ declare parent: RecordReference;
37
+ declare parentIdentifier: StableRecordIdentifier;
38
+
39
+ // unsubscribe tokens given to us by the notification manager
40
+ #token!: Object;
41
+ #relatedToken: Object | null = null;
42
+
43
+ @tracked _ref = 0;
44
+
45
+ constructor(
46
+ store: CoreStore,
47
+ parentIdentifier: StableRecordIdentifier,
48
+ belongsToRelationship: BelongsToRelationship,
49
+ key: string
50
+ ) {
51
+ super(store, parentIdentifier);
27
52
  this.key = key;
28
53
  this.belongsToRelationship = belongsToRelationship;
29
54
  this.type = belongsToRelationship.definition.type;
30
- this.parent = internalModelFactoryFor(store).peek(parentIMOrIdentifier).recordReference;
31
- this.parentIdentifier = parentIMOrIdentifier;
55
+ const parent = internalModelFactoryFor(store).peek(parentIdentifier);
56
+ this.parent = parent!.recordReference;
57
+ this.parentIdentifier = parentIdentifier;
58
+
59
+ this.#token = store._notificationManager.subscribe(
60
+ parentIdentifier,
61
+ (_: StableRecordIdentifier, bucket: NotificationType, notifiedKey?: string) => {
62
+ if ((bucket === 'relationships' || bucket === 'property') && notifiedKey === key) {
63
+ this._ref++;
64
+ }
65
+ }
66
+ );
32
67
 
33
68
  // TODO inverse
34
69
  }
35
70
 
71
+ destroy() {
72
+ unsubscribe(this.#token);
73
+ if (this.#relatedToken) {
74
+ unsubscribe(this.#relatedToken);
75
+ }
76
+ }
77
+
78
+ @cached
79
+ @dependentKeyCompat
80
+ get _relatedIdentifier(): StableRecordIdentifier | null {
81
+ this._ref; // consume the tracked prop
82
+ if (this.#relatedToken) {
83
+ unsubscribe(this.#relatedToken);
84
+ }
85
+
86
+ let resource = this._resource();
87
+ if (resource && resource.data) {
88
+ const identifier = this.store.identifierCache.getOrCreateRecordIdentifier(resource.data);
89
+ this.#relatedToken = this.store._notificationManager.subscribe(
90
+ identifier,
91
+ (_: StableRecordIdentifier, bucket: NotificationType, notifiedKey?: string) => {
92
+ if (bucket === 'identity' || ((bucket === 'attributes' || bucket === 'property') && notifiedKey === 'id')) {
93
+ this._ref++;
94
+ }
95
+ }
96
+ );
97
+
98
+ return identifier;
99
+ }
100
+
101
+ return null;
102
+ }
103
+
36
104
  /**
37
105
  The `id` of the record that this reference refers to. Together, the
38
106
  `type()` and `id()` methods form a composite key for the identity
@@ -73,13 +141,8 @@ export default class BelongsToReference extends Reference {
73
141
  @public
74
142
  @return {String} The id of the record in this belongsTo relationship.
75
143
  */
76
- id() {
77
- let id = null;
78
- let resource = this._resource();
79
- if (resource && resource.data) {
80
- id = resource.data.id;
81
- }
82
- return id;
144
+ id(): string | null {
145
+ return this._relatedIdentifier?.id || null;
83
146
  }
84
147
 
85
148
  _resource() {
@@ -132,10 +195,10 @@ export default class BelongsToReference extends Reference {
132
195
  @param {Object|Promise} objectOrPromise a promise that resolves to a JSONAPI document object describing the new value of this relationship.
133
196
  @return {Promise<record>} A promise that resolves with the new value in this belongs-to relationship.
134
197
  */
135
- push(objectOrPromise) {
198
+ async push(objectOrPromise: Object | SingleResourceDocument): Promise<Object> {
136
199
  // TODO deprecate thenable support
137
200
  return resolve(objectOrPromise).then((data) => {
138
- let record;
201
+ let record: Object;
139
202
 
140
203
  if (DEPRECATE_BELONGS_TO_REFERENCE_PUSH && peekRecordIdentifier(data)) {
141
204
  deprecate('Pushing a record into a BelongsToReference is deprecated', false, {
@@ -147,15 +210,16 @@ export default class BelongsToReference extends Reference {
147
210
  enabled: '3.16',
148
211
  },
149
212
  });
150
- record = data;
213
+ record = data as Object;
151
214
  } else {
152
- record = this.store.push(data);
215
+ record = this.store.push(data as SingleResourceDocument);
153
216
  }
154
217
 
218
+ // eslint-disable-next-line @typescript-eslint/no-unsafe-call
155
219
  assertPolymorphicType(
156
220
  this.belongsToRelationship.identifier,
157
221
  this.belongsToRelationship.definition,
158
- record._internalModel.identifier,
222
+ recordIdentifierFor(record),
159
223
  this.store
160
224
  );
161
225
 
@@ -223,7 +287,7 @@ export default class BelongsToReference extends Reference {
223
287
  @public
224
288
  @return {Model} the record in this relationship
225
289
  */
226
- value() {
290
+ value(): Object | null {
227
291
  let resource = this._resource();
228
292
  if (resource && resource.data) {
229
293
  let inverseInternalModel = this.store._internalModelForResource(resource.data);
@@ -299,7 +363,7 @@ export default class BelongsToReference extends Reference {
299
363
  */
300
364
  load(options) {
301
365
  let parentInternalModel = internalModelFactoryFor(this.store).peek(this.parentIdentifier);
302
- return parentInternalModel.getBelongsTo(this.key, options);
366
+ return parentInternalModel!.getBelongsTo(this.key, options);
303
367
  }
304
368
 
305
369
  /**
@@ -354,7 +418,7 @@ export default class BelongsToReference extends Reference {
354
418
  */
355
419
  reload(options) {
356
420
  let parentInternalModel = internalModelFactoryFor(this.store).peek(this.parentIdentifier);
357
- return parentInternalModel.reloadBelongsTo(this.key, options).then((internalModel) => {
421
+ return parentInternalModel!.reloadBelongsTo(this.key, options).then((internalModel) => {
358
422
  return this.value();
359
423
  });
360
424
  }
@@ -1,10 +1,22 @@
1
+ import { dependentKeyCompat } from '@ember/object/compat';
1
2
  import { DEBUG } from '@glimmer/env';
3
+ import { cached, tracked } from '@glimmer/tracking';
2
4
 
3
5
  import { resolve } from 'rsvp';
4
6
 
7
+ import type { ManyRelationship } from '@ember-data/record-data/-private';
5
8
  import { assertPolymorphicType } from '@ember-data/store/-debug';
6
9
 
10
+ import {
11
+ CollectionResourceDocument,
12
+ ExistingResourceObject,
13
+ SingleResourceDocument,
14
+ } from '../../ts-interfaces/ember-data-json-api';
15
+ import { StableRecordIdentifier } from '../../ts-interfaces/identifier';
16
+ import CoreStore from '../core-store';
17
+ import { NotificationType, unsubscribe } from '../record-notification-manager';
7
18
  import { internalModelFactoryFor, recordIdentifierFor } from '../store/internal-model-factory';
19
+ import RecordReference from './record';
8
20
  import Reference, { internalModelForReference } from './reference';
9
21
 
10
22
  /**
@@ -20,17 +32,84 @@ import Reference, { internalModelForReference } from './reference';
20
32
  @extends Reference
21
33
  */
22
34
  export default class HasManyReference extends Reference {
23
- constructor(store, parentIMOrIdentifier, hasManyRelationship, key) {
24
- super(store, parentIMOrIdentifier);
35
+ declare key: string;
36
+ declare hasManyRelationship: ManyRelationship;
37
+ declare type: string;
38
+ declare parent: RecordReference;
39
+ declare parentIdentifier: StableRecordIdentifier;
40
+
41
+ // unsubscribe tokens given to us by the notification manager
42
+ #token!: Object;
43
+ #relatedTokenMap!: Map<StableRecordIdentifier, Object>;
44
+
45
+ @tracked _ref = 0;
46
+
47
+ constructor(
48
+ store: CoreStore,
49
+ parentIdentifier: StableRecordIdentifier,
50
+ hasManyRelationship: ManyRelationship,
51
+ key: string
52
+ ) {
53
+ super(store, parentIdentifier);
25
54
  this.key = key;
26
55
  this.hasManyRelationship = hasManyRelationship;
27
56
  this.type = hasManyRelationship.definition.type;
28
57
 
29
- this.parent = internalModelFactoryFor(store).peek(parentIMOrIdentifier).recordReference;
58
+ this.parent = internalModelFactoryFor(store).peek(parentIdentifier)!.recordReference;
30
59
 
60
+ this.#token = store._notificationManager.subscribe(
61
+ parentIdentifier,
62
+ (_: StableRecordIdentifier, bucket: NotificationType, notifiedKey?: string) => {
63
+ if ((bucket === 'relationships' || bucket === 'property') && notifiedKey === key) {
64
+ this._ref++;
65
+ }
66
+ }
67
+ );
68
+ this.#relatedTokenMap = new Map();
31
69
  // TODO inverse
32
70
  }
33
71
 
72
+ destroy() {
73
+ unsubscribe(this.#token);
74
+ this.#relatedTokenMap.forEach((token) => {
75
+ unsubscribe(token);
76
+ });
77
+ this.#relatedTokenMap.clear();
78
+ }
79
+
80
+ @cached
81
+ @dependentKeyCompat
82
+ get _relatedIdentifiers(): StableRecordIdentifier[] {
83
+ this._ref; // consume the tracked prop
84
+
85
+ let resource = this._resource();
86
+
87
+ this.#relatedTokenMap.forEach((token) => {
88
+ unsubscribe(token);
89
+ });
90
+ this.#relatedTokenMap.clear();
91
+
92
+ if (resource && resource.data) {
93
+ return resource.data.map((resourceIdentifier) => {
94
+ const identifier = this.store.identifierCache.getOrCreateRecordIdentifier(resourceIdentifier);
95
+ const token = this.store._notificationManager.subscribe(
96
+ identifier,
97
+ (_: StableRecordIdentifier, bucket: NotificationType, notifiedKey?: string) => {
98
+ if (bucket === 'identity' || ((bucket === 'attributes' || bucket === 'property') && notifiedKey === 'id')) {
99
+ this._ref++;
100
+ }
101
+ }
102
+ );
103
+
104
+ this.#relatedTokenMap.set(identifier, token);
105
+
106
+ return identifier;
107
+ });
108
+ }
109
+
110
+ return [];
111
+ }
112
+
34
113
  _resource() {
35
114
  return this.recordData.getHasMany(this.key);
36
115
  }
@@ -77,7 +156,7 @@ export default class HasManyReference extends Reference {
77
156
  @public
78
157
  @return {String} The name of the remote type. This should either be `link` or `ids`
79
158
  */
80
- remoteType() {
159
+ remoteType(): 'link' | 'ids' {
81
160
  let value = this._resource();
82
161
  if (value && value.links && value.links.related) {
83
162
  return 'link';
@@ -121,15 +200,8 @@ export default class HasManyReference extends Reference {
121
200
  @public
122
201
  @return {Array} The ids in this has-many relationship
123
202
  */
124
- ids() {
125
- let resource = this._resource();
126
-
127
- let ids = [];
128
- if (resource.data) {
129
- ids = resource.data.map((data) => data.id);
130
- }
131
-
132
- return ids;
203
+ ids(): Array<string | null> {
204
+ return this._relatedIdentifiers.map((identifier) => identifier.id);
133
205
  }
134
206
 
135
207
  /**
@@ -177,45 +249,50 @@ export default class HasManyReference extends Reference {
177
249
  @param {Array|Promise} objectOrPromise a promise that resolves to a JSONAPI document object describing the new value of this relationship.
178
250
  @return {ManyArray}
179
251
  */
180
- push(objectOrPromise) {
181
- return resolve(objectOrPromise).then((payload) => {
182
- let array = payload;
183
-
184
- if (typeof payload === 'object' && payload.data) {
185
- array = payload.data;
186
- }
252
+ async push(
253
+ objectOrPromise: ExistingResourceObject[] | CollectionResourceDocument | { data: SingleResourceDocument[] }
254
+ ): Promise<any> {
255
+ const payload = await resolve(objectOrPromise);
256
+ let array: Array<ExistingResourceObject | SingleResourceDocument>;
257
+
258
+ if (!Array.isArray(payload) && typeof payload === 'object' && Array.isArray(payload.data)) {
259
+ array = payload.data;
260
+ } else {
261
+ array = payload as ExistingResourceObject[];
262
+ }
187
263
 
188
- let internalModel = internalModelForReference(this);
264
+ const internalModel = internalModelForReference(this)!;
265
+ const { store } = this;
189
266
 
190
- let identifiers = array.map((obj) => {
191
- let record = this.store.push(obj);
267
+ let identifiers = array.map((obj) => {
268
+ let record;
269
+ if ('data' in obj) {
270
+ // TODO deprecate pushing non-valid JSON:API here
271
+ record = store.push(obj);
272
+ } else {
273
+ record = store.push({ data: obj });
274
+ }
192
275
 
193
- if (DEBUG) {
194
- let relationshipMeta = this.hasManyRelationship.definition;
195
- assertPolymorphicType(
196
- internalModel.identifier,
197
- relationshipMeta,
198
- record._internalModel.identifier,
199
- this.store
200
- );
201
- }
202
- return recordIdentifierFor(record);
203
- });
276
+ if (DEBUG) {
277
+ let relationshipMeta = this.hasManyRelationship.definition;
278
+ let identifier = this.hasManyRelationship.identifier;
279
+ assertPolymorphicType(identifier, relationshipMeta, recordIdentifierFor(record), store);
280
+ }
281
+ return recordIdentifierFor(record);
282
+ });
204
283
 
205
- const { graph, identifier } = this.hasManyRelationship;
206
- this.store._backburner.join(() => {
207
- graph.push({
208
- op: 'replaceRelatedRecords',
209
- record: identifier,
210
- field: this.key,
211
- value: identifiers,
212
- });
284
+ const { graph, identifier } = this.hasManyRelationship;
285
+ store._backburner.join(() => {
286
+ graph.push({
287
+ op: 'replaceRelatedRecords',
288
+ record: identifier,
289
+ field: this.key,
290
+ value: identifiers,
213
291
  });
214
-
215
- return internalModel.getHasMany(this.key);
216
- // TODO IGOR it seems wrong that we were returning the many array here
217
- //return this.hasManyRelationship.manyArray;
218
292
  });
293
+
294
+ // TODO IGOR it seems wrong that we were returning the many array here
295
+ return internalModel.getHasMany(this.key);
219
296
  }
220
297
 
221
298
  _isLoaded() {
@@ -275,7 +352,7 @@ export default class HasManyReference extends Reference {
275
352
  @return {ManyArray}
276
353
  */
277
354
  value() {
278
- let internalModel = internalModelForReference(this);
355
+ let internalModel = internalModelForReference(this)!;
279
356
  if (this._isLoaded()) {
280
357
  return internalModel.getManyArray(this.key);
281
358
  }
@@ -348,7 +425,7 @@ export default class HasManyReference extends Reference {
348
425
  this has-many relationship.
349
426
  */
350
427
  load(options) {
351
- let internalModel = internalModelForReference(this);
428
+ let internalModel = internalModelForReference(this)!;
352
429
  return internalModel.getHasMany(this.key, options);
353
430
  }
354
431
 
@@ -403,7 +480,7 @@ export default class HasManyReference extends Reference {
403
480
  @return {Promise} a promise that resolves with the ManyArray in this has-many relationship.
404
481
  */
405
482
  reload(options) {
406
- let internalModel = internalModelForReference(this);
483
+ let internalModel = internalModelForReference(this)!;
407
484
  return internalModel.reloadHasMany(this.key, options);
408
485
  }
409
486
  }
@@ -1,8 +1,13 @@
1
+ import { dependentKeyCompat } from '@ember/object/compat';
2
+ import { cached, tracked } from '@glimmer/tracking';
3
+
1
4
  import RSVP, { resolve } from 'rsvp';
2
5
 
3
6
  import type { SingleResourceDocument } from '../../ts-interfaces/ember-data-json-api';
4
7
  import type { StableRecordIdentifier } from '../../ts-interfaces/identifier';
5
8
  import type { RecordInstance } from '../../ts-interfaces/record-instance';
9
+ import type CoreStore from '../core-store';
10
+ import { NotificationType, unsubscribe } from '../record-notification-manager';
6
11
  import Reference, { internalModelForReference, REFERENCE_CACHE } from './reference';
7
12
 
8
13
  /**
@@ -18,11 +23,35 @@ import Reference, { internalModelForReference, REFERENCE_CACHE } from './referen
18
23
  @extends Reference
19
24
  */
20
25
  export default class RecordReference extends Reference {
26
+ // unsubscribe token given to us by the notification manager
27
+ #token!: Object;
28
+
29
+ @tracked _ref = 0;
30
+
31
+ constructor(public store: CoreStore, identifier: StableRecordIdentifier) {
32
+ super(store, identifier);
33
+ this.#token = store._notificationManager.subscribe(
34
+ identifier,
35
+ (_: StableRecordIdentifier, bucket: NotificationType, notifiedKey?: string) => {
36
+ if (bucket === 'identity' || ((bucket === 'attributes' || bucket === 'property') && notifiedKey === 'id')) {
37
+ this._ref++;
38
+ }
39
+ }
40
+ );
41
+ }
42
+
43
+ destroy() {
44
+ unsubscribe(this.#token);
45
+ }
46
+
21
47
  public get type(): string {
22
48
  return this.identifier().type;
23
49
  }
24
50
 
51
+ @cached
52
+ @dependentKeyCompat
25
53
  private get _id(): string | null {
54
+ this._ref; // consume the tracked prop
26
55
  let identifier = this.identifier();
27
56
  if (identifier) {
28
57
  return identifier.id;
@@ -92,7 +121,7 @@ export default class RecordReference extends Reference {
92
121
  @public
93
122
  @return {String} 'identity'
94
123
  */
95
- remoteType(): 'link' | 'id' | 'identity' {
124
+ remoteType(): 'identity' {
96
125
  return 'identity';
97
126
  }
98
127
 
@@ -159,7 +188,7 @@ export default class RecordReference extends Reference {
159
188
  @return {Model} the record for this RecordReference
160
189
  */
161
190
  value(): RecordInstance | null {
162
- if (this._id !== null) {
191
+ if (this.id() !== null) {
163
192
  let internalModel = internalModelForReference(this);
164
193
  if (internalModel && internalModel.currentState.isLoaded) {
165
194
  return internalModel.getRecord();
@@ -186,8 +215,9 @@ export default class RecordReference extends Reference {
186
215
  @return {Promise<record>} the record for this RecordReference
187
216
  */
188
217
  load() {
189
- if (this._id !== null) {
190
- return this.store.findRecord(this.type, this._id);
218
+ const id = this.id();
219
+ if (id !== null) {
220
+ return this.store.findRecord(this.type, id);
191
221
  }
192
222
  throw new Error(`Unable to fetch record of type ${this.type} without an id`);
193
223
  }
@@ -210,8 +240,9 @@ export default class RecordReference extends Reference {
210
240
  @return {Promise<record>} the record for this RecordReference
211
241
  */
212
242
  reload() {
213
- if (this._id !== null) {
214
- return this.store.findRecord(this.type, this._id, { reload: true });
243
+ const id = this.id();
244
+ if (id !== null) {
245
+ return this.store.findRecord(this.type, id, { reload: true });
215
246
  }
216
247
  throw new Error(`Unable to fetch record of type ${this.type} without an id`);
217
248
  }
@@ -94,7 +94,7 @@ abstract class Reference {
94
94
  @public
95
95
  @return {String} The name of the remote type. This should either be "link" or "ids"
96
96
  */
97
- remoteType(): 'link' | 'id' | 'identity' {
97
+ remoteType(): 'link' | 'id' | 'ids' | 'identity' {
98
98
  let value = this._resource();
99
99
  if (isResourceIdentiferWithRelatedLinks(value)) {
100
100
  return 'link';