@ember-data/model 4.5.0-beta.0 → 4.7.0-beta.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/addon/-private/attr.js +14 -5
- package/addon/-private/belongs-to.js +9 -5
- package/addon/-private/{system/diff-array.ts → diff-array.ts} +0 -0
- package/addon/-private/errors.ts +7 -69
- package/addon/-private/has-many.js +7 -6
- package/addon/-private/index.ts +6 -5
- package/addon/-private/legacy-data-fetch.js +370 -0
- package/addon/-private/legacy-data-utils.ts +92 -0
- package/addon/-private/legacy-relationships-support.ts +709 -0
- package/addon/-private/{system/many-array.ts → many-array.ts} +38 -33
- package/addon/-private/{system/model-for-mixin.ts → model-for-mixin.ts} +3 -3
- package/addon/-private/model.js +104 -50
- package/addon/-private/notify-changes.ts +21 -13
- package/addon/-private/{system/promise-belongs-to.ts → promise-belongs-to.ts} +9 -8
- package/addon/-private/{system/promise-many-array.ts → promise-many-array.ts} +3 -3
- package/addon/-private/record-state.ts +31 -21
- package/addon/-private/references/belongs-to.ts +585 -0
- package/addon/-private/references/has-many.ts +621 -0
- package/addon/-private/{system/relationships/relationship-meta.ts → relationship-meta.ts} +5 -5
- package/blueprints/model-test/index.js +2 -0
- package/blueprints/model-test/mocha-rfc-232-files/__root__/__path__/__test__.js +1 -1
- package/blueprints/model-test/qunit-files/__root__/__path__/__test__.js +1 -1
- package/index.js +4 -0
- package/package.json +24 -22
package/addon/-private/attr.js
CHANGED
|
@@ -2,6 +2,7 @@ import { assert } from '@ember/debug';
|
|
|
2
2
|
import { computed } from '@ember/object';
|
|
3
3
|
import { DEBUG } from '@glimmer/env';
|
|
4
4
|
|
|
5
|
+
import { recordIdentifierFor, storeFor } from '@ember-data/store';
|
|
5
6
|
import { recordDataFor } from '@ember-data/store/-private';
|
|
6
7
|
|
|
7
8
|
import { computedMacroWithOptionalParams } from './util';
|
|
@@ -150,17 +151,25 @@ function attr(type, options) {
|
|
|
150
151
|
);
|
|
151
152
|
}
|
|
152
153
|
}
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
154
|
+
assert(
|
|
155
|
+
`Attempted to set '${key}' on the deleted record ${recordIdentifierFor(this)}`,
|
|
156
|
+
!this.currentState.isDeleted
|
|
157
|
+
);
|
|
158
|
+
const recordData = storeFor(this)._instanceCache.getRecordData(recordIdentifierFor(this));
|
|
159
|
+
let currentValue = recordData.getAttr(key);
|
|
160
|
+
if (currentValue !== value) {
|
|
161
|
+
recordData.setDirtyAttribute(key, value);
|
|
162
|
+
|
|
163
|
+
if (!this.isValid) {
|
|
156
164
|
const { errors } = this;
|
|
157
165
|
if (errors.get(key)) {
|
|
158
166
|
errors.remove(key);
|
|
159
|
-
this.
|
|
167
|
+
this.currentState.cleanErrorRequests();
|
|
160
168
|
}
|
|
161
169
|
}
|
|
162
170
|
}
|
|
163
|
-
|
|
171
|
+
|
|
172
|
+
return value;
|
|
164
173
|
},
|
|
165
174
|
}).meta(meta);
|
|
166
175
|
}
|
|
@@ -2,6 +2,7 @@ import { assert, inspect, warn } from '@ember/debug';
|
|
|
2
2
|
import { computed } from '@ember/object';
|
|
3
3
|
import { DEBUG } from '@glimmer/env';
|
|
4
4
|
|
|
5
|
+
import { LEGACY_SUPPORT } from './model';
|
|
5
6
|
import { computedMacroWithOptionalParams } from './util';
|
|
6
7
|
|
|
7
8
|
/**
|
|
@@ -142,6 +143,8 @@ function belongsTo(modelName, options) {
|
|
|
142
143
|
|
|
143
144
|
return computed({
|
|
144
145
|
get(key) {
|
|
146
|
+
const support = LEGACY_SUPPORT.lookup(this);
|
|
147
|
+
|
|
145
148
|
if (DEBUG) {
|
|
146
149
|
if (['_internalModel', 'recordData', 'currentState'].indexOf(key) !== -1) {
|
|
147
150
|
throw new Error(
|
|
@@ -150,7 +153,7 @@ function belongsTo(modelName, options) {
|
|
|
150
153
|
}
|
|
151
154
|
if (Object.prototype.hasOwnProperty.call(opts, 'serialize')) {
|
|
152
155
|
warn(
|
|
153
|
-
`You provided a serialize option on the "${key}" property in the "${
|
|
156
|
+
`You provided a serialize option on the "${key}" property in the "${support.identifier.type}" class, this belongs in the serializer. See Serializer and it's implementations https://api.emberjs.com/ember-data/release/classes/Serializer`,
|
|
154
157
|
false,
|
|
155
158
|
{
|
|
156
159
|
id: 'ds.model.serialize-option-in-belongs-to',
|
|
@@ -160,7 +163,7 @@ function belongsTo(modelName, options) {
|
|
|
160
163
|
|
|
161
164
|
if (Object.prototype.hasOwnProperty.call(opts, 'embedded')) {
|
|
162
165
|
warn(
|
|
163
|
-
`You provided an embedded option on the "${key}" property in the "${
|
|
166
|
+
`You provided an embedded option on the "${key}" property in the "${support.identifier.type}" class, this belongs in the serializer. See EmbeddedRecordsMixin https://api.emberjs.com/ember-data/release/classes/EmbeddedRecordsMixin`,
|
|
164
167
|
false,
|
|
165
168
|
{
|
|
166
169
|
id: 'ds.model.embedded-option-in-belongs-to',
|
|
@@ -169,9 +172,10 @@ function belongsTo(modelName, options) {
|
|
|
169
172
|
}
|
|
170
173
|
}
|
|
171
174
|
|
|
172
|
-
return
|
|
175
|
+
return support.getBelongsTo(key);
|
|
173
176
|
},
|
|
174
177
|
set(key, value) {
|
|
178
|
+
const support = LEGACY_SUPPORT.lookup(this);
|
|
175
179
|
if (DEBUG) {
|
|
176
180
|
if (['_internalModel', 'recordData', 'currentState'].indexOf(key) !== -1) {
|
|
177
181
|
throw new Error(
|
|
@@ -180,10 +184,10 @@ function belongsTo(modelName, options) {
|
|
|
180
184
|
}
|
|
181
185
|
}
|
|
182
186
|
this.store._backburner.join(() => {
|
|
183
|
-
|
|
187
|
+
support.setDirtyBelongsTo(key, value);
|
|
184
188
|
});
|
|
185
189
|
|
|
186
|
-
return
|
|
190
|
+
return support.getBelongsTo(key);
|
|
187
191
|
},
|
|
188
192
|
}).meta(meta);
|
|
189
193
|
}
|
|
File without changes
|
package/addon/-private/errors.ts
CHANGED
|
@@ -4,6 +4,8 @@ import ArrayProxy from '@ember/array/proxy';
|
|
|
4
4
|
import { computed, get } from '@ember/object';
|
|
5
5
|
import { mapBy, not } from '@ember/object/computed';
|
|
6
6
|
|
|
7
|
+
import type RecordState from './record-state';
|
|
8
|
+
|
|
7
9
|
type ValidationError = {
|
|
8
10
|
attribute: string;
|
|
9
11
|
message: string;
|
|
@@ -101,24 +103,7 @@ const ArrayProxyWithCustomOverrides = ArrayProxy as unknown as new <T, M = T>()
|
|
|
101
103
|
@extends Ember.ArrayProxy
|
|
102
104
|
*/
|
|
103
105
|
export default class Errors extends ArrayProxyWithCustomOverrides<ValidationError> {
|
|
104
|
-
declare
|
|
105
|
-
becameInvalid: () => void;
|
|
106
|
-
becameValid: () => void;
|
|
107
|
-
};
|
|
108
|
-
|
|
109
|
-
/**
|
|
110
|
-
Register with target handler
|
|
111
|
-
|
|
112
|
-
@method _registerHandlers
|
|
113
|
-
@private
|
|
114
|
-
*/
|
|
115
|
-
_registerHandlers(becameInvalid: () => void, becameValid: () => void): void {
|
|
116
|
-
this._registeredHandlers = {
|
|
117
|
-
becameInvalid,
|
|
118
|
-
becameValid,
|
|
119
|
-
};
|
|
120
|
-
}
|
|
121
|
-
|
|
106
|
+
declare __record: { currentState: RecordState };
|
|
122
107
|
/**
|
|
123
108
|
@property errorsByAttributeName
|
|
124
109
|
@type {MapWithDefault}
|
|
@@ -266,26 +251,11 @@ export default class Errors extends ArrayProxyWithCustomOverrides<ValidationErro
|
|
|
266
251
|
@param {string[]|string} messages - an error message or array of error messages for the attribute
|
|
267
252
|
*/
|
|
268
253
|
add(attribute: string, messages: string[] | string): void {
|
|
269
|
-
let wasEmpty: boolean = this.isEmpty;
|
|
270
|
-
|
|
271
|
-
this._add(attribute, messages);
|
|
272
|
-
|
|
273
|
-
if (wasEmpty && !this.isEmpty) {
|
|
274
|
-
this._registeredHandlers && this._registeredHandlers.becameInvalid();
|
|
275
|
-
}
|
|
276
|
-
}
|
|
277
|
-
|
|
278
|
-
/**
|
|
279
|
-
Adds error messages to a given attribute without sending event.
|
|
280
|
-
|
|
281
|
-
@method _add
|
|
282
|
-
@private
|
|
283
|
-
*/
|
|
284
|
-
_add(attribute: string, messages: string[] | string) {
|
|
285
254
|
const errors = this._findOrCreateMessages(attribute, messages);
|
|
286
255
|
this.addObjects(errors);
|
|
287
256
|
|
|
288
257
|
this.errorsFor(attribute).addObjects(errors);
|
|
258
|
+
this.__record.currentState.notify('isValid');
|
|
289
259
|
|
|
290
260
|
this.notifyPropertyChange(attribute);
|
|
291
261
|
}
|
|
@@ -347,24 +317,6 @@ export default class Errors extends ArrayProxyWithCustomOverrides<ValidationErro
|
|
|
347
317
|
return;
|
|
348
318
|
}
|
|
349
319
|
|
|
350
|
-
this._remove(attribute);
|
|
351
|
-
|
|
352
|
-
if (this.isEmpty) {
|
|
353
|
-
this._registeredHandlers && this._registeredHandlers.becameValid();
|
|
354
|
-
}
|
|
355
|
-
}
|
|
356
|
-
|
|
357
|
-
/**
|
|
358
|
-
Removes all error messages from the given attribute without sending event.
|
|
359
|
-
|
|
360
|
-
@method _remove
|
|
361
|
-
@private
|
|
362
|
-
*/
|
|
363
|
-
_remove(attribute: string) {
|
|
364
|
-
if (this.isEmpty) {
|
|
365
|
-
return;
|
|
366
|
-
}
|
|
367
|
-
|
|
368
320
|
let content = this.rejectBy('attribute', attribute);
|
|
369
321
|
this.content.setObjects(content);
|
|
370
322
|
|
|
@@ -379,6 +331,7 @@ export default class Errors extends ArrayProxyWithCustomOverrides<ValidationErro
|
|
|
379
331
|
}
|
|
380
332
|
this.errorsByAttributeName.delete(attribute);
|
|
381
333
|
|
|
334
|
+
this.__record.currentState.notify('isValid');
|
|
382
335
|
this.notifyPropertyChange(attribute);
|
|
383
336
|
this.notifyPropertyChange('length');
|
|
384
337
|
}
|
|
@@ -427,22 +380,6 @@ export default class Errors extends ArrayProxyWithCustomOverrides<ValidationErro
|
|
|
427
380
|
return;
|
|
428
381
|
}
|
|
429
382
|
|
|
430
|
-
this._clear();
|
|
431
|
-
this._registeredHandlers && this._registeredHandlers.becameValid();
|
|
432
|
-
}
|
|
433
|
-
|
|
434
|
-
/**
|
|
435
|
-
Removes all error messages.
|
|
436
|
-
to the record.
|
|
437
|
-
|
|
438
|
-
@method _clear
|
|
439
|
-
@private
|
|
440
|
-
*/
|
|
441
|
-
_clear(): void {
|
|
442
|
-
if (this.isEmpty) {
|
|
443
|
-
return;
|
|
444
|
-
}
|
|
445
|
-
|
|
446
383
|
let errorsByAttributeName = this.errorsByAttributeName;
|
|
447
384
|
let attributes: string[] = [];
|
|
448
385
|
|
|
@@ -455,7 +392,8 @@ export default class Errors extends ArrayProxyWithCustomOverrides<ValidationErro
|
|
|
455
392
|
this.notifyPropertyChange(attribute);
|
|
456
393
|
});
|
|
457
394
|
|
|
458
|
-
|
|
395
|
+
this.__record.currentState.notify('isValid');
|
|
396
|
+
super.clear();
|
|
459
397
|
}
|
|
460
398
|
|
|
461
399
|
/**
|
|
@@ -5,6 +5,7 @@ import { assert, inspect } from '@ember/debug';
|
|
|
5
5
|
import { computed } from '@ember/object';
|
|
6
6
|
import { DEBUG } from '@glimmer/env';
|
|
7
7
|
|
|
8
|
+
import { LEGACY_SUPPORT } from './model';
|
|
8
9
|
import { computedMacroWithOptionalParams } from './util';
|
|
9
10
|
|
|
10
11
|
/**
|
|
@@ -182,28 +183,28 @@ function hasMany(type, options) {
|
|
|
182
183
|
return computed({
|
|
183
184
|
get(key) {
|
|
184
185
|
if (DEBUG) {
|
|
185
|
-
if (['_internalModel', '
|
|
186
|
+
if (['_internalModel', 'currentState'].indexOf(key) !== -1) {
|
|
186
187
|
throw new Error(
|
|
187
188
|
`'${key}' is a reserved property name on instances of classes extending Model. Please choose a different property name for your hasMany on ${this.constructor.toString()}`
|
|
188
189
|
);
|
|
189
190
|
}
|
|
190
191
|
}
|
|
191
|
-
return this.
|
|
192
|
+
return LEGACY_SUPPORT.lookup(this).getHasMany(key);
|
|
192
193
|
},
|
|
193
194
|
set(key, records) {
|
|
194
195
|
if (DEBUG) {
|
|
195
|
-
if (['_internalModel', '
|
|
196
|
+
if (['_internalModel', 'currentState'].indexOf(key) !== -1) {
|
|
196
197
|
throw new Error(
|
|
197
198
|
`'${key}' is a reserved property name on instances of classes extending Model. Please choose a different property name for your hasMany on ${this.constructor.toString()}`
|
|
198
199
|
);
|
|
199
200
|
}
|
|
200
201
|
}
|
|
201
|
-
|
|
202
|
+
const support = LEGACY_SUPPORT.lookup(this);
|
|
202
203
|
this.store._backburner.join(() => {
|
|
203
|
-
|
|
204
|
+
support.setDirtyHasMany(key, records);
|
|
204
205
|
});
|
|
205
206
|
|
|
206
|
-
return
|
|
207
|
+
return support.getHasMany(key);
|
|
207
208
|
},
|
|
208
209
|
}).meta(meta);
|
|
209
210
|
}
|
package/addon/-private/index.ts
CHANGED
|
@@ -4,10 +4,11 @@ export { default as hasMany } from './has-many';
|
|
|
4
4
|
export { default as Model } from './model';
|
|
5
5
|
export { default as Errors } from './errors';
|
|
6
6
|
|
|
7
|
-
export { default as ManyArray } from './
|
|
8
|
-
export { default as PromiseBelongsTo } from './
|
|
9
|
-
export { default as PromiseManyArray } from './
|
|
10
|
-
export { default as _modelForMixin } from './
|
|
7
|
+
export { default as ManyArray } from './many-array';
|
|
8
|
+
export { default as PromiseBelongsTo } from './promise-belongs-to';
|
|
9
|
+
export { default as PromiseManyArray } from './promise-many-array';
|
|
10
|
+
export { default as _modelForMixin } from './model-for-mixin';
|
|
11
11
|
|
|
12
12
|
// // Used by tests
|
|
13
|
-
export { default as diffArray } from './
|
|
13
|
+
export { default as diffArray } from './diff-array';
|
|
14
|
+
export { LEGACY_SUPPORT } from './model';
|
|
@@ -0,0 +1,370 @@
|
|
|
1
|
+
import { assert, deprecate } from '@ember/debug';
|
|
2
|
+
import { DEBUG } from '@glimmer/env';
|
|
3
|
+
|
|
4
|
+
import { resolve } from 'rsvp';
|
|
5
|
+
|
|
6
|
+
import { DEPRECATE_RSVP_PROMISE } from '@ember-data/private-build-infra/deprecations';
|
|
7
|
+
|
|
8
|
+
import { iterateData, normalizeResponseHelper } from './legacy-data-utils';
|
|
9
|
+
|
|
10
|
+
export function _findHasMany(adapter, store, identifier, link, relationship, options) {
|
|
11
|
+
const snapshot = store._instanceCache.createSnapshot(identifier, options);
|
|
12
|
+
let modelClass = store.modelFor(relationship.type);
|
|
13
|
+
let useLink = !link || typeof link === 'string';
|
|
14
|
+
let relatedLink = useLink ? link : link.href;
|
|
15
|
+
let promise = adapter.findHasMany(store, snapshot, relatedLink, relationship);
|
|
16
|
+
let label = `DS: Handle Adapter#findHasMany of '${identifier.type}' : '${relationship.type}'`;
|
|
17
|
+
|
|
18
|
+
promise = guardDestroyedStore(promise, store, label);
|
|
19
|
+
promise = promise.then(
|
|
20
|
+
(adapterPayload) => {
|
|
21
|
+
if (!_objectIsAlive(store._instanceCache.getInternalModel(identifier))) {
|
|
22
|
+
if (DEPRECATE_RSVP_PROMISE) {
|
|
23
|
+
deprecate(
|
|
24
|
+
`A Promise for fetching ${relationship.type} did not resolve by the time your model was destroyed. This will error in a future release.`,
|
|
25
|
+
false,
|
|
26
|
+
{
|
|
27
|
+
id: 'ember-data:rsvp-unresolved-async',
|
|
28
|
+
until: '5.0',
|
|
29
|
+
for: '@ember-data/store',
|
|
30
|
+
since: {
|
|
31
|
+
available: '4.5',
|
|
32
|
+
enabled: '4.5',
|
|
33
|
+
},
|
|
34
|
+
}
|
|
35
|
+
);
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
assert(
|
|
40
|
+
`You made a 'findHasMany' request for a ${identifier.type}'s '${relationship.key}' relationship, using link '${link}' , but the adapter's response did not have any data`,
|
|
41
|
+
payloadIsNotBlank(adapterPayload)
|
|
42
|
+
);
|
|
43
|
+
let serializer = store.serializerFor(relationship.type);
|
|
44
|
+
let payload = normalizeResponseHelper(serializer, store, modelClass, adapterPayload, null, 'findHasMany');
|
|
45
|
+
|
|
46
|
+
assert(
|
|
47
|
+
`fetched the hasMany relationship '${relationship.name}' for ${identifier.type}:${identifier.id} with link '${link}', but no data member is present in the response. If no data exists, the response should set { data: [] }`,
|
|
48
|
+
'data' in payload && Array.isArray(payload.data)
|
|
49
|
+
);
|
|
50
|
+
|
|
51
|
+
payload = syncRelationshipDataFromLink(store, payload, identifier, relationship);
|
|
52
|
+
|
|
53
|
+
return store._push(payload);
|
|
54
|
+
},
|
|
55
|
+
null,
|
|
56
|
+
`DS: Extract payload of '${identifier.type}' : hasMany '${relationship.type}'`
|
|
57
|
+
);
|
|
58
|
+
|
|
59
|
+
if (DEPRECATE_RSVP_PROMISE) {
|
|
60
|
+
promise = _guard(promise, _bind(_objectIsAlive, store._instanceCache.getInternalModel(identifier)));
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
return promise;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
export function _findBelongsTo(store, identifier, link, relationship, options) {
|
|
67
|
+
let adapter = store.adapterFor(identifier.type);
|
|
68
|
+
|
|
69
|
+
assert(`You tried to load a belongsTo relationship but you have no adapter (for ${identifier.type})`, adapter);
|
|
70
|
+
assert(
|
|
71
|
+
`You tried to load a belongsTo relationship from a specified 'link' in the original payload but your adapter does not implement 'findBelongsTo'`,
|
|
72
|
+
typeof adapter.findBelongsTo === 'function'
|
|
73
|
+
);
|
|
74
|
+
let snapshot = store._instanceCache.createSnapshot(identifier, options);
|
|
75
|
+
let modelClass = store.modelFor(relationship.type);
|
|
76
|
+
let useLink = !link || typeof link === 'string';
|
|
77
|
+
let relatedLink = useLink ? link : link.href;
|
|
78
|
+
let promise = adapter.findBelongsTo(store, snapshot, relatedLink, relationship);
|
|
79
|
+
let label = `DS: Handle Adapter#findBelongsTo of ${identifier.type} : ${relationship.type}`;
|
|
80
|
+
|
|
81
|
+
promise = guardDestroyedStore(promise, store, label);
|
|
82
|
+
promise = _guard(promise, _bind(_objectIsAlive, store._instanceCache.getInternalModel(identifier)));
|
|
83
|
+
|
|
84
|
+
promise = promise.then(
|
|
85
|
+
(adapterPayload) => {
|
|
86
|
+
if (!_objectIsAlive(store._instanceCache.getInternalModel(identifier))) {
|
|
87
|
+
if (DEPRECATE_RSVP_PROMISE) {
|
|
88
|
+
deprecate(
|
|
89
|
+
`A Promise for fetching ${relationship.type} did not resolve by the time your model was destroyed. This will error in a future release.`,
|
|
90
|
+
false,
|
|
91
|
+
{
|
|
92
|
+
id: 'ember-data:rsvp-unresolved-async',
|
|
93
|
+
until: '5.0',
|
|
94
|
+
for: '@ember-data/store',
|
|
95
|
+
since: {
|
|
96
|
+
available: '4.5',
|
|
97
|
+
enabled: '4.5',
|
|
98
|
+
},
|
|
99
|
+
}
|
|
100
|
+
);
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
let serializer = store.serializerFor(relationship.type);
|
|
105
|
+
let payload = normalizeResponseHelper(serializer, store, modelClass, adapterPayload, null, 'findBelongsTo');
|
|
106
|
+
|
|
107
|
+
assert(
|
|
108
|
+
`fetched the belongsTo relationship '${relationship.name}' for ${identifier.type}:${identifier.id} with link '${link}', but no data member is present in the response. If no data exists, the response should set { data: null }`,
|
|
109
|
+
'data' in payload &&
|
|
110
|
+
(payload.data === null || (typeof payload.data === 'object' && !Array.isArray(payload.data)))
|
|
111
|
+
);
|
|
112
|
+
|
|
113
|
+
if (!payload.data && !payload.links && !payload.meta) {
|
|
114
|
+
return null;
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
payload = syncRelationshipDataFromLink(store, payload, identifier, relationship);
|
|
118
|
+
|
|
119
|
+
return store._push(payload);
|
|
120
|
+
},
|
|
121
|
+
null,
|
|
122
|
+
`DS: Extract payload of ${identifier.type} : ${relationship.type}`
|
|
123
|
+
);
|
|
124
|
+
|
|
125
|
+
if (DEPRECATE_RSVP_PROMISE) {
|
|
126
|
+
promise = _guard(promise, _bind(_objectIsAlive, store._instanceCache.getInternalModel(identifier)));
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
return promise;
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
// sync
|
|
133
|
+
// iterate over records in payload.data
|
|
134
|
+
// for each record
|
|
135
|
+
// assert that record.relationships[inverse] is either undefined (so we can fix it)
|
|
136
|
+
// or provide a data: {id, type} that matches the record that requested it
|
|
137
|
+
// return the relationship data for the parent
|
|
138
|
+
function syncRelationshipDataFromLink(store, payload, parentIdentifier, relationship) {
|
|
139
|
+
// ensure the right hand side (incoming payload) points to the parent record that
|
|
140
|
+
// requested this relationship
|
|
141
|
+
let relationshipData = payload.data
|
|
142
|
+
? iterateData(payload.data, (data, index) => {
|
|
143
|
+
const { id, type } = data;
|
|
144
|
+
ensureRelationshipIsSetToParent(data, parentIdentifier, store, relationship, index);
|
|
145
|
+
return { id, type };
|
|
146
|
+
})
|
|
147
|
+
: null;
|
|
148
|
+
|
|
149
|
+
const relatedDataHash = {};
|
|
150
|
+
|
|
151
|
+
if ('meta' in payload) {
|
|
152
|
+
relatedDataHash.meta = payload.meta;
|
|
153
|
+
}
|
|
154
|
+
if ('links' in payload) {
|
|
155
|
+
relatedDataHash.links = payload.links;
|
|
156
|
+
}
|
|
157
|
+
if ('data' in payload) {
|
|
158
|
+
relatedDataHash.data = relationshipData;
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
// now, push the left hand side (the parent record) to ensure things are in sync, since
|
|
162
|
+
// the payload will be pushed with store._push
|
|
163
|
+
const parentPayload = {
|
|
164
|
+
id: parentIdentifier.id,
|
|
165
|
+
type: parentIdentifier.type,
|
|
166
|
+
relationships: {
|
|
167
|
+
[relationship.key]: relatedDataHash,
|
|
168
|
+
},
|
|
169
|
+
};
|
|
170
|
+
|
|
171
|
+
if (!Array.isArray(payload.included)) {
|
|
172
|
+
payload.included = [];
|
|
173
|
+
}
|
|
174
|
+
payload.included.push(parentPayload);
|
|
175
|
+
|
|
176
|
+
return payload;
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
function ensureRelationshipIsSetToParent(payload, parentIdentifier, store, parentRelationship, index) {
|
|
180
|
+
let { id, type } = payload;
|
|
181
|
+
|
|
182
|
+
if (!payload.relationships) {
|
|
183
|
+
payload.relationships = {};
|
|
184
|
+
}
|
|
185
|
+
let { relationships } = payload;
|
|
186
|
+
|
|
187
|
+
let inverse = getInverse(store, parentIdentifier, parentRelationship, type);
|
|
188
|
+
if (inverse) {
|
|
189
|
+
let { inverseKey, kind } = inverse;
|
|
190
|
+
|
|
191
|
+
let relationshipData = relationships[inverseKey] && relationships[inverseKey].data;
|
|
192
|
+
|
|
193
|
+
if (
|
|
194
|
+
DEBUG &&
|
|
195
|
+
typeof relationshipData !== 'undefined' &&
|
|
196
|
+
!relationshipDataPointsToParent(relationshipData, parentIdentifier)
|
|
197
|
+
) {
|
|
198
|
+
let inspect = function inspect(thing) {
|
|
199
|
+
return `'${JSON.stringify(thing)}'`;
|
|
200
|
+
};
|
|
201
|
+
let quotedType = inspect(type);
|
|
202
|
+
let quotedInverse = inspect(inverseKey);
|
|
203
|
+
let expected = inspect({
|
|
204
|
+
id: parentIdentifier.id,
|
|
205
|
+
type: parentIdentifier.type,
|
|
206
|
+
});
|
|
207
|
+
let expectedModel = `${parentIdentifier.type}:${parentIdentifier.id}`;
|
|
208
|
+
let got = inspect(relationshipData);
|
|
209
|
+
let prefix = typeof index === 'number' ? `data[${index}]` : `data`;
|
|
210
|
+
let path = `${prefix}.relationships.${inverseKey}.data`;
|
|
211
|
+
let other = relationshipData ? `<${relationshipData.type}:${relationshipData.id}>` : null;
|
|
212
|
+
let relationshipFetched = `${expectedModel}.${parentRelationship.kind}("${parentRelationship.name}")`;
|
|
213
|
+
let includedRecord = `<${type}:${id}>`;
|
|
214
|
+
let message = [
|
|
215
|
+
`Encountered mismatched relationship: Ember Data expected ${path} in the payload from ${relationshipFetched} to include ${expected} but got ${got} instead.\n`,
|
|
216
|
+
`The ${includedRecord} record loaded at ${prefix} in the payload specified ${other} as its ${quotedInverse}, but should have specified ${expectedModel} (the record the relationship is being loaded from) as its ${quotedInverse} instead.`,
|
|
217
|
+
`This could mean that the response for ${relationshipFetched} may have accidentally returned ${quotedType} records that aren't related to ${expectedModel} and could be related to a different ${parentIdentifier.type} record instead.`,
|
|
218
|
+
`Ember Data has corrected the ${includedRecord} record's ${quotedInverse} relationship to ${expectedModel} so that ${relationshipFetched} will include ${includedRecord}.`,
|
|
219
|
+
`Please update the response from the server or change your serializer to either ensure that the response for only includes ${quotedType} records that specify ${expectedModel} as their ${quotedInverse}, or omit the ${quotedInverse} relationship from the response.`,
|
|
220
|
+
].join('\n');
|
|
221
|
+
|
|
222
|
+
assert(message);
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
if (kind !== 'hasMany' || typeof relationshipData !== 'undefined') {
|
|
226
|
+
relationships[inverseKey] = relationships[inverseKey] || {};
|
|
227
|
+
relationships[inverseKey].data = fixRelationshipData(relationshipData, kind, parentIdentifier);
|
|
228
|
+
}
|
|
229
|
+
}
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
function getInverse(store, parentInternalModel, parentRelationship, type) {
|
|
233
|
+
return recordDataFindInverseRelationshipInfo(store, parentInternalModel, parentRelationship, type);
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
function recordDataFindInverseRelationshipInfo(store, parentIdentifier, parentRelationship, type) {
|
|
237
|
+
let { name: lhs_relationshipName } = parentRelationship;
|
|
238
|
+
let { type: parentType } = parentIdentifier;
|
|
239
|
+
let inverseKey = store._instanceCache._storeWrapper.inverseForRelationship(parentType, lhs_relationshipName);
|
|
240
|
+
|
|
241
|
+
if (inverseKey) {
|
|
242
|
+
let {
|
|
243
|
+
meta: { kind },
|
|
244
|
+
} = store._instanceCache._storeWrapper.relationshipsDefinitionFor(type)[inverseKey];
|
|
245
|
+
return {
|
|
246
|
+
inverseKey,
|
|
247
|
+
kind,
|
|
248
|
+
};
|
|
249
|
+
}
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
function relationshipDataPointsToParent(relationshipData, identifier) {
|
|
253
|
+
if (relationshipData === null) {
|
|
254
|
+
return false;
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
if (Array.isArray(relationshipData)) {
|
|
258
|
+
if (relationshipData.length === 0) {
|
|
259
|
+
return false;
|
|
260
|
+
}
|
|
261
|
+
for (let i = 0; i < relationshipData.length; i++) {
|
|
262
|
+
let entry = relationshipData[i];
|
|
263
|
+
if (validateRelationshipEntry(entry, identifier)) {
|
|
264
|
+
return true;
|
|
265
|
+
}
|
|
266
|
+
}
|
|
267
|
+
} else {
|
|
268
|
+
return validateRelationshipEntry(relationshipData, identifier);
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
return false;
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
function fixRelationshipData(relationshipData, relationshipKind, { id, type }) {
|
|
275
|
+
let parentRelationshipData = {
|
|
276
|
+
id,
|
|
277
|
+
type,
|
|
278
|
+
};
|
|
279
|
+
|
|
280
|
+
let payload;
|
|
281
|
+
|
|
282
|
+
if (relationshipKind === 'hasMany') {
|
|
283
|
+
payload = relationshipData || [];
|
|
284
|
+
if (relationshipData) {
|
|
285
|
+
// these arrays could be massive so this is better than filter
|
|
286
|
+
// Note: this is potentially problematic if type/id are not in the
|
|
287
|
+
// same state of normalization.
|
|
288
|
+
let found = relationshipData.find((v) => {
|
|
289
|
+
return v.type === parentRelationshipData.type && v.id === parentRelationshipData.id;
|
|
290
|
+
});
|
|
291
|
+
if (!found) {
|
|
292
|
+
payload.push(parentRelationshipData);
|
|
293
|
+
}
|
|
294
|
+
} else {
|
|
295
|
+
payload.push(parentRelationshipData);
|
|
296
|
+
}
|
|
297
|
+
} else {
|
|
298
|
+
payload = relationshipData || {};
|
|
299
|
+
Object.assign(payload, parentRelationshipData);
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
return payload;
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
function validateRelationshipEntry({ id }, { id: parentModelID }) {
|
|
306
|
+
return id && id.toString() === parentModelID;
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
function _bind(fn, ...args) {
|
|
310
|
+
return function () {
|
|
311
|
+
return fn.apply(undefined, args);
|
|
312
|
+
};
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
function _guard(promise, test) {
|
|
316
|
+
let guarded = promise.finally(() => {
|
|
317
|
+
if (!test()) {
|
|
318
|
+
guarded._subscribers.length = 0;
|
|
319
|
+
}
|
|
320
|
+
});
|
|
321
|
+
|
|
322
|
+
return guarded;
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
function _objectIsAlive(object) {
|
|
326
|
+
return !(object.isDestroyed || object.isDestroying);
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
function payloadIsNotBlank(adapterPayload) {
|
|
330
|
+
if (Array.isArray(adapterPayload)) {
|
|
331
|
+
return true;
|
|
332
|
+
} else {
|
|
333
|
+
return Object.keys(adapterPayload || {}).length;
|
|
334
|
+
}
|
|
335
|
+
}
|
|
336
|
+
|
|
337
|
+
function guardDestroyedStore(promise, store, label) {
|
|
338
|
+
let token;
|
|
339
|
+
if (DEBUG) {
|
|
340
|
+
token = store._trackAsyncRequestStart(label);
|
|
341
|
+
}
|
|
342
|
+
let wrapperPromise = resolve(promise, label).then((_v) => {
|
|
343
|
+
if (!_objectIsAlive(store)) {
|
|
344
|
+
if (DEPRECATE_RSVP_PROMISE) {
|
|
345
|
+
deprecate(
|
|
346
|
+
`A Promise did not resolve by the time the store was destroyed. This will error in a future release.`,
|
|
347
|
+
false,
|
|
348
|
+
{
|
|
349
|
+
id: 'ember-data:rsvp-unresolved-async',
|
|
350
|
+
until: '5.0',
|
|
351
|
+
for: '@ember-data/store',
|
|
352
|
+
since: {
|
|
353
|
+
available: '4.5',
|
|
354
|
+
enabled: '4.5',
|
|
355
|
+
},
|
|
356
|
+
}
|
|
357
|
+
);
|
|
358
|
+
}
|
|
359
|
+
}
|
|
360
|
+
|
|
361
|
+
return promise;
|
|
362
|
+
});
|
|
363
|
+
|
|
364
|
+
return _guard(wrapperPromise, () => {
|
|
365
|
+
if (DEBUG) {
|
|
366
|
+
store._trackAsyncRequestEnd(token);
|
|
367
|
+
}
|
|
368
|
+
return _objectIsAlive(store);
|
|
369
|
+
});
|
|
370
|
+
}
|