@ember-data/store 4.8.0-alpha.2 → 4.8.0-alpha.3
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/-debug/index.js +1 -1
- package/addon/-private/{identifier-cache.ts → caches/identifier-cache.ts} +83 -8
- package/addon/-private/caches/instance-cache.ts +759 -0
- package/addon/-private/{record-data-for.ts → caches/record-data-for.ts} +2 -2
- package/addon/-private/index.ts +12 -15
- package/addon/-private/{model → legacy-model-support}/record-reference.ts +3 -3
- package/addon/-private/{schema-definition-service.ts → legacy-model-support/schema-definition-service.ts} +4 -5
- package/addon/-private/{model → legacy-model-support}/shim-model-class.ts +2 -2
- package/addon/-private/{record-array-manager.ts → managers/record-array-manager.ts} +16 -44
- package/addon/-private/{record-data-store-wrapper.ts → managers/record-data-store-wrapper.ts} +34 -56
- package/addon/-private/managers/record-notification-manager.ts +96 -0
- package/addon/-private/{fetch-manager.ts → network/fetch-manager.ts} +18 -16
- package/addon/-private/{finders.js → network/finders.js} +4 -12
- package/addon/-private/{request-cache.ts → network/request-cache.ts} +0 -0
- package/addon/-private/{snapshot-record-array.ts → network/snapshot-record-array.ts} +3 -3
- package/addon/-private/{snapshot.ts → network/snapshot.ts} +24 -31
- package/addon/-private/{promise-proxies.ts → proxies/promise-proxies.ts} +4 -4
- package/addon/-private/{promise-proxy-base.js → proxies/promise-proxy-base.js} +0 -0
- package/addon/-private/record-arrays/adapter-populated-record-array.ts +9 -11
- package/addon/-private/record-arrays/record-array.ts +16 -14
- package/addon/-private/{core-store.ts → store-service.ts} +186 -127
- package/addon/-private/{coerce-id.ts → utils/coerce-id.ts} +1 -1
- package/addon/-private/{common.js → utils/common.js} +1 -2
- package/addon/-private/utils/construct-resource.ts +2 -2
- package/addon/-private/{identifer-debug-consts.ts → utils/identifer-debug-consts.ts} +0 -0
- package/addon/-private/{normalize-model-name.ts → utils/normalize-model-name.ts} +0 -0
- package/addon/-private/utils/promise-record.ts +3 -3
- package/addon/-private/{serializer-response.ts → utils/serializer-response.ts} +2 -2
- package/addon/-private/{weak-cache.ts → utils/weak-cache.ts} +0 -0
- package/package.json +5 -5
- package/addon/-private/identity-map.ts +0 -54
- package/addon/-private/instance-cache.ts +0 -387
- package/addon/-private/internal-model-factory.ts +0 -359
- package/addon/-private/internal-model-map.ts +0 -121
- package/addon/-private/model/internal-model.ts +0 -600
- package/addon/-private/record-notification-manager.ts +0 -73
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
*/
|
|
4
4
|
import { getOwner, setOwner } from '@ember/application';
|
|
5
5
|
import { assert, deprecate } from '@ember/debug';
|
|
6
|
-
import { _backburner as emberBackburner } from '@ember/runloop';
|
|
6
|
+
import { _backburner as emberBackburner, run } from '@ember/runloop';
|
|
7
7
|
import type { Backburner } from '@ember/runloop/-private/backburner';
|
|
8
8
|
import Service from '@ember/service';
|
|
9
9
|
import { registerWaiter, unregisterWaiter } from '@ember/test';
|
|
@@ -17,7 +17,6 @@ import { HAS_MODEL_PACKAGE, HAS_RECORD_DATA_PACKAGE } from '@ember-data/private-
|
|
|
17
17
|
import {
|
|
18
18
|
DEPRECATE_HAS_RECORD,
|
|
19
19
|
DEPRECATE_JSON_API_FALLBACK,
|
|
20
|
-
DEPRECATE_RECORD_WAS_INVALID,
|
|
21
20
|
DEPRECATE_STORE_FIND,
|
|
22
21
|
} from '@ember-data/private-build-infra/deprecations';
|
|
23
22
|
import type { RecordData as RecordDataClass } from '@ember-data/record-data/-private';
|
|
@@ -33,38 +32,41 @@ import type { StableExistingRecordIdentifier, StableRecordIdentifier } from '@em
|
|
|
33
32
|
import type { MinimumAdapterInterface } from '@ember-data/types/q/minimum-adapter-interface';
|
|
34
33
|
import type { MinimumSerializerInterface } from '@ember-data/types/q/minimum-serializer-interface';
|
|
35
34
|
import type { RecordData } from '@ember-data/types/q/record-data';
|
|
36
|
-
import
|
|
35
|
+
import { JsonApiValidationError } from '@ember-data/types/q/record-data-json-api';
|
|
36
|
+
import type { RecordDataWrapper } from '@ember-data/types/q/record-data-record-wrapper';
|
|
37
37
|
import type { RecordInstance } from '@ember-data/types/q/record-instance';
|
|
38
38
|
import type { SchemaDefinitionService } from '@ember-data/types/q/schema-definition-service';
|
|
39
39
|
import type { FindOptions } from '@ember-data/types/q/store';
|
|
40
40
|
import type { Dict } from '@ember-data/types/q/utils';
|
|
41
41
|
|
|
42
42
|
import edBackburner from './backburner';
|
|
43
|
-
import
|
|
44
|
-
import FetchManager, { SaveOp } from './fetch-manager';
|
|
45
|
-
import { _findAll, _query, _queryRecord } from './finders';
|
|
46
|
-
import { IdentifierCache } from './identifier-cache';
|
|
47
|
-
import { InstanceCache, storeFor, StoreMap } from './instance-cache';
|
|
43
|
+
import { IdentifierCache } from './caches/identifier-cache';
|
|
48
44
|
import {
|
|
49
|
-
|
|
45
|
+
InstanceCache,
|
|
50
46
|
peekRecordIdentifier,
|
|
47
|
+
recordDataIsFullyDeleted,
|
|
51
48
|
recordIdentifierFor,
|
|
52
49
|
setRecordIdentifier,
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
import {
|
|
57
|
-
import
|
|
58
|
-
import {
|
|
59
|
-
import
|
|
50
|
+
storeFor,
|
|
51
|
+
StoreMap,
|
|
52
|
+
} from './caches/instance-cache';
|
|
53
|
+
import { setRecordDataFor } from './caches/record-data-for';
|
|
54
|
+
import RecordReference from './legacy-model-support/record-reference';
|
|
55
|
+
import { DSModelSchemaDefinitionService, getModelFactory } from './legacy-model-support/schema-definition-service';
|
|
56
|
+
import type ShimModelClass from './legacy-model-support/shim-model-class';
|
|
57
|
+
import { getShimClass } from './legacy-model-support/shim-model-class';
|
|
58
|
+
import RecordArrayManager from './managers/record-array-manager';
|
|
59
|
+
import RecordDataStoreWrapper from './managers/record-data-store-wrapper';
|
|
60
|
+
import NotificationManager from './managers/record-notification-manager';
|
|
61
|
+
import FetchManager, { SaveOp } from './network/fetch-manager';
|
|
62
|
+
import { _findAll, _query, _queryRecord } from './network/finders';
|
|
63
|
+
import type RequestCache from './network/request-cache';
|
|
64
|
+
import { PromiseArray, promiseArray, PromiseObject, promiseObject } from './proxies/promise-proxies';
|
|
60
65
|
import AdapterPopulatedRecordArray from './record-arrays/adapter-populated-record-array';
|
|
61
66
|
import RecordArray from './record-arrays/record-array';
|
|
62
|
-
import {
|
|
63
|
-
import RecordDataStoreWrapper from './record-data-store-wrapper';
|
|
64
|
-
import NotificationManager from './record-notification-manager';
|
|
65
|
-
import type RequestCache from './request-cache';
|
|
66
|
-
import { DSModelSchemaDefinitionService, getModelFactory } from './schema-definition-service';
|
|
67
|
+
import coerceId, { ensureStringId } from './utils/coerce-id';
|
|
67
68
|
import constructResource from './utils/construct-resource';
|
|
69
|
+
import normalizeModelName from './utils/normalize-model-name';
|
|
68
70
|
import promiseRecord from './utils/promise-record';
|
|
69
71
|
|
|
70
72
|
export { storeFor };
|
|
@@ -191,6 +193,7 @@ class Store extends Service {
|
|
|
191
193
|
declare _trackAsyncRequestStart: (str: string) => void;
|
|
192
194
|
declare _trackAsyncRequestEnd: (token: AsyncTrackingToken) => void;
|
|
193
195
|
declare __asyncWaiter: () => boolean;
|
|
196
|
+
declare DISABLE_WAITER?: boolean;
|
|
194
197
|
|
|
195
198
|
/**
|
|
196
199
|
@method init
|
|
@@ -268,7 +271,7 @@ class Store extends Service {
|
|
|
268
271
|
|
|
269
272
|
this.__asyncWaiter = () => {
|
|
270
273
|
let tracked = this._trackedAsyncRequests;
|
|
271
|
-
return tracked.length === 0;
|
|
274
|
+
return this.DISABLE_WAITER || tracked.length === 0;
|
|
272
275
|
};
|
|
273
276
|
|
|
274
277
|
registerWaiter(this.__asyncWaiter);
|
|
@@ -282,23 +285,22 @@ class Store extends Service {
|
|
|
282
285
|
instantiateRecord(
|
|
283
286
|
identifier: StableRecordIdentifier,
|
|
284
287
|
createRecordArgs: { [key: string]: unknown },
|
|
285
|
-
recordDataFor: (identifier: StableRecordIdentifier) =>
|
|
288
|
+
recordDataFor: (identifier: StableRecordIdentifier) => RecordDataWrapper,
|
|
286
289
|
notificationManager: NotificationManager
|
|
287
290
|
): DSModel | RecordInstance {
|
|
288
291
|
if (HAS_MODEL_PACKAGE) {
|
|
289
292
|
let modelName = identifier.type;
|
|
290
293
|
let store = this;
|
|
291
294
|
|
|
292
|
-
let
|
|
295
|
+
let recordData = this._instanceCache.getRecordData(identifier);
|
|
293
296
|
let createOptions: any = {
|
|
294
|
-
_internalModel: internalModel,
|
|
295
297
|
// TODO deprecate allowing unknown args setting
|
|
296
298
|
_createProps: createRecordArgs,
|
|
297
299
|
// TODO @deprecate consider deprecating accessing record properties during init which the below is necessary for
|
|
298
300
|
_secretInit: (record: RecordInstance): void => {
|
|
299
301
|
setRecordIdentifier(record, identifier);
|
|
300
302
|
StoreMap.set(record, store);
|
|
301
|
-
setRecordDataFor(record,
|
|
303
|
+
setRecordDataFor(record, recordData);
|
|
302
304
|
},
|
|
303
305
|
container: null, // necessary hack for setOwner?
|
|
304
306
|
};
|
|
@@ -306,7 +308,6 @@ class Store extends Service {
|
|
|
306
308
|
// ensure that `getOwner(this)` works inside a model instance
|
|
307
309
|
setOwner(createOptions, getOwner(this));
|
|
308
310
|
delete createOptions.container;
|
|
309
|
-
// TODO this needs to not use the private property here to get modelFactoryCache so as to not break interop
|
|
310
311
|
return getModelFactory(this, this._modelFactoryCache, modelName).create(createOptions);
|
|
311
312
|
}
|
|
312
313
|
assert(`You must implement the store's instantiateRecord hook for your custom model class.`);
|
|
@@ -326,6 +327,8 @@ class Store extends Service {
|
|
|
326
327
|
|
|
327
328
|
getSchemaDefinitionService(): SchemaDefinitionService {
|
|
328
329
|
if (HAS_MODEL_PACKAGE && !this._schemaDefinitionService) {
|
|
330
|
+
// it is potentially a mistake for the RFC to have not enabled chaining these services, though highlander rule is nice.
|
|
331
|
+
// what ember-m3 did via private API to allow both worlds to interop would be much much harder using this.
|
|
329
332
|
this._schemaDefinitionService = new DSModelSchemaDefinitionService(this);
|
|
330
333
|
}
|
|
331
334
|
assert(
|
|
@@ -367,10 +370,6 @@ class Store extends Service {
|
|
|
367
370
|
);
|
|
368
371
|
if (HAS_MODEL_PACKAGE) {
|
|
369
372
|
let normalizedModelName = normalizeModelName(modelName);
|
|
370
|
-
// TODO this is safe only because
|
|
371
|
-
// apps would be horribly broken if the schema service were using DS_MODEL but not using DS_MODEL's schema service.
|
|
372
|
-
// it is potentially a mistake for the RFC to have not enabled chaining these services, though highlander rule is nice.
|
|
373
|
-
// what ember-m3 did via private API to allow both worlds to interop would be much much harder using this.
|
|
374
373
|
let maybeFactory = getModelFactory(this, this._modelFactoryCache, normalizedModelName);
|
|
375
374
|
|
|
376
375
|
// for factorFor factory/class split
|
|
@@ -461,12 +460,20 @@ class Store extends Service {
|
|
|
461
460
|
|
|
462
461
|
// Coerce ID to a string
|
|
463
462
|
properties.id = coerceId(properties.id);
|
|
463
|
+
const resource = { type: normalizedModelName, id: properties.id };
|
|
464
464
|
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
const { identifier } = internalModel;
|
|
465
|
+
if (resource.id) {
|
|
466
|
+
const identifier = this.identifierCache.peekRecordIdentifier(resource as ResourceIdentifierObject);
|
|
468
467
|
|
|
469
|
-
|
|
468
|
+
assert(
|
|
469
|
+
`The id ${properties.id} has already been used with another '${normalizedModelName}' record.`,
|
|
470
|
+
!identifier
|
|
471
|
+
);
|
|
472
|
+
}
|
|
473
|
+
|
|
474
|
+
const identifier = this.identifierCache.createIdentifierForNewRecord(resource);
|
|
475
|
+
const recordData = this._instanceCache.getRecordData(identifier);
|
|
476
|
+
recordData.clientDidCreate();
|
|
470
477
|
this.recordArrayManager.recordDidChange(identifier);
|
|
471
478
|
|
|
472
479
|
return this._instanceCache.getRecord(identifier, properties);
|
|
@@ -495,13 +502,27 @@ class Store extends Service {
|
|
|
495
502
|
if (DEBUG) {
|
|
496
503
|
assertDestroyingStore(this, 'deleteRecord');
|
|
497
504
|
}
|
|
505
|
+
// TODO eliminate this interleaving
|
|
506
|
+
// it is unlikely we need both an outer join and the inner run
|
|
507
|
+
// of our own queue
|
|
498
508
|
this._backburner.join(() => {
|
|
499
|
-
|
|
509
|
+
const identifier = peekRecordIdentifier(record);
|
|
500
510
|
if (identifier) {
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
511
|
+
run(() => {
|
|
512
|
+
const backburner = this._backburner;
|
|
513
|
+
backburner.run(() => {
|
|
514
|
+
const recordData = this._instanceCache.peek({ identifier, bucket: 'recordData' });
|
|
515
|
+
|
|
516
|
+
if (recordData) {
|
|
517
|
+
if (recordData?.setIsDeleted) {
|
|
518
|
+
recordData.setIsDeleted(true);
|
|
519
|
+
}
|
|
520
|
+
if (recordData.isNew?.()) {
|
|
521
|
+
this._instanceCache.unloadRecord(identifier);
|
|
522
|
+
}
|
|
523
|
+
}
|
|
524
|
+
});
|
|
525
|
+
});
|
|
505
526
|
}
|
|
506
527
|
});
|
|
507
528
|
}
|
|
@@ -528,10 +549,7 @@ class Store extends Service {
|
|
|
528
549
|
}
|
|
529
550
|
let identifier = peekRecordIdentifier(record);
|
|
530
551
|
if (identifier) {
|
|
531
|
-
|
|
532
|
-
if (internalModel) {
|
|
533
|
-
internalModel.unloadRecord();
|
|
534
|
-
}
|
|
552
|
+
this._instanceCache.unloadRecord(identifier);
|
|
535
553
|
}
|
|
536
554
|
}
|
|
537
555
|
|
|
@@ -745,7 +763,7 @@ class Store extends Service {
|
|
|
745
763
|
// }
|
|
746
764
|
// ]
|
|
747
765
|
store.findRecord('post', 1, { reload: true }).then(function(post) {
|
|
748
|
-
post.
|
|
766
|
+
post.revision; // 2
|
|
749
767
|
});
|
|
750
768
|
```
|
|
751
769
|
|
|
@@ -783,7 +801,7 @@ class Store extends Service {
|
|
|
783
801
|
});
|
|
784
802
|
|
|
785
803
|
let blogPost = store.findRecord('post', 1).then(function(post) {
|
|
786
|
-
post.
|
|
804
|
+
post.revision; // 1
|
|
787
805
|
});
|
|
788
806
|
|
|
789
807
|
// later, once adapter#findRecord resolved with
|
|
@@ -795,7 +813,7 @@ class Store extends Service {
|
|
|
795
813
|
// }
|
|
796
814
|
// ]
|
|
797
815
|
|
|
798
|
-
blogPost.
|
|
816
|
+
blogPost.revision; // 2
|
|
799
817
|
```
|
|
800
818
|
|
|
801
819
|
If you would like to force or prevent background reloading, you can set a
|
|
@@ -983,13 +1001,12 @@ class Store extends Service {
|
|
|
983
1001
|
resource = constructResource(type, normalizedId);
|
|
984
1002
|
}
|
|
985
1003
|
|
|
986
|
-
const
|
|
987
|
-
const { identifier } = internalModel;
|
|
1004
|
+
const identifier = this.identifierCache.getOrCreateRecordIdentifier(resource);
|
|
988
1005
|
let promise;
|
|
989
1006
|
options = options || {};
|
|
990
1007
|
|
|
991
1008
|
// if not loaded start loading
|
|
992
|
-
if (!
|
|
1009
|
+
if (!this._instanceCache.recordIsLoaded(identifier)) {
|
|
993
1010
|
promise = this._instanceCache._fetchDataIfNeededForIdentifier(identifier, options);
|
|
994
1011
|
|
|
995
1012
|
// Refetch if the reload option is passed
|
|
@@ -1144,10 +1161,10 @@ class Store extends Service {
|
|
|
1144
1161
|
peekRecord(identifier: ResourceIdentifierObject | string, id?: string | number): RecordInstance | null {
|
|
1145
1162
|
if (arguments.length === 1 && isMaybeIdentifier(identifier)) {
|
|
1146
1163
|
const stableIdentifier = this.identifierCache.peekRecordIdentifier(identifier);
|
|
1147
|
-
const
|
|
1164
|
+
const isLoaded = stableIdentifier && this._instanceCache.recordIsLoaded(stableIdentifier);
|
|
1148
1165
|
// TODO come up with a better mechanism for determining if we have data and could peek.
|
|
1149
1166
|
// this is basically an "are we not empty" query.
|
|
1150
|
-
return
|
|
1167
|
+
return isLoaded ? this._instanceCache.getRecord(stableIdentifier) : null;
|
|
1151
1168
|
}
|
|
1152
1169
|
|
|
1153
1170
|
if (DEBUG) {
|
|
@@ -1164,9 +1181,9 @@ class Store extends Service {
|
|
|
1164
1181
|
const normalizedId = ensureStringId(id);
|
|
1165
1182
|
const resource = { type, id: normalizedId };
|
|
1166
1183
|
const stableIdentifier = this.identifierCache.peekRecordIdentifier(resource);
|
|
1167
|
-
const
|
|
1184
|
+
const isLoaded = stableIdentifier && this._instanceCache.recordIsLoaded(stableIdentifier);
|
|
1168
1185
|
|
|
1169
|
-
return
|
|
1186
|
+
return isLoaded ? this._instanceCache.getRecord(stableIdentifier) : null;
|
|
1170
1187
|
}
|
|
1171
1188
|
|
|
1172
1189
|
/**
|
|
@@ -1211,9 +1228,7 @@ class Store extends Service {
|
|
|
1211
1228
|
const resource = { type, id: trueId };
|
|
1212
1229
|
|
|
1213
1230
|
const identifier = this.identifierCache.peekRecordIdentifier(resource);
|
|
1214
|
-
|
|
1215
|
-
|
|
1216
|
-
return !!internalModel && internalModel.isLoaded;
|
|
1231
|
+
return Boolean(identifier && this._instanceCache.recordIsLoaded(identifier));
|
|
1217
1232
|
}
|
|
1218
1233
|
assert(`store.hasRecordForId has been removed`);
|
|
1219
1234
|
}
|
|
@@ -1336,8 +1351,8 @@ class Store extends Service {
|
|
|
1336
1351
|
|
|
1337
1352
|
```javascript
|
|
1338
1353
|
store.queryRecord('user', {}).then(function(user) {
|
|
1339
|
-
let username = user.
|
|
1340
|
-
|
|
1354
|
+
let username = user.username;
|
|
1355
|
+
// do thing
|
|
1341
1356
|
});
|
|
1342
1357
|
```
|
|
1343
1358
|
|
|
@@ -1374,9 +1389,9 @@ class Store extends Service {
|
|
|
1374
1389
|
|
|
1375
1390
|
```javascript
|
|
1376
1391
|
store.query('user', { username: 'unique' }).then(function(users) {
|
|
1377
|
-
return users.
|
|
1392
|
+
return users.firstObject;
|
|
1378
1393
|
}).then(function(user) {
|
|
1379
|
-
let id = user.
|
|
1394
|
+
let id = user.id;
|
|
1380
1395
|
});
|
|
1381
1396
|
```
|
|
1382
1397
|
|
|
@@ -1394,7 +1409,7 @@ class Store extends Service {
|
|
|
1394
1409
|
|
|
1395
1410
|
```javascript
|
|
1396
1411
|
store.queryRecord('user', { username: 'unique' }).then(function(user) {
|
|
1397
|
-
|
|
1412
|
+
// user is null
|
|
1398
1413
|
});
|
|
1399
1414
|
```
|
|
1400
1415
|
|
|
@@ -1752,50 +1767,14 @@ class Store extends Service {
|
|
|
1752
1767
|
!modelName || typeof modelName === 'string'
|
|
1753
1768
|
);
|
|
1754
1769
|
|
|
1755
|
-
const factory = internalModelFactoryFor(this);
|
|
1756
|
-
|
|
1757
1770
|
if (modelName === undefined) {
|
|
1758
|
-
|
|
1771
|
+
this._instanceCache.clear();
|
|
1759
1772
|
} else {
|
|
1760
1773
|
let normalizedModelName = normalizeModelName(modelName);
|
|
1761
|
-
|
|
1774
|
+
this._instanceCache.clear(normalizedModelName);
|
|
1762
1775
|
}
|
|
1763
1776
|
}
|
|
1764
1777
|
|
|
1765
|
-
/**
|
|
1766
|
-
This method is called once the promise returned by an
|
|
1767
|
-
adapter's `createRecord`, `updateRecord` or `deleteRecord`
|
|
1768
|
-
is rejected with a `InvalidError`.
|
|
1769
|
-
|
|
1770
|
-
@method recordWasInvalid
|
|
1771
|
-
@private
|
|
1772
|
-
@deprecated
|
|
1773
|
-
@param {InternalModel} internalModel
|
|
1774
|
-
@param {Object} errors
|
|
1775
|
-
*/
|
|
1776
|
-
recordWasInvalid(internalModel, parsedErrors, error) {
|
|
1777
|
-
if (DEPRECATE_RECORD_WAS_INVALID) {
|
|
1778
|
-
deprecate(
|
|
1779
|
-
`The private API recordWasInvalid will be removed in an upcoming release. Use record.errors add/remove instead if the intent was to move the record into an invalid state manually.`,
|
|
1780
|
-
false,
|
|
1781
|
-
{
|
|
1782
|
-
id: 'ember-data:deprecate-record-was-invalid',
|
|
1783
|
-
for: 'ember-data',
|
|
1784
|
-
until: '5.0',
|
|
1785
|
-
since: { enabled: '4.5', available: '4.5' },
|
|
1786
|
-
}
|
|
1787
|
-
);
|
|
1788
|
-
if (DEBUG) {
|
|
1789
|
-
assertDestroyingStore(this, 'recordWasInvalid');
|
|
1790
|
-
}
|
|
1791
|
-
error = error || new Error(`unknown invalid error`);
|
|
1792
|
-
error = typeof error === 'string' ? new Error(error) : error;
|
|
1793
|
-
error._parsedErrors = parsedErrors;
|
|
1794
|
-
internalModel.adapterDidInvalidate(error);
|
|
1795
|
-
}
|
|
1796
|
-
assert(`store.recordWasInvalid has been removed`);
|
|
1797
|
-
}
|
|
1798
|
-
|
|
1799
1778
|
/**
|
|
1800
1779
|
Push some data for a given type into the store.
|
|
1801
1780
|
|
|
@@ -1975,7 +1954,7 @@ class Store extends Service {
|
|
|
1975
1954
|
@method _push
|
|
1976
1955
|
@private
|
|
1977
1956
|
@param {Object} jsonApiDoc
|
|
1978
|
-
@return {
|
|
1957
|
+
@return {StableRecordIdentifier|Array<StableRecordIdentifier>} identifiers for the primary records that had data loaded
|
|
1979
1958
|
*/
|
|
1980
1959
|
_push(jsonApiDoc): StableExistingRecordIdentifier | StableExistingRecordIdentifier[] | null {
|
|
1981
1960
|
if (DEBUG) {
|
|
@@ -1987,7 +1966,7 @@ class Store extends Service {
|
|
|
1987
1966
|
|
|
1988
1967
|
if (included) {
|
|
1989
1968
|
for (i = 0, length = included.length; i < length; i++) {
|
|
1990
|
-
this._instanceCache.
|
|
1969
|
+
this._instanceCache.loadData(included[i]);
|
|
1991
1970
|
}
|
|
1992
1971
|
}
|
|
1993
1972
|
|
|
@@ -1996,7 +1975,7 @@ class Store extends Service {
|
|
|
1996
1975
|
let identifiers = new Array(length);
|
|
1997
1976
|
|
|
1998
1977
|
for (i = 0; i < length; i++) {
|
|
1999
|
-
identifiers[i] = this._instanceCache.
|
|
1978
|
+
identifiers[i] = this._instanceCache.loadData(jsonApiDoc.data[i]);
|
|
2000
1979
|
}
|
|
2001
1980
|
return identifiers;
|
|
2002
1981
|
}
|
|
@@ -2012,7 +1991,7 @@ class Store extends Service {
|
|
|
2012
1991
|
typeof jsonApiDoc.data === 'object'
|
|
2013
1992
|
);
|
|
2014
1993
|
|
|
2015
|
-
return this._instanceCache.
|
|
1994
|
+
return this._instanceCache.loadData(jsonApiDoc.data);
|
|
2016
1995
|
});
|
|
2017
1996
|
|
|
2018
1997
|
// this typecast is necessary because `backburner.join` is mistyped to return void
|
|
@@ -2115,37 +2094,35 @@ class Store extends Service {
|
|
|
2115
2094
|
saveRecord(record: RecordInstance, options: Dict<unknown> = {}): Promise<RecordInstance> {
|
|
2116
2095
|
assert(`Unable to initate save for a record in a disconnected state`, storeFor(record));
|
|
2117
2096
|
let identifier = recordIdentifierFor(record);
|
|
2118
|
-
let
|
|
2097
|
+
let recordData = identifier && this._instanceCache.peek({ identifier, bucket: 'recordData' });
|
|
2119
2098
|
|
|
2120
|
-
if (!
|
|
2099
|
+
if (!recordData) {
|
|
2121
2100
|
// this commonly means we're disconnected
|
|
2122
2101
|
// but just in case we reject here to prevent bad things.
|
|
2123
2102
|
return reject(`Record Is Disconnected`);
|
|
2124
2103
|
}
|
|
2125
2104
|
// TODO we used to check if the record was destroyed here
|
|
2126
|
-
// Casting can be removed once REQUEST_SERVICE ff is turned on
|
|
2127
|
-
// because a `Record` is provided there will always be a matching internalModel
|
|
2128
|
-
|
|
2129
2105
|
assert(
|
|
2130
2106
|
`Cannot initiate a save request for an unloaded record: ${identifier}`,
|
|
2131
|
-
|
|
2107
|
+
recordData && this._instanceCache.recordIsLoaded(identifier)
|
|
2132
2108
|
);
|
|
2133
|
-
if (
|
|
2109
|
+
if (recordDataIsFullyDeleted(this._instanceCache, identifier)) {
|
|
2134
2110
|
return resolve(record);
|
|
2135
2111
|
}
|
|
2136
2112
|
|
|
2137
|
-
|
|
2113
|
+
recordData.willCommit();
|
|
2114
|
+
if (isDSModel(record)) {
|
|
2115
|
+
record.errors.clear();
|
|
2116
|
+
}
|
|
2138
2117
|
|
|
2139
2118
|
if (!options) {
|
|
2140
2119
|
options = {};
|
|
2141
2120
|
}
|
|
2142
|
-
let recordData = this._instanceCache.getRecordData(identifier);
|
|
2143
2121
|
let operation: 'createRecord' | 'deleteRecord' | 'updateRecord' = 'updateRecord';
|
|
2144
2122
|
|
|
2145
|
-
|
|
2146
|
-
if (recordData.isNew && recordData.isNew()) {
|
|
2123
|
+
if (recordData.isNew?.()) {
|
|
2147
2124
|
operation = 'createRecord';
|
|
2148
|
-
} else if (recordData.isDeleted
|
|
2125
|
+
} else if (recordData.isDeleted?.()) {
|
|
2149
2126
|
operation = 'deleteRecord';
|
|
2150
2127
|
}
|
|
2151
2128
|
|
|
@@ -2173,20 +2150,21 @@ class Store extends Service {
|
|
|
2173
2150
|
let data = payload && payload.data;
|
|
2174
2151
|
if (!data) {
|
|
2175
2152
|
assert(
|
|
2176
|
-
`Your ${
|
|
2177
|
-
|
|
2153
|
+
`Your ${identifier.type} record was saved to the server, but the response does not have an id and no id has been set client side. Records must have ids. Please update the server response to provide an id in the response or generate the id on the client side either before saving the record or while normalizing the response.`,
|
|
2154
|
+
identifier.id
|
|
2178
2155
|
);
|
|
2179
2156
|
}
|
|
2180
2157
|
|
|
2181
2158
|
const cache = this.identifierCache;
|
|
2159
|
+
let actualIdentifier = identifier;
|
|
2182
2160
|
if (operation !== 'deleteRecord' && data) {
|
|
2183
|
-
cache.updateRecordIdentifier(identifier, data);
|
|
2161
|
+
actualIdentifier = cache.updateRecordIdentifier(identifier, data);
|
|
2184
2162
|
}
|
|
2185
2163
|
|
|
2186
2164
|
//We first make sure the primary data has been updated
|
|
2187
|
-
|
|
2188
|
-
|
|
2189
|
-
this.recordArrayManager.recordDidChange(
|
|
2165
|
+
const recordData = this._instanceCache.getRecordData(actualIdentifier);
|
|
2166
|
+
recordData.didCommit(data);
|
|
2167
|
+
this.recordArrayManager.recordDidChange(actualIdentifier);
|
|
2190
2168
|
|
|
2191
2169
|
if (payload && payload.included) {
|
|
2192
2170
|
this._push({ data: null, included: payload.included });
|
|
@@ -2201,7 +2179,7 @@ class Store extends Service {
|
|
|
2201
2179
|
} else if (typeof e === 'string') {
|
|
2202
2180
|
err = new Error(e);
|
|
2203
2181
|
}
|
|
2204
|
-
|
|
2182
|
+
adapterDidInvalidate(this, identifier, err);
|
|
2205
2183
|
throw err;
|
|
2206
2184
|
}
|
|
2207
2185
|
);
|
|
@@ -2215,13 +2193,13 @@ class Store extends Service {
|
|
|
2215
2193
|
* @public
|
|
2216
2194
|
* @param modelName
|
|
2217
2195
|
* @param id
|
|
2218
|
-
* @param
|
|
2196
|
+
* @param lid
|
|
2219
2197
|
* @param storeWrapper
|
|
2220
2198
|
*/
|
|
2221
2199
|
createRecordDataFor(
|
|
2222
2200
|
modelName: string,
|
|
2223
2201
|
id: string | null,
|
|
2224
|
-
|
|
2202
|
+
lid: string,
|
|
2225
2203
|
storeWrapper: RecordDataStoreWrapper
|
|
2226
2204
|
): RecordData {
|
|
2227
2205
|
if (HAS_RECORD_DATA_PACKAGE) {
|
|
@@ -2233,13 +2211,13 @@ class Store extends Service {
|
|
|
2233
2211
|
if (_RecordData === undefined) {
|
|
2234
2212
|
_RecordData = (
|
|
2235
2213
|
importSync('@ember-data/record-data/-private') as typeof import('@ember-data/record-data/-private')
|
|
2236
|
-
).RecordData
|
|
2214
|
+
).RecordData;
|
|
2237
2215
|
}
|
|
2238
2216
|
|
|
2239
2217
|
let identifier = this.identifierCache.getOrCreateRecordIdentifier({
|
|
2240
2218
|
type: modelName,
|
|
2241
2219
|
id,
|
|
2242
|
-
lid
|
|
2220
|
+
lid,
|
|
2243
2221
|
});
|
|
2244
2222
|
return new _RecordData(identifier, storeWrapper);
|
|
2245
2223
|
}
|
|
@@ -2515,3 +2493,84 @@ export function assertIdentifierHasId(
|
|
|
2515
2493
|
): asserts identifier is StableExistingRecordIdentifier {
|
|
2516
2494
|
assert(`Attempted to schedule a fetch for a record without an id.`, identifier.id !== null);
|
|
2517
2495
|
}
|
|
2496
|
+
|
|
2497
|
+
function isDSModel(record: RecordInstance | null): record is DSModel {
|
|
2498
|
+
return (
|
|
2499
|
+
HAS_MODEL_PACKAGE &&
|
|
2500
|
+
!!record &&
|
|
2501
|
+
'constructor' in record &&
|
|
2502
|
+
'isModel' in record.constructor &&
|
|
2503
|
+
record.constructor.isModel === true
|
|
2504
|
+
);
|
|
2505
|
+
}
|
|
2506
|
+
|
|
2507
|
+
type AdapterErrors = Error & { errors?: unknown[]; isAdapterError?: true; code?: string };
|
|
2508
|
+
type SerializerWithParseErrors = MinimumSerializerInterface & {
|
|
2509
|
+
extractErrors?(store: Store, modelClass: ShimModelClass, error: AdapterErrors, recordId: string | null): any;
|
|
2510
|
+
};
|
|
2511
|
+
|
|
2512
|
+
function adapterDidInvalidate(
|
|
2513
|
+
store: Store,
|
|
2514
|
+
identifier: StableRecordIdentifier,
|
|
2515
|
+
error: Error & { errors?: JsonApiValidationError[]; isAdapterError?: true; code?: string }
|
|
2516
|
+
) {
|
|
2517
|
+
if (error && error.isAdapterError === true && error.code === 'InvalidError') {
|
|
2518
|
+
let serializer = store.serializerFor(identifier.type) as SerializerWithParseErrors;
|
|
2519
|
+
|
|
2520
|
+
// TODO @deprecate extractErrors being called
|
|
2521
|
+
// TODO remove extractErrors from the default serializers.
|
|
2522
|
+
if (serializer && typeof serializer.extractErrors === 'function') {
|
|
2523
|
+
let errorsHash = serializer.extractErrors(store, store.modelFor(identifier.type), error, identifier.id);
|
|
2524
|
+
error.errors = errorsHashToArray(errorsHash);
|
|
2525
|
+
}
|
|
2526
|
+
}
|
|
2527
|
+
const recordData = store._instanceCache.getRecordData(identifier);
|
|
2528
|
+
|
|
2529
|
+
if (error.errors) {
|
|
2530
|
+
assert(
|
|
2531
|
+
`Expected the RecordData implementation for ${identifier} to have a getErrors(identifier) method for retreiving errors.`,
|
|
2532
|
+
typeof recordData.getErrors === 'function'
|
|
2533
|
+
);
|
|
2534
|
+
|
|
2535
|
+
let jsonApiErrors: JsonApiValidationError[] = error.errors;
|
|
2536
|
+
if (jsonApiErrors.length === 0) {
|
|
2537
|
+
jsonApiErrors = [{ title: 'Invalid Error', detail: '', source: { pointer: '/data' } }];
|
|
2538
|
+
}
|
|
2539
|
+
recordData.commitWasRejected(identifier, jsonApiErrors);
|
|
2540
|
+
} else {
|
|
2541
|
+
recordData.commitWasRejected(identifier);
|
|
2542
|
+
}
|
|
2543
|
+
}
|
|
2544
|
+
|
|
2545
|
+
function makeArray(value) {
|
|
2546
|
+
return Array.isArray(value) ? value : [value];
|
|
2547
|
+
}
|
|
2548
|
+
|
|
2549
|
+
const PRIMARY_ATTRIBUTE_KEY = 'base';
|
|
2550
|
+
|
|
2551
|
+
function errorsHashToArray(errors): JsonApiValidationError[] {
|
|
2552
|
+
const out: JsonApiValidationError[] = [];
|
|
2553
|
+
|
|
2554
|
+
if (errors) {
|
|
2555
|
+
Object.keys(errors).forEach((key) => {
|
|
2556
|
+
let messages = makeArray(errors[key]);
|
|
2557
|
+
for (let i = 0; i < messages.length; i++) {
|
|
2558
|
+
let title = 'Invalid Attribute';
|
|
2559
|
+
let pointer = `/data/attributes/${key}`;
|
|
2560
|
+
if (key === PRIMARY_ATTRIBUTE_KEY) {
|
|
2561
|
+
title = 'Invalid Document';
|
|
2562
|
+
pointer = `/data`;
|
|
2563
|
+
}
|
|
2564
|
+
out.push({
|
|
2565
|
+
title: title,
|
|
2566
|
+
detail: messages[i],
|
|
2567
|
+
source: {
|
|
2568
|
+
pointer: pointer,
|
|
2569
|
+
},
|
|
2570
|
+
});
|
|
2571
|
+
}
|
|
2572
|
+
});
|
|
2573
|
+
}
|
|
2574
|
+
|
|
2575
|
+
return out;
|
|
2576
|
+
}
|
|
@@ -1,5 +1,4 @@
|
|
|
1
1
|
import { deprecate } from '@ember/debug';
|
|
2
|
-
import { get } from '@ember/object';
|
|
3
2
|
import { DEBUG } from '@glimmer/env';
|
|
4
3
|
|
|
5
4
|
import { resolve } from 'rsvp';
|
|
@@ -27,7 +26,7 @@ export function _guard(promise, test) {
|
|
|
27
26
|
}
|
|
28
27
|
|
|
29
28
|
export function _objectIsAlive(object) {
|
|
30
|
-
return !(
|
|
29
|
+
return !(object.isDestroyed || object.isDestroying);
|
|
31
30
|
}
|
|
32
31
|
|
|
33
32
|
export function guardDestroyedStore(promise, store, label) {
|
|
@@ -5,8 +5,8 @@ import type {
|
|
|
5
5
|
ResourceIdentifierObject,
|
|
6
6
|
} from '@ember-data/types/q/ember-data-json-api';
|
|
7
7
|
|
|
8
|
-
import
|
|
9
|
-
import
|
|
8
|
+
import { isStableIdentifier } from '../caches/identifier-cache';
|
|
9
|
+
import coerceId from './coerce-id';
|
|
10
10
|
import isNonEmptyString from './is-non-empty-string';
|
|
11
11
|
|
|
12
12
|
function constructResource(type: ResourceIdentifierObject): ResourceIdentifierObject;
|
|
File without changes
|
|
File without changes
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
import type { StableRecordIdentifier } from '@ember-data/types/q/identifier';
|
|
2
2
|
import type { RecordInstance } from '@ember-data/types/q/record-instance';
|
|
3
3
|
|
|
4
|
-
import type
|
|
5
|
-
import
|
|
6
|
-
import
|
|
4
|
+
import type { PromiseObject } from '../proxies/promise-proxies';
|
|
5
|
+
import { promiseObject } from '../proxies/promise-proxies';
|
|
6
|
+
import type Store from '../store-service';
|
|
7
7
|
|
|
8
8
|
export default function promiseRecord(
|
|
9
9
|
store: Store,
|