@ember-data/store 4.0.1 → 4.0.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/addon/-private/system/core-store.ts +1 -1
- package/addon/-private/system/ds-model-store.ts +2 -1
- package/addon/-private/system/model/internal-model.ts +35 -15
- package/addon/-private/system/model/states.js +7 -1
- package/addon/-private/system/record-notification-manager.ts +14 -6
- package/addon/-private/system/references/{belongs-to.js → belongs-to.ts} +95 -16
- package/addon/-private/system/references/{has-many.js → has-many.ts} +141 -45
- package/addon/-private/system/references/record.ts +52 -7
- package/addon/-private/system/references/reference.ts +1 -1
- package/addon/-private/ts-interfaces/ds-model.ts +1 -0
- package/index.js +3 -0
- package/package.json +5 -4
|
@@ -1759,7 +1759,7 @@ abstract class CoreStore extends Service {
|
|
|
1759
1759
|
if (arguments.length === 1 && isMaybeIdentifier(identifier)) {
|
|
1760
1760
|
let stableIdentifier = identifierCacheFor(this).peekRecordIdentifier(identifier);
|
|
1761
1761
|
if (stableIdentifier) {
|
|
1762
|
-
return internalModelFactoryFor(this).peek(stableIdentifier)?.getRecord();
|
|
1762
|
+
return internalModelFactoryFor(this).peek(stableIdentifier)?.getRecord() || null;
|
|
1763
1763
|
}
|
|
1764
1764
|
return null;
|
|
1765
1765
|
}
|
|
@@ -37,9 +37,10 @@ class Store extends CoreStore {
|
|
|
37
37
|
let createOptions: any = {
|
|
38
38
|
store: this,
|
|
39
39
|
_internalModel: internalModel,
|
|
40
|
+
// TODO deprecate allowing unknown args setting
|
|
41
|
+
_createProps: createRecordArgs,
|
|
40
42
|
container: null,
|
|
41
43
|
};
|
|
42
|
-
Object.assign(createOptions, createRecordArgs);
|
|
43
44
|
|
|
44
45
|
// ensure that `getOwner(this)` works inside a model instance
|
|
45
46
|
setOwner(createOptions, getOwner(this));
|
|
@@ -24,6 +24,7 @@ import type {
|
|
|
24
24
|
import type { UpgradedMeta } from '@ember-data/record-data/-private/graph/-edge-definition';
|
|
25
25
|
|
|
26
26
|
import { identifierCacheFor } from '../../identifiers/cache';
|
|
27
|
+
import { DSModel } from '../../ts-interfaces/ds-model';
|
|
27
28
|
import type { StableRecordIdentifier } from '../../ts-interfaces/identifier';
|
|
28
29
|
import type { RecordData } from '../../ts-interfaces/record-data';
|
|
29
30
|
import type { JsonApiResource, JsonApiValidationError } from '../../ts-interfaces/record-data-json-api';
|
|
@@ -127,7 +128,7 @@ export default class InternalModel {
|
|
|
127
128
|
declare _deferredTriggers: any;
|
|
128
129
|
declare __recordArrays: any;
|
|
129
130
|
declare references: any;
|
|
130
|
-
declare _recordReference:
|
|
131
|
+
declare _recordReference: RecordReference;
|
|
131
132
|
declare _manyArrayCache: ConfidentDict<ManyArray>;
|
|
132
133
|
|
|
133
134
|
declare _relationshipPromisesCache: ConfidentDict<RSVP.Promise<any>>;
|
|
@@ -199,7 +200,7 @@ export default class InternalModel {
|
|
|
199
200
|
}
|
|
200
201
|
}
|
|
201
202
|
|
|
202
|
-
get recordReference() {
|
|
203
|
+
get recordReference(): RecordReference {
|
|
203
204
|
if (this._recordReference === null) {
|
|
204
205
|
this._recordReference = new RecordReference(this.store, this.identifier);
|
|
205
206
|
}
|
|
@@ -291,7 +292,7 @@ export default class InternalModel {
|
|
|
291
292
|
}
|
|
292
293
|
}
|
|
293
294
|
|
|
294
|
-
getRecord(properties?) {
|
|
295
|
+
getRecord(properties?): Object {
|
|
295
296
|
if (!this._record && !this._isDematerializing) {
|
|
296
297
|
let { store } = this;
|
|
297
298
|
|
|
@@ -613,7 +614,7 @@ export default class InternalModel {
|
|
|
613
614
|
"' with id " +
|
|
614
615
|
parentInternalModel.id +
|
|
615
616
|
' 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 })`)',
|
|
616
|
-
toReturn === null || !toReturn.
|
|
617
|
+
toReturn === null || !(toReturn as DSModel).isEmpty
|
|
617
618
|
);
|
|
618
619
|
return toReturn;
|
|
619
620
|
}
|
|
@@ -672,7 +673,7 @@ export default class InternalModel {
|
|
|
672
673
|
assert(`hasMany only works with the @ember-data/record-data package`);
|
|
673
674
|
}
|
|
674
675
|
|
|
675
|
-
getHasMany(key: string, options) {
|
|
676
|
+
getHasMany(key: string, options?) {
|
|
676
677
|
if (HAS_RECORD_DATA_PACKAGE) {
|
|
677
678
|
const graphFor = require('@ember-data/record-data/-private').graphFor;
|
|
678
679
|
const relationship = graphFor(this.store).get(this.identifier, key);
|
|
@@ -790,11 +791,22 @@ export default class InternalModel {
|
|
|
790
791
|
!this._record || this._record.get('isDestroyed') || this._record.get('isDestroying')
|
|
791
792
|
);
|
|
792
793
|
this.isDestroying = true;
|
|
794
|
+
if (this._recordReference) {
|
|
795
|
+
this._recordReference.destroy();
|
|
796
|
+
}
|
|
797
|
+
this._recordReference = null;
|
|
793
798
|
let cache = this._manyArrayCache;
|
|
794
799
|
Object.keys(cache).forEach((key) => {
|
|
795
800
|
cache[key].destroy();
|
|
796
801
|
delete cache[key];
|
|
797
802
|
});
|
|
803
|
+
if (this.references) {
|
|
804
|
+
cache = this.references;
|
|
805
|
+
Object.keys(cache).forEach((key) => {
|
|
806
|
+
cache[key].destroy();
|
|
807
|
+
delete cache[key];
|
|
808
|
+
});
|
|
809
|
+
}
|
|
798
810
|
|
|
799
811
|
internalModelFactoryFor(this.store).remove(this);
|
|
800
812
|
this._isDestroyed = true;
|
|
@@ -803,6 +815,7 @@ export default class InternalModel {
|
|
|
803
815
|
setupData(data) {
|
|
804
816
|
let changedKeys = this._recordData.pushData(data, this.hasRecord);
|
|
805
817
|
if (this.hasRecord) {
|
|
818
|
+
// TODO @runspired should this be going through the notification manager?
|
|
806
819
|
this._record._notifyProperties(changedKeys);
|
|
807
820
|
}
|
|
808
821
|
this.send('pushedData');
|
|
@@ -902,11 +915,18 @@ export default class InternalModel {
|
|
|
902
915
|
|
|
903
916
|
notifyHasManyChange(key: string) {
|
|
904
917
|
if (this.hasRecord) {
|
|
918
|
+
let manyArray = this._manyArrayCache[key];
|
|
919
|
+
let hasPromise = !!this._relationshipPromisesCache[key];
|
|
920
|
+
|
|
921
|
+
if (manyArray && hasPromise) {
|
|
922
|
+
// do nothing, we will notify the ManyArray directly
|
|
923
|
+
// once the fetch has completed.
|
|
924
|
+
return;
|
|
925
|
+
}
|
|
926
|
+
|
|
905
927
|
if (CUSTOM_MODEL_CLASS) {
|
|
906
928
|
this.store._notificationManager.notify(this.identifier, 'relationships', key);
|
|
907
929
|
} else {
|
|
908
|
-
let manyArray = this._manyArrayCache[key];
|
|
909
|
-
|
|
910
930
|
if (manyArray) {
|
|
911
931
|
manyArray.notify();
|
|
912
932
|
|
|
@@ -956,10 +976,10 @@ export default class InternalModel {
|
|
|
956
976
|
this.store._notificationManager.notify(this.identifier, 'state');
|
|
957
977
|
} else {
|
|
958
978
|
if (!key || key === 'isNew') {
|
|
959
|
-
this.getRecord().notifyPropertyChange('isNew');
|
|
979
|
+
(this.getRecord() as DSModel).notifyPropertyChange('isNew');
|
|
960
980
|
}
|
|
961
981
|
if (!key || key === 'isDeleted') {
|
|
962
|
-
this.getRecord().notifyPropertyChange('isDeleted');
|
|
982
|
+
(this.getRecord() as DSModel).notifyPropertyChange('isDeleted');
|
|
963
983
|
}
|
|
964
984
|
}
|
|
965
985
|
}
|
|
@@ -1263,12 +1283,12 @@ export default class InternalModel {
|
|
|
1263
1283
|
if (this._recordData.getErrors) {
|
|
1264
1284
|
return this._recordData.getErrors(this.identifier).length > 0;
|
|
1265
1285
|
} else {
|
|
1266
|
-
let errors =
|
|
1267
|
-
return errors.
|
|
1286
|
+
let errors = (this.getRecord() as DSModel).errors;
|
|
1287
|
+
return errors.length > 0;
|
|
1268
1288
|
}
|
|
1269
1289
|
} else {
|
|
1270
|
-
let errors =
|
|
1271
|
-
return errors.
|
|
1290
|
+
let errors = (this.getRecord() as DSModel).errors;
|
|
1291
|
+
return errors.length > 0;
|
|
1272
1292
|
}
|
|
1273
1293
|
}
|
|
1274
1294
|
|
|
@@ -1283,7 +1303,7 @@ export default class InternalModel {
|
|
|
1283
1303
|
if (!this._recordData.getErrors) {
|
|
1284
1304
|
for (attribute in parsedErrors) {
|
|
1285
1305
|
if (hasOwnProperty.call(parsedErrors, attribute)) {
|
|
1286
|
-
this.getRecord().errors._add(attribute, parsedErrors[attribute]);
|
|
1306
|
+
(this.getRecord() as DSModel).errors._add(attribute, parsedErrors[attribute]);
|
|
1287
1307
|
}
|
|
1288
1308
|
}
|
|
1289
1309
|
}
|
|
@@ -1303,7 +1323,7 @@ export default class InternalModel {
|
|
|
1303
1323
|
|
|
1304
1324
|
for (attribute in parsedErrors) {
|
|
1305
1325
|
if (hasOwnProperty.call(parsedErrors, attribute)) {
|
|
1306
|
-
this.getRecord().errors._add(attribute, parsedErrors[attribute]);
|
|
1326
|
+
(this.getRecord() as DSModel).errors._add(attribute, parsedErrors[attribute]);
|
|
1307
1327
|
}
|
|
1308
1328
|
}
|
|
1309
1329
|
|
|
@@ -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
|
};
|
|
@@ -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.
|
|
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.
|
|
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
|
|
53
|
-
if (!
|
|
58
|
+
let callbackMap = Cache.get(stableIdentifier);
|
|
59
|
+
if (!callbackMap || !callbackMap.size) {
|
|
54
60
|
return false;
|
|
55
61
|
}
|
|
56
|
-
|
|
62
|
+
callbackMap.forEach((cb) => {
|
|
63
|
+
cb(stableIdentifier, value, key);
|
|
64
|
+
});
|
|
57
65
|
return true;
|
|
58
66
|
}
|
|
59
67
|
}
|
|
@@ -1,11 +1,20 @@
|
|
|
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';
|
|
9
|
+
import type { BelongsToRelationship } from '@ember-data/record-data/-private';
|
|
6
10
|
import { assertPolymorphicType } from '@ember-data/store/-debug';
|
|
7
11
|
|
|
12
|
+
import { SingleResourceDocument } from '../../ts-interfaces/ember-data-json-api';
|
|
13
|
+
import { StableRecordIdentifier } from '../../ts-interfaces/identifier';
|
|
14
|
+
import CoreStore from '../core-store';
|
|
15
|
+
import { NotificationType, unsubscribe } from '../record-notification-manager';
|
|
8
16
|
import { internalModelFactoryFor, peekRecordIdentifier, recordIdentifierFor } from '../store/internal-model-factory';
|
|
17
|
+
import RecordReference from './record';
|
|
9
18
|
import Reference from './reference';
|
|
10
19
|
|
|
11
20
|
/**
|
|
@@ -22,17 +31,81 @@ import Reference from './reference';
|
|
|
22
31
|
@extends Reference
|
|
23
32
|
*/
|
|
24
33
|
export default class BelongsToReference extends Reference {
|
|
25
|
-
|
|
26
|
-
|
|
34
|
+
declare key: string;
|
|
35
|
+
declare belongsToRelationship: BelongsToRelationship;
|
|
36
|
+
declare type: string;
|
|
37
|
+
declare parent: RecordReference;
|
|
38
|
+
declare parentIdentifier: StableRecordIdentifier;
|
|
39
|
+
|
|
40
|
+
// unsubscribe tokens given to us by the notification manager
|
|
41
|
+
#token!: Object;
|
|
42
|
+
#relatedToken: Object | null = null;
|
|
43
|
+
|
|
44
|
+
@tracked _ref = 0;
|
|
45
|
+
|
|
46
|
+
constructor(
|
|
47
|
+
store: CoreStore,
|
|
48
|
+
parentIdentifier: StableRecordIdentifier,
|
|
49
|
+
belongsToRelationship: BelongsToRelationship,
|
|
50
|
+
key: string
|
|
51
|
+
) {
|
|
52
|
+
super(store, parentIdentifier);
|
|
27
53
|
this.key = key;
|
|
28
54
|
this.belongsToRelationship = belongsToRelationship;
|
|
29
55
|
this.type = belongsToRelationship.definition.type;
|
|
30
|
-
|
|
31
|
-
this.
|
|
56
|
+
const parent = internalModelFactoryFor(store).peek(parentIdentifier);
|
|
57
|
+
this.parent = parent!.recordReference;
|
|
58
|
+
this.parentIdentifier = parentIdentifier;
|
|
59
|
+
|
|
60
|
+
if (CUSTOM_MODEL_CLASS) {
|
|
61
|
+
this.#token = store._notificationManager.subscribe(
|
|
62
|
+
parentIdentifier,
|
|
63
|
+
(_: StableRecordIdentifier, bucket: NotificationType, notifiedKey?: string) => {
|
|
64
|
+
if ((bucket === 'relationships' || bucket === 'property') && notifiedKey === key) {
|
|
65
|
+
this._ref++;
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
);
|
|
69
|
+
}
|
|
32
70
|
|
|
33
71
|
// TODO inverse
|
|
34
72
|
}
|
|
35
73
|
|
|
74
|
+
destroy() {
|
|
75
|
+
if (CUSTOM_MODEL_CLASS) {
|
|
76
|
+
unsubscribe(this.#token);
|
|
77
|
+
if (this.#relatedToken) {
|
|
78
|
+
unsubscribe(this.#relatedToken);
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
@cached
|
|
84
|
+
@dependentKeyCompat
|
|
85
|
+
get _relatedIdentifier(): StableRecordIdentifier | null {
|
|
86
|
+
this._ref; // consume the tracked prop
|
|
87
|
+
if (this.#relatedToken) {
|
|
88
|
+
unsubscribe(this.#relatedToken);
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
let resource = this._resource();
|
|
92
|
+
if (resource && resource.data) {
|
|
93
|
+
const identifier = this.store.identifierCache.getOrCreateRecordIdentifier(resource.data);
|
|
94
|
+
this.#relatedToken = this.store._notificationManager.subscribe(
|
|
95
|
+
identifier,
|
|
96
|
+
(_: StableRecordIdentifier, bucket: NotificationType, notifiedKey?: string) => {
|
|
97
|
+
if (bucket === 'identity' || ((bucket === 'attributes' || bucket === 'property') && notifiedKey === 'id')) {
|
|
98
|
+
this._ref++;
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
);
|
|
102
|
+
|
|
103
|
+
return identifier;
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
return null;
|
|
107
|
+
}
|
|
108
|
+
|
|
36
109
|
/**
|
|
37
110
|
The `id` of the record that this reference refers to. Together, the
|
|
38
111
|
`type()` and `id()` methods form a composite key for the identity
|
|
@@ -73,13 +146,18 @@ export default class BelongsToReference extends Reference {
|
|
|
73
146
|
@public
|
|
74
147
|
@return {String} The id of the record in this belongsTo relationship.
|
|
75
148
|
*/
|
|
76
|
-
id() {
|
|
77
|
-
|
|
149
|
+
id(): string | null {
|
|
150
|
+
if (CUSTOM_MODEL_CLASS) {
|
|
151
|
+
return this._relatedIdentifier?.id || null;
|
|
152
|
+
}
|
|
78
153
|
let resource = this._resource();
|
|
79
154
|
if (resource && resource.data) {
|
|
80
|
-
|
|
155
|
+
const identifier = this.store.identifierCache.getOrCreateRecordIdentifier(resource.data);
|
|
156
|
+
|
|
157
|
+
return identifier.id;
|
|
81
158
|
}
|
|
82
|
-
|
|
159
|
+
|
|
160
|
+
return null;
|
|
83
161
|
}
|
|
84
162
|
|
|
85
163
|
_resource() {
|
|
@@ -132,10 +210,10 @@ export default class BelongsToReference extends Reference {
|
|
|
132
210
|
@param {Object|Promise} objectOrPromise a promise that resolves to a JSONAPI document object describing the new value of this relationship.
|
|
133
211
|
@return {Promise<record>} A promise that resolves with the new value in this belongs-to relationship.
|
|
134
212
|
*/
|
|
135
|
-
push(objectOrPromise) {
|
|
213
|
+
async push(objectOrPromise: Object | SingleResourceDocument): Promise<Object> {
|
|
136
214
|
// TODO deprecate thenable support
|
|
137
215
|
return resolve(objectOrPromise).then((data) => {
|
|
138
|
-
let record;
|
|
216
|
+
let record: Object;
|
|
139
217
|
|
|
140
218
|
if (DEPRECATE_BELONGS_TO_REFERENCE_PUSH && peekRecordIdentifier(data)) {
|
|
141
219
|
deprecate('Pushing a record into a BelongsToReference is deprecated', false, {
|
|
@@ -147,15 +225,16 @@ export default class BelongsToReference extends Reference {
|
|
|
147
225
|
enabled: '3.16',
|
|
148
226
|
},
|
|
149
227
|
});
|
|
150
|
-
record = data;
|
|
228
|
+
record = data as Object;
|
|
151
229
|
} else {
|
|
152
|
-
record = this.store.push(data);
|
|
230
|
+
record = this.store.push(data as SingleResourceDocument);
|
|
153
231
|
}
|
|
154
232
|
|
|
233
|
+
// eslint-disable-next-line @typescript-eslint/no-unsafe-call
|
|
155
234
|
assertPolymorphicType(
|
|
156
235
|
this.belongsToRelationship.identifier,
|
|
157
236
|
this.belongsToRelationship.definition,
|
|
158
|
-
record
|
|
237
|
+
recordIdentifierFor(record),
|
|
159
238
|
this.store
|
|
160
239
|
);
|
|
161
240
|
|
|
@@ -223,7 +302,7 @@ export default class BelongsToReference extends Reference {
|
|
|
223
302
|
@public
|
|
224
303
|
@return {Model} the record in this relationship
|
|
225
304
|
*/
|
|
226
|
-
value() {
|
|
305
|
+
value(): Object | null {
|
|
227
306
|
let resource = this._resource();
|
|
228
307
|
if (resource && resource.data) {
|
|
229
308
|
let inverseInternalModel = this.store._internalModelForResource(resource.data);
|
|
@@ -299,7 +378,7 @@ export default class BelongsToReference extends Reference {
|
|
|
299
378
|
*/
|
|
300
379
|
load(options) {
|
|
301
380
|
let parentInternalModel = internalModelFactoryFor(this.store).peek(this.parentIdentifier);
|
|
302
|
-
return parentInternalModel
|
|
381
|
+
return parentInternalModel!.getBelongsTo(this.key, options);
|
|
303
382
|
}
|
|
304
383
|
|
|
305
384
|
/**
|
|
@@ -354,7 +433,7 @@ export default class BelongsToReference extends Reference {
|
|
|
354
433
|
*/
|
|
355
434
|
reload(options) {
|
|
356
435
|
let parentInternalModel = internalModelFactoryFor(this.store).peek(this.parentIdentifier);
|
|
357
|
-
return parentInternalModel
|
|
436
|
+
return parentInternalModel!.reloadBelongsTo(this.key, options).then((internalModel) => {
|
|
358
437
|
return this.value();
|
|
359
438
|
});
|
|
360
439
|
}
|
|
@@ -1,10 +1,23 @@
|
|
|
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';
|
|
8
|
+
import type { ManyRelationship } from '@ember-data/record-data/-private';
|
|
5
9
|
import { assertPolymorphicType } from '@ember-data/store/-debug';
|
|
6
10
|
|
|
11
|
+
import {
|
|
12
|
+
CollectionResourceDocument,
|
|
13
|
+
ExistingResourceObject,
|
|
14
|
+
SingleResourceDocument,
|
|
15
|
+
} from '../../ts-interfaces/ember-data-json-api';
|
|
16
|
+
import { StableRecordIdentifier } from '../../ts-interfaces/identifier';
|
|
17
|
+
import CoreStore from '../core-store';
|
|
18
|
+
import { NotificationType, unsubscribe } from '../record-notification-manager';
|
|
7
19
|
import { internalModelFactoryFor, recordIdentifierFor } from '../store/internal-model-factory';
|
|
20
|
+
import RecordReference from './record';
|
|
8
21
|
import Reference, { internalModelForReference } from './reference';
|
|
9
22
|
|
|
10
23
|
/**
|
|
@@ -20,17 +33,88 @@ import Reference, { internalModelForReference } from './reference';
|
|
|
20
33
|
@extends Reference
|
|
21
34
|
*/
|
|
22
35
|
export default class HasManyReference extends Reference {
|
|
23
|
-
|
|
24
|
-
|
|
36
|
+
declare key: string;
|
|
37
|
+
declare hasManyRelationship: ManyRelationship;
|
|
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
|
+
#relatedTokenMap!: Map<StableRecordIdentifier, Object>;
|
|
45
|
+
|
|
46
|
+
@tracked _ref = 0;
|
|
47
|
+
|
|
48
|
+
constructor(
|
|
49
|
+
store: CoreStore,
|
|
50
|
+
parentIdentifier: StableRecordIdentifier,
|
|
51
|
+
hasManyRelationship: ManyRelationship,
|
|
52
|
+
key: string
|
|
53
|
+
) {
|
|
54
|
+
super(store, parentIdentifier);
|
|
25
55
|
this.key = key;
|
|
26
56
|
this.hasManyRelationship = hasManyRelationship;
|
|
27
57
|
this.type = hasManyRelationship.definition.type;
|
|
28
58
|
|
|
29
|
-
this.parent = internalModelFactoryFor(store).peek(
|
|
59
|
+
this.parent = internalModelFactoryFor(store).peek(parentIdentifier)!.recordReference;
|
|
30
60
|
|
|
61
|
+
if (CUSTOM_MODEL_CLASS) {
|
|
62
|
+
this.#token = store._notificationManager.subscribe(
|
|
63
|
+
parentIdentifier,
|
|
64
|
+
(_: StableRecordIdentifier, bucket: NotificationType, notifiedKey?: string) => {
|
|
65
|
+
if ((bucket === 'relationships' || bucket === 'property') && notifiedKey === key) {
|
|
66
|
+
this._ref++;
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
);
|
|
70
|
+
this.#relatedTokenMap = new Map();
|
|
71
|
+
}
|
|
31
72
|
// TODO inverse
|
|
32
73
|
}
|
|
33
74
|
|
|
75
|
+
destroy() {
|
|
76
|
+
if (CUSTOM_MODEL_CLASS) {
|
|
77
|
+
unsubscribe(this.#token);
|
|
78
|
+
this.#relatedTokenMap.forEach((token) => {
|
|
79
|
+
unsubscribe(token);
|
|
80
|
+
});
|
|
81
|
+
this.#relatedTokenMap.clear();
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
@cached
|
|
86
|
+
@dependentKeyCompat
|
|
87
|
+
get _relatedIdentifiers(): StableRecordIdentifier[] {
|
|
88
|
+
this._ref; // consume the tracked prop
|
|
89
|
+
|
|
90
|
+
let resource = this._resource();
|
|
91
|
+
|
|
92
|
+
this.#relatedTokenMap.forEach((token) => {
|
|
93
|
+
unsubscribe(token);
|
|
94
|
+
});
|
|
95
|
+
this.#relatedTokenMap.clear();
|
|
96
|
+
|
|
97
|
+
if (resource && resource.data) {
|
|
98
|
+
return resource.data.map((resourceIdentifier) => {
|
|
99
|
+
const identifier = this.store.identifierCache.getOrCreateRecordIdentifier(resourceIdentifier);
|
|
100
|
+
const token = this.store._notificationManager.subscribe(
|
|
101
|
+
identifier,
|
|
102
|
+
(_: StableRecordIdentifier, bucket: NotificationType, notifiedKey?: string) => {
|
|
103
|
+
if (bucket === 'identity' || ((bucket === 'attributes' || bucket === 'property') && notifiedKey === 'id')) {
|
|
104
|
+
this._ref++;
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
);
|
|
108
|
+
|
|
109
|
+
this.#relatedTokenMap.set(identifier, token);
|
|
110
|
+
|
|
111
|
+
return identifier;
|
|
112
|
+
});
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
return [];
|
|
116
|
+
}
|
|
117
|
+
|
|
34
118
|
_resource() {
|
|
35
119
|
return this.recordData.getHasMany(this.key);
|
|
36
120
|
}
|
|
@@ -77,7 +161,7 @@ export default class HasManyReference extends Reference {
|
|
|
77
161
|
@public
|
|
78
162
|
@return {String} The name of the remote type. This should either be `link` or `ids`
|
|
79
163
|
*/
|
|
80
|
-
remoteType() {
|
|
164
|
+
remoteType(): 'link' | 'ids' {
|
|
81
165
|
let value = this._resource();
|
|
82
166
|
if (value && value.links && value.links.related) {
|
|
83
167
|
return 'link';
|
|
@@ -121,15 +205,22 @@ export default class HasManyReference extends Reference {
|
|
|
121
205
|
@public
|
|
122
206
|
@return {Array} The ids in this has-many relationship
|
|
123
207
|
*/
|
|
124
|
-
ids() {
|
|
208
|
+
ids(): Array<string | null> {
|
|
209
|
+
if (CUSTOM_MODEL_CLASS) {
|
|
210
|
+
return this._relatedIdentifiers.map((identifier) => identifier.id);
|
|
211
|
+
}
|
|
212
|
+
|
|
125
213
|
let resource = this._resource();
|
|
126
214
|
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
215
|
+
if (resource && resource.data) {
|
|
216
|
+
return resource.data.map((resourceIdentifier) => {
|
|
217
|
+
const identifier = this.store.identifierCache.getOrCreateRecordIdentifier(resourceIdentifier);
|
|
218
|
+
|
|
219
|
+
return identifier.id;
|
|
220
|
+
});
|
|
130
221
|
}
|
|
131
222
|
|
|
132
|
-
return
|
|
223
|
+
return [];
|
|
133
224
|
}
|
|
134
225
|
|
|
135
226
|
/**
|
|
@@ -177,45 +268,50 @@ export default class HasManyReference extends Reference {
|
|
|
177
268
|
@param {Array|Promise} objectOrPromise a promise that resolves to a JSONAPI document object describing the new value of this relationship.
|
|
178
269
|
@return {ManyArray}
|
|
179
270
|
*/
|
|
180
|
-
push(
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
271
|
+
async push(
|
|
272
|
+
objectOrPromise: ExistingResourceObject[] | CollectionResourceDocument | { data: SingleResourceDocument[] }
|
|
273
|
+
): Promise<any> {
|
|
274
|
+
const payload = await resolve(objectOrPromise);
|
|
275
|
+
let array: Array<ExistingResourceObject | SingleResourceDocument>;
|
|
276
|
+
|
|
277
|
+
if (!Array.isArray(payload) && typeof payload === 'object' && Array.isArray(payload.data)) {
|
|
278
|
+
array = payload.data;
|
|
279
|
+
} else {
|
|
280
|
+
array = payload as ExistingResourceObject[];
|
|
281
|
+
}
|
|
187
282
|
|
|
188
|
-
|
|
283
|
+
const internalModel = internalModelForReference(this)!;
|
|
284
|
+
const { store } = this;
|
|
189
285
|
|
|
190
|
-
|
|
191
|
-
|
|
286
|
+
let identifiers = array.map((obj) => {
|
|
287
|
+
let record;
|
|
288
|
+
if ('data' in obj) {
|
|
289
|
+
// TODO deprecate pushing non-valid JSON:API here
|
|
290
|
+
record = store.push(obj);
|
|
291
|
+
} else {
|
|
292
|
+
record = store.push({ data: obj });
|
|
293
|
+
}
|
|
192
294
|
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
);
|
|
201
|
-
}
|
|
202
|
-
return recordIdentifierFor(record);
|
|
203
|
-
});
|
|
295
|
+
if (DEBUG) {
|
|
296
|
+
let relationshipMeta = this.hasManyRelationship.definition;
|
|
297
|
+
let identifier = this.hasManyRelationship.identifier;
|
|
298
|
+
assertPolymorphicType(identifier, relationshipMeta, recordIdentifierFor(record), store);
|
|
299
|
+
}
|
|
300
|
+
return recordIdentifierFor(record);
|
|
301
|
+
});
|
|
204
302
|
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
});
|
|
303
|
+
const { graph, identifier } = this.hasManyRelationship;
|
|
304
|
+
store._backburner.join(() => {
|
|
305
|
+
graph.push({
|
|
306
|
+
op: 'replaceRelatedRecords',
|
|
307
|
+
record: identifier,
|
|
308
|
+
field: this.key,
|
|
309
|
+
value: identifiers,
|
|
213
310
|
});
|
|
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
311
|
});
|
|
312
|
+
|
|
313
|
+
// TODO IGOR it seems wrong that we were returning the many array here
|
|
314
|
+
return internalModel.getHasMany(this.key);
|
|
219
315
|
}
|
|
220
316
|
|
|
221
317
|
_isLoaded() {
|
|
@@ -275,7 +371,7 @@ export default class HasManyReference extends Reference {
|
|
|
275
371
|
@return {ManyArray}
|
|
276
372
|
*/
|
|
277
373
|
value() {
|
|
278
|
-
let internalModel = internalModelForReference(this)
|
|
374
|
+
let internalModel = internalModelForReference(this)!;
|
|
279
375
|
if (this._isLoaded()) {
|
|
280
376
|
return internalModel.getManyArray(this.key);
|
|
281
377
|
}
|
|
@@ -348,7 +444,7 @@ export default class HasManyReference extends Reference {
|
|
|
348
444
|
this has-many relationship.
|
|
349
445
|
*/
|
|
350
446
|
load(options) {
|
|
351
|
-
let internalModel = internalModelForReference(this)
|
|
447
|
+
let internalModel = internalModelForReference(this)!;
|
|
352
448
|
return internalModel.getHasMany(this.key, options);
|
|
353
449
|
}
|
|
354
450
|
|
|
@@ -403,7 +499,7 @@ export default class HasManyReference extends Reference {
|
|
|
403
499
|
@return {Promise} a promise that resolves with the ManyArray in this has-many relationship.
|
|
404
500
|
*/
|
|
405
501
|
reload(options) {
|
|
406
|
-
let internalModel = internalModelForReference(this)
|
|
502
|
+
let internalModel = internalModelForReference(this)!;
|
|
407
503
|
return internalModel.reloadHasMany(this.key, options);
|
|
408
504
|
}
|
|
409
505
|
}
|
|
@@ -1,8 +1,15 @@
|
|
|
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
|
+
|
|
3
8
|
import type { SingleResourceDocument } from '../../ts-interfaces/ember-data-json-api';
|
|
4
9
|
import type { StableRecordIdentifier } from '../../ts-interfaces/identifier';
|
|
5
10
|
import type { RecordInstance } from '../../ts-interfaces/record-instance';
|
|
11
|
+
import type CoreStore from '../core-store';
|
|
12
|
+
import { NotificationType, unsubscribe } from '../record-notification-manager';
|
|
6
13
|
import Reference, { internalModelForReference, REFERENCE_CACHE } from './reference';
|
|
7
14
|
|
|
8
15
|
/**
|
|
@@ -18,11 +25,39 @@ import Reference, { internalModelForReference, REFERENCE_CACHE } from './referen
|
|
|
18
25
|
@extends Reference
|
|
19
26
|
*/
|
|
20
27
|
export default class RecordReference extends Reference {
|
|
28
|
+
// unsubscribe token given to us by the notification manager
|
|
29
|
+
#token!: Object;
|
|
30
|
+
|
|
31
|
+
@tracked _ref = 0;
|
|
32
|
+
|
|
33
|
+
constructor(public store: CoreStore, identifier: StableRecordIdentifier) {
|
|
34
|
+
super(store, identifier);
|
|
35
|
+
if (CUSTOM_MODEL_CLASS) {
|
|
36
|
+
this.#token = store._notificationManager.subscribe(
|
|
37
|
+
identifier,
|
|
38
|
+
(_: StableRecordIdentifier, bucket: NotificationType, notifiedKey?: string) => {
|
|
39
|
+
if (bucket === 'identity' || ((bucket === 'attributes' || bucket === 'property') && notifiedKey === 'id')) {
|
|
40
|
+
this._ref++;
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
);
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
destroy() {
|
|
48
|
+
if (CUSTOM_MODEL_CLASS) {
|
|
49
|
+
unsubscribe(this.#token);
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
|
|
21
53
|
public get type(): string {
|
|
22
54
|
return this.identifier().type;
|
|
23
55
|
}
|
|
24
56
|
|
|
57
|
+
@cached
|
|
58
|
+
@dependentKeyCompat
|
|
25
59
|
private get _id(): string | null {
|
|
60
|
+
this._ref; // consume the tracked prop
|
|
26
61
|
let identifier = this.identifier();
|
|
27
62
|
if (identifier) {
|
|
28
63
|
return identifier.id;
|
|
@@ -50,7 +85,15 @@ export default class RecordReference extends Reference {
|
|
|
50
85
|
@return {String} The id of the record.
|
|
51
86
|
*/
|
|
52
87
|
id() {
|
|
53
|
-
|
|
88
|
+
if (CUSTOM_MODEL_CLASS) {
|
|
89
|
+
return this._id;
|
|
90
|
+
}
|
|
91
|
+
let identifier = this.identifier();
|
|
92
|
+
if (identifier) {
|
|
93
|
+
return identifier.id;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
return null;
|
|
54
97
|
}
|
|
55
98
|
|
|
56
99
|
/**
|
|
@@ -92,7 +135,7 @@ export default class RecordReference extends Reference {
|
|
|
92
135
|
@public
|
|
93
136
|
@return {String} 'identity'
|
|
94
137
|
*/
|
|
95
|
-
remoteType(): '
|
|
138
|
+
remoteType(): 'identity' {
|
|
96
139
|
return 'identity';
|
|
97
140
|
}
|
|
98
141
|
|
|
@@ -159,7 +202,7 @@ export default class RecordReference extends Reference {
|
|
|
159
202
|
@return {Model} the record for this RecordReference
|
|
160
203
|
*/
|
|
161
204
|
value(): RecordInstance | null {
|
|
162
|
-
if (this.
|
|
205
|
+
if (this.id() !== null) {
|
|
163
206
|
let internalModel = internalModelForReference(this);
|
|
164
207
|
if (internalModel && internalModel.currentState.isLoaded) {
|
|
165
208
|
return internalModel.getRecord();
|
|
@@ -186,8 +229,9 @@ export default class RecordReference extends Reference {
|
|
|
186
229
|
@return {Promise<record>} the record for this RecordReference
|
|
187
230
|
*/
|
|
188
231
|
load() {
|
|
189
|
-
|
|
190
|
-
|
|
232
|
+
const id = this.id();
|
|
233
|
+
if (id !== null) {
|
|
234
|
+
return this.store.findRecord(this.type, id);
|
|
191
235
|
}
|
|
192
236
|
throw new Error(`Unable to fetch record of type ${this.type} without an id`);
|
|
193
237
|
}
|
|
@@ -210,8 +254,9 @@ export default class RecordReference extends Reference {
|
|
|
210
254
|
@return {Promise<record>} the record for this RecordReference
|
|
211
255
|
*/
|
|
212
256
|
reload() {
|
|
213
|
-
|
|
214
|
-
|
|
257
|
+
const id = this.id();
|
|
258
|
+
if (id !== null) {
|
|
259
|
+
return this.store.findRecord(this.type, id, { reload: true });
|
|
215
260
|
}
|
|
216
261
|
throw new Error(`Unable to fetch record of type ${this.type} without an id`);
|
|
217
262
|
}
|
|
@@ -98,7 +98,7 @@ abstract class Reference {
|
|
|
98
98
|
@public
|
|
99
99
|
@return {String} The name of the remote type. This should either be "link" or "ids"
|
|
100
100
|
*/
|
|
101
|
-
remoteType(): 'link' | 'id' | 'identity' {
|
|
101
|
+
remoteType(): 'link' | 'id' | 'ids' | 'identity' {
|
|
102
102
|
let value = this._resource();
|
|
103
103
|
if (isResourceIdentiferWithRelatedLinks(value)) {
|
|
104
104
|
return 'link';
|
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": "4.0.
|
|
3
|
+
"version": "4.0.2",
|
|
4
4
|
"description": "The default blueprint for ember-cli addons.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"ember-addon"
|
|
@@ -17,17 +17,18 @@
|
|
|
17
17
|
"start": "ember serve"
|
|
18
18
|
},
|
|
19
19
|
"dependencies": {
|
|
20
|
-
"@ember-data/canary-features": "4.0.
|
|
21
|
-
"@ember-data/private-build-infra": "4.0.
|
|
20
|
+
"@ember-data/canary-features": "4.0.2",
|
|
21
|
+
"@ember-data/private-build-infra": "4.0.2",
|
|
22
22
|
"@ember/string": "^3.0.0",
|
|
23
23
|
"@glimmer/tracking": "^1.0.4",
|
|
24
24
|
"ember-auto-import": "^2.2.4",
|
|
25
|
+
"ember-cached-decorator-polyfill": "^0.1.4",
|
|
25
26
|
"ember-cli-babel": "^7.26.6",
|
|
26
27
|
"ember-cli-path-utils": "^1.0.0",
|
|
27
28
|
"ember-cli-typescript": "^4.1.0"
|
|
28
29
|
},
|
|
29
30
|
"devDependencies": {
|
|
30
|
-
"@ember-data/unpublished-test-infra": "4.0.
|
|
31
|
+
"@ember-data/unpublished-test-infra": "4.0.2",
|
|
31
32
|
"@ember/optional-features": "^2.0.0",
|
|
32
33
|
"@ember/test-helpers": "^2.6.0",
|
|
33
34
|
"@types/ember": "^3.16.5",
|