@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.
Files changed (36) hide show
  1. package/addon/-debug/index.js +1 -1
  2. package/addon/-private/{identifier-cache.ts → caches/identifier-cache.ts} +83 -8
  3. package/addon/-private/caches/instance-cache.ts +759 -0
  4. package/addon/-private/{record-data-for.ts → caches/record-data-for.ts} +2 -2
  5. package/addon/-private/index.ts +12 -15
  6. package/addon/-private/{model → legacy-model-support}/record-reference.ts +3 -3
  7. package/addon/-private/{schema-definition-service.ts → legacy-model-support/schema-definition-service.ts} +4 -5
  8. package/addon/-private/{model → legacy-model-support}/shim-model-class.ts +2 -2
  9. package/addon/-private/{record-array-manager.ts → managers/record-array-manager.ts} +16 -44
  10. package/addon/-private/{record-data-store-wrapper.ts → managers/record-data-store-wrapper.ts} +34 -56
  11. package/addon/-private/managers/record-notification-manager.ts +96 -0
  12. package/addon/-private/{fetch-manager.ts → network/fetch-manager.ts} +18 -16
  13. package/addon/-private/{finders.js → network/finders.js} +4 -12
  14. package/addon/-private/{request-cache.ts → network/request-cache.ts} +0 -0
  15. package/addon/-private/{snapshot-record-array.ts → network/snapshot-record-array.ts} +3 -3
  16. package/addon/-private/{snapshot.ts → network/snapshot.ts} +24 -31
  17. package/addon/-private/{promise-proxies.ts → proxies/promise-proxies.ts} +4 -4
  18. package/addon/-private/{promise-proxy-base.js → proxies/promise-proxy-base.js} +0 -0
  19. package/addon/-private/record-arrays/adapter-populated-record-array.ts +9 -11
  20. package/addon/-private/record-arrays/record-array.ts +16 -14
  21. package/addon/-private/{core-store.ts → store-service.ts} +186 -127
  22. package/addon/-private/{coerce-id.ts → utils/coerce-id.ts} +1 -1
  23. package/addon/-private/{common.js → utils/common.js} +1 -2
  24. package/addon/-private/utils/construct-resource.ts +2 -2
  25. package/addon/-private/{identifer-debug-consts.ts → utils/identifer-debug-consts.ts} +0 -0
  26. package/addon/-private/{normalize-model-name.ts → utils/normalize-model-name.ts} +0 -0
  27. package/addon/-private/utils/promise-record.ts +3 -3
  28. package/addon/-private/{serializer-response.ts → utils/serializer-response.ts} +2 -2
  29. package/addon/-private/{weak-cache.ts → utils/weak-cache.ts} +0 -0
  30. package/package.json +5 -5
  31. package/addon/-private/identity-map.ts +0 -54
  32. package/addon/-private/instance-cache.ts +0 -387
  33. package/addon/-private/internal-model-factory.ts +0 -359
  34. package/addon/-private/internal-model-map.ts +0 -121
  35. package/addon/-private/model/internal-model.ts +0 -600
  36. 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 type { RecordDataRecordWrapper } from '@ember-data/types/q/record-data-record-wrapper';
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 coerceId, { ensureStringId } from './coerce-id';
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
- internalModelFactoryFor,
45
+ InstanceCache,
50
46
  peekRecordIdentifier,
47
+ recordDataIsFullyDeleted,
51
48
  recordIdentifierFor,
52
49
  setRecordIdentifier,
53
- } from './internal-model-factory';
54
- import RecordReference from './model/record-reference';
55
- import type ShimModelClass from './model/shim-model-class';
56
- import { getShimClass } from './model/shim-model-class';
57
- import normalizeModelName from './normalize-model-name';
58
- import { PromiseArray, promiseArray, PromiseObject, promiseObject } from './promise-proxies';
59
- import RecordArrayManager from './record-array-manager';
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 { setRecordDataFor } from './record-data-for';
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) => RecordDataRecordWrapper,
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 internalModel = this._instanceCache._internalModelForResource(identifier);
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, internalModel._recordData);
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
- const factory = internalModelFactoryFor(this);
466
- const internalModel = factory.build({ type: normalizedModelName, id: properties.id });
467
- const { identifier } = internalModel;
465
+ if (resource.id) {
466
+ const identifier = this.identifierCache.peekRecordIdentifier(resource as ResourceIdentifierObject);
468
467
 
469
- this._instanceCache.getRecordData(identifier).clientDidCreate();
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
- let identifier = peekRecordIdentifier(record);
509
+ const identifier = peekRecordIdentifier(record);
500
510
  if (identifier) {
501
- let internalModel = internalModelFactoryFor(this).peek(identifier);
502
- if (internalModel) {
503
- internalModel.deleteRecord();
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
- let internalModel = internalModelFactoryFor(this).peek(identifier);
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.get('revision'); // 2
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.get('revision'); // 1
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.get('revision'); // 2
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 internalModel = internalModelFactoryFor(this).lookup(resource);
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 (!internalModel.isLoaded) {
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 internalModel = stableIdentifier && internalModelFactoryFor(this).peek(stableIdentifier);
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 internalModel && internalModel.isLoaded ? this._instanceCache.getRecord(stableIdentifier) : null;
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 internalModel = stableIdentifier && internalModelFactoryFor(this).peek(stableIdentifier);
1184
+ const isLoaded = stableIdentifier && this._instanceCache.recordIsLoaded(stableIdentifier);
1168
1185
 
1169
- return internalModel && internalModel.isLoaded ? this._instanceCache.getRecord(stableIdentifier) : null;
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
- const internalModel = identifier && internalModelFactoryFor(this).peek(identifier);
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.get('username');
1340
- console.log(`Currently logged in as ${username}`);
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.get('firstObject');
1392
+ return users.firstObject;
1378
1393
  }).then(function(user) {
1379
- let id = user.get('id');
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
- console.log(user); // null
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
- factory.clear();
1771
+ this._instanceCache.clear();
1759
1772
  } else {
1760
1773
  let normalizedModelName = normalizeModelName(modelName);
1761
- factory.clear(normalizedModelName);
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 {InternalModel|Array<InternalModel>} pushed InternalModel(s)
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._load(included[i]);
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._load(jsonApiDoc.data[i]);
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._load(jsonApiDoc.data);
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 internalModel = identifier && internalModelFactoryFor(this).peek(identifier)!;
2097
+ let recordData = identifier && this._instanceCache.peek({ identifier, bucket: 'recordData' });
2119
2098
 
2120
- if (!internalModel) {
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
- !internalModel.isEmpty && !internalModel.isDestroyed
2107
+ recordData && this._instanceCache.recordIsLoaded(identifier)
2132
2108
  );
2133
- if (internalModel._isRecordFullyDeleted()) {
2109
+ if (recordDataIsFullyDeleted(this._instanceCache, identifier)) {
2134
2110
  return resolve(record);
2135
2111
  }
2136
2112
 
2137
- internalModel.adapterWillCommit();
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
- // TODO handle missing isNew
2146
- if (recordData.isNew && recordData.isNew()) {
2123
+ if (recordData.isNew?.()) {
2147
2124
  operation = 'createRecord';
2148
- } else if (recordData.isDeleted && 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 ${internalModel.modelName} 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.`,
2177
- internalModel.id
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
- //TODO try to move notification to the user to the end of the runloop
2188
- internalModel._recordData.didCommit(data);
2189
- this.recordArrayManager.recordDidChange(internalModel.identifier);
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
- internalModel.adapterDidInvalidate(err);
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 clientId
2196
+ * @param lid
2219
2197
  * @param storeWrapper
2220
2198
  */
2221
2199
  createRecordDataFor(
2222
2200
  modelName: string,
2223
2201
  id: string | null,
2224
- clientId: string,
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 as RecordDataConstruct;
2214
+ ).RecordData;
2237
2215
  }
2238
2216
 
2239
2217
  let identifier = this.identifierCache.getOrCreateRecordIdentifier({
2240
2218
  type: modelName,
2241
2219
  id,
2242
- lid: clientId,
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
+ }
@@ -35,7 +35,7 @@ export function ensureStringId(id: Coercable): string {
35
35
  throw new Error(`Expected id to be a string or number, received ${String(id)}`);
36
36
  }
37
37
 
38
- return normalized!;
38
+ return normalized;
39
39
  }
40
40
 
41
41
  export default coerceId;
@@ -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 !(get(object, 'isDestroyed') || get(object, 'isDestroying'));
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 coerceId from '../coerce-id';
9
- import { isStableIdentifier } from '../identifier-cache';
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;
@@ -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 Store from '../core-store';
5
- import type { PromiseObject } from '../promise-proxies';
6
- import { promiseObject } from '../promise-proxies';
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,