@ember-data/model 4.6.1 → 4.7.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 +9 -24
- package/addon/-private/belongs-to.js +87 -25
- package/addon/-private/deprecated-promise-proxy.ts +54 -0
- package/addon/-private/errors.ts +6 -6
- package/addon/-private/has-many.js +84 -22
- package/addon/-private/legacy-data-fetch.js +35 -13
- package/addon/-private/legacy-data-utils.ts +1 -1
- package/addon/-private/legacy-relationships-support.ts +279 -218
- package/addon/-private/many-array.ts +216 -242
- package/addon/-private/model-for-mixin.ts +2 -4
- package/addon/-private/model.js +565 -172
- package/addon/-private/notify-changes.ts +2 -2
- package/addon/-private/promise-belongs-to.ts +1 -1
- package/addon/-private/promise-many-array.ts +204 -124
- package/addon/-private/promise-proxy-base.js +4 -0
- package/addon/-private/record-state.ts +63 -46
- package/addon/-private/references/belongs-to.ts +65 -38
- package/addon/-private/references/has-many.ts +86 -55
- package/addon/-private/relationship-meta.ts +8 -26
- package/index.js +3 -0
- package/package.json +12 -8
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { cacheFor } from '@ember/object/internals';
|
|
2
2
|
|
|
3
3
|
import type Store from '@ember-data/store';
|
|
4
|
-
import type { NotificationType } from '@ember-data/store/-private/record-notification-manager';
|
|
4
|
+
import type { NotificationType } from '@ember-data/store/-private/managers/record-notification-manager';
|
|
5
5
|
import type { StableRecordIdentifier } from '@ember-data/types/q/identifier';
|
|
6
6
|
|
|
7
7
|
import type Model from './model';
|
|
@@ -66,7 +66,7 @@ function notifyRelationship(identifier: StableRecordIdentifier, key: string, rec
|
|
|
66
66
|
function notifyAttribute(store: Store, identifier: StableRecordIdentifier, key: string, record: Model) {
|
|
67
67
|
let currentValue = cacheFor(record, key);
|
|
68
68
|
|
|
69
|
-
if (currentValue !== store._instanceCache.getRecordData(identifier).getAttr(key)) {
|
|
69
|
+
if (currentValue !== store._instanceCache.getRecordData(identifier).getAttr(identifier, key)) {
|
|
70
70
|
record.notifyPropertyChange(key);
|
|
71
71
|
}
|
|
72
72
|
}
|
|
@@ -4,11 +4,11 @@ import type PromiseProxyMixin from '@ember/object/promise-proxy-mixin';
|
|
|
4
4
|
import type ObjectProxy from '@ember/object/proxy';
|
|
5
5
|
|
|
6
6
|
import type Store from '@ember-data/store';
|
|
7
|
-
import { PromiseObject } from '@ember-data/store/-private';
|
|
8
7
|
import type { RecordInstance } from '@ember-data/types/q/record-instance';
|
|
9
8
|
import type { Dict } from '@ember-data/types/q/utils';
|
|
10
9
|
|
|
11
10
|
import { LegacySupport } from './legacy-relationships-support';
|
|
11
|
+
import { PromiseObject } from './promise-proxy-base';
|
|
12
12
|
|
|
13
13
|
export interface BelongsToProxyMeta {
|
|
14
14
|
key: string;
|
|
@@ -1,16 +1,23 @@
|
|
|
1
1
|
import ArrayMixin, { NativeArray } from '@ember/array';
|
|
2
2
|
import type ArrayProxy from '@ember/array/proxy';
|
|
3
|
-
import { assert } from '@ember/debug';
|
|
3
|
+
import { assert, deprecate } from '@ember/debug';
|
|
4
4
|
import { dependentKeyCompat } from '@ember/object/compat';
|
|
5
|
+
import { DEBUG } from '@glimmer/env';
|
|
5
6
|
import { tracked } from '@glimmer/tracking';
|
|
6
7
|
import Ember from 'ember';
|
|
7
8
|
|
|
8
9
|
import { resolve } from 'rsvp';
|
|
9
10
|
|
|
10
|
-
import
|
|
11
|
-
|
|
12
|
-
|
|
11
|
+
import {
|
|
12
|
+
DEPRECATE_A_USAGE,
|
|
13
|
+
DEPRECATE_COMPUTED_CHAINS,
|
|
14
|
+
DEPRECATE_PROMISE_MANY_ARRAY_BEHAVIORS,
|
|
15
|
+
} from '@ember-data/private-build-infra/deprecations';
|
|
16
|
+
import { StableRecordIdentifier } from '@ember-data/types/q/identifier';
|
|
13
17
|
import type { RecordInstance } from '@ember-data/types/q/record-instance';
|
|
18
|
+
import { FindOptions } from '@ember-data/types/q/store';
|
|
19
|
+
|
|
20
|
+
import type ManyArray from './many-array';
|
|
14
21
|
|
|
15
22
|
export interface HasManyProxyCreateArgs {
|
|
16
23
|
promise: Promise<ManyArray>;
|
|
@@ -24,16 +31,10 @@ export interface HasManyProxyCreateArgs {
|
|
|
24
31
|
This class is returned as the result of accessing an async hasMany relationship
|
|
25
32
|
on an instance of a Model extending from `@ember-data/model`.
|
|
26
33
|
|
|
27
|
-
A PromiseManyArray is an
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
Right now we proxy:
|
|
31
|
-
|
|
32
|
-
* `reload()`
|
|
33
|
-
* `createRecord()`
|
|
34
|
+
A PromiseManyArray is an iterable proxy that allows templates to consume related
|
|
35
|
+
ManyArrays and update once their contents are no longer pending.
|
|
34
36
|
|
|
35
|
-
|
|
36
|
-
nicely with templates. In your JS code you should resolve the promise first.
|
|
37
|
+
In your JS code you should resolve the promise first.
|
|
37
38
|
|
|
38
39
|
```js
|
|
39
40
|
const comments = await post.comments;
|
|
@@ -42,10 +43,14 @@ export interface HasManyProxyCreateArgs {
|
|
|
42
43
|
@class PromiseManyArray
|
|
43
44
|
@public
|
|
44
45
|
*/
|
|
45
|
-
export default interface PromiseManyArray extends Omit<ArrayProxy<
|
|
46
|
+
export default interface PromiseManyArray extends Omit<ArrayProxy<StableRecordIdentifier, RecordInstance>, 'destroy'> {
|
|
47
|
+
createRecord(): RecordInstance;
|
|
48
|
+
reload(options: FindOptions): PromiseManyArray;
|
|
49
|
+
}
|
|
46
50
|
export default class PromiseManyArray {
|
|
47
51
|
declare promise: Promise<ManyArray> | null;
|
|
48
52
|
declare isDestroyed: boolean;
|
|
53
|
+
// @deprecated (isDestroyed is not deprecated)
|
|
49
54
|
declare isDestroying: boolean;
|
|
50
55
|
|
|
51
56
|
constructor(promise: Promise<ManyArray>, content?: ManyArray) {
|
|
@@ -53,13 +58,27 @@ export default class PromiseManyArray {
|
|
|
53
58
|
this.isDestroyed = false;
|
|
54
59
|
this.isDestroying = false;
|
|
55
60
|
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
61
|
+
if (DEPRECATE_A_USAGE) {
|
|
62
|
+
const meta = Ember.meta(this);
|
|
63
|
+
meta.hasMixin = (mixin: Object) => {
|
|
64
|
+
deprecate(`Do not use A() on an EmberData PromiseManyArray`, false, {
|
|
65
|
+
id: 'ember-data:no-a-with-array-like',
|
|
66
|
+
until: '5.0',
|
|
67
|
+
since: { enabled: '4.8', available: '4.8' },
|
|
68
|
+
for: 'ember-data',
|
|
69
|
+
});
|
|
70
|
+
// @ts-expect-error ArrayMixin is more than a type
|
|
71
|
+
if (mixin === NativeArray || mixin === ArrayMixin) {
|
|
72
|
+
return true;
|
|
73
|
+
}
|
|
74
|
+
return false;
|
|
75
|
+
};
|
|
76
|
+
} else if (DEBUG) {
|
|
77
|
+
const meta = Ember.meta(this);
|
|
78
|
+
meta.hasMixin = (mixin: Object) => {
|
|
79
|
+
assert(`Do not use A() on an EmberData PromiseManyArray`);
|
|
80
|
+
};
|
|
81
|
+
}
|
|
63
82
|
}
|
|
64
83
|
|
|
65
84
|
//---- Methods/Properties on ArrayProxy that we will keep as our API
|
|
@@ -75,7 +94,9 @@ export default class PromiseManyArray {
|
|
|
75
94
|
get length(): number {
|
|
76
95
|
// shouldn't be needed, but ends up being needed
|
|
77
96
|
// for computed chains even in 4.x
|
|
78
|
-
|
|
97
|
+
if (DEPRECATE_COMPUTED_CHAINS) {
|
|
98
|
+
this['[]'];
|
|
99
|
+
}
|
|
79
100
|
return this.content ? this.content.length : 0;
|
|
80
101
|
}
|
|
81
102
|
|
|
@@ -85,11 +106,15 @@ export default class PromiseManyArray {
|
|
|
85
106
|
// to recompute. We entangle the '[]' tag from
|
|
86
107
|
@dependentKeyCompat
|
|
87
108
|
get '[]'() {
|
|
88
|
-
|
|
109
|
+
if (DEPRECATE_COMPUTED_CHAINS) {
|
|
110
|
+
return this.content?.length && this.content;
|
|
111
|
+
}
|
|
89
112
|
}
|
|
90
113
|
|
|
91
114
|
/**
|
|
92
115
|
* Iterate the proxied content. Called by the glimmer iterator in #each
|
|
116
|
+
* We do not guarantee that forEach will always be available. This
|
|
117
|
+
* may eventually be made to use Symbol.Iterator once glimmer supports it.
|
|
93
118
|
*
|
|
94
119
|
* @method forEach
|
|
95
120
|
* @param cb
|
|
@@ -97,12 +122,24 @@ export default class PromiseManyArray {
|
|
|
97
122
|
* @private
|
|
98
123
|
*/
|
|
99
124
|
forEach(cb) {
|
|
100
|
-
this['[]']; // needed for < 3.23 support e.g. 3.20 lts
|
|
101
125
|
if (this.content && this.length) {
|
|
102
126
|
this.content.forEach(cb);
|
|
103
127
|
}
|
|
104
128
|
}
|
|
105
129
|
|
|
130
|
+
/**
|
|
131
|
+
* Reload the relationship
|
|
132
|
+
* @method reload
|
|
133
|
+
* @public
|
|
134
|
+
* @param options
|
|
135
|
+
* @returns
|
|
136
|
+
*/
|
|
137
|
+
reload(options: FindOptions) {
|
|
138
|
+
assert('You are trying to reload an async manyArray before it has been created', this.content);
|
|
139
|
+
this.content.reload(options);
|
|
140
|
+
return this;
|
|
141
|
+
}
|
|
142
|
+
|
|
106
143
|
//---- Properties/Methods from the PromiseProxyMixin that we will keep as our API
|
|
107
144
|
|
|
108
145
|
/**
|
|
@@ -201,19 +238,6 @@ export default class PromiseManyArray {
|
|
|
201
238
|
return this.content ? this.content.meta : undefined;
|
|
202
239
|
}
|
|
203
240
|
|
|
204
|
-
/**
|
|
205
|
-
* Reload the relationship
|
|
206
|
-
* @method reload
|
|
207
|
-
* @public
|
|
208
|
-
* @param options
|
|
209
|
-
* @returns
|
|
210
|
-
*/
|
|
211
|
-
reload(options) {
|
|
212
|
-
assert('You are trying to reload an async manyArray before it has been created', this.content);
|
|
213
|
-
this.content.reload(options);
|
|
214
|
-
return this;
|
|
215
|
-
}
|
|
216
|
-
|
|
217
241
|
//---- Our own stuff
|
|
218
242
|
|
|
219
243
|
_update(promise: Promise<ManyArray>, content?: ManyArray) {
|
|
@@ -227,22 +251,55 @@ export default class PromiseManyArray {
|
|
|
227
251
|
static create({ promise, content }: HasManyProxyCreateArgs): PromiseManyArray {
|
|
228
252
|
return new this(promise, content);
|
|
229
253
|
}
|
|
254
|
+
}
|
|
230
255
|
|
|
231
|
-
|
|
232
|
-
createRecord(...args) {
|
|
256
|
+
if (DEPRECATE_PROMISE_MANY_ARRAY_BEHAVIORS) {
|
|
257
|
+
PromiseManyArray.prototype.createRecord = function createRecord(...args) {
|
|
258
|
+
deprecate(
|
|
259
|
+
`The createRecord method on ember-data's PromiseManyArray is deprecated. await the promise and work with the ManyArray directly.`,
|
|
260
|
+
false,
|
|
261
|
+
{
|
|
262
|
+
id: 'ember-data:deprecate-promise-many-array-behaviors',
|
|
263
|
+
until: '5.0',
|
|
264
|
+
since: { enabled: '4.8', available: '4.8' },
|
|
265
|
+
for: 'ember-data',
|
|
266
|
+
}
|
|
267
|
+
);
|
|
233
268
|
assert('You are trying to createRecord on an async manyArray before it has been created', this.content);
|
|
234
269
|
return this.content.createRecord(...args);
|
|
235
|
-
}
|
|
236
|
-
|
|
237
|
-
// Properties/Methods on ArrayProxy we should deprecate
|
|
238
|
-
|
|
239
|
-
get firstObject() {
|
|
240
|
-
return this.content ? this.content.firstObject : undefined;
|
|
241
|
-
}
|
|
270
|
+
};
|
|
242
271
|
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
272
|
+
Object.defineProperty(PromiseManyArray.prototype, 'firstObject', {
|
|
273
|
+
get() {
|
|
274
|
+
deprecate(
|
|
275
|
+
`The firstObject property on ember-data's PromiseManyArray is deprecated. await the promise and work with the ManyArray directly.`,
|
|
276
|
+
false,
|
|
277
|
+
{
|
|
278
|
+
id: 'ember-data:deprecate-promise-many-array-behaviors',
|
|
279
|
+
until: '5.0',
|
|
280
|
+
since: { enabled: '4.8', available: '4.8' },
|
|
281
|
+
for: 'ember-data',
|
|
282
|
+
}
|
|
283
|
+
);
|
|
284
|
+
return this.content ? this.content.firstObject : undefined;
|
|
285
|
+
},
|
|
286
|
+
});
|
|
287
|
+
|
|
288
|
+
Object.defineProperty(PromiseManyArray.prototype, 'lastObject', {
|
|
289
|
+
get() {
|
|
290
|
+
deprecate(
|
|
291
|
+
`The lastObject property on ember-data's PromiseManyArray is deprecated. await the promise and work with the ManyArray directly.`,
|
|
292
|
+
false,
|
|
293
|
+
{
|
|
294
|
+
id: 'ember-data:deprecate-promise-many-array-behaviors',
|
|
295
|
+
until: '5.0',
|
|
296
|
+
since: { enabled: '4.8', available: '4.8' },
|
|
297
|
+
for: 'ember-data',
|
|
298
|
+
}
|
|
299
|
+
);
|
|
300
|
+
return this.content ? this.content.lastObject : undefined;
|
|
301
|
+
},
|
|
302
|
+
});
|
|
246
303
|
}
|
|
247
304
|
|
|
248
305
|
function tapPromise(proxy: PromiseManyArray, promise: Promise<ManyArray>) {
|
|
@@ -268,78 +325,101 @@ function tapPromise(proxy: PromiseManyArray, promise: Promise<ManyArray>) {
|
|
|
268
325
|
);
|
|
269
326
|
}
|
|
270
327
|
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
}
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
328
|
+
if (DEPRECATE_PROMISE_MANY_ARRAY_BEHAVIORS) {
|
|
329
|
+
const EmberObjectMethods = [
|
|
330
|
+
'addObserver',
|
|
331
|
+
'cacheFor',
|
|
332
|
+
'decrementProperty',
|
|
333
|
+
'get',
|
|
334
|
+
'getProperties',
|
|
335
|
+
'incrementProperty',
|
|
336
|
+
'notifyPropertyChange',
|
|
337
|
+
'removeObserver',
|
|
338
|
+
'set',
|
|
339
|
+
'setProperties',
|
|
340
|
+
'toggleProperty',
|
|
341
|
+
];
|
|
342
|
+
EmberObjectMethods.forEach((method) => {
|
|
343
|
+
PromiseManyArray.prototype[method] = function delegatedMethod(...args) {
|
|
344
|
+
deprecate(
|
|
345
|
+
`The ${method} method on ember-data's PromiseManyArray is deprecated. await the promise and work with the ManyArray directly.`,
|
|
346
|
+
false,
|
|
347
|
+
{
|
|
348
|
+
id: 'ember-data:deprecate-promise-many-array-behaviors',
|
|
349
|
+
until: '5.0',
|
|
350
|
+
since: { enabled: '4.8', available: '4.8' },
|
|
351
|
+
for: 'ember-data',
|
|
352
|
+
}
|
|
353
|
+
);
|
|
354
|
+
return Ember[method](this, ...args);
|
|
355
|
+
};
|
|
356
|
+
});
|
|
357
|
+
|
|
358
|
+
const InheritedProxyMethods = [
|
|
359
|
+
'addArrayObserver',
|
|
360
|
+
'addObject',
|
|
361
|
+
'addObjects',
|
|
362
|
+
'any',
|
|
363
|
+
'arrayContentDidChange',
|
|
364
|
+
'arrayContentWillChange',
|
|
365
|
+
'clear',
|
|
366
|
+
'compact',
|
|
367
|
+
'every',
|
|
368
|
+
'filter',
|
|
369
|
+
'filterBy',
|
|
370
|
+
'find',
|
|
371
|
+
'findBy',
|
|
372
|
+
'getEach',
|
|
373
|
+
'includes',
|
|
374
|
+
'indexOf',
|
|
375
|
+
'insertAt',
|
|
376
|
+
'invoke',
|
|
377
|
+
'isAny',
|
|
378
|
+
'isEvery',
|
|
379
|
+
'lastIndexOf',
|
|
380
|
+
'map',
|
|
381
|
+
'mapBy',
|
|
382
|
+
// TODO update RFC to note objectAt was deprecated (forEach was left for iteration)
|
|
383
|
+
'objectAt',
|
|
384
|
+
'objectsAt',
|
|
385
|
+
'popObject',
|
|
386
|
+
'pushObject',
|
|
387
|
+
'pushObjects',
|
|
388
|
+
'reduce',
|
|
389
|
+
'reject',
|
|
390
|
+
'rejectBy',
|
|
391
|
+
'removeArrayObserver',
|
|
392
|
+
'removeAt',
|
|
393
|
+
'removeObject',
|
|
394
|
+
'removeObjects',
|
|
395
|
+
'replace',
|
|
396
|
+
'reverseObjects',
|
|
397
|
+
'setEach',
|
|
398
|
+
'setObjects',
|
|
399
|
+
'shiftObject',
|
|
400
|
+
'slice',
|
|
401
|
+
'sortBy',
|
|
402
|
+
'toArray',
|
|
403
|
+
'uniq',
|
|
404
|
+
'uniqBy',
|
|
405
|
+
'unshiftObject',
|
|
406
|
+
'unshiftObjects',
|
|
407
|
+
'without',
|
|
408
|
+
];
|
|
409
|
+
InheritedProxyMethods.forEach((method) => {
|
|
410
|
+
PromiseManyArray.prototype[method] = function proxiedMethod(...args) {
|
|
411
|
+
deprecate(
|
|
412
|
+
`The ${method} method on ember-data's PromiseManyArray is deprecated. await the promise and work with the ManyArray directly.`,
|
|
413
|
+
false,
|
|
414
|
+
{
|
|
415
|
+
id: 'ember-data:deprecate-promise-many-array-behaviors',
|
|
416
|
+
until: '5.0',
|
|
417
|
+
since: { enabled: '4.8', available: '4.8' },
|
|
418
|
+
for: 'ember-data',
|
|
419
|
+
}
|
|
420
|
+
);
|
|
421
|
+
assert(`Cannot call ${method} before content is assigned.`, this.content);
|
|
422
|
+
return this.content[method](...args);
|
|
423
|
+
};
|
|
424
|
+
});
|
|
425
|
+
}
|
|
@@ -5,14 +5,17 @@ import { cached, tracked } from '@glimmer/tracking';
|
|
|
5
5
|
|
|
6
6
|
import type Store from '@ember-data/store';
|
|
7
7
|
import { storeFor } from '@ember-data/store';
|
|
8
|
-
import {
|
|
9
|
-
import type { NotificationType } from '@ember-data/store/-private/record-notification-manager';
|
|
10
|
-
import type RequestCache from '@ember-data/store/-private/request-cache';
|
|
8
|
+
import { recordIdentifierFor } from '@ember-data/store/-private';
|
|
9
|
+
import type { NotificationType } from '@ember-data/store/-private/managers/record-notification-manager';
|
|
10
|
+
import type RequestCache from '@ember-data/store/-private/network/request-cache';
|
|
11
11
|
import type { StableRecordIdentifier } from '@ember-data/types/q/identifier';
|
|
12
12
|
import type { RecordData } from '@ember-data/types/q/record-data';
|
|
13
13
|
|
|
14
14
|
type Model = InstanceType<typeof import('./model')>;
|
|
15
15
|
|
|
16
|
+
const SOURCE_POINTER_REGEXP = /^\/?data\/(attributes|relationships)\/(.*)/;
|
|
17
|
+
const SOURCE_POINTER_PRIMARY_REGEXP = /^\/?data/;
|
|
18
|
+
const PRIMARY_ATTRIBUTE_KEY = 'base';
|
|
16
19
|
function isInvalidError(error) {
|
|
17
20
|
return error && error.isAdapterError === true && error.code === 'InvalidError';
|
|
18
21
|
}
|
|
@@ -101,8 +104,9 @@ export function tagged(_target, key, desc) {
|
|
|
101
104
|
}
|
|
102
105
|
|
|
103
106
|
/**
|
|
104
|
-
Historically
|
|
105
|
-
the
|
|
107
|
+
Historically EmberData managed a state machine
|
|
108
|
+
for each record, the localState for which
|
|
109
|
+
was reflected onto Model.
|
|
106
110
|
|
|
107
111
|
This implements the flags and stateName for backwards compat
|
|
108
112
|
with the state tree that used to be possible (listed below).
|
|
@@ -150,6 +154,7 @@ export default class RecordState {
|
|
|
150
154
|
declare recordData: RecordData;
|
|
151
155
|
declare _errorRequests: any[];
|
|
152
156
|
declare _lastError: any;
|
|
157
|
+
declare handler: object;
|
|
153
158
|
|
|
154
159
|
constructor(record: Model) {
|
|
155
160
|
const store = storeFor(record)!;
|
|
@@ -176,7 +181,6 @@ export default class RecordState {
|
|
|
176
181
|
break;
|
|
177
182
|
case 'rejected':
|
|
178
183
|
this.isSaving = false;
|
|
179
|
-
|
|
180
184
|
this._lastError = req;
|
|
181
185
|
if (!(req.response && isInvalidError(req.response.data))) {
|
|
182
186
|
this._errorRequests.push(req);
|
|
@@ -230,47 +234,63 @@ export default class RecordState {
|
|
|
230
234
|
}
|
|
231
235
|
}
|
|
232
236
|
|
|
233
|
-
notifications.subscribe(
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
// take this path currently, but we should probably
|
|
252
|
-
// fix that.
|
|
253
|
-
this.updateInvalidErrors();
|
|
254
|
-
this.notify('isValid');
|
|
255
|
-
break;
|
|
237
|
+
this.handler = notifications.subscribe(
|
|
238
|
+
identity,
|
|
239
|
+
(identifier: StableRecordIdentifier, type: NotificationType, key?: string) => {
|
|
240
|
+
switch (type) {
|
|
241
|
+
case 'state':
|
|
242
|
+
this.notify('isNew');
|
|
243
|
+
this.notify('isDeleted');
|
|
244
|
+
this.notify('isDirty');
|
|
245
|
+
break;
|
|
246
|
+
case 'attributes':
|
|
247
|
+
this.notify('isEmpty');
|
|
248
|
+
this.notify('isDirty');
|
|
249
|
+
break;
|
|
250
|
+
case 'errors':
|
|
251
|
+
this.updateInvalidErrors(this.record.errors);
|
|
252
|
+
this.notify('isValid');
|
|
253
|
+
break;
|
|
254
|
+
}
|
|
256
255
|
}
|
|
257
|
-
|
|
256
|
+
);
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
destroy() {
|
|
260
|
+
storeFor(this.record)!._notificationManager.unsubscribe(this.handler);
|
|
258
261
|
}
|
|
259
262
|
|
|
260
263
|
notify(key) {
|
|
261
264
|
getTag(this, key).notify();
|
|
262
265
|
}
|
|
263
266
|
|
|
264
|
-
updateInvalidErrors() {
|
|
265
|
-
|
|
267
|
+
updateInvalidErrors(errors) {
|
|
268
|
+
assert(
|
|
269
|
+
`Expected the RecordData instance for ${this.identifier} to implement getErrors(identifier)`,
|
|
270
|
+
typeof this.recordData.getErrors === 'function'
|
|
271
|
+
);
|
|
272
|
+
let jsonApiErrors = this.recordData.getErrors(this.identifier);
|
|
266
273
|
|
|
267
|
-
const { errors } = this.record;
|
|
268
274
|
errors.clear();
|
|
269
|
-
let newErrors = errorsArrayToHash(jsonApiErrors);
|
|
270
|
-
let errorKeys = Object.keys(newErrors);
|
|
271
275
|
|
|
272
|
-
for (let i = 0; i <
|
|
273
|
-
|
|
276
|
+
for (let i = 0; i < jsonApiErrors.length; i++) {
|
|
277
|
+
let error = jsonApiErrors[i];
|
|
278
|
+
|
|
279
|
+
if (error.source && error.source.pointer) {
|
|
280
|
+
let keyMatch = error.source.pointer.match(SOURCE_POINTER_REGEXP);
|
|
281
|
+
let key: string | undefined;
|
|
282
|
+
|
|
283
|
+
if (keyMatch) {
|
|
284
|
+
key = keyMatch[2];
|
|
285
|
+
} else if (error.source.pointer.search(SOURCE_POINTER_PRIMARY_REGEXP) !== -1) {
|
|
286
|
+
key = PRIMARY_ATTRIBUTE_KEY;
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
if (key) {
|
|
290
|
+
let errMsg = error.detail || error.title;
|
|
291
|
+
errors.add(key, errMsg);
|
|
292
|
+
}
|
|
293
|
+
}
|
|
274
294
|
}
|
|
275
295
|
}
|
|
276
296
|
|
|
@@ -289,7 +309,6 @@ export default class RecordState {
|
|
|
289
309
|
return !this.isLoaded && this.pendingCount > 0 && this.fulfilledCount === 0;
|
|
290
310
|
}
|
|
291
311
|
|
|
292
|
-
// TODO @runspired handle "unloadRecord" see note in InternalModel
|
|
293
312
|
@tagged
|
|
294
313
|
get isLoaded() {
|
|
295
314
|
if (this.isNew) {
|
|
@@ -303,7 +322,7 @@ export default class RecordState {
|
|
|
303
322
|
let rd = this.recordData;
|
|
304
323
|
if (this.isDeleted) {
|
|
305
324
|
assert(`Expected RecordData to implement isDeletionCommitted()`, rd.isDeletionCommitted);
|
|
306
|
-
return rd.isDeletionCommitted();
|
|
325
|
+
return rd.isDeletionCommitted(this.identifier);
|
|
307
326
|
}
|
|
308
327
|
if (this.isNew || this.isEmpty || !this.isValid || this.isDirty || this.isLoading) {
|
|
309
328
|
return false;
|
|
@@ -317,21 +336,21 @@ export default class RecordState {
|
|
|
317
336
|
// TODO this is not actually an RFC'd concept. Determine the
|
|
318
337
|
// correct heuristic to replace this with.
|
|
319
338
|
assert(`Expected RecordData to implement isEmpty()`, rd.isEmpty);
|
|
320
|
-
return !this.isNew && rd.isEmpty();
|
|
339
|
+
return !this.isNew && rd.isEmpty(this.identifier);
|
|
321
340
|
}
|
|
322
341
|
|
|
323
342
|
@tagged
|
|
324
343
|
get isNew() {
|
|
325
344
|
let rd = this.recordData;
|
|
326
345
|
assert(`Expected RecordData to implement isNew()`, rd.isNew);
|
|
327
|
-
return rd.isNew();
|
|
346
|
+
return rd.isNew(this.identifier);
|
|
328
347
|
}
|
|
329
348
|
|
|
330
349
|
@tagged
|
|
331
350
|
get isDeleted() {
|
|
332
351
|
let rd = this.recordData;
|
|
333
352
|
assert(`Expected RecordData to implement isDeleted()`, rd.isDeleted);
|
|
334
|
-
return rd.isDeleted();
|
|
353
|
+
return rd.isDeleted(this.identifier);
|
|
335
354
|
}
|
|
336
355
|
|
|
337
356
|
@tagged
|
|
@@ -342,12 +361,10 @@ export default class RecordState {
|
|
|
342
361
|
@tagged
|
|
343
362
|
get isDirty() {
|
|
344
363
|
let rd = this.recordData;
|
|
345
|
-
|
|
346
|
-
assert(`Expected RecordData to implement isDeletionCommitted()`, rd.isDeletionCommitted);
|
|
347
|
-
if (rd.isDeletionCommitted() || (this.isDeleted && this.isNew)) {
|
|
364
|
+
if (rd.isDeletionCommitted(this.identifier) || (this.isDeleted && this.isNew)) {
|
|
348
365
|
return false;
|
|
349
366
|
}
|
|
350
|
-
return this.isNew || rd.
|
|
367
|
+
return this.isNew || rd.hasChangedAttrs(this.identifier);
|
|
351
368
|
}
|
|
352
369
|
|
|
353
370
|
@tagged
|