@ember-data/model 4.10.0 → 4.12.0-alpha.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/diff-array.ts → -private.js} +41 -13
- package/addon/-private.js.map +1 -0
- package/addon/has-many-a275fe0d.js +6283 -0
- package/addon/has-many-a275fe0d.js.map +1 -0
- package/addon/index.js +1 -0
- package/addon/index.js.map +1 -0
- package/addon-main.js +90 -0
- package/package.json +43 -15
- package/addon/-private/attr.js +0 -162
- package/addon/-private/belongs-to.js +0 -268
- package/addon/-private/debug/assert-polymorphic-type.js +0 -71
- package/addon/-private/deprecated-promise-proxy.ts +0 -76
- package/addon/-private/errors.ts +0 -425
- package/addon/-private/has-many.js +0 -280
- package/addon/-private/index.ts +0 -14
- package/addon/-private/legacy-data-fetch.js +0 -395
- package/addon/-private/legacy-data-utils.ts +0 -92
- package/addon/-private/legacy-relationships-support.ts +0 -774
- package/addon/-private/many-array.ts +0 -401
- package/addon/-private/model-for-mixin.ts +0 -38
- package/addon/-private/model.js +0 -2516
- package/addon/-private/notify-changes.ts +0 -72
- package/addon/-private/promise-belongs-to.ts +0 -73
- package/addon/-private/promise-many-array.ts +0 -425
- package/addon/-private/promise-proxy-base.js +0 -4
- package/addon/-private/record-state.ts +0 -468
- package/addon/-private/references/belongs-to.ts +0 -624
- package/addon/-private/references/has-many.ts +0 -669
- package/addon/-private/relationship-meta.ts +0 -98
- package/addon/-private/util.ts +0 -31
- package/addon/index.ts +0 -39
- package/blueprints/model/HELP.md +0 -26
- package/blueprints/model/files/__root__/__path__/__name__.js +0 -5
- package/blueprints/model/index.js +0 -158
- package/blueprints/model/native-files/__root__/__path__/__name__.js +0 -5
- package/blueprints/model-test/index.js +0 -33
- package/blueprints/model-test/mocha-files/__root__/__path__/__test__.js +0 -18
- package/blueprints/model-test/mocha-rfc-232-files/__root__/__path__/__test__.js +0 -15
- package/blueprints/model-test/qunit-files/__root__/__path__/__test__.js +0 -14
- package/index.js +0 -46
|
@@ -1,280 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
@module @ember-data/model
|
|
3
|
-
*/
|
|
4
|
-
import { A } from '@ember/array';
|
|
5
|
-
import { assert, deprecate, inspect } from '@ember/debug';
|
|
6
|
-
import { computed } from '@ember/object';
|
|
7
|
-
import { dasherize } from '@ember/string';
|
|
8
|
-
import { DEBUG } from '@glimmer/env';
|
|
9
|
-
|
|
10
|
-
import { singularize } from 'ember-inflector';
|
|
11
|
-
|
|
12
|
-
import {
|
|
13
|
-
DEPRECATE_RELATIONSHIPS_WITHOUT_ASYNC,
|
|
14
|
-
DEPRECATE_RELATIONSHIPS_WITHOUT_INVERSE,
|
|
15
|
-
DEPRECATE_RELATIONSHIPS_WITHOUT_TYPE,
|
|
16
|
-
} from '@ember-data/private-build-infra/deprecations';
|
|
17
|
-
|
|
18
|
-
import { lookupLegacySupport } from './model';
|
|
19
|
-
import { computedMacroWithOptionalParams } from './util';
|
|
20
|
-
|
|
21
|
-
function normalizeType(type) {
|
|
22
|
-
if (DEPRECATE_RELATIONSHIPS_WITHOUT_TYPE) {
|
|
23
|
-
if (!type) {
|
|
24
|
-
return;
|
|
25
|
-
}
|
|
26
|
-
}
|
|
27
|
-
|
|
28
|
-
return singularize(dasherize(type));
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
/**
|
|
32
|
-
`hasMany` is used to define One-To-Many and Many-To-Many
|
|
33
|
-
relationships on a [Model](/ember-data/release/classes/Model).
|
|
34
|
-
|
|
35
|
-
`hasMany` takes an optional hash as a second parameter, currently
|
|
36
|
-
supported options are:
|
|
37
|
-
|
|
38
|
-
- `async`: A boolean value used to explicitly declare this to be an async relationship. The default is true.
|
|
39
|
-
- `inverse`: A string used to identify the inverse property on a related model.
|
|
40
|
-
- `polymorphic` A boolean value to mark the relationship as polymorphic
|
|
41
|
-
|
|
42
|
-
#### One-To-Many
|
|
43
|
-
To declare a one-to-many relationship between two models, use
|
|
44
|
-
`belongsTo` in combination with `hasMany`, like this:
|
|
45
|
-
|
|
46
|
-
```app/models/post.js
|
|
47
|
-
import Model, { hasMany } from '@ember-data/model';
|
|
48
|
-
|
|
49
|
-
export default class PostModel extends Model {
|
|
50
|
-
@hasMany('comment') comments;
|
|
51
|
-
}
|
|
52
|
-
```
|
|
53
|
-
|
|
54
|
-
```app/models/comment.js
|
|
55
|
-
import Model, { belongsTo } from '@ember-data/model';
|
|
56
|
-
|
|
57
|
-
export default class CommentModel extends Model {
|
|
58
|
-
@belongsTo('post') post;
|
|
59
|
-
}
|
|
60
|
-
```
|
|
61
|
-
|
|
62
|
-
#### Many-To-Many
|
|
63
|
-
To declare a many-to-many relationship between two models, use
|
|
64
|
-
`hasMany`:
|
|
65
|
-
|
|
66
|
-
```app/models/post.js
|
|
67
|
-
import Model, { hasMany } from '@ember-data/model';
|
|
68
|
-
|
|
69
|
-
export default class PostModel extends Model {
|
|
70
|
-
@hasMany('tag') tags;
|
|
71
|
-
}
|
|
72
|
-
```
|
|
73
|
-
|
|
74
|
-
```app/models/tag.js
|
|
75
|
-
import Model, { hasMany } from '@ember-data/model';
|
|
76
|
-
|
|
77
|
-
export default class TagModel extends Model {
|
|
78
|
-
@hasMany('post') posts;
|
|
79
|
-
}
|
|
80
|
-
```
|
|
81
|
-
|
|
82
|
-
You can avoid passing a string as the first parameter. In that case Ember Data
|
|
83
|
-
will infer the type from the singularized key name.
|
|
84
|
-
|
|
85
|
-
```app/models/post.js
|
|
86
|
-
import Model, { hasMany } from '@ember-data/model';
|
|
87
|
-
|
|
88
|
-
export default class PostModel extends Model {
|
|
89
|
-
@hasMany tags;
|
|
90
|
-
}
|
|
91
|
-
```
|
|
92
|
-
|
|
93
|
-
will lookup for a Tag type.
|
|
94
|
-
|
|
95
|
-
#### Explicit Inverses
|
|
96
|
-
|
|
97
|
-
Ember Data will do its best to discover which relationships map to
|
|
98
|
-
one another. In the one-to-many code above, for example, Ember Data
|
|
99
|
-
can figure out that changing the `comments` relationship should update
|
|
100
|
-
the `post` relationship on the inverse because post is the only
|
|
101
|
-
relationship to that model.
|
|
102
|
-
|
|
103
|
-
However, sometimes you may have multiple `belongsTo`/`hasMany` for the
|
|
104
|
-
same type. You can specify which property on the related model is
|
|
105
|
-
the inverse using `hasMany`'s `inverse` option:
|
|
106
|
-
|
|
107
|
-
```app/models/comment.js
|
|
108
|
-
import Model, { belongsTo } from '@ember-data/model';
|
|
109
|
-
|
|
110
|
-
export default class CommentModel extends Model {
|
|
111
|
-
@belongsTo('post') onePost;
|
|
112
|
-
@belongsTo('post') twoPost
|
|
113
|
-
@belongsTo('post') redPost;
|
|
114
|
-
@belongsTo('post') bluePost;
|
|
115
|
-
}
|
|
116
|
-
```
|
|
117
|
-
|
|
118
|
-
```app/models/post.js
|
|
119
|
-
import Model, { hasMany } from '@ember-data/model';
|
|
120
|
-
|
|
121
|
-
export default class PostModel extends Model {
|
|
122
|
-
@hasMany('comment', {
|
|
123
|
-
inverse: 'redPost'
|
|
124
|
-
})
|
|
125
|
-
comments;
|
|
126
|
-
}
|
|
127
|
-
```
|
|
128
|
-
|
|
129
|
-
You can also specify an inverse on a `belongsTo`, which works how
|
|
130
|
-
you'd expect.
|
|
131
|
-
|
|
132
|
-
#### Sync relationships
|
|
133
|
-
|
|
134
|
-
Ember Data resolves sync relationships with the related resources
|
|
135
|
-
available in its local store, hence it is expected these resources
|
|
136
|
-
to be loaded before or along-side the primary resource.
|
|
137
|
-
|
|
138
|
-
```app/models/post.js
|
|
139
|
-
import Model, { hasMany } from '@ember-data/model';
|
|
140
|
-
|
|
141
|
-
export default class PostModel extends Model {
|
|
142
|
-
@hasMany('comment', {
|
|
143
|
-
async: false
|
|
144
|
-
})
|
|
145
|
-
comments;
|
|
146
|
-
}
|
|
147
|
-
```
|
|
148
|
-
|
|
149
|
-
In contrast to async relationship, accessing a sync relationship
|
|
150
|
-
will always return a [ManyArray](/ember-data/release/classes/ManyArray) instance
|
|
151
|
-
containing the existing local resources. But it will error on access
|
|
152
|
-
when any of the known related resources have not been loaded.
|
|
153
|
-
|
|
154
|
-
```
|
|
155
|
-
post.comments.forEach((comment) => {
|
|
156
|
-
|
|
157
|
-
});
|
|
158
|
-
|
|
159
|
-
```
|
|
160
|
-
|
|
161
|
-
If you are using `links` with sync relationships, you have to use
|
|
162
|
-
`ref.reload` to fetch the resources.
|
|
163
|
-
|
|
164
|
-
@method hasMany
|
|
165
|
-
@public
|
|
166
|
-
@static
|
|
167
|
-
@for @ember-data/model
|
|
168
|
-
@param {String} type (optional) type of the relationship
|
|
169
|
-
@param {Object} options (optional) a hash of options
|
|
170
|
-
@return {Ember.computed} relationship
|
|
171
|
-
*/
|
|
172
|
-
function hasMany(type, options) {
|
|
173
|
-
if (DEPRECATE_RELATIONSHIPS_WITHOUT_TYPE) {
|
|
174
|
-
if (typeof type !== 'string' || !type.length) {
|
|
175
|
-
deprecate(
|
|
176
|
-
'hasMany(<type>, <options>) must specify the string type of the related resource as the first parameter',
|
|
177
|
-
false,
|
|
178
|
-
{
|
|
179
|
-
id: 'ember-data:deprecate-non-strict-relationships',
|
|
180
|
-
for: 'ember-data',
|
|
181
|
-
until: '5.0',
|
|
182
|
-
since: { enabled: '4.7', available: '4.7' },
|
|
183
|
-
}
|
|
184
|
-
);
|
|
185
|
-
if (typeof type === 'object') {
|
|
186
|
-
options = type;
|
|
187
|
-
type = undefined;
|
|
188
|
-
}
|
|
189
|
-
|
|
190
|
-
assert(
|
|
191
|
-
`The first argument to hasMany must be a string representing a model type key, not an instance of ${inspect(
|
|
192
|
-
type
|
|
193
|
-
)}. E.g., to define a relation to the Comment model, use hasMany('comment')`,
|
|
194
|
-
typeof type === 'string' || typeof type === 'undefined'
|
|
195
|
-
);
|
|
196
|
-
}
|
|
197
|
-
}
|
|
198
|
-
|
|
199
|
-
if (DEPRECATE_RELATIONSHIPS_WITHOUT_ASYNC) {
|
|
200
|
-
if (!options || typeof options.async !== 'boolean') {
|
|
201
|
-
options = options || {};
|
|
202
|
-
if (!('async' in options)) {
|
|
203
|
-
options.async = true;
|
|
204
|
-
}
|
|
205
|
-
deprecate('hasMany(<type>, <options>) must specify options.async as either `true` or `false`.', false, {
|
|
206
|
-
id: 'ember-data:deprecate-non-strict-relationships',
|
|
207
|
-
for: 'ember-data',
|
|
208
|
-
until: '5.0',
|
|
209
|
-
since: { enabled: '4.7', available: '4.7' },
|
|
210
|
-
});
|
|
211
|
-
} else {
|
|
212
|
-
assert(`Expected hasMany options.async to be a boolean`, options && typeof options.async === 'boolean');
|
|
213
|
-
}
|
|
214
|
-
} else {
|
|
215
|
-
assert(`Expected hasMany options.async to be a boolean`, options && typeof options.async === 'boolean');
|
|
216
|
-
}
|
|
217
|
-
|
|
218
|
-
if (DEPRECATE_RELATIONSHIPS_WITHOUT_INVERSE) {
|
|
219
|
-
if (options.inverse !== null && (typeof options.inverse !== 'string' || options.inverse.length === 0)) {
|
|
220
|
-
deprecate(
|
|
221
|
-
'hasMany(<type>, <options>) must specify options.inverse as either `null` or string type of the related resource.',
|
|
222
|
-
false,
|
|
223
|
-
{
|
|
224
|
-
id: 'ember-data:deprecate-non-strict-relationships',
|
|
225
|
-
for: 'ember-data',
|
|
226
|
-
until: '5.0',
|
|
227
|
-
since: { enabled: '4.7', available: '4.7' },
|
|
228
|
-
}
|
|
229
|
-
);
|
|
230
|
-
}
|
|
231
|
-
}
|
|
232
|
-
|
|
233
|
-
// Metadata about relationships is stored on the meta of
|
|
234
|
-
// the relationship. This is used for introspection and
|
|
235
|
-
// serialization. Note that `key` is populated lazily
|
|
236
|
-
// the first time the CP is called.
|
|
237
|
-
let meta = {
|
|
238
|
-
type: normalizeType(type),
|
|
239
|
-
options,
|
|
240
|
-
isRelationship: true,
|
|
241
|
-
kind: 'hasMany',
|
|
242
|
-
name: 'Has Many',
|
|
243
|
-
key: null,
|
|
244
|
-
};
|
|
245
|
-
|
|
246
|
-
return computed({
|
|
247
|
-
get(key) {
|
|
248
|
-
if (DEBUG) {
|
|
249
|
-
if (['currentState'].indexOf(key) !== -1) {
|
|
250
|
-
throw new Error(
|
|
251
|
-
`'${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()}`
|
|
252
|
-
);
|
|
253
|
-
}
|
|
254
|
-
}
|
|
255
|
-
if (this.isDestroying || this.isDestroyed) {
|
|
256
|
-
return A();
|
|
257
|
-
}
|
|
258
|
-
return lookupLegacySupport(this).getHasMany(key);
|
|
259
|
-
},
|
|
260
|
-
set(key, records) {
|
|
261
|
-
if (DEBUG) {
|
|
262
|
-
if (['currentState'].indexOf(key) !== -1) {
|
|
263
|
-
throw new Error(
|
|
264
|
-
`'${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()}`
|
|
265
|
-
);
|
|
266
|
-
}
|
|
267
|
-
}
|
|
268
|
-
const support = lookupLegacySupport(this);
|
|
269
|
-
const manyArray = support.getManyArray(key);
|
|
270
|
-
assert(`You must pass an array of records to set a hasMany relationship`, Array.isArray(records));
|
|
271
|
-
this.store._join(() => {
|
|
272
|
-
manyArray.splice(0, manyArray.length, ...records);
|
|
273
|
-
});
|
|
274
|
-
|
|
275
|
-
return support.getHasMany(key);
|
|
276
|
-
},
|
|
277
|
-
}).meta(meta);
|
|
278
|
-
}
|
|
279
|
-
|
|
280
|
-
export default computedMacroWithOptionalParams(hasMany);
|
package/addon/-private/index.ts
DELETED
|
@@ -1,14 +0,0 @@
|
|
|
1
|
-
export { default as attr } from './attr';
|
|
2
|
-
export { default as belongsTo } from './belongs-to';
|
|
3
|
-
export { default as hasMany } from './has-many';
|
|
4
|
-
export { default as Model } from './model';
|
|
5
|
-
export { default as Errors } from './errors';
|
|
6
|
-
|
|
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
|
-
|
|
12
|
-
// // Used by tests
|
|
13
|
-
export { default as diffArray } from './diff-array';
|
|
14
|
-
export { LEGACY_SUPPORT } from './model';
|
|
@@ -1,395 +0,0 @@
|
|
|
1
|
-
import { assert, deprecate } from '@ember/debug';
|
|
2
|
-
import { DEBUG } from '@glimmer/env';
|
|
3
|
-
|
|
4
|
-
import { resolve } from 'rsvp';
|
|
5
|
-
|
|
6
|
-
import {
|
|
7
|
-
DEPRECATE_RELATIONSHIPS_WITHOUT_INVERSE,
|
|
8
|
-
DEPRECATE_RSVP_PROMISE,
|
|
9
|
-
} from '@ember-data/private-build-infra/deprecations';
|
|
10
|
-
|
|
11
|
-
import { iterateData, normalizeResponseHelper } from './legacy-data-utils';
|
|
12
|
-
|
|
13
|
-
export function _findHasMany(adapter, store, identifier, link, relationship, options) {
|
|
14
|
-
const record = store._instanceCache.getRecord(identifier);
|
|
15
|
-
const snapshot = store._instanceCache.createSnapshot(identifier, options);
|
|
16
|
-
let modelClass = store.modelFor(relationship.type);
|
|
17
|
-
let useLink = !link || typeof link === 'string';
|
|
18
|
-
let relatedLink = useLink ? link : link.href;
|
|
19
|
-
let promise = adapter.findHasMany(store, snapshot, relatedLink, relationship);
|
|
20
|
-
let label = `DS: Handle Adapter#findHasMany of '${identifier.type}' : '${relationship.type}'`;
|
|
21
|
-
|
|
22
|
-
promise = guardDestroyedStore(promise, store, label);
|
|
23
|
-
promise = promise.then(
|
|
24
|
-
(adapterPayload) => {
|
|
25
|
-
if (!_objectIsAlive(record)) {
|
|
26
|
-
if (DEPRECATE_RSVP_PROMISE) {
|
|
27
|
-
deprecate(
|
|
28
|
-
`A Promise for fetching ${relationship.type} did not resolve by the time your model was destroyed. This will error in a future release.`,
|
|
29
|
-
false,
|
|
30
|
-
{
|
|
31
|
-
id: 'ember-data:rsvp-unresolved-async',
|
|
32
|
-
until: '5.0',
|
|
33
|
-
for: '@ember-data/store',
|
|
34
|
-
since: {
|
|
35
|
-
available: '4.5',
|
|
36
|
-
enabled: '4.5',
|
|
37
|
-
},
|
|
38
|
-
}
|
|
39
|
-
);
|
|
40
|
-
}
|
|
41
|
-
}
|
|
42
|
-
|
|
43
|
-
assert(
|
|
44
|
-
`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`,
|
|
45
|
-
payloadIsNotBlank(adapterPayload)
|
|
46
|
-
);
|
|
47
|
-
let serializer = store.serializerFor(relationship.type);
|
|
48
|
-
let payload = normalizeResponseHelper(serializer, store, modelClass, adapterPayload, null, 'findHasMany');
|
|
49
|
-
|
|
50
|
-
assert(
|
|
51
|
-
`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: [] }`,
|
|
52
|
-
'data' in payload && Array.isArray(payload.data)
|
|
53
|
-
);
|
|
54
|
-
|
|
55
|
-
payload = syncRelationshipDataFromLink(store, payload, identifier, relationship);
|
|
56
|
-
|
|
57
|
-
return store._push(payload);
|
|
58
|
-
},
|
|
59
|
-
null,
|
|
60
|
-
`DS: Extract payload of '${identifier.type}' : hasMany '${relationship.type}'`
|
|
61
|
-
);
|
|
62
|
-
|
|
63
|
-
if (DEPRECATE_RSVP_PROMISE) {
|
|
64
|
-
promise = _guard(promise, _bind(_objectIsAlive, record));
|
|
65
|
-
}
|
|
66
|
-
|
|
67
|
-
return promise;
|
|
68
|
-
}
|
|
69
|
-
|
|
70
|
-
export function _findBelongsTo(store, identifier, link, relationship, options) {
|
|
71
|
-
const record = store._instanceCache.getRecord(identifier);
|
|
72
|
-
let adapter = store.adapterFor(identifier.type);
|
|
73
|
-
|
|
74
|
-
assert(`You tried to load a belongsTo relationship but you have no adapter (for ${identifier.type})`, adapter);
|
|
75
|
-
assert(
|
|
76
|
-
`You tried to load a belongsTo relationship from a specified 'link' in the original payload but your adapter does not implement 'findBelongsTo'`,
|
|
77
|
-
typeof adapter.findBelongsTo === 'function'
|
|
78
|
-
);
|
|
79
|
-
let snapshot = store._instanceCache.createSnapshot(identifier, options);
|
|
80
|
-
let modelClass = store.modelFor(relationship.type);
|
|
81
|
-
let useLink = !link || typeof link === 'string';
|
|
82
|
-
let relatedLink = useLink ? link : link.href;
|
|
83
|
-
let promise = adapter.findBelongsTo(store, snapshot, relatedLink, relationship);
|
|
84
|
-
let label = `DS: Handle Adapter#findBelongsTo of ${identifier.type} : ${relationship.type}`;
|
|
85
|
-
|
|
86
|
-
promise = guardDestroyedStore(promise, store, label);
|
|
87
|
-
promise = _guard(promise, _bind(_objectIsAlive, record));
|
|
88
|
-
|
|
89
|
-
promise = promise.then(
|
|
90
|
-
(adapterPayload) => {
|
|
91
|
-
if (!_objectIsAlive(record)) {
|
|
92
|
-
if (DEPRECATE_RSVP_PROMISE) {
|
|
93
|
-
deprecate(
|
|
94
|
-
`A Promise for fetching ${relationship.type} did not resolve by the time your model was destroyed. This will error in a future release.`,
|
|
95
|
-
false,
|
|
96
|
-
{
|
|
97
|
-
id: 'ember-data:rsvp-unresolved-async',
|
|
98
|
-
until: '5.0',
|
|
99
|
-
for: '@ember-data/store',
|
|
100
|
-
since: {
|
|
101
|
-
available: '4.5',
|
|
102
|
-
enabled: '4.5',
|
|
103
|
-
},
|
|
104
|
-
}
|
|
105
|
-
);
|
|
106
|
-
}
|
|
107
|
-
}
|
|
108
|
-
|
|
109
|
-
let serializer = store.serializerFor(relationship.type);
|
|
110
|
-
let payload = normalizeResponseHelper(serializer, store, modelClass, adapterPayload, null, 'findBelongsTo');
|
|
111
|
-
|
|
112
|
-
assert(
|
|
113
|
-
`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 }`,
|
|
114
|
-
'data' in payload &&
|
|
115
|
-
(payload.data === null || (typeof payload.data === 'object' && !Array.isArray(payload.data)))
|
|
116
|
-
);
|
|
117
|
-
|
|
118
|
-
if (!payload.data && !payload.links && !payload.meta) {
|
|
119
|
-
return null;
|
|
120
|
-
}
|
|
121
|
-
|
|
122
|
-
payload = syncRelationshipDataFromLink(store, payload, identifier, relationship);
|
|
123
|
-
|
|
124
|
-
return store._push(payload);
|
|
125
|
-
},
|
|
126
|
-
null,
|
|
127
|
-
`DS: Extract payload of ${identifier.type} : ${relationship.type}`
|
|
128
|
-
);
|
|
129
|
-
|
|
130
|
-
if (DEPRECATE_RSVP_PROMISE) {
|
|
131
|
-
promise = _guard(promise, _bind(_objectIsAlive, record));
|
|
132
|
-
}
|
|
133
|
-
|
|
134
|
-
return promise;
|
|
135
|
-
}
|
|
136
|
-
|
|
137
|
-
// sync
|
|
138
|
-
// iterate over records in payload.data
|
|
139
|
-
// for each record
|
|
140
|
-
// assert that record.relationships[inverse] is either undefined (so we can fix it)
|
|
141
|
-
// or provide a data: {id, type} that matches the record that requested it
|
|
142
|
-
// return the relationship data for the parent
|
|
143
|
-
function syncRelationshipDataFromLink(store, payload, parentIdentifier, relationship) {
|
|
144
|
-
// ensure the right hand side (incoming payload) points to the parent record that
|
|
145
|
-
// requested this relationship
|
|
146
|
-
let relationshipData = payload.data
|
|
147
|
-
? iterateData(payload.data, (data, index) => {
|
|
148
|
-
const { id, type } = data;
|
|
149
|
-
ensureRelationshipIsSetToParent(data, parentIdentifier, store, relationship, index);
|
|
150
|
-
return { id, type };
|
|
151
|
-
})
|
|
152
|
-
: null;
|
|
153
|
-
|
|
154
|
-
const relatedDataHash = {};
|
|
155
|
-
|
|
156
|
-
if ('meta' in payload) {
|
|
157
|
-
relatedDataHash.meta = payload.meta;
|
|
158
|
-
}
|
|
159
|
-
if ('links' in payload) {
|
|
160
|
-
relatedDataHash.links = payload.links;
|
|
161
|
-
}
|
|
162
|
-
if ('data' in payload) {
|
|
163
|
-
relatedDataHash.data = relationshipData;
|
|
164
|
-
}
|
|
165
|
-
|
|
166
|
-
// now, push the left hand side (the parent record) to ensure things are in sync, since
|
|
167
|
-
// the payload will be pushed with store._push
|
|
168
|
-
const parentPayload = {
|
|
169
|
-
id: parentIdentifier.id,
|
|
170
|
-
type: parentIdentifier.type,
|
|
171
|
-
relationships: {
|
|
172
|
-
[relationship.key]: relatedDataHash,
|
|
173
|
-
},
|
|
174
|
-
};
|
|
175
|
-
|
|
176
|
-
if (!Array.isArray(payload.included)) {
|
|
177
|
-
payload.included = [];
|
|
178
|
-
}
|
|
179
|
-
payload.included.push(parentPayload);
|
|
180
|
-
|
|
181
|
-
return payload;
|
|
182
|
-
}
|
|
183
|
-
|
|
184
|
-
function ensureRelationshipIsSetToParent(payload, parentIdentifier, store, parentRelationship, index) {
|
|
185
|
-
let { id, type } = payload;
|
|
186
|
-
|
|
187
|
-
if (!payload.relationships) {
|
|
188
|
-
payload.relationships = {};
|
|
189
|
-
}
|
|
190
|
-
let { relationships } = payload;
|
|
191
|
-
|
|
192
|
-
let inverse = getInverse(store, parentIdentifier, parentRelationship, type);
|
|
193
|
-
if (inverse) {
|
|
194
|
-
let { inverseKey, kind } = inverse;
|
|
195
|
-
|
|
196
|
-
let relationshipData = relationships[inverseKey] && relationships[inverseKey].data;
|
|
197
|
-
|
|
198
|
-
if (DEBUG) {
|
|
199
|
-
if (
|
|
200
|
-
typeof relationshipData !== 'undefined' &&
|
|
201
|
-
!relationshipDataPointsToParent(relationshipData, parentIdentifier)
|
|
202
|
-
) {
|
|
203
|
-
let inspect = function inspect(thing) {
|
|
204
|
-
return `'${JSON.stringify(thing)}'`;
|
|
205
|
-
};
|
|
206
|
-
let quotedType = inspect(type);
|
|
207
|
-
let quotedInverse = inspect(inverseKey);
|
|
208
|
-
let expected = inspect({
|
|
209
|
-
id: parentIdentifier.id,
|
|
210
|
-
type: parentIdentifier.type,
|
|
211
|
-
});
|
|
212
|
-
let expectedModel = `${parentIdentifier.type}:${parentIdentifier.id}`;
|
|
213
|
-
let got = inspect(relationshipData);
|
|
214
|
-
let prefix = typeof index === 'number' ? `data[${index}]` : `data`;
|
|
215
|
-
let path = `${prefix}.relationships.${inverseKey}.data`;
|
|
216
|
-
let other = relationshipData ? `<${relationshipData.type}:${relationshipData.id}>` : null;
|
|
217
|
-
let relationshipFetched = `${expectedModel}.${parentRelationship.kind}("${parentRelationship.name}")`;
|
|
218
|
-
let includedRecord = `<${type}:${id}>`;
|
|
219
|
-
let message = [
|
|
220
|
-
`Encountered mismatched relationship: Ember Data expected ${path} in the payload from ${relationshipFetched} to include ${expected} but got ${got} instead.\n`,
|
|
221
|
-
`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.`,
|
|
222
|
-
`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.`,
|
|
223
|
-
`Ember Data has corrected the ${includedRecord} record's ${quotedInverse} relationship to ${expectedModel} so that ${relationshipFetched} will include ${includedRecord}.`,
|
|
224
|
-
`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.`,
|
|
225
|
-
].join('\n');
|
|
226
|
-
|
|
227
|
-
assert(message);
|
|
228
|
-
}
|
|
229
|
-
}
|
|
230
|
-
|
|
231
|
-
if (kind !== 'hasMany' || typeof relationshipData !== 'undefined') {
|
|
232
|
-
relationships[inverseKey] = relationships[inverseKey] || {};
|
|
233
|
-
relationships[inverseKey].data = fixRelationshipData(relationshipData, kind, parentIdentifier);
|
|
234
|
-
}
|
|
235
|
-
}
|
|
236
|
-
}
|
|
237
|
-
|
|
238
|
-
function metaIsRelationshipDefinition(meta) {
|
|
239
|
-
return typeof meta._inverseKey === 'function';
|
|
240
|
-
}
|
|
241
|
-
|
|
242
|
-
function inverseForRelationship(store, identifier, key) {
|
|
243
|
-
const definition = store.getSchemaDefinitionService().relationshipsDefinitionFor(identifier)[key];
|
|
244
|
-
if (!definition) {
|
|
245
|
-
return null;
|
|
246
|
-
}
|
|
247
|
-
|
|
248
|
-
if (DEPRECATE_RELATIONSHIPS_WITHOUT_INVERSE) {
|
|
249
|
-
if (metaIsRelationshipDefinition(definition)) {
|
|
250
|
-
const modelClass = store.modelFor(identifier.type);
|
|
251
|
-
return definition._inverseKey(store, modelClass);
|
|
252
|
-
}
|
|
253
|
-
}
|
|
254
|
-
assert(
|
|
255
|
-
`Expected the relationship defintion to specify the inverse type or null.`,
|
|
256
|
-
definition.options?.inverse === null ||
|
|
257
|
-
(typeof definition.options?.inverse === 'string' && definition.options.inverse.length > 0)
|
|
258
|
-
);
|
|
259
|
-
return definition.options.inverse;
|
|
260
|
-
}
|
|
261
|
-
|
|
262
|
-
function getInverse(store, parentIdentifier, parentRelationship, type) {
|
|
263
|
-
let { name: lhs_relationshipName } = parentRelationship;
|
|
264
|
-
let { type: parentType } = parentIdentifier;
|
|
265
|
-
let inverseKey = inverseForRelationship(store, { type: parentType }, lhs_relationshipName);
|
|
266
|
-
|
|
267
|
-
if (inverseKey) {
|
|
268
|
-
const definition = store.getSchemaDefinitionService().relationshipsDefinitionFor({ type });
|
|
269
|
-
let { kind } = definition[inverseKey];
|
|
270
|
-
return {
|
|
271
|
-
inverseKey,
|
|
272
|
-
kind,
|
|
273
|
-
};
|
|
274
|
-
}
|
|
275
|
-
}
|
|
276
|
-
|
|
277
|
-
function relationshipDataPointsToParent(relationshipData, identifier) {
|
|
278
|
-
if (relationshipData === null) {
|
|
279
|
-
return false;
|
|
280
|
-
}
|
|
281
|
-
|
|
282
|
-
if (Array.isArray(relationshipData)) {
|
|
283
|
-
if (relationshipData.length === 0) {
|
|
284
|
-
return false;
|
|
285
|
-
}
|
|
286
|
-
for (let i = 0; i < relationshipData.length; i++) {
|
|
287
|
-
let entry = relationshipData[i];
|
|
288
|
-
if (validateRelationshipEntry(entry, identifier)) {
|
|
289
|
-
return true;
|
|
290
|
-
}
|
|
291
|
-
}
|
|
292
|
-
} else {
|
|
293
|
-
return validateRelationshipEntry(relationshipData, identifier);
|
|
294
|
-
}
|
|
295
|
-
|
|
296
|
-
return false;
|
|
297
|
-
}
|
|
298
|
-
|
|
299
|
-
function fixRelationshipData(relationshipData, relationshipKind, { id, type }) {
|
|
300
|
-
let parentRelationshipData = {
|
|
301
|
-
id,
|
|
302
|
-
type,
|
|
303
|
-
};
|
|
304
|
-
|
|
305
|
-
let payload;
|
|
306
|
-
|
|
307
|
-
if (relationshipKind === 'hasMany') {
|
|
308
|
-
payload = relationshipData || [];
|
|
309
|
-
if (relationshipData) {
|
|
310
|
-
// these arrays could be massive so this is better than filter
|
|
311
|
-
// Note: this is potentially problematic if type/id are not in the
|
|
312
|
-
// same state of normalization.
|
|
313
|
-
let found = relationshipData.find((v) => {
|
|
314
|
-
return v.type === parentRelationshipData.type && v.id === parentRelationshipData.id;
|
|
315
|
-
});
|
|
316
|
-
if (!found) {
|
|
317
|
-
payload.push(parentRelationshipData);
|
|
318
|
-
}
|
|
319
|
-
} else {
|
|
320
|
-
payload.push(parentRelationshipData);
|
|
321
|
-
}
|
|
322
|
-
} else {
|
|
323
|
-
payload = relationshipData || {};
|
|
324
|
-
Object.assign(payload, parentRelationshipData);
|
|
325
|
-
}
|
|
326
|
-
|
|
327
|
-
return payload;
|
|
328
|
-
}
|
|
329
|
-
|
|
330
|
-
function validateRelationshipEntry({ id }, { id: parentModelID }) {
|
|
331
|
-
return id && id.toString() === parentModelID;
|
|
332
|
-
}
|
|
333
|
-
|
|
334
|
-
function _bind(fn, ...args) {
|
|
335
|
-
return function () {
|
|
336
|
-
return fn.apply(undefined, args);
|
|
337
|
-
};
|
|
338
|
-
}
|
|
339
|
-
|
|
340
|
-
function _guard(promise, test) {
|
|
341
|
-
let guarded = promise.finally(() => {
|
|
342
|
-
if (!test()) {
|
|
343
|
-
guarded._subscribers.length = 0;
|
|
344
|
-
}
|
|
345
|
-
});
|
|
346
|
-
|
|
347
|
-
return guarded;
|
|
348
|
-
}
|
|
349
|
-
|
|
350
|
-
function _objectIsAlive(object) {
|
|
351
|
-
return !(object.isDestroyed || object.isDestroying);
|
|
352
|
-
}
|
|
353
|
-
|
|
354
|
-
function payloadIsNotBlank(adapterPayload) {
|
|
355
|
-
if (Array.isArray(adapterPayload)) {
|
|
356
|
-
return true;
|
|
357
|
-
} else {
|
|
358
|
-
return Object.keys(adapterPayload || {}).length;
|
|
359
|
-
}
|
|
360
|
-
}
|
|
361
|
-
|
|
362
|
-
function guardDestroyedStore(promise, store, label) {
|
|
363
|
-
let token;
|
|
364
|
-
if (DEBUG) {
|
|
365
|
-
token = store._trackAsyncRequestStart(label);
|
|
366
|
-
}
|
|
367
|
-
let wrapperPromise = resolve(promise, label).then((_v) => {
|
|
368
|
-
if (!_objectIsAlive(store)) {
|
|
369
|
-
if (DEPRECATE_RSVP_PROMISE) {
|
|
370
|
-
deprecate(
|
|
371
|
-
`A Promise did not resolve by the time the store was destroyed. This will error in a future release.`,
|
|
372
|
-
false,
|
|
373
|
-
{
|
|
374
|
-
id: 'ember-data:rsvp-unresolved-async',
|
|
375
|
-
until: '5.0',
|
|
376
|
-
for: '@ember-data/store',
|
|
377
|
-
since: {
|
|
378
|
-
available: '4.5',
|
|
379
|
-
enabled: '4.5',
|
|
380
|
-
},
|
|
381
|
-
}
|
|
382
|
-
);
|
|
383
|
-
}
|
|
384
|
-
}
|
|
385
|
-
|
|
386
|
-
return promise;
|
|
387
|
-
});
|
|
388
|
-
|
|
389
|
-
return _guard(wrapperPromise, () => {
|
|
390
|
-
if (DEBUG) {
|
|
391
|
-
store._trackAsyncRequestEnd(token);
|
|
392
|
-
}
|
|
393
|
-
return _objectIsAlive(store);
|
|
394
|
-
});
|
|
395
|
-
}
|