@ember-data/store 4.8.0-alpha.3 → 4.8.0-alpha.6

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 (27) hide show
  1. package/addon/-private/caches/identifier-cache.ts +65 -66
  2. package/addon/-private/caches/instance-cache.ts +173 -236
  3. package/addon/-private/caches/record-data-for.ts +1 -6
  4. package/addon/-private/index.ts +13 -10
  5. package/addon/-private/legacy-model-support/record-reference.ts +12 -10
  6. package/addon/-private/legacy-model-support/schema-definition-service.ts +9 -4
  7. package/addon/-private/legacy-model-support/shim-model-class.ts +17 -10
  8. package/addon/-private/managers/record-array-manager.ts +282 -321
  9. package/addon/-private/managers/record-data-manager.ts +822 -0
  10. package/addon/-private/managers/record-data-store-wrapper.ts +295 -91
  11. package/addon/-private/managers/record-notification-manager.ts +45 -32
  12. package/addon/-private/network/fetch-manager.ts +292 -300
  13. package/addon/-private/network/finders.js +11 -6
  14. package/addon/-private/network/request-cache.ts +20 -17
  15. package/addon/-private/network/snapshot-record-array.ts +12 -29
  16. package/addon/-private/network/snapshot.ts +25 -27
  17. package/addon/-private/proxies/promise-proxies.ts +72 -11
  18. package/addon/-private/record-arrays/identifier-array.ts +924 -0
  19. package/addon/-private/store-service.ts +380 -114
  20. package/addon/-private/utils/is-non-empty-string.ts +1 -1
  21. package/addon/-private/utils/promise-record.ts +2 -3
  22. package/addon/-private/utils/uuid-polyfill.ts +71 -0
  23. package/package.json +11 -7
  24. package/addon/-private/backburner.js +0 -25
  25. package/addon/-private/record-arrays/adapter-populated-record-array.ts +0 -128
  26. package/addon/-private/record-arrays/record-array.ts +0 -320
  27. package/addon/-private/utils/weak-cache.ts +0 -125
@@ -4,7 +4,6 @@
4
4
  import { getOwner, setOwner } from '@ember/application';
5
5
  import { assert, deprecate } from '@ember/debug';
6
6
  import { _backburner as emberBackburner, run } from '@ember/runloop';
7
- import type { Backburner } from '@ember/runloop/-private/backburner';
8
7
  import Service from '@ember/service';
9
8
  import { registerWaiter, unregisterWaiter } from '@ember/test';
10
9
  import { DEBUG } from '@glimmer/env';
@@ -17,7 +16,9 @@ import { HAS_MODEL_PACKAGE, HAS_RECORD_DATA_PACKAGE } from '@ember-data/private-
17
16
  import {
18
17
  DEPRECATE_HAS_RECORD,
19
18
  DEPRECATE_JSON_API_FALLBACK,
19
+ DEPRECATE_PROMISE_PROXIES,
20
20
  DEPRECATE_STORE_FIND,
21
+ DEPRECATE_V1CACHE_STORE_APIS,
21
22
  } from '@ember-data/private-build-infra/deprecations';
22
23
  import type { RecordData as RecordDataClass } from '@ember-data/record-data/-private';
23
24
  import type { DSModel } from '@ember-data/types/q/ds-model';
@@ -31,15 +32,14 @@ import type {
31
32
  import type { StableExistingRecordIdentifier, StableRecordIdentifier } from '@ember-data/types/q/identifier';
32
33
  import type { MinimumAdapterInterface } from '@ember-data/types/q/minimum-adapter-interface';
33
34
  import type { MinimumSerializerInterface } from '@ember-data/types/q/minimum-serializer-interface';
34
- import type { RecordData } from '@ember-data/types/q/record-data';
35
+ import type { RecordData, RecordDataV1 } from '@ember-data/types/q/record-data';
35
36
  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
+ import type { RecordDataStoreWrapper } from '@ember-data/types/q/record-data-store-wrapper';
37
38
  import type { RecordInstance } from '@ember-data/types/q/record-instance';
38
39
  import type { SchemaDefinitionService } from '@ember-data/types/q/schema-definition-service';
39
40
  import type { FindOptions } from '@ember-data/types/q/store';
40
41
  import type { Dict } from '@ember-data/types/q/utils';
41
42
 
42
- import edBackburner from './backburner';
43
43
  import { IdentifierCache } from './caches/identifier-cache';
44
44
  import {
45
45
  InstanceCache,
@@ -50,20 +50,21 @@ import {
50
50
  storeFor,
51
51
  StoreMap,
52
52
  } from './caches/instance-cache';
53
- import { setRecordDataFor } from './caches/record-data-for';
53
+ import recordDataFor, { setRecordDataFor } from './caches/record-data-for';
54
54
  import RecordReference from './legacy-model-support/record-reference';
55
55
  import { DSModelSchemaDefinitionService, getModelFactory } from './legacy-model-support/schema-definition-service';
56
56
  import type ShimModelClass from './legacy-model-support/shim-model-class';
57
57
  import { getShimClass } from './legacy-model-support/shim-model-class';
58
58
  import RecordArrayManager from './managers/record-array-manager';
59
- import RecordDataStoreWrapper from './managers/record-data-store-wrapper';
59
+ import type { NonSingletonRecordDataManager } from './managers/record-data-manager';
60
60
  import NotificationManager from './managers/record-notification-manager';
61
61
  import FetchManager, { SaveOp } from './network/fetch-manager';
62
62
  import { _findAll, _query, _queryRecord } from './network/finders';
63
63
  import type RequestCache from './network/request-cache';
64
+ import type Snapshot from './network/snapshot';
65
+ import SnapshotRecordArray from './network/snapshot-record-array';
64
66
  import { PromiseArray, promiseArray, PromiseObject, promiseObject } from './proxies/promise-proxies';
65
- import AdapterPopulatedRecordArray from './record-arrays/adapter-populated-record-array';
66
- import RecordArray from './record-arrays/record-array';
67
+ import IdentifierArray, { Collection } from './record-arrays/identifier-array';
67
68
  import coerceId, { ensureStringId } from './utils/coerce-id';
68
69
  import constructResource from './utils/construct-resource';
69
70
  import normalizeModelName from './utils/normalize-model-name';
@@ -164,18 +165,8 @@ export interface CreateRecordProperties {
164
165
  */
165
166
 
166
167
  class Store extends Service {
167
- /**
168
- * Ember Data uses several specialized micro-queues for organizing
169
- and coalescing similar async work.
168
+ __private_singleton_recordData!: RecordData;
170
169
 
171
- These queues are currently controlled by a flush scheduled into
172
- ember-data's custom backburner instance.
173
- *
174
- * EmberData specific backburner instance
175
- * @property _backburner
176
- * @private
177
- */
178
- declare _backburner: Backburner;
179
170
  declare recordArrayManager: RecordArrayManager;
180
171
 
181
172
  declare _notificationManager: NotificationManager;
@@ -227,10 +218,6 @@ class Store extends Service {
227
218
  this._serializerCache = Object.create(null);
228
219
  this._modelFactoryCache = Object.create(null);
229
220
 
230
- // private
231
- // TODO we should find a path to something simpler than backburner
232
- this._backburner = edBackburner;
233
-
234
221
  if (DEBUG) {
235
222
  if (this.generateStackTracesForTrackedRequests === undefined) {
236
223
  this.generateStackTracesForTrackedRequests = false;
@@ -278,41 +265,108 @@ class Store extends Service {
278
265
  }
279
266
  }
280
267
 
268
+ declare _cbs: { coalesce?: () => void; sync?: () => void; notify?: () => void } | null;
269
+ _run(cb: () => void) {
270
+ assert(`EmberData should never encounter a nested run`, !this._cbs);
271
+ const _cbs: { coalesce?: () => void; sync?: () => void; notify?: () => void } = (this._cbs = {});
272
+ cb();
273
+ if (_cbs.coalesce) {
274
+ _cbs.coalesce();
275
+ }
276
+ if (_cbs.sync) {
277
+ _cbs.sync();
278
+ }
279
+ if (_cbs.notify) {
280
+ _cbs.notify();
281
+ }
282
+ this._cbs = null;
283
+ }
284
+ _join(cb: () => void): void {
285
+ if (this._cbs) {
286
+ cb();
287
+ } else {
288
+ this._run(cb);
289
+ }
290
+ }
291
+
292
+ _schedule(name: 'coalesce' | 'sync' | 'notify', cb: () => void): void {
293
+ assert(`EmberData expects to schedule only when there is an active run`, !!this._cbs);
294
+ assert(`EmberData expects only one flush per queue name, cannot schedule ${name}`, !this._cbs[name]);
295
+
296
+ this._cbs[name] = cb;
297
+ }
298
+
299
+ /**
300
+ * Retrieve the RequestStateService instance
301
+ * associated with this Store.
302
+ *
303
+ * This can be used to query the status of requests
304
+ * that have been initiated for a given identifier.
305
+ *
306
+ * @method getRequestStateService
307
+ * @returns {RequestStateService}
308
+ * @public
309
+ */
281
310
  getRequestStateService(): RequestCache {
282
311
  return this._fetchManager.requestCache;
283
312
  }
284
313
 
314
+ /**
315
+ * A hook which an app or addon may implement. Called when
316
+ * the Store is attempting to create a Record Instance for
317
+ * a resource.
318
+ *
319
+ * This hook can be used to select or instantiate any desired
320
+ * mechanism of presentating cache data to the ui for access
321
+ * mutation, and interaction.
322
+ *
323
+ * @method instantiateRecord (hook)
324
+ * @param identifier
325
+ * @param createRecordArgs
326
+ * @param recordDataFor
327
+ * @param notificationManager
328
+ * @returns A record instance
329
+ * @public
330
+ */
285
331
  instantiateRecord(
286
332
  identifier: StableRecordIdentifier,
287
333
  createRecordArgs: { [key: string]: unknown },
288
- recordDataFor: (identifier: StableRecordIdentifier) => RecordDataWrapper,
334
+ recordDataFor: (identifier: StableRecordIdentifier) => RecordData,
289
335
  notificationManager: NotificationManager
290
336
  ): DSModel | RecordInstance {
291
337
  if (HAS_MODEL_PACKAGE) {
292
338
  let modelName = identifier.type;
293
- let store = this;
294
339
 
295
340
  let recordData = this._instanceCache.getRecordData(identifier);
341
+ // TODO deprecate allowing unknown args setting
296
342
  let createOptions: any = {
297
- // TODO deprecate allowing unknown args setting
298
343
  _createProps: createRecordArgs,
299
344
  // TODO @deprecate consider deprecating accessing record properties during init which the below is necessary for
300
- _secretInit: (record: RecordInstance): void => {
301
- setRecordIdentifier(record, identifier);
302
- StoreMap.set(record, store);
303
- setRecordDataFor(record, recordData);
345
+ _secretInit: {
346
+ identifier,
347
+ recordData,
348
+ store: this,
349
+ cb: secretInit,
304
350
  },
305
- container: null, // necessary hack for setOwner?
306
351
  };
307
352
 
308
353
  // ensure that `getOwner(this)` works inside a model instance
309
354
  setOwner(createOptions, getOwner(this));
310
- delete createOptions.container;
311
- return getModelFactory(this, this._modelFactoryCache, modelName).create(createOptions);
355
+ return getModelFactory(this, this._modelFactoryCache, modelName).class.create(createOptions);
312
356
  }
313
357
  assert(`You must implement the store's instantiateRecord hook for your custom model class.`);
314
358
  }
315
359
 
360
+ /**
361
+ * A hook which an app or addon may implement. Called when
362
+ * the Store is destroying a Record Instance. This hook should
363
+ * be used to teardown any custom record instances instantiated
364
+ * with `instantiateRecord`.
365
+ *
366
+ * @method teardownRecord (hook)
367
+ * @public
368
+ * @param record
369
+ */
316
370
  teardownRecord(record: DSModel | RecordInstance): void {
317
371
  if (HAS_MODEL_PACKAGE) {
318
372
  assert(
@@ -325,6 +379,16 @@ class Store extends Service {
325
379
  }
326
380
  }
327
381
 
382
+ /**
383
+ * Provides access to the SchemaDefinitionService instance
384
+ * for this Store instance.
385
+ *
386
+ * The SchemaDefinitionService can be used to query for
387
+ * information about the schema of a resource.
388
+ *
389
+ * @method getSchemaDefinitionService
390
+ * @public
391
+ */
328
392
  getSchemaDefinitionService(): SchemaDefinitionService {
329
393
  if (HAS_MODEL_PACKAGE && !this._schemaDefinitionService) {
330
394
  // it is potentially a mistake for the RFC to have not enabled chaining these services, though highlander rule is nice.
@@ -338,7 +402,20 @@ class Store extends Service {
338
402
  return this._schemaDefinitionService;
339
403
  }
340
404
 
405
+ /**
406
+ * Allows an app to register a custom SchemaDefinitionService
407
+ * for use when information about a resource's schema needs
408
+ * to be queried.
409
+ *
410
+ * This method can only be called once and needs to be called before
411
+ * `getSchemaDefinitionService` is called when using `@ember-data/model`.
412
+ *
413
+ * @method registerSchemaDefinitionService
414
+ * @param {SchemaDefinitionService} schema
415
+ * @public
416
+ */
341
417
  registerSchemaDefinitionService(schema: SchemaDefinitionService) {
418
+ assert(`Cannot register a schema definition service when one already exists`, !this._schemaDefinitionService);
342
419
  this._schemaDefinitionService = schema;
343
420
  }
344
421
 
@@ -348,6 +425,12 @@ class Store extends Service {
348
425
  When used with Model from @ember-data/model the return is the model class,
349
426
  but this is not guaranteed.
350
427
 
428
+ If looking to query attribute or relationship information it is
429
+ recommended to use `getSchemaDefinitionService` instead. This method
430
+ should be considered legacy and exists primarily to continue to support
431
+ Adapter/Serializer APIs which expect it's return value in their method
432
+ signatures.
433
+
351
434
  The class of a model might be useful if you want to get a list of all the
352
435
  relationship names of the model, see
353
436
  [`relationshipNames`](/ember-data/release/classes/Model?anchor=relationshipNames)
@@ -438,8 +521,9 @@ class Store extends Service {
438
521
  // of record-arrays via ember's run loop, not our own.
439
522
  //
440
523
  // to remove this, we would need to move to a new `async` API.
441
- return emberBackburner.join(() => {
442
- return this._backburner.join(() => {
524
+ let record!: RecordInstance;
525
+ emberBackburner.join(() => {
526
+ this._join(() => {
443
527
  let normalizedModelName = normalizeModelName(modelName);
444
528
  let properties = { ...inputProperties };
445
529
 
@@ -473,12 +557,20 @@ class Store extends Service {
473
557
 
474
558
  const identifier = this.identifierCache.createIdentifierForNewRecord(resource);
475
559
  const recordData = this._instanceCache.getRecordData(identifier);
476
- recordData.clientDidCreate();
477
- this.recordArrayManager.recordDidChange(identifier);
478
560
 
479
- return this._instanceCache.getRecord(identifier, properties);
561
+ const createOptions = normalizeProperties(
562
+ this,
563
+ identifier,
564
+ properties,
565
+ (recordData as NonSingletonRecordDataManager).managedVersion === '1'
566
+ );
567
+ const resultProps = recordData.clientDidCreate(identifier, createOptions);
568
+ this.recordArrayManager.identifierAdded(identifier);
569
+
570
+ record = this._instanceCache.getRecord(identifier, resultProps);
480
571
  });
481
572
  });
573
+ return record;
482
574
  }
483
575
 
484
576
  /**
@@ -502,26 +594,16 @@ class Store extends Service {
502
594
  if (DEBUG) {
503
595
  assertDestroyingStore(this, 'deleteRecord');
504
596
  }
505
- // TODO eliminate this interleaving
506
- // it is unlikely we need both an outer join and the inner run
507
- // of our own queue
508
- this._backburner.join(() => {
509
- const identifier = peekRecordIdentifier(record);
510
- if (identifier) {
511
- run(() => {
512
- const backburner = this._backburner;
513
- backburner.run(() => {
514
- const recordData = this._instanceCache.peek({ identifier, bucket: 'recordData' });
515
597
 
516
- if (recordData) {
517
- if (recordData?.setIsDeleted) {
518
- recordData.setIsDeleted(true);
519
- }
520
- if (recordData.isNew?.()) {
521
- this._instanceCache.unloadRecord(identifier);
522
- }
523
- }
524
- });
598
+ const identifier = peekRecordIdentifier(record);
599
+ const recordData = identifier && this._instanceCache.peek({ identifier, bucket: 'recordData' });
600
+ assert(`expected a recordData instance to exist for the record`, recordData);
601
+ this._join(() => {
602
+ recordData.setIsDeleted(identifier, true);
603
+
604
+ if (recordData.isNew(identifier)) {
605
+ run(() => {
606
+ this._instanceCache.unloadRecord(identifier);
525
607
  });
526
608
  }
527
609
  });
@@ -547,7 +629,7 @@ class Store extends Service {
547
629
  if (DEBUG) {
548
630
  assertDestroyingStore(this, 'unloadRecord');
549
631
  }
550
- let identifier = peekRecordIdentifier(record);
632
+ const identifier = peekRecordIdentifier(record);
551
633
  if (identifier) {
552
634
  this._instanceCache.unloadRecord(identifier);
553
635
  }
@@ -1014,14 +1096,14 @@ class Store extends Service {
1014
1096
  assertIdentifierHasId(identifier);
1015
1097
  promise = this._fetchManager.scheduleFetch(identifier, options);
1016
1098
  } else {
1017
- let snapshot = this._instanceCache.createSnapshot(identifier, options);
1099
+ let snapshot: Snapshot | null = null;
1018
1100
  let adapter = this.adapterFor(identifier.type);
1019
1101
 
1020
1102
  // Refetch the record if the adapter thinks the record is stale
1021
1103
  if (
1022
1104
  typeof options.reload === 'undefined' &&
1023
1105
  adapter.shouldReloadRecord &&
1024
- adapter.shouldReloadRecord(this, snapshot)
1106
+ adapter.shouldReloadRecord(this, (snapshot = this._instanceCache.createSnapshot(identifier, options)))
1025
1107
  ) {
1026
1108
  assertIdentifierHasId(identifier);
1027
1109
  promise = this._fetchManager.scheduleFetch(identifier, options);
@@ -1031,7 +1113,10 @@ class Store extends Service {
1031
1113
  options.backgroundReload !== false &&
1032
1114
  (options.backgroundReload ||
1033
1115
  !adapter.shouldBackgroundReloadRecord ||
1034
- adapter.shouldBackgroundReloadRecord(this, snapshot))
1116
+ adapter.shouldBackgroundReloadRecord(
1117
+ this,
1118
+ (snapshot = snapshot || this._instanceCache.createSnapshot(identifier, options))
1119
+ ))
1035
1120
  ) {
1036
1121
  assertIdentifierHasId(identifier);
1037
1122
  this._fetchManager.scheduleFetch(identifier, options);
@@ -1042,7 +1127,11 @@ class Store extends Service {
1042
1127
  }
1043
1128
  }
1044
1129
 
1045
- return promiseRecord(this, promise, `DS: Store#findRecord ${identifier}`);
1130
+ if (DEPRECATE_PROMISE_PROXIES) {
1131
+ return promiseRecord(this, promise);
1132
+ }
1133
+
1134
+ return promise.then((identifier: StableRecordIdentifier) => this.peekRecord(identifier));
1046
1135
  }
1047
1136
 
1048
1137
  /**
@@ -1201,6 +1290,7 @@ class Store extends Service {
1201
1290
  ```
1202
1291
 
1203
1292
  @method hasRecordForId
1293
+ @deprecated
1204
1294
  @public
1205
1295
  @param {String} modelName
1206
1296
  @param {(String|Integer)} id
@@ -1272,8 +1362,8 @@ class Store extends Service {
1272
1362
  decoded: "/api/v1/person?ids[]=1&ids[]=2&ids[]=3"
1273
1363
  ```
1274
1364
 
1275
- This method returns a promise, which is resolved with an
1276
- [`AdapterPopulatedRecordArray`](/ember-data/release/classes/AdapterPopulatedRecordArray)
1365
+ This method returns a promise, which is resolved with a
1366
+ [`Collection`](/ember-data/release/classes/Collection)
1277
1367
  once the server returns.
1278
1368
 
1279
1369
  @since 1.13.0
@@ -1284,7 +1374,7 @@ class Store extends Service {
1284
1374
  @param {Object} options optional, may include `adapterOptions` hash which will be passed to adapter.query
1285
1375
  @return {Promise} promise
1286
1376
  */
1287
- query(modelName: string, query, options): PromiseArray<RecordInstance, AdapterPopulatedRecordArray> {
1377
+ query(modelName: string, query, options): PromiseArray<RecordInstance, Collection> | Promise<Collection> {
1288
1378
  if (DEBUG) {
1289
1379
  assertDestroyingStore(this, 'query');
1290
1380
  }
@@ -1318,9 +1408,12 @@ class Store extends Service {
1318
1408
  query,
1319
1409
  recordArray,
1320
1410
  adapterOptionsWrapper
1321
- ) as unknown as Promise<AdapterPopulatedRecordArray>;
1411
+ ) as unknown as Promise<Collection>;
1322
1412
 
1323
- return promiseArray(queryPromise);
1413
+ if (DEPRECATE_PROMISE_PROXIES) {
1414
+ return promiseArray(queryPromise);
1415
+ }
1416
+ return queryPromise;
1324
1417
  }
1325
1418
 
1326
1419
  /**
@@ -1421,7 +1514,11 @@ class Store extends Service {
1421
1514
  @param {Object} options optional, may include `adapterOptions` hash which will be passed to adapter.queryRecord
1422
1515
  @return {Promise} promise which resolves with the found record or `null`
1423
1516
  */
1424
- queryRecord(modelName: string, query, options?): PromiseObject<RecordInstance | null> {
1517
+ queryRecord(
1518
+ modelName: string,
1519
+ query,
1520
+ options?
1521
+ ): PromiseObject<RecordInstance | null> | Promise<RecordInstance | null> {
1425
1522
  if (DEBUG) {
1426
1523
  assertDestroyingStore(this, 'queryRecord');
1427
1524
  }
@@ -1454,7 +1551,10 @@ class Store extends Service {
1454
1551
  adapterOptionsWrapper
1455
1552
  ) as Promise<StableRecordIdentifier | null>;
1456
1553
 
1457
- return promiseObject(promise.then((identifier) => identifier && this.peekRecord(identifier)));
1554
+ if (DEPRECATE_PROMISE_PROXIES) {
1555
+ return promiseObject(promise.then((identifier) => identifier && this.peekRecord(identifier)));
1556
+ }
1557
+ return promise.then((identifier) => identifier && this.peekRecord(identifier));
1458
1558
  }
1459
1559
 
1460
1560
  /**
@@ -1648,7 +1748,7 @@ class Store extends Service {
1648
1748
  findAll(
1649
1749
  modelName: string,
1650
1750
  options: { reload?: boolean; backgroundReload?: boolean } = {}
1651
- ): PromiseArray<RecordInstance, RecordArray> {
1751
+ ): PromiseArray<RecordInstance, IdentifierArray> {
1652
1752
  if (DEBUG) {
1653
1753
  assertDestroyingStore(this, 'findAll');
1654
1754
  }
@@ -1674,7 +1774,7 @@ class Store extends Service {
1674
1774
  array.isUpdating = true;
1675
1775
  fetch = _findAll(adapter, this, normalizedModelName, options);
1676
1776
  } else {
1677
- let snapshotArray = array._createSnapshot(options);
1777
+ let snapshotArray = new SnapshotRecordArray(this, array, options);
1678
1778
 
1679
1779
  if (options.reload !== false) {
1680
1780
  if (
@@ -1682,7 +1782,7 @@ class Store extends Service {
1682
1782
  (!adapter.shouldReloadAll && snapshotArray.length === 0)
1683
1783
  ) {
1684
1784
  array.isUpdating = true;
1685
- fetch = _findAll(adapter, this, modelName, options);
1785
+ fetch = _findAll(adapter, this, modelName, options, snapshotArray);
1686
1786
  }
1687
1787
  }
1688
1788
 
@@ -1695,14 +1795,17 @@ class Store extends Service {
1695
1795
  adapter.shouldBackgroundReloadAll(this, snapshotArray)
1696
1796
  ) {
1697
1797
  array.isUpdating = true;
1698
- _findAll(adapter, this, modelName, options);
1798
+ _findAll(adapter, this, modelName, options, snapshotArray);
1699
1799
  }
1700
1800
 
1701
1801
  fetch = resolve(array);
1702
1802
  }
1703
1803
  }
1704
1804
 
1705
- return promiseArray(fetch);
1805
+ if (DEPRECATE_PROMISE_PROXIES) {
1806
+ return promiseArray(fetch);
1807
+ }
1808
+ return fetch;
1706
1809
  }
1707
1810
 
1708
1811
  /**
@@ -1730,7 +1833,7 @@ class Store extends Service {
1730
1833
  @param {String} modelName
1731
1834
  @return {RecordArray}
1732
1835
  */
1733
- peekAll(modelName) {
1836
+ peekAll(modelName: string): IdentifierArray {
1734
1837
  if (DEBUG) {
1735
1838
  assertDestroyingStore(this, 'peekAll');
1736
1839
  }
@@ -1739,8 +1842,9 @@ class Store extends Service {
1739
1842
  `Passing classes to store methods has been removed. Please pass a dasherized string instead of ${modelName}`,
1740
1843
  typeof modelName === 'string'
1741
1844
  );
1742
- let normalizedModelName = normalizeModelName(modelName);
1743
- return this.recordArrayManager.liveRecordArrayFor(normalizedModelName);
1845
+
1846
+ let type = normalizeModelName(modelName);
1847
+ return this.recordArrayManager.liveArrayFor(type);
1744
1848
  }
1745
1849
 
1746
1850
  /**
@@ -1767,12 +1871,29 @@ class Store extends Service {
1767
1871
  !modelName || typeof modelName === 'string'
1768
1872
  );
1769
1873
 
1770
- if (modelName === undefined) {
1771
- this._instanceCache.clear();
1772
- } else {
1773
- let normalizedModelName = normalizeModelName(modelName);
1774
- this._instanceCache.clear(normalizedModelName);
1775
- }
1874
+ this._join(() => {
1875
+ if (modelName === undefined) {
1876
+ // destroy the graph before unloadAll
1877
+ // since then we avoid churning relationships
1878
+ // during unload
1879
+ if (HAS_RECORD_DATA_PACKAGE) {
1880
+ const peekGraph = (
1881
+ importSync('@ember-data/record-data/-private') as typeof import('@ember-data/record-data/-private')
1882
+ ).peekGraph;
1883
+ let graph = peekGraph(this);
1884
+ if (graph) {
1885
+ graph.identifiers.clear();
1886
+ }
1887
+ }
1888
+ this._notificationManager.destroy();
1889
+
1890
+ this.recordArrayManager.clear();
1891
+ this._instanceCache.clear();
1892
+ } else {
1893
+ let normalizedModelName = normalizeModelName(modelName);
1894
+ this._instanceCache.clear(normalizedModelName);
1895
+ }
1896
+ });
1776
1897
  }
1777
1898
 
1778
1899
  /**
@@ -1960,7 +2081,8 @@ class Store extends Service {
1960
2081
  if (DEBUG) {
1961
2082
  assertDestroyingStore(this, '_push');
1962
2083
  }
1963
- let identifiers = this._backburner.join(() => {
2084
+ let ret;
2085
+ this._join(() => {
1964
2086
  let included = jsonApiDoc.included;
1965
2087
  let i, length;
1966
2088
 
@@ -1977,11 +2099,13 @@ class Store extends Service {
1977
2099
  for (i = 0; i < length; i++) {
1978
2100
  identifiers[i] = this._instanceCache.loadData(jsonApiDoc.data[i]);
1979
2101
  }
1980
- return identifiers;
2102
+ ret = identifiers;
2103
+ return;
1981
2104
  }
1982
2105
 
1983
2106
  if (jsonApiDoc.data === null) {
1984
- return null;
2107
+ ret = null;
2108
+ return;
1985
2109
  }
1986
2110
 
1987
2111
  assert(
@@ -1991,11 +2115,11 @@ class Store extends Service {
1991
2115
  typeof jsonApiDoc.data === 'object'
1992
2116
  );
1993
2117
 
1994
- return this._instanceCache.loadData(jsonApiDoc.data);
2118
+ ret = this._instanceCache.loadData(jsonApiDoc.data);
2119
+ return;
1995
2120
  });
1996
2121
 
1997
- // this typecast is necessary because `backburner.join` is mistyped to return void
1998
- return identifiers;
2122
+ return ret;
1999
2123
  }
2000
2124
 
2001
2125
  /**
@@ -2090,7 +2214,15 @@ class Store extends Service {
2090
2214
  return this._instanceCache.createSnapshot(recordIdentifierFor(record)).serialize(options);
2091
2215
  }
2092
2216
 
2093
- // todo @runspired this should likely be publicly @documented for custom records
2217
+ /**
2218
+ * Trigger a save for a Record.
2219
+ *
2220
+ * @method saveRecord
2221
+ * @public
2222
+ * @param {RecordInstance} record
2223
+ * @param options
2224
+ * @returns {Promise<RecordInstance>}
2225
+ */
2094
2226
  saveRecord(record: RecordInstance, options: Dict<unknown> = {}): Promise<RecordInstance> {
2095
2227
  assert(`Unable to initate save for a record in a disconnected state`, storeFor(record));
2096
2228
  let identifier = recordIdentifierFor(record);
@@ -2110,7 +2242,7 @@ class Store extends Service {
2110
2242
  return resolve(record);
2111
2243
  }
2112
2244
 
2113
- recordData.willCommit();
2245
+ recordData.willCommit(identifier);
2114
2246
  if (isDSModel(record)) {
2115
2247
  record.errors.clear();
2116
2248
  }
@@ -2120,9 +2252,9 @@ class Store extends Service {
2120
2252
  }
2121
2253
  let operation: 'createRecord' | 'deleteRecord' | 'updateRecord' = 'updateRecord';
2122
2254
 
2123
- if (recordData.isNew?.()) {
2255
+ if (recordData.isNew(identifier)) {
2124
2256
  operation = 'createRecord';
2125
- } else if (recordData.isDeleted?.()) {
2257
+ } else if (recordData.isDeleted(identifier)) {
2126
2258
  operation = 'deleteRecord';
2127
2259
  }
2128
2260
 
@@ -2142,7 +2274,7 @@ class Store extends Service {
2142
2274
  have an outer run loop available still from the first
2143
2275
  call to `store._push`;
2144
2276
  */
2145
- this._backburner.join(() => {
2277
+ this._join(() => {
2146
2278
  if (DEBUG) {
2147
2279
  assertDestroyingStore(this, 'saveRecord');
2148
2280
  }
@@ -2163,8 +2295,11 @@ class Store extends Service {
2163
2295
 
2164
2296
  //We first make sure the primary data has been updated
2165
2297
  const recordData = this._instanceCache.getRecordData(actualIdentifier);
2166
- recordData.didCommit(data);
2167
- this.recordArrayManager.recordDidChange(actualIdentifier);
2298
+ recordData.didCommit(identifier, data);
2299
+
2300
+ if (operation === 'deleteRecord') {
2301
+ this.recordArrayManager.identifierRemoved(actualIdentifier);
2302
+ }
2168
2303
 
2169
2304
  if (payload && payload.included) {
2170
2305
  this._push({ data: null, included: payload.included });
@@ -2189,19 +2324,15 @@ class Store extends Service {
2189
2324
  * Instantiation hook allowing applications or addons to configure the store
2190
2325
  * to utilize a custom RecordData implementation.
2191
2326
  *
2192
- * @method createRecordDataFor
2327
+ * @method createRecordDataFor (hook)
2193
2328
  * @public
2194
- * @param modelName
2195
- * @param id
2196
- * @param lid
2329
+ * @param identifier
2197
2330
  * @param storeWrapper
2198
2331
  */
2199
2332
  createRecordDataFor(
2200
- modelName: string,
2201
- id: string | null,
2202
- lid: string,
2333
+ identifier: StableRecordIdentifier,
2203
2334
  storeWrapper: RecordDataStoreWrapper
2204
- ): RecordData {
2335
+ ): RecordData | RecordDataV1 {
2205
2336
  if (HAS_RECORD_DATA_PACKAGE) {
2206
2337
  // we can't greedily use require as this causes
2207
2338
  // a cycle we can't easily fix (or clearly pin point) at present.
@@ -2214,12 +2345,30 @@ class Store extends Service {
2214
2345
  ).RecordData;
2215
2346
  }
2216
2347
 
2217
- let identifier = this.identifierCache.getOrCreateRecordIdentifier({
2218
- type: modelName,
2219
- id,
2220
- lid,
2221
- });
2222
- return new _RecordData(identifier, storeWrapper);
2348
+ if (DEPRECATE_V1CACHE_STORE_APIS && arguments.length === 4) {
2349
+ deprecate(
2350
+ `Store.createRecordDataFor(<type>, <id>, <lid>, <storeWrapper>) has been deprecated in favor of Store.createRecordDataFor(<identifier>, <storeWrapper>)`,
2351
+ false,
2352
+ {
2353
+ id: 'ember-data:deprecate-v1cache-store-apis',
2354
+ for: 'ember-data',
2355
+ until: '5.0',
2356
+ since: { enabled: '4.8', available: '4.8' },
2357
+ }
2358
+ );
2359
+ identifier = this.identifierCache.getOrCreateRecordIdentifier({
2360
+ type: arguments[0],
2361
+ id: arguments[1],
2362
+ lid: arguments[2],
2363
+ });
2364
+ storeWrapper = arguments[3];
2365
+ }
2366
+
2367
+ this.__private_singleton_recordData = this.__private_singleton_recordData || new _RecordData(storeWrapper);
2368
+ (
2369
+ this.__private_singleton_recordData as RecordData & { createCache(identifier: StableRecordIdentifier): void }
2370
+ ).createCache(identifier);
2371
+ return this.__private_singleton_recordData;
2223
2372
  }
2224
2373
 
2225
2374
  assert(`Expected store.createRecordDataFor to be implemented but it wasn't`);
@@ -2424,12 +2573,8 @@ class Store extends Service {
2424
2573
  willDestroy() {
2425
2574
  super.willDestroy();
2426
2575
  this.recordArrayManager.destroy();
2427
-
2428
2576
  this.identifierCache.destroy();
2429
2577
 
2430
- // destroy the graph before unloadAll
2431
- // since then we avoid churning relationships
2432
- // during unload
2433
2578
  if (HAS_RECORD_DATA_PACKAGE) {
2434
2579
  const peekGraph = (
2435
2580
  importSync('@ember-data/record-data/-private') as typeof import('@ember-data/record-data/-private')
@@ -2574,3 +2719,124 @@ function errorsHashToArray(errors): JsonApiValidationError[] {
2574
2719
 
2575
2720
  return out;
2576
2721
  }
2722
+
2723
+ function normalizeProperties(
2724
+ store: Store,
2725
+ identifier: StableRecordIdentifier,
2726
+ properties?: { [key: string]: unknown },
2727
+ isForV1: boolean = false
2728
+ ): { [key: string]: unknown } | undefined {
2729
+ // assert here
2730
+ if (properties !== undefined) {
2731
+ if ('id' in properties) {
2732
+ assert(`expected id to be a string or null`, properties.id !== undefined);
2733
+ }
2734
+ assert(
2735
+ `You passed '${typeof properties}' as properties for record creation instead of an object.`,
2736
+ typeof properties === 'object' && properties !== null
2737
+ );
2738
+
2739
+ const { type } = identifier;
2740
+
2741
+ // convert relationship Records to RecordDatas before passing to RecordData
2742
+ let defs = store.getSchemaDefinitionService().relationshipsDefinitionFor({ type });
2743
+
2744
+ if (defs !== null) {
2745
+ let keys = Object.keys(properties);
2746
+ let relationshipValue;
2747
+
2748
+ for (let i = 0; i < keys.length; i++) {
2749
+ let prop = keys[i];
2750
+ let def = defs[prop];
2751
+
2752
+ if (def !== undefined) {
2753
+ if (def.kind === 'hasMany') {
2754
+ if (DEBUG) {
2755
+ assertRecordsPassedToHasMany(properties[prop] as RecordInstance[]);
2756
+ }
2757
+ relationshipValue = extractIdentifiersFromRecords(properties[prop] as RecordInstance[], isForV1);
2758
+ } else {
2759
+ relationshipValue = extractIdentifierFromRecord(properties[prop] as RecordInstance, isForV1);
2760
+ }
2761
+
2762
+ properties[prop] = relationshipValue;
2763
+ }
2764
+ }
2765
+ }
2766
+ }
2767
+ return properties;
2768
+ }
2769
+
2770
+ function assertRecordsPassedToHasMany(records: RecordInstance[]) {
2771
+ assert(`You must pass an array of records to set a hasMany relationship`, Array.isArray(records));
2772
+ assert(
2773
+ `All elements of a hasMany relationship must be instances of Model, you passed ${records
2774
+ .map((r) => `${typeof r}`)
2775
+ .join(', ')}`,
2776
+ (function () {
2777
+ return records.every((record) => {
2778
+ try {
2779
+ recordIdentifierFor(record);
2780
+ return true;
2781
+ } catch {
2782
+ return false;
2783
+ }
2784
+ });
2785
+ })()
2786
+ );
2787
+ }
2788
+
2789
+ function extractIdentifiersFromRecords(records: RecordInstance[], isForV1: boolean = false): StableRecordIdentifier[] {
2790
+ return records.map((record) => extractIdentifierFromRecord(record, isForV1)) as StableRecordIdentifier[];
2791
+ }
2792
+
2793
+ type PromiseProxyRecord = { then(): void; content: RecordInstance | null | undefined };
2794
+
2795
+ function extractIdentifierFromRecord(
2796
+ recordOrPromiseRecord: PromiseProxyRecord | RecordInstance | null,
2797
+ isForV1: boolean = false
2798
+ ) {
2799
+ if (!recordOrPromiseRecord) {
2800
+ return null;
2801
+ }
2802
+ const extract = isForV1 ? recordDataFor : recordIdentifierFor;
2803
+
2804
+ if (DEPRECATE_PROMISE_PROXIES && isPromiseRecord(recordOrPromiseRecord)) {
2805
+ let content = recordOrPromiseRecord.content;
2806
+ assert(
2807
+ 'You passed in a promise that did not originate from an EmberData relationship. You can only pass promises that come from a belongsTo or hasMany relationship to the get call.',
2808
+ content !== undefined
2809
+ );
2810
+ deprecate(
2811
+ `You passed in a PromiseProxy to a Relationship API that now expects a resolved value. await the value before setting it.`,
2812
+ false,
2813
+ {
2814
+ id: 'ember-data:deprecate-promise-proxies',
2815
+ until: '5.0',
2816
+ since: {
2817
+ enabled: '4.8',
2818
+ available: '4.8',
2819
+ },
2820
+ for: 'ember-data',
2821
+ }
2822
+ );
2823
+ return content ? extract(content) : null;
2824
+ }
2825
+
2826
+ return extract(recordOrPromiseRecord);
2827
+ }
2828
+
2829
+ function isPromiseRecord(record: PromiseProxyRecord | RecordInstance): record is PromiseProxyRecord {
2830
+ return !!record.then;
2831
+ }
2832
+
2833
+ function secretInit(
2834
+ record: RecordInstance,
2835
+ recordData: RecordData,
2836
+ identifier: StableRecordIdentifier,
2837
+ store: Store
2838
+ ): void {
2839
+ setRecordIdentifier(record, identifier);
2840
+ StoreMap.set(record, store);
2841
+ setRecordDataFor(record, recordData);
2842
+ }