@ember-data/store 4.2.0-alpha.8 → 4.2.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/system/core-store.ts +466 -140
- package/addon/-private/system/ds-model-store.ts +63 -12
- package/addon/-private/system/fetch-manager.ts +4 -9
- package/addon/-private/system/model/internal-model.ts +342 -90
- package/addon/-private/system/model/states.js +14 -2
- package/addon/-private/system/record-array-manager.js +30 -3
- package/addon/-private/system/references/belongs-to.ts +26 -11
- package/addon/-private/system/references/has-many.ts +33 -14
- package/addon/-private/system/references/record.ts +23 -9
- package/addon/-private/system/snapshot.ts +48 -22
- package/addon/-private/system/store/finders.js +58 -2
- package/addon/-private/system/store/record-data-store-wrapper.ts +24 -17
- package/addon/-private/ts-interfaces/fetch-manager.ts +0 -4
- package/package.json +10 -10
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
import { getOwner } from '@ember/application';
|
|
5
5
|
import { A } from '@ember/array';
|
|
6
6
|
import { assert, deprecate, inspect, warn } from '@ember/debug';
|
|
7
|
-
import { computed, defineProperty, set } from '@ember/object';
|
|
7
|
+
import { computed, defineProperty, get, set } from '@ember/object';
|
|
8
8
|
import { _backburner as emberBackburner } from '@ember/runloop';
|
|
9
9
|
import type { Backburner } from '@ember/runloop/-private/backburner';
|
|
10
10
|
import Service from '@ember/service';
|
|
@@ -14,8 +14,14 @@ import { DEBUG } from '@glimmer/env';
|
|
|
14
14
|
import Ember from 'ember';
|
|
15
15
|
|
|
16
16
|
import require from 'require';
|
|
17
|
-
import { all, default as RSVP, Promise, resolve } from 'rsvp';
|
|
17
|
+
import { all, default as RSVP, defer, Promise, resolve } from 'rsvp';
|
|
18
18
|
|
|
19
|
+
import {
|
|
20
|
+
CUSTOM_MODEL_CLASS,
|
|
21
|
+
RECORD_DATA_ERRORS,
|
|
22
|
+
RECORD_DATA_STATE,
|
|
23
|
+
REQUEST_SERVICE,
|
|
24
|
+
} from '@ember-data/canary-features';
|
|
19
25
|
import {
|
|
20
26
|
HAS_ADAPTER_PACKAGE,
|
|
21
27
|
HAS_EMBER_DATA_PACKAGE,
|
|
@@ -24,6 +30,7 @@ import {
|
|
|
24
30
|
} from '@ember-data/private-build-infra';
|
|
25
31
|
import {
|
|
26
32
|
DEPRECATE_DEFAULT_ADAPTER,
|
|
33
|
+
DEPRECATE_DEFAULT_SERIALIZER,
|
|
27
34
|
DEPRECATE_LEGACY_TEST_REGISTRATIONS,
|
|
28
35
|
} from '@ember-data/private-build-infra/deprecations';
|
|
29
36
|
import type {
|
|
@@ -62,6 +69,7 @@ import constructResource from '../utils/construct-resource';
|
|
|
62
69
|
import promiseRecord from '../utils/promise-record';
|
|
63
70
|
import edBackburner from './backburner';
|
|
64
71
|
import coerceId, { ensureStringId } from './coerce-id';
|
|
72
|
+
import { errorsArrayToHash } from './errors-utils';
|
|
65
73
|
import FetchManager, { SaveOp } from './fetch-manager';
|
|
66
74
|
import type InternalModel from './model/internal-model';
|
|
67
75
|
import {
|
|
@@ -79,7 +87,8 @@ import NotificationManager from './record-notification-manager';
|
|
|
79
87
|
import type { BelongsToReference, HasManyReference } from './references';
|
|
80
88
|
import { RecordReference } from './references';
|
|
81
89
|
import type RequestCache from './request-cache';
|
|
82
|
-
import type { default as Snapshot } from './snapshot';
|
|
90
|
+
import type { default as Snapshot, PrivateSnapshot } from './snapshot';
|
|
91
|
+
import { _bind, _guard, _objectIsAlive, guardDestroyedStore } from './store/common';
|
|
83
92
|
import { _find, _findAll, _findBelongsTo, _findHasMany, _findMany, _query, _queryRecord } from './store/finders';
|
|
84
93
|
import {
|
|
85
94
|
internalModelFactoryFor,
|
|
@@ -88,6 +97,7 @@ import {
|
|
|
88
97
|
setRecordIdentifier,
|
|
89
98
|
} from './store/internal-model-factory';
|
|
90
99
|
import RecordDataStoreWrapper from './store/record-data-store-wrapper';
|
|
100
|
+
import { normalizeResponseHelper } from './store/serializer-response';
|
|
91
101
|
|
|
92
102
|
type RecordDataConstruct = typeof RecordDataClass;
|
|
93
103
|
let _RecordData: RecordDataConstruct | undefined;
|
|
@@ -301,9 +311,13 @@ abstract class CoreStore extends Service {
|
|
|
301
311
|
constructor() {
|
|
302
312
|
super(...arguments);
|
|
303
313
|
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
314
|
+
if (REQUEST_SERVICE) {
|
|
315
|
+
this._fetchManager = new FetchManager(this);
|
|
316
|
+
}
|
|
317
|
+
if (CUSTOM_MODEL_CLASS) {
|
|
318
|
+
this._notificationManager = new NotificationManager(this);
|
|
319
|
+
this.__recordDataFor = this.__recordDataFor.bind(this);
|
|
320
|
+
}
|
|
307
321
|
|
|
308
322
|
if (DEBUG) {
|
|
309
323
|
if (HAS_EMBER_DATA_PACKAGE && HAS_SERIALIZER_PACKAGE) {
|
|
@@ -408,7 +422,10 @@ abstract class CoreStore extends Service {
|
|
|
408
422
|
}
|
|
409
423
|
|
|
410
424
|
getRequestStateService(): RequestCache {
|
|
411
|
-
|
|
425
|
+
if (REQUEST_SERVICE) {
|
|
426
|
+
return this._fetchManager.requestCache;
|
|
427
|
+
}
|
|
428
|
+
assert('RequestService is not available unless the feature flag is on and running on a canary build', false);
|
|
412
429
|
}
|
|
413
430
|
|
|
414
431
|
/**
|
|
@@ -432,51 +449,55 @@ abstract class CoreStore extends Service {
|
|
|
432
449
|
identifier: StableRecordIdentifier,
|
|
433
450
|
properties?: { [key: string]: any }
|
|
434
451
|
) {
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
if ('id' in properties) {
|
|
443
|
-
internalModel.setId(properties.id);
|
|
444
|
-
}
|
|
445
|
-
|
|
446
|
-
// convert relationship Records to RecordDatas before passing to RecordData
|
|
447
|
-
let defs = this._relationshipsDefinitionFor(modelName);
|
|
448
|
-
|
|
449
|
-
if (defs !== null) {
|
|
450
|
-
let keys = Object.keys(properties);
|
|
451
|
-
let relationshipValue;
|
|
452
|
+
if (CUSTOM_MODEL_CLASS) {
|
|
453
|
+
// assert here
|
|
454
|
+
if (properties !== undefined) {
|
|
455
|
+
assert(
|
|
456
|
+
`You passed '${properties}' as properties for record creation instead of an object.`,
|
|
457
|
+
typeof properties === 'object' && properties !== null
|
|
458
|
+
);
|
|
452
459
|
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
460
|
+
if ('id' in properties) {
|
|
461
|
+
internalModel.setId(properties.id);
|
|
462
|
+
}
|
|
456
463
|
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
464
|
+
// convert relationship Records to RecordDatas before passing to RecordData
|
|
465
|
+
let defs = this._relationshipsDefinitionFor(modelName);
|
|
466
|
+
|
|
467
|
+
if (defs !== null) {
|
|
468
|
+
let keys = Object.keys(properties);
|
|
469
|
+
let relationshipValue;
|
|
470
|
+
|
|
471
|
+
for (let i = 0; i < keys.length; i++) {
|
|
472
|
+
let prop = keys[i];
|
|
473
|
+
let def = defs[prop];
|
|
474
|
+
|
|
475
|
+
if (def !== undefined) {
|
|
476
|
+
if (def.kind === 'hasMany') {
|
|
477
|
+
if (DEBUG) {
|
|
478
|
+
assertRecordsPassedToHasMany(properties[prop]);
|
|
479
|
+
}
|
|
480
|
+
relationshipValue = extractRecordDatasFromRecords(properties[prop]);
|
|
481
|
+
} else {
|
|
482
|
+
relationshipValue = extractRecordDataFromRecord(properties[prop]);
|
|
461
483
|
}
|
|
462
|
-
relationshipValue = extractRecordDatasFromRecords(properties[prop]);
|
|
463
|
-
} else {
|
|
464
|
-
relationshipValue = extractRecordDataFromRecord(properties[prop]);
|
|
465
|
-
}
|
|
466
484
|
|
|
467
|
-
|
|
485
|
+
properties[prop] = relationshipValue;
|
|
486
|
+
}
|
|
468
487
|
}
|
|
469
488
|
}
|
|
470
489
|
}
|
|
490
|
+
|
|
491
|
+
// TODO guard against initRecordOptions no being there
|
|
492
|
+
let createOptions = recordData._initRecordCreateOptions(properties);
|
|
493
|
+
//TODO Igor pass a wrapper instead of RD
|
|
494
|
+
let record = this.instantiateRecord(identifier, createOptions, this.__recordDataFor, this._notificationManager);
|
|
495
|
+
setRecordIdentifier(record, identifier);
|
|
496
|
+
//recordToInternalModelMap.set(record, internalModel);
|
|
497
|
+
return record;
|
|
471
498
|
}
|
|
472
499
|
|
|
473
|
-
|
|
474
|
-
let createOptions = recordData._initRecordCreateOptions(properties);
|
|
475
|
-
//TODO Igor pass a wrapper instead of RD
|
|
476
|
-
let record = this.instantiateRecord(identifier, createOptions, this.__recordDataFor, this._notificationManager);
|
|
477
|
-
setRecordIdentifier(record, identifier);
|
|
478
|
-
//recordToInternalModelMap.set(record, internalModel);
|
|
479
|
-
return record;
|
|
500
|
+
assert('should not be here, custom model class ff error', false);
|
|
480
501
|
}
|
|
481
502
|
|
|
482
503
|
abstract instantiateRecord(
|
|
@@ -514,7 +535,10 @@ abstract class CoreStore extends Service {
|
|
|
514
535
|
}
|
|
515
536
|
|
|
516
537
|
getSchemaDefinitionService(): SchemaDefinitionService {
|
|
517
|
-
|
|
538
|
+
if (CUSTOM_MODEL_CLASS) {
|
|
539
|
+
return this._schemaDefinitionService;
|
|
540
|
+
}
|
|
541
|
+
assert('need to enable CUSTOM_MODEL_CLASS feature flag in order to access SchemaDefinitionService');
|
|
518
542
|
}
|
|
519
543
|
|
|
520
544
|
// TODO Double check this return value is correct
|
|
@@ -692,12 +716,16 @@ abstract class CoreStore extends Service {
|
|
|
692
716
|
assertDestroyingStore(this, 'deleteRecord');
|
|
693
717
|
}
|
|
694
718
|
this._backburner.join(() => {
|
|
695
|
-
|
|
696
|
-
|
|
697
|
-
|
|
698
|
-
|
|
699
|
-
internalModel
|
|
719
|
+
if (CUSTOM_MODEL_CLASS) {
|
|
720
|
+
let identifier = peekRecordIdentifier(record);
|
|
721
|
+
if (identifier) {
|
|
722
|
+
let internalModel = internalModelFactoryFor(this).peek(identifier);
|
|
723
|
+
if (internalModel) {
|
|
724
|
+
internalModel.deleteRecord();
|
|
725
|
+
}
|
|
700
726
|
}
|
|
727
|
+
} else {
|
|
728
|
+
record.deleteRecord();
|
|
701
729
|
}
|
|
702
730
|
});
|
|
703
731
|
}
|
|
@@ -722,12 +750,16 @@ abstract class CoreStore extends Service {
|
|
|
722
750
|
if (DEBUG) {
|
|
723
751
|
assertDestroyingStore(this, 'unloadRecord');
|
|
724
752
|
}
|
|
725
|
-
|
|
726
|
-
|
|
727
|
-
|
|
728
|
-
|
|
729
|
-
internalModel
|
|
753
|
+
if (CUSTOM_MODEL_CLASS) {
|
|
754
|
+
let identifier = peekRecordIdentifier(record);
|
|
755
|
+
if (identifier) {
|
|
756
|
+
let internalModel = internalModelFactoryFor(this).peek(identifier);
|
|
757
|
+
if (internalModel) {
|
|
758
|
+
internalModel.unloadRecord();
|
|
759
|
+
}
|
|
730
760
|
}
|
|
761
|
+
} else {
|
|
762
|
+
record.unloadRecord();
|
|
731
763
|
}
|
|
732
764
|
}
|
|
733
765
|
|
|
@@ -1216,7 +1248,7 @@ abstract class CoreStore extends Service {
|
|
|
1216
1248
|
return Promise.resolve(internalModel);
|
|
1217
1249
|
}
|
|
1218
1250
|
|
|
1219
|
-
_findByInternalModel(internalModel
|
|
1251
|
+
_findByInternalModel(internalModel, options: { preload?: any } = {}) {
|
|
1220
1252
|
if (options.preload) {
|
|
1221
1253
|
this._backburner.join(() => {
|
|
1222
1254
|
internalModel.preloadData(options.preload);
|
|
@@ -1231,17 +1263,24 @@ abstract class CoreStore extends Service {
|
|
|
1231
1263
|
);
|
|
1232
1264
|
}
|
|
1233
1265
|
|
|
1234
|
-
_findEmptyInternalModel(internalModel
|
|
1266
|
+
_findEmptyInternalModel(internalModel, options) {
|
|
1235
1267
|
if (internalModel.currentState.isEmpty) {
|
|
1236
1268
|
return this._scheduleFetch(internalModel, options);
|
|
1237
1269
|
}
|
|
1238
1270
|
|
|
1239
|
-
|
|
1240
|
-
|
|
1241
|
-
if (
|
|
1242
|
-
return
|
|
1271
|
+
//TODO double check about reloading
|
|
1272
|
+
if (!REQUEST_SERVICE) {
|
|
1273
|
+
if (internalModel.currentState.isLoading) {
|
|
1274
|
+
return internalModel._promiseProxy;
|
|
1275
|
+
}
|
|
1276
|
+
} else {
|
|
1277
|
+
if (internalModel.currentState.isLoading) {
|
|
1278
|
+
let pending = this._fetchManager.getPendingFetch(internalModel.identifier);
|
|
1279
|
+
if (pending) {
|
|
1280
|
+
return pending.then(() => Promise.resolve(internalModel));
|
|
1281
|
+
}
|
|
1282
|
+
return this._scheduleFetch(internalModel, options);
|
|
1243
1283
|
}
|
|
1244
|
-
return this._scheduleFetch(internalModel, options);
|
|
1245
1284
|
}
|
|
1246
1285
|
|
|
1247
1286
|
return Promise.resolve(internalModel);
|
|
@@ -1347,12 +1386,74 @@ abstract class CoreStore extends Service {
|
|
|
1347
1386
|
}
|
|
1348
1387
|
|
|
1349
1388
|
_scheduleFetch(internalModel: InternalModel, options): RSVP.Promise<InternalModel> {
|
|
1350
|
-
|
|
1389
|
+
if (REQUEST_SERVICE) {
|
|
1390
|
+
return this._scheduleFetchThroughFetchManager(internalModel, options);
|
|
1391
|
+
} else {
|
|
1392
|
+
if (internalModel._promiseProxy) {
|
|
1393
|
+
return internalModel._promiseProxy;
|
|
1394
|
+
}
|
|
1395
|
+
|
|
1396
|
+
assertIdentifierHasId(internalModel.identifier);
|
|
1397
|
+
|
|
1398
|
+
let { id, modelName } = internalModel;
|
|
1399
|
+
let resolver = defer<InternalModel>(`Fetching ${modelName}' with id: ${id}`);
|
|
1400
|
+
let pendingFetchItem: PendingFetchItem = {
|
|
1401
|
+
internalModel,
|
|
1402
|
+
resolver,
|
|
1403
|
+
options,
|
|
1404
|
+
};
|
|
1405
|
+
|
|
1406
|
+
if (DEBUG) {
|
|
1407
|
+
if (this.generateStackTracesForTrackedRequests === true) {
|
|
1408
|
+
let trace;
|
|
1409
|
+
|
|
1410
|
+
try {
|
|
1411
|
+
throw new Error(`Trace Origin for scheduled fetch for ${modelName}:${id}.`);
|
|
1412
|
+
} catch (e) {
|
|
1413
|
+
trace = e;
|
|
1414
|
+
}
|
|
1415
|
+
|
|
1416
|
+
// enable folks to discover the origin of this findRecord call when
|
|
1417
|
+
// debugging. Ideally we would have a tracked queue for requests with
|
|
1418
|
+
// labels or local IDs that could be used to merge this trace with
|
|
1419
|
+
// the trace made available when we detect an async leak
|
|
1420
|
+
pendingFetchItem.trace = trace;
|
|
1421
|
+
}
|
|
1422
|
+
}
|
|
1423
|
+
|
|
1424
|
+
let promise = resolver.promise;
|
|
1425
|
+
|
|
1426
|
+
internalModel.send('loadingData', promise);
|
|
1427
|
+
if (this._pendingFetch.size === 0) {
|
|
1428
|
+
emberBackburner.schedule('actions', this, this.flushAllPendingFetches);
|
|
1429
|
+
}
|
|
1430
|
+
|
|
1431
|
+
let fetches = this._pendingFetch;
|
|
1432
|
+
let pending = fetches.get(modelName);
|
|
1433
|
+
|
|
1434
|
+
if (pending === undefined) {
|
|
1435
|
+
pending = [];
|
|
1436
|
+
fetches.set(modelName, pending);
|
|
1437
|
+
}
|
|
1438
|
+
|
|
1439
|
+
pending.push(pendingFetchItem);
|
|
1440
|
+
|
|
1441
|
+
return promise;
|
|
1442
|
+
}
|
|
1351
1443
|
}
|
|
1352
1444
|
|
|
1353
1445
|
flushAllPendingFetches() {
|
|
1354
|
-
|
|
1355
|
-
|
|
1446
|
+
if (REQUEST_SERVICE) {
|
|
1447
|
+
return;
|
|
1448
|
+
//assert here
|
|
1449
|
+
} else {
|
|
1450
|
+
if (this.isDestroyed || this.isDestroying) {
|
|
1451
|
+
return;
|
|
1452
|
+
}
|
|
1453
|
+
|
|
1454
|
+
this._pendingFetch.forEach(this._flushPendingFetchForType, this);
|
|
1455
|
+
this._pendingFetch.clear();
|
|
1456
|
+
}
|
|
1356
1457
|
}
|
|
1357
1458
|
|
|
1358
1459
|
_flushPendingFetchForType(pendingFetchItems: PendingFetchItem[], modelName: string) {
|
|
@@ -1668,7 +1769,9 @@ abstract class CoreStore extends Service {
|
|
|
1668
1769
|
@return {Promise} promise
|
|
1669
1770
|
*/
|
|
1670
1771
|
_reloadRecord(internalModel, options): RSVP.Promise<InternalModel> {
|
|
1671
|
-
|
|
1772
|
+
if (REQUEST_SERVICE) {
|
|
1773
|
+
options.isReloading = true;
|
|
1774
|
+
}
|
|
1672
1775
|
let { id, modelName } = internalModel;
|
|
1673
1776
|
let adapter = this.adapterFor(modelName);
|
|
1674
1777
|
|
|
@@ -1915,9 +2018,17 @@ abstract class CoreStore extends Service {
|
|
|
1915
2018
|
|
|
1916
2019
|
if (internalModel) {
|
|
1917
2020
|
// short circuit if we are already loading
|
|
1918
|
-
|
|
1919
|
-
|
|
1920
|
-
|
|
2021
|
+
if (REQUEST_SERVICE) {
|
|
2022
|
+
let pendingRequest = this._fetchManager.getPendingFetch(internalModel.identifier);
|
|
2023
|
+
if (pendingRequest) {
|
|
2024
|
+
return pendingRequest.then(() => internalModel.getRecord());
|
|
2025
|
+
}
|
|
2026
|
+
} else {
|
|
2027
|
+
if (internalModel.currentState.isLoading) {
|
|
2028
|
+
return internalModel._promiseProxy.then(() => {
|
|
2029
|
+
return internalModel.getRecord();
|
|
2030
|
+
});
|
|
2031
|
+
}
|
|
1921
2032
|
}
|
|
1922
2033
|
}
|
|
1923
2034
|
|
|
@@ -1940,10 +2051,6 @@ abstract class CoreStore extends Service {
|
|
|
1940
2051
|
return resolve(null);
|
|
1941
2052
|
}
|
|
1942
2053
|
|
|
1943
|
-
if (!internalModel) {
|
|
1944
|
-
assert(`No InternalModel found for ${resource.lid}`, internalModel);
|
|
1945
|
-
}
|
|
1946
|
-
|
|
1947
2054
|
return this._findByInternalModel(internalModel, options);
|
|
1948
2055
|
}
|
|
1949
2056
|
|
|
@@ -2550,32 +2657,33 @@ abstract class CoreStore extends Service {
|
|
|
2550
2657
|
@param {Object} options
|
|
2551
2658
|
*/
|
|
2552
2659
|
scheduleSave(internalModel: InternalModel, resolver: RSVP.Deferred<void>, options): void | RSVP.Promise<void> {
|
|
2553
|
-
if (
|
|
2554
|
-
|
|
2555
|
-
|
|
2556
|
-
|
|
2660
|
+
if (REQUEST_SERVICE) {
|
|
2661
|
+
if (internalModel._isRecordFullyDeleted()) {
|
|
2662
|
+
resolver.resolve();
|
|
2663
|
+
return resolver.promise;
|
|
2664
|
+
}
|
|
2557
2665
|
|
|
2558
|
-
|
|
2666
|
+
internalModel.adapterWillCommit();
|
|
2559
2667
|
|
|
2560
|
-
|
|
2561
|
-
|
|
2562
|
-
|
|
2563
|
-
|
|
2564
|
-
|
|
2668
|
+
if (!options) {
|
|
2669
|
+
options = {};
|
|
2670
|
+
}
|
|
2671
|
+
let recordData = internalModel._recordData;
|
|
2672
|
+
let operation: 'createRecord' | 'deleteRecord' | 'updateRecord' = 'updateRecord';
|
|
2565
2673
|
|
|
2566
|
-
|
|
2567
|
-
|
|
2568
|
-
|
|
2569
|
-
|
|
2570
|
-
|
|
2571
|
-
|
|
2674
|
+
// TODO handle missing isNew
|
|
2675
|
+
if (recordData.isNew && recordData.isNew()) {
|
|
2676
|
+
operation = 'createRecord';
|
|
2677
|
+
} else if (recordData.isDeleted && recordData.isDeleted()) {
|
|
2678
|
+
operation = 'deleteRecord';
|
|
2679
|
+
}
|
|
2572
2680
|
|
|
2573
|
-
|
|
2681
|
+
options[SaveOp] = operation;
|
|
2574
2682
|
|
|
2575
|
-
|
|
2576
|
-
|
|
2577
|
-
|
|
2578
|
-
|
|
2683
|
+
let fetchManagerPromise = this._fetchManager.scheduleSave(internalModel.identifier, options);
|
|
2684
|
+
let promise = fetchManagerPromise.then(
|
|
2685
|
+
(payload) => {
|
|
2686
|
+
/*
|
|
2579
2687
|
Note to future spelunkers hoping to optimize.
|
|
2580
2688
|
We rely on this `run` to create a run loop if needed
|
|
2581
2689
|
that `store._push` and `store.didSaveRecord` will both share.
|
|
@@ -2584,25 +2692,40 @@ abstract class CoreStore extends Service {
|
|
|
2584
2692
|
have an outer run loop available still from the first
|
|
2585
2693
|
call to `store._push`;
|
|
2586
2694
|
*/
|
|
2587
|
-
|
|
2588
|
-
|
|
2589
|
-
|
|
2590
|
-
|
|
2591
|
-
|
|
2695
|
+
this._backburner.join(() => {
|
|
2696
|
+
let data = payload && payload.data;
|
|
2697
|
+
this.didSaveRecord(internalModel, { data }, operation);
|
|
2698
|
+
if (payload && payload.included) {
|
|
2699
|
+
this._push({ data: null, included: payload.included });
|
|
2700
|
+
}
|
|
2701
|
+
});
|
|
2702
|
+
},
|
|
2703
|
+
(e) => {
|
|
2704
|
+
if (typeof e === 'string') {
|
|
2705
|
+
throw e;
|
|
2592
2706
|
}
|
|
2593
|
-
|
|
2594
|
-
|
|
2595
|
-
|
|
2596
|
-
if (typeof e === 'string') {
|
|
2597
|
-
throw e;
|
|
2707
|
+
const { error, parsedErrors } = e;
|
|
2708
|
+
this.recordWasInvalid(internalModel, parsedErrors, error);
|
|
2709
|
+
throw error;
|
|
2598
2710
|
}
|
|
2599
|
-
|
|
2600
|
-
|
|
2601
|
-
|
|
2711
|
+
);
|
|
2712
|
+
|
|
2713
|
+
return promise;
|
|
2714
|
+
} else {
|
|
2715
|
+
if (internalModel._isRecordFullyDeleted()) {
|
|
2716
|
+
resolver.resolve();
|
|
2717
|
+
return;
|
|
2602
2718
|
}
|
|
2603
|
-
);
|
|
2604
2719
|
|
|
2605
|
-
|
|
2720
|
+
let snapshot = internalModel.createSnapshot(options);
|
|
2721
|
+
internalModel.adapterWillCommit();
|
|
2722
|
+
this._pendingSave.push({
|
|
2723
|
+
snapshot: snapshot,
|
|
2724
|
+
resolver: resolver,
|
|
2725
|
+
});
|
|
2726
|
+
|
|
2727
|
+
emberBackburner.scheduleOnce('actions', this, this.flushPendingSave);
|
|
2728
|
+
}
|
|
2606
2729
|
}
|
|
2607
2730
|
|
|
2608
2731
|
/**
|
|
@@ -2613,8 +2736,47 @@ abstract class CoreStore extends Service {
|
|
|
2613
2736
|
@private
|
|
2614
2737
|
*/
|
|
2615
2738
|
flushPendingSave() {
|
|
2616
|
-
|
|
2617
|
-
|
|
2739
|
+
if (REQUEST_SERVICE) {
|
|
2740
|
+
// assert here
|
|
2741
|
+
return;
|
|
2742
|
+
}
|
|
2743
|
+
let pending = this._pendingSave.slice();
|
|
2744
|
+
this._pendingSave = [];
|
|
2745
|
+
|
|
2746
|
+
for (let i = 0, j = pending.length; i < j; i++) {
|
|
2747
|
+
let pendingItem = pending[i];
|
|
2748
|
+
let snapshot = pendingItem.snapshot;
|
|
2749
|
+
let resolver = pendingItem.resolver;
|
|
2750
|
+
// TODO We have to cast due to our reliance on this private property
|
|
2751
|
+
// this will be refactored away once we change our pending API to be identifier based
|
|
2752
|
+
let internalModel = (snapshot as unknown as PrivateSnapshot)._internalModel;
|
|
2753
|
+
let adapter = this.adapterFor(internalModel.modelName);
|
|
2754
|
+
let operation;
|
|
2755
|
+
|
|
2756
|
+
if (RECORD_DATA_STATE) {
|
|
2757
|
+
// TODO move this out of internalModel
|
|
2758
|
+
if (internalModel.isNew()) {
|
|
2759
|
+
operation = 'createRecord';
|
|
2760
|
+
} else if (internalModel.isDeleted()) {
|
|
2761
|
+
operation = 'deleteRecord';
|
|
2762
|
+
} else {
|
|
2763
|
+
operation = 'updateRecord';
|
|
2764
|
+
}
|
|
2765
|
+
} else {
|
|
2766
|
+
if (internalModel.currentState.stateName === 'root.deleted.saved') {
|
|
2767
|
+
resolver.resolve();
|
|
2768
|
+
continue;
|
|
2769
|
+
} else if (internalModel.isNew()) {
|
|
2770
|
+
operation = 'createRecord';
|
|
2771
|
+
} else if (internalModel.isDeleted()) {
|
|
2772
|
+
operation = 'deleteRecord';
|
|
2773
|
+
} else {
|
|
2774
|
+
operation = 'updateRecord';
|
|
2775
|
+
}
|
|
2776
|
+
}
|
|
2777
|
+
|
|
2778
|
+
resolver.resolve(_commit(adapter, this, operation, snapshot));
|
|
2779
|
+
}
|
|
2618
2780
|
}
|
|
2619
2781
|
|
|
2620
2782
|
/**
|
|
@@ -2672,7 +2834,11 @@ abstract class CoreStore extends Service {
|
|
|
2672
2834
|
if (DEBUG) {
|
|
2673
2835
|
assertDestroyingStore(this, 'recordWasInvalid');
|
|
2674
2836
|
}
|
|
2675
|
-
|
|
2837
|
+
if (RECORD_DATA_ERRORS) {
|
|
2838
|
+
internalModel.adapterDidInvalidate(parsedErrors, error);
|
|
2839
|
+
} else {
|
|
2840
|
+
internalModel.adapterDidInvalidate(parsedErrors);
|
|
2841
|
+
}
|
|
2676
2842
|
}
|
|
2677
2843
|
|
|
2678
2844
|
/**
|
|
@@ -2997,17 +3163,30 @@ abstract class CoreStore extends Service {
|
|
|
2997
3163
|
|
|
2998
3164
|
if (ENV.DS_WARN_ON_UNKNOWN_KEYS) {
|
|
2999
3165
|
let unknownAttributes, unknownRelationships;
|
|
3000
|
-
|
|
3001
|
-
|
|
3002
|
-
|
|
3003
|
-
|
|
3004
|
-
|
|
3005
|
-
|
|
3006
|
-
|
|
3007
|
-
|
|
3008
|
-
|
|
3009
|
-
|
|
3010
|
-
|
|
3166
|
+
if (CUSTOM_MODEL_CLASS) {
|
|
3167
|
+
let relationships = this.getSchemaDefinitionService().relationshipsDefinitionFor(modelName);
|
|
3168
|
+
let attributes = this.getSchemaDefinitionService().attributesDefinitionFor(modelName);
|
|
3169
|
+
// Check unknown attributes
|
|
3170
|
+
unknownAttributes = Object.keys(data.attributes || {}).filter((key) => {
|
|
3171
|
+
return !attributes[key];
|
|
3172
|
+
});
|
|
3173
|
+
|
|
3174
|
+
// Check unknown relationships
|
|
3175
|
+
unknownRelationships = Object.keys(data.relationships || {}).filter((key) => {
|
|
3176
|
+
return !relationships[key];
|
|
3177
|
+
});
|
|
3178
|
+
} else {
|
|
3179
|
+
let modelClass = this.modelFor(modelName);
|
|
3180
|
+
// Check unknown attributes
|
|
3181
|
+
unknownAttributes = Object.keys(data.attributes || {}).filter((key) => {
|
|
3182
|
+
return !get(modelClass, 'fields').has(key);
|
|
3183
|
+
});
|
|
3184
|
+
|
|
3185
|
+
// Check unknown relationships
|
|
3186
|
+
unknownRelationships = Object.keys(data.relationships || {}).filter((key) => {
|
|
3187
|
+
return !get(modelClass, 'fields').has(key);
|
|
3188
|
+
});
|
|
3189
|
+
}
|
|
3011
3190
|
let unknownAttributesMessage = `The payload for '${modelName}' contains these unknown attributes: ${unknownAttributes}. Make sure they've been defined in your model.`;
|
|
3012
3191
|
warn(unknownAttributesMessage, unknownAttributes.length === 0, {
|
|
3013
3192
|
id: 'ds.store.unknown-keys-in-payload',
|
|
@@ -3137,26 +3316,38 @@ abstract class CoreStore extends Service {
|
|
|
3137
3316
|
}
|
|
3138
3317
|
|
|
3139
3318
|
serializeRecord(record: RecordInstance, options?: Dict<unknown>): unknown {
|
|
3140
|
-
|
|
3141
|
-
|
|
3142
|
-
|
|
3143
|
-
|
|
3319
|
+
if (CUSTOM_MODEL_CLASS) {
|
|
3320
|
+
let identifier = recordIdentifierFor(record);
|
|
3321
|
+
let internalModel = internalModelFactoryFor(this).peek(identifier);
|
|
3322
|
+
// TODO we used to check if the record was destroyed here
|
|
3323
|
+
return internalModel!.createSnapshot(options).serialize(options);
|
|
3324
|
+
}
|
|
3325
|
+
|
|
3326
|
+
assert('serializeRecord is only available when CUSTOM_MODEL_CLASS ff is on', false);
|
|
3144
3327
|
}
|
|
3145
3328
|
|
|
3146
3329
|
saveRecord(record: RecordInstance, options?: Dict<unknown>): RSVP.Promise<RecordInstance> {
|
|
3147
|
-
|
|
3148
|
-
|
|
3149
|
-
|
|
3150
|
-
|
|
3151
|
-
|
|
3152
|
-
|
|
3330
|
+
if (CUSTOM_MODEL_CLASS) {
|
|
3331
|
+
let identifier = recordIdentifierFor(record);
|
|
3332
|
+
let internalModel = internalModelFactoryFor(this).peek(identifier);
|
|
3333
|
+
// TODO we used to check if the record was destroyed here
|
|
3334
|
+
// Casting can be removed once REQUEST_SERVICE ff is turned on
|
|
3335
|
+
// because a `Record` is provided there will always be a matching internalModel
|
|
3336
|
+
return (internalModel!.save(options) as RSVP.Promise<void>).then(() => record);
|
|
3337
|
+
}
|
|
3338
|
+
|
|
3339
|
+
assert('saveRecord is only available when CUSTOM_MODEL_CLASS ff is on');
|
|
3153
3340
|
}
|
|
3154
3341
|
|
|
3155
3342
|
relationshipReferenceFor(identifier: RecordIdentifier, key: string): BelongsToReference | HasManyReference {
|
|
3156
|
-
|
|
3157
|
-
|
|
3158
|
-
|
|
3159
|
-
|
|
3343
|
+
if (CUSTOM_MODEL_CLASS) {
|
|
3344
|
+
let stableIdentifier = identifierCacheFor(this).getOrCreateRecordIdentifier(identifier);
|
|
3345
|
+
let internalModel = internalModelFactoryFor(this).peek(stableIdentifier);
|
|
3346
|
+
// TODO we used to check if the record was destroyed here
|
|
3347
|
+
return internalModel!.referenceFor(null, key);
|
|
3348
|
+
}
|
|
3349
|
+
|
|
3350
|
+
assert('relationshipReferenceFor is only available when CUSTOM_MODEL_CLASS ff is on', false);
|
|
3160
3351
|
}
|
|
3161
3352
|
|
|
3162
3353
|
/**
|
|
@@ -3410,6 +3601,10 @@ abstract class CoreStore extends Service {
|
|
|
3410
3601
|
for an `App.ApplicationSerializer` (the default serializer for
|
|
3411
3602
|
your entire application).
|
|
3412
3603
|
|
|
3604
|
+
if no `App.ApplicationSerializer` is found, it will attempt
|
|
3605
|
+
to get the `defaultSerializer` from the `PersonAdapter`
|
|
3606
|
+
(`adapterFor('person')`).
|
|
3607
|
+
|
|
3413
3608
|
If a serializer cannot be found on the adapter, it will fall back
|
|
3414
3609
|
to an instance of `JSONSerializer`.
|
|
3415
3610
|
|
|
@@ -3477,6 +3672,31 @@ abstract class CoreStore extends Service {
|
|
|
3477
3672
|
}
|
|
3478
3673
|
|
|
3479
3674
|
let serializerName;
|
|
3675
|
+
if (DEPRECATE_DEFAULT_SERIALIZER) {
|
|
3676
|
+
// no model specific serializer or application serializer, check for the `defaultSerializer`
|
|
3677
|
+
// property defined on the adapter
|
|
3678
|
+
let adapter = this.adapterFor(modelName);
|
|
3679
|
+
serializerName = get(adapter, 'defaultSerializer');
|
|
3680
|
+
|
|
3681
|
+
deprecate(
|
|
3682
|
+
`store.serializerFor("${modelName}") resolved the "${serializerName}" serializer via the deprecated \`adapter.defaultSerializer\` property.\n\n\tPreviously, if no application or type-specific serializer was specified, the store would attempt to lookup a serializer via the \`defaultSerializer\` property on the type's adapter. This behavior is deprecated in favor of explicitly defining a type-specific serializer or application serializer`,
|
|
3683
|
+
!serializerName,
|
|
3684
|
+
{
|
|
3685
|
+
id: 'ember-data:default-serializer',
|
|
3686
|
+
until: '4.0',
|
|
3687
|
+
url: 'https://deprecations.emberjs.com/ember-data/v3.x/#toc_ember-data-default-serializers',
|
|
3688
|
+
for: '@ember-data/store',
|
|
3689
|
+
since: {
|
|
3690
|
+
available: '3.15',
|
|
3691
|
+
enabled: '3.15',
|
|
3692
|
+
},
|
|
3693
|
+
}
|
|
3694
|
+
);
|
|
3695
|
+
|
|
3696
|
+
serializer = serializerName
|
|
3697
|
+
? _serializerCache[serializerName] || owner.lookup(`serializer:${serializerName}`)
|
|
3698
|
+
: undefined;
|
|
3699
|
+
}
|
|
3480
3700
|
|
|
3481
3701
|
if (DEPRECATE_LEGACY_TEST_REGISTRATIONS) {
|
|
3482
3702
|
// in production this is handled by the re-export
|
|
@@ -3507,10 +3727,49 @@ abstract class CoreStore extends Service {
|
|
|
3507
3727
|
}
|
|
3508
3728
|
}
|
|
3509
3729
|
|
|
3510
|
-
|
|
3511
|
-
|
|
3512
|
-
serializer
|
|
3513
|
-
|
|
3730
|
+
if (DEPRECATE_DEFAULT_SERIALIZER) {
|
|
3731
|
+
// final fallback, no model specific serializer, no application serializer, no
|
|
3732
|
+
// `serializer` property on store: use the convenience JSONSerializer
|
|
3733
|
+
serializer = _serializerCache['-default'] || owner.lookup('serializer:-default');
|
|
3734
|
+
if (DEBUG && HAS_EMBER_DATA_PACKAGE && HAS_SERIALIZER_PACKAGE && serializer === undefined) {
|
|
3735
|
+
const JSONSerializer = require('@ember-data/serializer/json').default;
|
|
3736
|
+
owner.register('serializer:-default', JSONSerializer);
|
|
3737
|
+
serializer = owner.lookup('serializer:-default');
|
|
3738
|
+
|
|
3739
|
+
serializer && deprecateTestRegistration('serializer', '-default');
|
|
3740
|
+
}
|
|
3741
|
+
|
|
3742
|
+
deprecate(
|
|
3743
|
+
`store.serializerFor("${modelName}") resolved the "-default" serializer via the deprecated "-default" lookup fallback.\n\n\tPreviously, when no type-specific serializer, application serializer, or adapter.defaultSerializer had been defined by the app, the "-default" serializer would be used which defaulted to the \`JSONSerializer\`. This behavior is deprecated in favor of explicitly defining an application or type-specific serializer`,
|
|
3744
|
+
!serializer,
|
|
3745
|
+
{
|
|
3746
|
+
id: 'ember-data:default-serializer',
|
|
3747
|
+
until: '4.0',
|
|
3748
|
+
url: 'https://deprecations.emberjs.com/ember-data/v3.x/#toc_ember-data-default-serializers',
|
|
3749
|
+
for: '@ember-data/store',
|
|
3750
|
+
since: {
|
|
3751
|
+
available: '3.15',
|
|
3752
|
+
enabled: '3.15',
|
|
3753
|
+
},
|
|
3754
|
+
}
|
|
3755
|
+
);
|
|
3756
|
+
|
|
3757
|
+
assert(
|
|
3758
|
+
`No serializer was found for '${modelName}' and no 'application' serializer was found as a fallback`,
|
|
3759
|
+
serializer !== undefined
|
|
3760
|
+
);
|
|
3761
|
+
|
|
3762
|
+
set(serializer, 'store', this);
|
|
3763
|
+
_serializerCache[normalizedModelName] = serializer;
|
|
3764
|
+
_serializerCache['-default'] = serializer;
|
|
3765
|
+
|
|
3766
|
+
return serializer;
|
|
3767
|
+
} else {
|
|
3768
|
+
assert(
|
|
3769
|
+
`No serializer was found for '${modelName}' and no 'application' serializer was found as a fallback`,
|
|
3770
|
+
serializer !== undefined
|
|
3771
|
+
);
|
|
3772
|
+
}
|
|
3514
3773
|
}
|
|
3515
3774
|
|
|
3516
3775
|
destroy() {
|
|
@@ -3639,6 +3898,73 @@ if (DEPRECATE_DEFAULT_ADAPTER) {
|
|
|
3639
3898
|
|
|
3640
3899
|
export default CoreStore;
|
|
3641
3900
|
|
|
3901
|
+
function _commit(adapter, store, operation, snapshot) {
|
|
3902
|
+
let internalModel = snapshot._internalModel;
|
|
3903
|
+
let modelName = snapshot.modelName;
|
|
3904
|
+
let modelClass = store.modelFor(modelName);
|
|
3905
|
+
assert(`You tried to update a record but you have no adapter (for ${modelName})`, adapter);
|
|
3906
|
+
assert(
|
|
3907
|
+
`You tried to update a record but your adapter (for ${modelName}) does not implement '${operation}'`,
|
|
3908
|
+
typeof adapter[operation] === 'function'
|
|
3909
|
+
);
|
|
3910
|
+
|
|
3911
|
+
let promise = Promise.resolve().then(() => adapter[operation](store, modelClass, snapshot));
|
|
3912
|
+
let serializer = store.serializerFor(modelName);
|
|
3913
|
+
let label = `DS: Extract and notify about ${operation} completion of ${internalModel}`;
|
|
3914
|
+
|
|
3915
|
+
promise = guardDestroyedStore(promise, store, label);
|
|
3916
|
+
promise = _guard(promise, _bind(_objectIsAlive, internalModel));
|
|
3917
|
+
|
|
3918
|
+
return promise.then(
|
|
3919
|
+
(adapterPayload) => {
|
|
3920
|
+
/*
|
|
3921
|
+
Note to future spelunkers hoping to optimize.
|
|
3922
|
+
We rely on this `run` to create a run loop if needed
|
|
3923
|
+
that `store._push` and `store.didSaveRecord` will both share.
|
|
3924
|
+
|
|
3925
|
+
We use `join` because it is often the case that we
|
|
3926
|
+
have an outer run loop available still from the first
|
|
3927
|
+
call to `store._push`;
|
|
3928
|
+
*/
|
|
3929
|
+
store._backburner.join(() => {
|
|
3930
|
+
let payload, data, sideloaded;
|
|
3931
|
+
if (adapterPayload) {
|
|
3932
|
+
payload = normalizeResponseHelper(serializer, store, modelClass, adapterPayload, snapshot.id, operation);
|
|
3933
|
+
if (payload.included) {
|
|
3934
|
+
sideloaded = payload.included;
|
|
3935
|
+
}
|
|
3936
|
+
data = payload.data;
|
|
3937
|
+
}
|
|
3938
|
+
store.didSaveRecord(internalModel, { data }, operation);
|
|
3939
|
+
// seems risky, but if the tests pass might be fine?
|
|
3940
|
+
if (sideloaded) {
|
|
3941
|
+
store._push({ data: null, included: sideloaded });
|
|
3942
|
+
}
|
|
3943
|
+
});
|
|
3944
|
+
|
|
3945
|
+
return internalModel;
|
|
3946
|
+
},
|
|
3947
|
+
function (error) {
|
|
3948
|
+
if (error && error.isAdapterError === true && error.code === 'InvalidError') {
|
|
3949
|
+
let parsedErrors;
|
|
3950
|
+
|
|
3951
|
+
if (typeof serializer.extractErrors === 'function') {
|
|
3952
|
+
parsedErrors = serializer.extractErrors(store, modelClass, error, snapshot.id);
|
|
3953
|
+
} else {
|
|
3954
|
+
parsedErrors = errorsArrayToHash(error.errors);
|
|
3955
|
+
}
|
|
3956
|
+
|
|
3957
|
+
store.recordWasInvalid(internalModel, parsedErrors, error);
|
|
3958
|
+
} else {
|
|
3959
|
+
store.recordWasError(internalModel, error);
|
|
3960
|
+
}
|
|
3961
|
+
|
|
3962
|
+
throw error;
|
|
3963
|
+
},
|
|
3964
|
+
label
|
|
3965
|
+
);
|
|
3966
|
+
}
|
|
3967
|
+
|
|
3642
3968
|
let assertDestroyingStore: Function;
|
|
3643
3969
|
let assertDestroyedStoreOnly: Function;
|
|
3644
3970
|
|