@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.
- package/addon/-private/system/core-store.ts +141 -467
- package/addon/-private/system/ds-model-store.ts +14 -64
- package/addon/-private/system/fetch-manager.ts +9 -4
- package/addon/-private/system/model/internal-model.ts +117 -349
- package/addon/-private/system/model/states.js +3 -9
- package/addon/-private/system/record-array-manager.js +3 -30
- package/addon/-private/system/record-notification-manager.ts +14 -6
- package/addon/-private/system/references/{belongs-to.js → belongs-to.ts} +83 -19
- package/addon/-private/system/references/{has-many.js → has-many.ts} +126 -49
- package/addon/-private/system/references/record.ts +37 -6
- package/addon/-private/system/references/reference.ts +1 -1
- package/addon/-private/system/snapshot.ts +22 -48
- package/addon/-private/system/store/finders.js +2 -58
- package/addon/-private/system/store/record-data-store-wrapper.ts +17 -24
- package/addon/-private/ts-interfaces/ds-model.ts +1 -0
- package/addon/-private/ts-interfaces/fetch-manager.ts +4 -0
- package/index.js +3 -0
- package/package.json +10 -9
|
@@ -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(
|
|
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 (!
|
|
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
|
-
|
|
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 (!
|
|
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.
|
|
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,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
|
-
|
|
26
|
-
|
|
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
|
-
|
|
31
|
-
this.
|
|
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
|
-
|
|
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
|
|
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
|
|
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
|
|
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
|
-
|
|
24
|
-
|
|
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(
|
|
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
|
-
|
|
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(
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
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
|
-
|
|
264
|
+
const internalModel = internalModelForReference(this)!;
|
|
265
|
+
const { store } = this;
|
|
189
266
|
|
|
190
|
-
|
|
191
|
-
|
|
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
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
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
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
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(): '
|
|
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.
|
|
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
|
-
|
|
190
|
-
|
|
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
|
-
|
|
214
|
-
|
|
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';
|