@ember-data/store 3.28.9 → 3.28.10

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.
@@ -28,6 +28,8 @@ import Snapshot from '../snapshot';
28
28
  import { internalModelFactoryFor, setRecordIdentifier } from '../store/internal-model-factory';
29
29
  import RootState from './states';
30
30
 
31
+ type DSModel = import('../../ts-interfaces/ds-model').DSModel;
32
+
31
33
  type BelongsToRelationship = import('@ember-data/record-data/-private').BelongsToRelationship;
32
34
  type ManyRelationship = import('@ember-data/record-data/-private').ManyRelationship;
33
35
 
@@ -130,7 +132,7 @@ export default class InternalModel {
130
132
  declare _deferredTriggers: any;
131
133
  declare __recordArrays: any;
132
134
  declare references: any;
133
- declare _recordReference: any;
135
+ declare _recordReference: RecordReference;
134
136
  declare _manyArrayCache: ConfidentDict<ManyArray>;
135
137
 
136
138
  declare _relationshipPromisesCache: ConfidentDict<RSVP.Promise<any>>;
@@ -202,7 +204,7 @@ export default class InternalModel {
202
204
  }
203
205
  }
204
206
 
205
- get recordReference() {
207
+ get recordReference(): RecordReference {
206
208
  if (this._recordReference === null) {
207
209
  this._recordReference = new RecordReference(this.store, this.identifier);
208
210
  }
@@ -294,7 +296,7 @@ export default class InternalModel {
294
296
  }
295
297
  }
296
298
 
297
- getRecord(properties?) {
299
+ getRecord(properties?): Object {
298
300
  if (!this._record && !this._isDematerializing) {
299
301
  let { store } = this;
300
302
 
@@ -616,7 +618,7 @@ export default class InternalModel {
616
618
  "' with id " +
617
619
  parentInternalModel.id +
618
620
  ' 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 })`)',
619
- toReturn === null || !toReturn.get('isEmpty')
621
+ toReturn === null || !(toReturn as DSModel).isEmpty
620
622
  );
621
623
  return toReturn;
622
624
  }
@@ -675,7 +677,7 @@ export default class InternalModel {
675
677
  assert(`hasMany only works with the @ember-data/record-data package`);
676
678
  }
677
679
 
678
- getHasMany(key: string, options) {
680
+ getHasMany(key: string, options?) {
679
681
  if (HAS_RECORD_DATA_PACKAGE) {
680
682
  const graphFor = require('@ember-data/record-data/-private').graphFor;
681
683
  const relationship = graphFor(this.store).get(this.identifier, key);
@@ -793,11 +795,22 @@ export default class InternalModel {
793
795
  !this._record || this._record.get('isDestroyed') || this._record.get('isDestroying')
794
796
  );
795
797
  this.isDestroying = true;
798
+ if (this._recordReference) {
799
+ this._recordReference.destroy();
800
+ }
801
+ this._recordReference = null;
796
802
  let cache = this._manyArrayCache;
797
803
  Object.keys(cache).forEach((key) => {
798
804
  cache[key].destroy();
799
805
  delete cache[key];
800
806
  });
807
+ if (this.references) {
808
+ cache = this.references;
809
+ Object.keys(cache).forEach((key) => {
810
+ cache[key].destroy();
811
+ delete cache[key];
812
+ });
813
+ }
801
814
 
802
815
  internalModelFactoryFor(this.store).remove(this);
803
816
  this._isDestroyed = true;
@@ -806,6 +819,7 @@ export default class InternalModel {
806
819
  setupData(data) {
807
820
  let changedKeys = this._recordData.pushData(data, this.hasRecord);
808
821
  if (this.hasRecord) {
822
+ // TODO @runspired should this be going through the notification manager?
809
823
  this._record._notifyProperties(changedKeys);
810
824
  }
811
825
  this.send('pushedData');
@@ -966,10 +980,10 @@ export default class InternalModel {
966
980
  this.store._notificationManager.notify(this.identifier, 'state');
967
981
  } else {
968
982
  if (!key || key === 'isNew') {
969
- this.getRecord().notifyPropertyChange('isNew');
983
+ (this.getRecord() as DSModel).notifyPropertyChange('isNew');
970
984
  }
971
985
  if (!key || key === 'isDeleted') {
972
- this.getRecord().notifyPropertyChange('isDeleted');
986
+ (this.getRecord() as DSModel).notifyPropertyChange('isDeleted');
973
987
  }
974
988
  }
975
989
  }
@@ -1273,12 +1287,12 @@ export default class InternalModel {
1273
1287
  if (this._recordData.getErrors) {
1274
1288
  return this._recordData.getErrors(this.identifier).length > 0;
1275
1289
  } else {
1276
- let errors = get(this.getRecord(), 'errors');
1277
- return errors.get('length') > 0;
1290
+ let errors = (this.getRecord() as DSModel).errors;
1291
+ return errors.length > 0;
1278
1292
  }
1279
1293
  } else {
1280
- let errors = get(this.getRecord(), 'errors');
1281
- return errors.get('length') > 0;
1294
+ let errors = (this.getRecord() as DSModel).errors;
1295
+ return errors.length > 0;
1282
1296
  }
1283
1297
  }
1284
1298
 
@@ -1293,7 +1307,7 @@ export default class InternalModel {
1293
1307
  if (!this._recordData.getErrors) {
1294
1308
  for (attribute in parsedErrors) {
1295
1309
  if (hasOwnProperty.call(parsedErrors, attribute)) {
1296
- this.getRecord().errors._add(attribute, parsedErrors[attribute]);
1310
+ (this.getRecord() as DSModel).errors._add(attribute, parsedErrors[attribute]);
1297
1311
  }
1298
1312
  }
1299
1313
  }
@@ -1313,7 +1327,7 @@ export default class InternalModel {
1313
1327
 
1314
1328
  for (attribute in parsedErrors) {
1315
1329
  if (hasOwnProperty.call(parsedErrors, attribute)) {
1316
- this.getRecord().errors._add(attribute, parsedErrors[attribute]);
1330
+ (this.getRecord() as DSModel).errors._add(attribute, parsedErrors[attribute]);
1317
1331
  }
1318
1332
  }
1319
1333
 
@@ -3,7 +3,7 @@
3
3
  */
4
4
  import { assert } from '@ember/debug';
5
5
 
6
- import { REQUEST_SERVICE } from '@ember-data/canary-features';
6
+ import { CUSTOM_MODEL_CLASS, REQUEST_SERVICE } from '@ember-data/canary-features';
7
7
  /*
8
8
  This file encapsulates the various states that a record can transition
9
9
  through during its lifecycle.
@@ -431,6 +431,12 @@ createdState.uncommitted.rollback = function (internalModel) {
431
431
  };
432
432
 
433
433
  createdState.uncommitted.pushedData = function (internalModel) {
434
+ // TODO @runspired consider where to do this once we kill off state machine
435
+ if (CUSTOM_MODEL_CLASS) {
436
+ internalModel.store._notificationManager.notify(internalModel.identifier, 'identity');
437
+ } else {
438
+ internalModel.notifyPropertyChange('id');
439
+ }
434
440
  internalModel.transitionTo('loaded.updated.uncommitted');
435
441
  internalModel.triggerLater('didLoad');
436
442
  };
@@ -27,7 +27,7 @@ export function recordArraysForIdentifier(identifierOrInternalModel) {
27
27
  return RecordArraysCache.get(identifierOrInternalModel);
28
28
  }
29
29
 
30
- const pendingForIdentifier = new Set([]);
30
+ const pendingForIdentifier = new Set();
31
31
  const IMDematerializing = new WeakMap();
32
32
 
33
33
  const getIdentifier = function getIdentifier(identifierOrInternalModel) {
@@ -6,7 +6,7 @@ type StableRecordIdentifier = import('../ts-interfaces/identifier').StableRecord
6
6
 
7
7
  type UnsubscribeToken = Object;
8
8
 
9
- const Cache = new WeakMap<StableRecordIdentifier, NotificationCallback>();
9
+ const Cache = new WeakMap<StableRecordIdentifier, Map<UnsubscribeToken, NotificationCallback>>();
10
10
  const Tokens = new WeakMap<UnsubscribeToken, StableRecordIdentifier>();
11
11
 
12
12
  export type NotificationType =
@@ -31,7 +31,8 @@ export function unsubscribe(token: UnsubscribeToken) {
31
31
  throw new Error('Passed unknown unsubscribe token to unsubscribe');
32
32
  }
33
33
  Tokens.delete(token);
34
- Cache.delete(identifier);
34
+ const map = Cache.get(identifier);
35
+ map?.delete(token);
35
36
  }
36
37
  /*
37
38
  Currently only support a single callback per identifier
@@ -41,8 +42,13 @@ export default class NotificationManager {
41
42
 
42
43
  subscribe(identifier: RecordIdentifier, callback: NotificationCallback): UnsubscribeToken {
43
44
  let stableIdentifier = identifierCacheFor(this.store).getOrCreateRecordIdentifier(identifier);
44
- Cache.set(stableIdentifier, callback);
45
+ let map = Cache.get(stableIdentifier);
46
+ if (map === undefined) {
47
+ map = new Map();
48
+ Cache.set(stableIdentifier, map);
49
+ }
45
50
  let unsubToken = {};
51
+ map.set(unsubToken, callback);
46
52
  Tokens.set(unsubToken, stableIdentifier);
47
53
  return unsubToken;
48
54
  }
@@ -51,11 +57,13 @@ export default class NotificationManager {
51
57
  notify(identifier: RecordIdentifier, value: 'errors' | 'meta' | 'identity' | 'unload' | 'state'): boolean;
52
58
  notify(identifier: RecordIdentifier, value: NotificationType, key?: string): boolean {
53
59
  let stableIdentifier = identifierCacheFor(this.store).getOrCreateRecordIdentifier(identifier);
54
- let callback = Cache.get(stableIdentifier);
55
- if (!callback) {
60
+ let callbackMap = Cache.get(stableIdentifier);
61
+ if (!callbackMap || !callbackMap.size) {
56
62
  return false;
57
63
  }
58
- callback(stableIdentifier, value, key);
64
+ callbackMap.forEach((cb) => {
65
+ cb(stableIdentifier, value, key);
66
+ });
59
67
  return true;
60
68
  }
61
69
  }
@@ -1,13 +1,24 @@
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
 
7
+ import { CUSTOM_MODEL_CLASS } from '@ember-data/canary-features';
5
8
  import { DEPRECATE_BELONGS_TO_REFERENCE_PUSH } from '@ember-data/private-build-infra/deprecations';
6
9
  import { assertPolymorphicType } from '@ember-data/store/-debug';
7
10
 
11
+ import { unsubscribe } from '../record-notification-manager';
8
12
  import { internalModelFactoryFor, peekRecordIdentifier, recordIdentifierFor } from '../store/internal-model-factory';
9
13
  import Reference from './reference';
10
14
 
15
+ type RecordReference = import('./record').default;
16
+ type NotificationType = import('../record-notification-manager').NotificationType;
17
+ type CoreStore = import('../core-store').default;
18
+ type StableRecordIdentifier = import('../../ts-interfaces/identifier').StableRecordIdentifier;
19
+ type SingleResourceDocument = import('../../ts-interfaces/ember-data-json-api').SingleResourceDocument;
20
+
21
+ type BelongsToRelationship = import('@ember-data/record-data/-private').BelongsToRelationship;
11
22
  /**
12
23
  @module @ember-data/store
13
24
  */
@@ -22,17 +33,81 @@ import Reference from './reference';
22
33
  @extends Reference
23
34
  */
24
35
  export default class BelongsToReference extends Reference {
25
- constructor(store, parentIMOrIdentifier, belongsToRelationship, key) {
26
- super(store, parentIMOrIdentifier);
36
+ declare key: string;
37
+ declare belongsToRelationship: BelongsToRelationship;
38
+ declare type: string;
39
+ declare parent: RecordReference;
40
+ declare parentIdentifier: StableRecordIdentifier;
41
+
42
+ // unsubscribe tokens given to us by the notification manager
43
+ #token!: Object;
44
+ #relatedToken: Object | null = null;
45
+
46
+ @tracked _ref = 0;
47
+
48
+ constructor(
49
+ store: CoreStore,
50
+ parentIdentifier: StableRecordIdentifier,
51
+ belongsToRelationship: BelongsToRelationship,
52
+ key: string
53
+ ) {
54
+ super(store, parentIdentifier);
27
55
  this.key = key;
28
56
  this.belongsToRelationship = belongsToRelationship;
29
57
  this.type = belongsToRelationship.definition.type;
30
- this.parent = internalModelFactoryFor(store).peek(parentIMOrIdentifier).recordReference;
31
- this.parentIdentifier = parentIMOrIdentifier;
58
+ const parent = internalModelFactoryFor(store).peek(parentIdentifier);
59
+ this.parent = parent!.recordReference;
60
+ this.parentIdentifier = parentIdentifier;
61
+
62
+ if (CUSTOM_MODEL_CLASS) {
63
+ this.#token = store._notificationManager.subscribe(
64
+ parentIdentifier,
65
+ (_: StableRecordIdentifier, bucket: NotificationType, notifiedKey?: string) => {
66
+ if ((bucket === 'relationships' || bucket === 'property') && notifiedKey === key) {
67
+ this._ref++;
68
+ }
69
+ }
70
+ );
71
+ }
32
72
 
33
73
  // TODO inverse
34
74
  }
35
75
 
76
+ destroy() {
77
+ if (CUSTOM_MODEL_CLASS) {
78
+ unsubscribe(this.#token);
79
+ if (this.#relatedToken) {
80
+ unsubscribe(this.#relatedToken);
81
+ }
82
+ }
83
+ }
84
+
85
+ @cached
86
+ @dependentKeyCompat
87
+ get _relatedIdentifier(): StableRecordIdentifier | null {
88
+ this._ref; // consume the tracked prop
89
+ if (this.#relatedToken) {
90
+ unsubscribe(this.#relatedToken);
91
+ }
92
+
93
+ let resource = this._resource();
94
+ if (resource && resource.data) {
95
+ const identifier = this.store.identifierCache.getOrCreateRecordIdentifier(resource.data);
96
+ this.#relatedToken = this.store._notificationManager.subscribe(
97
+ identifier,
98
+ (_: StableRecordIdentifier, bucket: NotificationType, notifiedKey?: string) => {
99
+ if (bucket === 'identity' || ((bucket === 'attributes' || bucket === 'property') && notifiedKey === 'id')) {
100
+ this._ref++;
101
+ }
102
+ }
103
+ );
104
+
105
+ return identifier;
106
+ }
107
+
108
+ return null;
109
+ }
110
+
36
111
  /**
37
112
  The `id` of the record that this reference refers to. Together, the
38
113
  `type()` and `id()` methods form a composite key for the identity
@@ -73,13 +148,18 @@ export default class BelongsToReference extends Reference {
73
148
  @public
74
149
  @return {String} The id of the record in this belongsTo relationship.
75
150
  */
76
- id() {
77
- let id = null;
151
+ id(): string | null {
152
+ if (CUSTOM_MODEL_CLASS) {
153
+ return this._relatedIdentifier?.id || null;
154
+ }
78
155
  let resource = this._resource();
79
156
  if (resource && resource.data) {
80
- id = resource.data.id;
157
+ const identifier = this.store.identifierCache.getOrCreateRecordIdentifier(resource.data);
158
+
159
+ return identifier.id;
81
160
  }
82
- return id;
161
+
162
+ return null;
83
163
  }
84
164
 
85
165
  _resource() {
@@ -132,10 +212,10 @@ export default class BelongsToReference extends Reference {
132
212
  @param {Object|Promise} objectOrPromise a promise that resolves to a JSONAPI document object describing the new value of this relationship.
133
213
  @return {Promise<record>} A promise that resolves with the new value in this belongs-to relationship.
134
214
  */
135
- push(objectOrPromise) {
215
+ async push(objectOrPromise: Object | SingleResourceDocument): Promise<Object> {
136
216
  // TODO deprecate thenable support
137
217
  return resolve(objectOrPromise).then((data) => {
138
- let record;
218
+ let record: Object;
139
219
 
140
220
  if (DEPRECATE_BELONGS_TO_REFERENCE_PUSH && peekRecordIdentifier(data)) {
141
221
  deprecate('Pushing a record into a BelongsToReference is deprecated', false, {
@@ -147,15 +227,16 @@ export default class BelongsToReference extends Reference {
147
227
  enabled: '3.16',
148
228
  },
149
229
  });
150
- record = data;
230
+ record = data as Object;
151
231
  } else {
152
- record = this.store.push(data);
232
+ record = this.store.push(data as SingleResourceDocument);
153
233
  }
154
234
 
235
+ // eslint-disable-next-line @typescript-eslint/no-unsafe-call
155
236
  assertPolymorphicType(
156
237
  this.belongsToRelationship.identifier,
157
238
  this.belongsToRelationship.definition,
158
- record._internalModel.identifier,
239
+ recordIdentifierFor(record),
159
240
  this.store
160
241
  );
161
242
 
@@ -223,7 +304,7 @@ export default class BelongsToReference extends Reference {
223
304
  @public
224
305
  @return {Model} the record in this relationship
225
306
  */
226
- value() {
307
+ value(): Object | null {
227
308
  let resource = this._resource();
228
309
  if (resource && resource.data) {
229
310
  let inverseInternalModel = this.store._internalModelForResource(resource.data);
@@ -299,7 +380,7 @@ export default class BelongsToReference extends Reference {
299
380
  */
300
381
  load(options) {
301
382
  let parentInternalModel = internalModelFactoryFor(this.store).peek(this.parentIdentifier);
302
- return parentInternalModel.getBelongsTo(this.key, options);
383
+ return parentInternalModel!.getBelongsTo(this.key, options);
303
384
  }
304
385
 
305
386
  /**
@@ -354,7 +435,7 @@ export default class BelongsToReference extends Reference {
354
435
  */
355
436
  reload(options) {
356
437
  let parentInternalModel = internalModelFactoryFor(this.store).peek(this.parentIdentifier);
357
- return parentInternalModel.reloadBelongsTo(this.key, options).then((internalModel) => {
438
+ return parentInternalModel!.reloadBelongsTo(this.key, options).then((internalModel) => {
358
439
  return this.value();
359
440
  });
360
441
  }
@@ -1,12 +1,26 @@
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 { CUSTOM_MODEL_CLASS } from '@ember-data/canary-features';
5
8
  import { assertPolymorphicType } from '@ember-data/store/-debug';
6
9
 
10
+ import { unsubscribe } from '../record-notification-manager';
7
11
  import { internalModelFactoryFor, recordIdentifierFor } from '../store/internal-model-factory';
8
12
  import Reference, { internalModelForReference } from './reference';
9
13
 
14
+ type RecordReference = import('./record').default;
15
+ type NotificationType = import('../record-notification-manager').NotificationType;
16
+ type CoreStore = import('../core-store').default;
17
+ type StableRecordIdentifier = import('../../ts-interfaces/identifier').StableRecordIdentifier;
18
+ type CollectionResourceDocument = import('../../ts-interfaces/ember-data-json-api').CollectionResourceDocument;
19
+ type ExistingResourceObject = import('../../ts-interfaces/ember-data-json-api').ExistingResourceObject;
20
+ type SingleResourceDocument = import('../../ts-interfaces/ember-data-json-api').SingleResourceDocument;
21
+
22
+ type ManyRelationship = import('@ember-data/record-data/-private').ManyRelationship;
23
+
10
24
  /**
11
25
  @module @ember-data/store
12
26
  */
@@ -20,17 +34,88 @@ import Reference, { internalModelForReference } from './reference';
20
34
  @extends Reference
21
35
  */
22
36
  export default class HasManyReference extends Reference {
23
- constructor(store, parentIMOrIdentifier, hasManyRelationship, key) {
24
- super(store, parentIMOrIdentifier);
37
+ declare key: string;
38
+ declare hasManyRelationship: ManyRelationship;
39
+ declare type: string;
40
+ declare parent: RecordReference;
41
+ declare parentIdentifier: StableRecordIdentifier;
42
+
43
+ // unsubscribe tokens given to us by the notification manager
44
+ #token!: Object;
45
+ #relatedTokenMap!: Map<StableRecordIdentifier, Object>;
46
+
47
+ @tracked _ref = 0;
48
+
49
+ constructor(
50
+ store: CoreStore,
51
+ parentIdentifier: StableRecordIdentifier,
52
+ hasManyRelationship: ManyRelationship,
53
+ key: string
54
+ ) {
55
+ super(store, parentIdentifier);
25
56
  this.key = key;
26
57
  this.hasManyRelationship = hasManyRelationship;
27
58
  this.type = hasManyRelationship.definition.type;
28
59
 
29
- this.parent = internalModelFactoryFor(store).peek(parentIMOrIdentifier).recordReference;
60
+ this.parent = internalModelFactoryFor(store).peek(parentIdentifier)!.recordReference;
30
61
 
62
+ if (CUSTOM_MODEL_CLASS) {
63
+ this.#token = store._notificationManager.subscribe(
64
+ parentIdentifier,
65
+ (_: StableRecordIdentifier, bucket: NotificationType, notifiedKey?: string) => {
66
+ if ((bucket === 'relationships' || bucket === 'property') && notifiedKey === key) {
67
+ this._ref++;
68
+ }
69
+ }
70
+ );
71
+ this.#relatedTokenMap = new Map();
72
+ }
31
73
  // TODO inverse
32
74
  }
33
75
 
76
+ destroy() {
77
+ if (CUSTOM_MODEL_CLASS) {
78
+ unsubscribe(this.#token);
79
+ this.#relatedTokenMap.forEach((token) => {
80
+ unsubscribe(token);
81
+ });
82
+ this.#relatedTokenMap.clear();
83
+ }
84
+ }
85
+
86
+ @cached
87
+ @dependentKeyCompat
88
+ get _relatedIdentifiers(): StableRecordIdentifier[] {
89
+ this._ref; // consume the tracked prop
90
+
91
+ let resource = this._resource();
92
+
93
+ this.#relatedTokenMap.forEach((token) => {
94
+ unsubscribe(token);
95
+ });
96
+ this.#relatedTokenMap.clear();
97
+
98
+ if (resource && resource.data) {
99
+ return resource.data.map((resourceIdentifier) => {
100
+ const identifier = this.store.identifierCache.getOrCreateRecordIdentifier(resourceIdentifier);
101
+ const token = this.store._notificationManager.subscribe(
102
+ identifier,
103
+ (_: StableRecordIdentifier, bucket: NotificationType, notifiedKey?: string) => {
104
+ if (bucket === 'identity' || ((bucket === 'attributes' || bucket === 'property') && notifiedKey === 'id')) {
105
+ this._ref++;
106
+ }
107
+ }
108
+ );
109
+
110
+ this.#relatedTokenMap.set(identifier, token);
111
+
112
+ return identifier;
113
+ });
114
+ }
115
+
116
+ return [];
117
+ }
118
+
34
119
  _resource() {
35
120
  return this.recordData.getHasMany(this.key);
36
121
  }
@@ -77,7 +162,7 @@ export default class HasManyReference extends Reference {
77
162
  @public
78
163
  @return {String} The name of the remote type. This should either be `link` or `ids`
79
164
  */
80
- remoteType() {
165
+ remoteType(): 'link' | 'ids' {
81
166
  let value = this._resource();
82
167
  if (value && value.links && value.links.related) {
83
168
  return 'link';
@@ -121,15 +206,22 @@ export default class HasManyReference extends Reference {
121
206
  @public
122
207
  @return {Array} The ids in this has-many relationship
123
208
  */
124
- ids() {
209
+ ids(): Array<string | null> {
210
+ if (CUSTOM_MODEL_CLASS) {
211
+ return this._relatedIdentifiers.map((identifier) => identifier.id);
212
+ }
213
+
125
214
  let resource = this._resource();
126
215
 
127
- let ids = [];
128
- if (resource.data) {
129
- ids = resource.data.map((data) => data.id);
216
+ if (resource && resource.data) {
217
+ return resource.data.map((resourceIdentifier) => {
218
+ const identifier = this.store.identifierCache.getOrCreateRecordIdentifier(resourceIdentifier);
219
+
220
+ return identifier.id;
221
+ });
130
222
  }
131
223
 
132
- return ids;
224
+ return [];
133
225
  }
134
226
 
135
227
  /**
@@ -177,45 +269,50 @@ export default class HasManyReference extends Reference {
177
269
  @param {Array|Promise} objectOrPromise a promise that resolves to a JSONAPI document object describing the new value of this relationship.
178
270
  @return {ManyArray}
179
271
  */
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
- }
272
+ async push(
273
+ objectOrPromise: ExistingResourceObject[] | CollectionResourceDocument | { data: SingleResourceDocument[] }
274
+ ): Promise<any> {
275
+ const payload = await resolve(objectOrPromise);
276
+ let array: Array<ExistingResourceObject | SingleResourceDocument>;
277
+
278
+ if (!Array.isArray(payload) && typeof payload === 'object' && Array.isArray(payload.data)) {
279
+ array = payload.data;
280
+ } else {
281
+ array = payload as ExistingResourceObject[];
282
+ }
187
283
 
188
- let internalModel = internalModelForReference(this);
284
+ const internalModel = internalModelForReference(this)!;
285
+ const { store } = this;
189
286
 
190
- let identifiers = array.map((obj) => {
191
- let record = this.store.push(obj);
287
+ let identifiers = array.map((obj) => {
288
+ let record;
289
+ if ('data' in obj) {
290
+ // TODO deprecate pushing non-valid JSON:API here
291
+ record = store.push(obj);
292
+ } else {
293
+ record = store.push({ data: obj });
294
+ }
192
295
 
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
- });
296
+ if (DEBUG) {
297
+ let relationshipMeta = this.hasManyRelationship.definition;
298
+ let identifier = this.hasManyRelationship.identifier;
299
+ assertPolymorphicType(identifier, relationshipMeta, recordIdentifierFor(record), store);
300
+ }
301
+ return recordIdentifierFor(record);
302
+ });
204
303
 
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
- });
304
+ const { graph, identifier } = this.hasManyRelationship;
305
+ store._backburner.join(() => {
306
+ graph.push({
307
+ op: 'replaceRelatedRecords',
308
+ record: identifier,
309
+ field: this.key,
310
+ value: identifiers,
213
311
  });
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
312
  });
313
+
314
+ // TODO IGOR it seems wrong that we were returning the many array here
315
+ return internalModel.getHasMany(this.key);
219
316
  }
220
317
 
221
318
  _isLoaded() {
@@ -275,7 +372,7 @@ export default class HasManyReference extends Reference {
275
372
  @return {ManyArray}
276
373
  */
277
374
  value() {
278
- let internalModel = internalModelForReference(this);
375
+ let internalModel = internalModelForReference(this)!;
279
376
  if (this._isLoaded()) {
280
377
  return internalModel.getManyArray(this.key);
281
378
  }
@@ -348,7 +445,7 @@ export default class HasManyReference extends Reference {
348
445
  this has-many relationship.
349
446
  */
350
447
  load(options) {
351
- let internalModel = internalModelForReference(this);
448
+ let internalModel = internalModelForReference(this)!;
352
449
  return internalModel.getHasMany(this.key, options);
353
450
  }
354
451
 
@@ -403,7 +500,7 @@ export default class HasManyReference extends Reference {
403
500
  @return {Promise} a promise that resolves with the ManyArray in this has-many relationship.
404
501
  */
405
502
  reload(options) {
406
- let internalModel = internalModelForReference(this);
503
+ let internalModel = internalModelForReference(this)!;
407
504
  return internalModel.reloadHasMany(this.key, options);
408
505
  }
409
506
  }
@@ -1,7 +1,16 @@
1
+ import { dependentKeyCompat } from '@ember/object/compat';
2
+ import { cached, tracked } from '@glimmer/tracking';
3
+
1
4
  import RSVP, { resolve } from 'rsvp';
2
5
 
6
+ import { CUSTOM_MODEL_CLASS } from '@ember-data/canary-features';
7
+
8
+ import { unsubscribe } from '../record-notification-manager';
3
9
  import Reference, { internalModelForReference, REFERENCE_CACHE } from './reference';
4
10
 
11
+ type NotificationType = import('../record-notification-manager').NotificationType;
12
+
13
+ type CoreStore = import('../core-store').default;
5
14
  type SingleResourceDocument = import('../../ts-interfaces/ember-data-json-api').SingleResourceDocument;
6
15
  type RecordInstance = import('../../ts-interfaces/record-instance').RecordInstance;
7
16
  type StableRecordIdentifier = import('../../ts-interfaces/identifier').StableRecordIdentifier;
@@ -19,11 +28,39 @@ type StableRecordIdentifier = import('../../ts-interfaces/identifier').StableRec
19
28
  @extends Reference
20
29
  */
21
30
  export default class RecordReference extends Reference {
31
+ // unsubscribe token given to us by the notification manager
32
+ #token!: Object;
33
+
34
+ @tracked _ref = 0;
35
+
36
+ constructor(public store: CoreStore, identifier: StableRecordIdentifier) {
37
+ super(store, identifier);
38
+ if (CUSTOM_MODEL_CLASS) {
39
+ this.#token = store._notificationManager.subscribe(
40
+ identifier,
41
+ (_: StableRecordIdentifier, bucket: NotificationType, notifiedKey?: string) => {
42
+ if (bucket === 'identity' || ((bucket === 'attributes' || bucket === 'property') && notifiedKey === 'id')) {
43
+ this._ref++;
44
+ }
45
+ }
46
+ );
47
+ }
48
+ }
49
+
50
+ destroy() {
51
+ if (CUSTOM_MODEL_CLASS) {
52
+ unsubscribe(this.#token);
53
+ }
54
+ }
55
+
22
56
  public get type(): string {
23
57
  return this.identifier().type;
24
58
  }
25
59
 
60
+ @cached
61
+ @dependentKeyCompat
26
62
  private get _id(): string | null {
63
+ this._ref; // consume the tracked prop
27
64
  let identifier = this.identifier();
28
65
  if (identifier) {
29
66
  return identifier.id;
@@ -51,7 +88,15 @@ export default class RecordReference extends Reference {
51
88
  @return {String} The id of the record.
52
89
  */
53
90
  id() {
54
- return this._id;
91
+ if (CUSTOM_MODEL_CLASS) {
92
+ return this._id;
93
+ }
94
+ let identifier = this.identifier();
95
+ if (identifier) {
96
+ return identifier.id;
97
+ }
98
+
99
+ return null;
55
100
  }
56
101
 
57
102
  /**
@@ -93,7 +138,7 @@ export default class RecordReference extends Reference {
93
138
  @public
94
139
  @return {String} 'identity'
95
140
  */
96
- remoteType(): 'link' | 'id' | 'identity' {
141
+ remoteType(): 'identity' {
97
142
  return 'identity';
98
143
  }
99
144
 
@@ -160,7 +205,7 @@ export default class RecordReference extends Reference {
160
205
  @return {Model} the record for this RecordReference
161
206
  */
162
207
  value(): RecordInstance | null {
163
- if (this._id !== null) {
208
+ if (this.id() !== null) {
164
209
  let internalModel = internalModelForReference(this);
165
210
  if (internalModel && internalModel.currentState.isLoaded) {
166
211
  return internalModel.getRecord();
@@ -187,8 +232,9 @@ export default class RecordReference extends Reference {
187
232
  @return {Promise<record>} the record for this RecordReference
188
233
  */
189
234
  load() {
190
- if (this._id !== null) {
191
- return this.store.findRecord(this.type, this._id);
235
+ const id = this.id();
236
+ if (id !== null) {
237
+ return this.store.findRecord(this.type, id);
192
238
  }
193
239
  throw new Error(`Unable to fetch record of type ${this.type} without an id`);
194
240
  }
@@ -211,8 +257,9 @@ export default class RecordReference extends Reference {
211
257
  @return {Promise<record>} the record for this RecordReference
212
258
  */
213
259
  reload() {
214
- if (this._id !== null) {
215
- return this.store.findRecord(this.type, this._id, { reload: true });
260
+ const id = this.id();
261
+ if (id !== null) {
262
+ return this.store.findRecord(this.type, id, { reload: true });
216
263
  }
217
264
  throw new Error(`Unable to fetch record of type ${this.type} without an id`);
218
265
  }
@@ -100,7 +100,7 @@ abstract class Reference {
100
100
  @public
101
101
  @return {String} The name of the remote type. This should either be "link" or "ids"
102
102
  */
103
- remoteType(): 'link' | 'id' | 'identity' {
103
+ remoteType(): 'link' | 'id' | 'ids' | 'identity' {
104
104
  let value = this._resource();
105
105
  if (isResourceIdentiferWithRelatedLinks(value)) {
106
106
  return 'link';
@@ -19,6 +19,7 @@ export interface DSModel extends RecordInstance, EmberObject {
19
19
  isDeleted: boolean;
20
20
  deleteRecord(): void;
21
21
  unloadRecord(): void;
22
+ errors: any;
22
23
  }
23
24
 
24
25
  // Implemented by both ShimModelClass and DSModel
package/index.js CHANGED
@@ -10,6 +10,8 @@ module.exports = Object.assign({}, addonBaseConfig, {
10
10
  shouldRollupPrivate: true,
11
11
  externalDependenciesForPrivateModule() {
12
12
  return [
13
+ 'ember-cached-decorator-polyfill',
14
+
13
15
  '@ember-data/canary-features',
14
16
  '@ember-data/store/-debug',
15
17
 
@@ -23,6 +25,7 @@ module.exports = Object.assign({}, addonBaseConfig, {
23
25
  '@ember/object/evented',
24
26
  '@ember/object/internals',
25
27
  '@ember/object/mixin',
28
+ '@ember/object/compat',
26
29
  '@ember/object/promise-proxy-mixin',
27
30
  '@ember/object/proxy',
28
31
  '@ember/polyfills',
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ember-data/store",
3
- "version": "3.28.9",
3
+ "version": "3.28.10",
4
4
  "description": "The default blueprint for ember-cli addons.",
5
5
  "keywords": [
6
6
  "ember-addon"
@@ -17,16 +17,17 @@
17
17
  "start": "ember serve"
18
18
  },
19
19
  "dependencies": {
20
- "@ember-data/canary-features": "3.28.9",
21
- "@ember-data/private-build-infra": "3.28.9",
20
+ "@ember-data/canary-features": "3.28.10",
21
+ "@ember-data/private-build-infra": "3.28.10",
22
22
  "@ember/string": "^3.0.0",
23
23
  "@glimmer/tracking": "^1.0.4",
24
+ "ember-cached-decorator-polyfill": "^0.1.4",
24
25
  "ember-cli-babel": "^7.26.6",
25
26
  "ember-cli-path-utils": "^1.0.0",
26
27
  "ember-cli-typescript": "^4.1.0"
27
28
  },
28
29
  "devDependencies": {
29
- "@ember-data/unpublished-test-infra": "3.28.9",
30
+ "@ember-data/unpublished-test-infra": "3.28.10",
30
31
  "@ember/optional-features": "^2.0.0",
31
32
  "@ember/test-helpers": "^2.2.5",
32
33
  "@types/ember": "^3.16.5",