@ember-data/store 4.6.1 → 4.7.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (45) hide show
  1. package/addon/-debug/index.js +35 -13
  2. package/addon/-private/{identifier-cache.ts → caches/identifier-cache.ts} +148 -73
  3. package/addon/-private/caches/instance-cache.ts +690 -0
  4. package/addon/-private/{record-data-for.ts → caches/record-data-for.ts} +2 -7
  5. package/addon/-private/index.ts +44 -24
  6. package/addon/-private/{model → legacy-model-support}/record-reference.ts +15 -13
  7. package/addon/-private/{schema-definition-service.ts → legacy-model-support/schema-definition-service.ts} +13 -9
  8. package/addon/-private/{model → legacy-model-support}/shim-model-class.ts +18 -11
  9. package/addon/-private/managers/record-array-manager.ts +377 -0
  10. package/addon/-private/managers/record-data-manager.ts +845 -0
  11. package/addon/-private/managers/record-data-store-wrapper.ts +421 -0
  12. package/addon/-private/managers/record-notification-manager.ts +109 -0
  13. package/addon/-private/network/fetch-manager.ts +567 -0
  14. package/addon/-private/{finders.js → network/finders.js} +14 -17
  15. package/addon/-private/{request-cache.ts → network/request-cache.ts} +21 -18
  16. package/addon/-private/{snapshot-record-array.ts → network/snapshot-record-array.ts} +14 -31
  17. package/addon/-private/{snapshot.ts → network/snapshot.ts} +40 -49
  18. package/addon/-private/{promise-proxies.ts → proxies/promise-proxies.ts} +76 -15
  19. package/addon/-private/{promise-proxy-base.js → proxies/promise-proxy-base.js} +0 -0
  20. package/addon/-private/record-arrays/identifier-array.ts +924 -0
  21. package/addon/-private/{core-store.ts → store-service.ts} +574 -215
  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/utils/is-non-empty-string.ts +1 -1
  27. package/addon/-private/{normalize-model-name.ts → utils/normalize-model-name.ts} +1 -3
  28. package/addon/-private/utils/promise-record.ts +5 -6
  29. package/addon/-private/{serializer-response.ts → utils/serializer-response.ts} +2 -2
  30. package/addon/-private/utils/uuid-polyfill.ts +73 -0
  31. package/package.json +12 -8
  32. package/addon/-private/backburner.js +0 -25
  33. package/addon/-private/errors-utils.js +0 -146
  34. package/addon/-private/fetch-manager.ts +0 -597
  35. package/addon/-private/identity-map.ts +0 -54
  36. package/addon/-private/instance-cache.ts +0 -387
  37. package/addon/-private/internal-model-factory.ts +0 -359
  38. package/addon/-private/internal-model-map.ts +0 -121
  39. package/addon/-private/model/internal-model.ts +0 -602
  40. package/addon/-private/record-array-manager.ts +0 -444
  41. package/addon/-private/record-arrays/adapter-populated-record-array.ts +0 -130
  42. package/addon/-private/record-arrays/record-array.ts +0 -318
  43. package/addon/-private/record-data-store-wrapper.ts +0 -243
  44. package/addon/-private/record-notification-manager.ts +0 -73
  45. package/addon/-private/weak-cache.ts +0 -125
@@ -3,8 +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';
7
- import type { Backburner } from '@ember/runloop/-private/backburner';
6
+ import { _backburner as emberBackburner, run } from '@ember/runloop';
8
7
  import Service from '@ember/service';
9
8
  import { registerWaiter, unregisterWaiter } from '@ember/test';
10
9
  import { DEBUG } from '@glimmer/env';
@@ -17,8 +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,
20
- DEPRECATE_RECORD_WAS_INVALID,
19
+ DEPRECATE_PROMISE_PROXIES,
21
20
  DEPRECATE_STORE_FIND,
21
+ DEPRECATE_V1CACHE_STORE_APIS,
22
22
  } from '@ember-data/private-build-infra/deprecations';
23
23
  import type { RecordData as RecordDataClass } from '@ember-data/record-data/-private';
24
24
  import type { DSModel } from '@ember-data/types/q/ds-model';
@@ -32,39 +32,42 @@ import type {
32
32
  import type { StableExistingRecordIdentifier, StableRecordIdentifier } from '@ember-data/types/q/identifier';
33
33
  import type { MinimumAdapterInterface } from '@ember-data/types/q/minimum-adapter-interface';
34
34
  import type { MinimumSerializerInterface } from '@ember-data/types/q/minimum-serializer-interface';
35
- 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 type { RecordData, RecordDataV1 } from '@ember-data/types/q/record-data';
36
+ import { JsonApiValidationError } from '@ember-data/types/q/record-data-json-api';
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
- 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';
60
- import AdapterPopulatedRecordArray from './record-arrays/adapter-populated-record-array';
61
- 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';
50
+ storeFor,
51
+ StoreMap,
52
+ } from './caches/instance-cache';
53
+ import recordDataFor, { 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 type { NonSingletonRecordDataManager } from './managers/record-data-manager';
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 type Snapshot from './network/snapshot';
65
+ import SnapshotRecordArray from './network/snapshot-record-array';
66
+ import { PromiseArray, promiseArray, PromiseObject, promiseObject } from './proxies/promise-proxies';
67
+ import IdentifierArray, { Collection } from './record-arrays/identifier-array';
68
+ import coerceId, { ensureStringId } from './utils/coerce-id';
67
69
  import constructResource from './utils/construct-resource';
70
+ import normalizeModelName from './utils/normalize-model-name';
68
71
  import promiseRecord from './utils/promise-record';
69
72
 
70
73
  export { storeFor };
@@ -162,18 +165,8 @@ export interface CreateRecordProperties {
162
165
  */
163
166
 
164
167
  class Store extends Service {
165
- /**
166
- * Ember Data uses several specialized micro-queues for organizing
167
- and coalescing similar async work.
168
+ __private_singleton_recordData!: RecordData;
168
169
 
169
- These queues are currently controlled by a flush scheduled into
170
- ember-data's custom backburner instance.
171
- *
172
- * EmberData specific backburner instance
173
- * @property _backburner
174
- * @private
175
- */
176
- declare _backburner: Backburner;
177
170
  declare recordArrayManager: RecordArrayManager;
178
171
 
179
172
  declare _notificationManager: NotificationManager;
@@ -191,6 +184,7 @@ class Store extends Service {
191
184
  declare _trackAsyncRequestStart: (str: string) => void;
192
185
  declare _trackAsyncRequestEnd: (token: AsyncTrackingToken) => void;
193
186
  declare __asyncWaiter: () => boolean;
187
+ declare DISABLE_WAITER?: boolean;
194
188
 
195
189
  /**
196
190
  @method init
@@ -224,10 +218,6 @@ class Store extends Service {
224
218
  this._serializerCache = Object.create(null);
225
219
  this._modelFactoryCache = Object.create(null);
226
220
 
227
- // private
228
- // TODO we should find a path to something simpler than backburner
229
- this._backburner = edBackburner;
230
-
231
221
  if (DEBUG) {
232
222
  if (this.generateStackTracesForTrackedRequests === undefined) {
233
223
  this.generateStackTracesForTrackedRequests = false;
@@ -268,50 +258,115 @@ class Store extends Service {
268
258
 
269
259
  this.__asyncWaiter = () => {
270
260
  let tracked = this._trackedAsyncRequests;
271
- return tracked.length === 0;
261
+ return this.DISABLE_WAITER || tracked.length === 0;
272
262
  };
273
263
 
274
264
  registerWaiter(this.__asyncWaiter);
275
265
  }
276
266
  }
277
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
+ */
278
310
  getRequestStateService(): RequestCache {
279
311
  return this._fetchManager.requestCache;
280
312
  }
281
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
+ */
282
331
  instantiateRecord(
283
332
  identifier: StableRecordIdentifier,
284
333
  createRecordArgs: { [key: string]: unknown },
285
- recordDataFor: (identifier: StableRecordIdentifier) => RecordDataRecordWrapper,
334
+ recordDataFor: (identifier: StableRecordIdentifier) => RecordData,
286
335
  notificationManager: NotificationManager
287
336
  ): DSModel | RecordInstance {
288
337
  if (HAS_MODEL_PACKAGE) {
289
338
  let modelName = identifier.type;
290
- let store = this;
291
339
 
292
- let internalModel = this._instanceCache._internalModelForResource(identifier);
340
+ let recordData = this._instanceCache.getRecordData(identifier);
341
+ // TODO deprecate allowing unknown args setting
293
342
  let createOptions: any = {
294
- _internalModel: internalModel,
295
- // TODO deprecate allowing unknown args setting
296
343
  _createProps: createRecordArgs,
297
344
  // TODO @deprecate consider deprecating accessing record properties during init which the below is necessary for
298
- _secretInit: (record: RecordInstance): void => {
299
- setRecordIdentifier(record, identifier);
300
- StoreMap.set(record, store);
301
- setRecordDataFor(record, internalModel._recordData);
345
+ _secretInit: {
346
+ identifier,
347
+ recordData,
348
+ store: this,
349
+ cb: secretInit,
302
350
  },
303
- container: null, // necessary hack for setOwner?
304
351
  };
305
352
 
306
353
  // ensure that `getOwner(this)` works inside a model instance
307
354
  setOwner(createOptions, getOwner(this));
308
- delete createOptions.container;
309
- // TODO this needs to not use the private property here to get modelFactoryCache so as to not break interop
310
- return getModelFactory(this, this._modelFactoryCache, modelName).create(createOptions);
355
+ return getModelFactory(this, this._modelFactoryCache, modelName).class.create(createOptions);
311
356
  }
312
357
  assert(`You must implement the store's instantiateRecord hook for your custom model class.`);
313
358
  }
314
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
+ */
315
370
  teardownRecord(record: DSModel | RecordInstance): void {
316
371
  if (HAS_MODEL_PACKAGE) {
317
372
  assert(
@@ -324,8 +379,20 @@ class Store extends Service {
324
379
  }
325
380
  }
326
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
+ */
327
392
  getSchemaDefinitionService(): SchemaDefinitionService {
328
393
  if (HAS_MODEL_PACKAGE && !this._schemaDefinitionService) {
394
+ // it is potentially a mistake for the RFC to have not enabled chaining these services, though highlander rule is nice.
395
+ // what ember-m3 did via private API to allow both worlds to interop would be much much harder using this.
329
396
  this._schemaDefinitionService = new DSModelSchemaDefinitionService(this);
330
397
  }
331
398
  assert(
@@ -335,6 +402,57 @@ class Store extends Service {
335
402
  return this._schemaDefinitionService;
336
403
  }
337
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 more than once, but only one schema
411
+ * definition service may exist. Therefore if you wish to chain services
412
+ * you must lookup the existing service and close over it with the new
413
+ * service by calling `getSchemaDefinitionService` prior to registration.
414
+ *
415
+ * For Example:
416
+ *
417
+ * ```ts
418
+ * import Store from '@ember-data/store';
419
+ *
420
+ * class SchemaDelegator {
421
+ * constructor(schema) {
422
+ * this._schema = schema;
423
+ * }
424
+ *
425
+ * doesTypeExist(type: string): boolean {
426
+ * if (AbstractSchemas.has(type)) {
427
+ * return true;
428
+ * }
429
+ * return this._schema.doesTypeExist(type);
430
+ * }
431
+ *
432
+ * attributesDefinitionFor(identifier: RecordIdentifier | { type: string }): AttributesSchema {
433
+ * return this._schema.attributesDefinitionFor(identifier);
434
+ * }
435
+ *
436
+ * relationshipsDefinitionFor(identifier: RecordIdentifier | { type: string }): RelationshipsSchema {
437
+ * const schema = AbstractSchemas.get(identifier.type);
438
+ * return schema || this._schema.relationshipsDefinitionFor(identifier);
439
+ * }
440
+ * }
441
+ *
442
+ * export default class extends Store {
443
+ * constructor(...args) {
444
+ * super(...args);
445
+ *
446
+ * const schema = this.getSchemaDefinitionService();
447
+ * this.registerSchemaDefinitionService(new SchemaDelegator(schema));
448
+ * }
449
+ * }
450
+ * ```
451
+ *
452
+ * @method registerSchemaDefinitionService
453
+ * @param {SchemaDefinitionService} schema
454
+ * @public
455
+ */
338
456
  registerSchemaDefinitionService(schema: SchemaDefinitionService) {
339
457
  this._schemaDefinitionService = schema;
340
458
  }
@@ -345,6 +463,12 @@ class Store extends Service {
345
463
  When used with Model from @ember-data/model the return is the model class,
346
464
  but this is not guaranteed.
347
465
 
466
+ If looking to query attribute or relationship information it is
467
+ recommended to use `getSchemaDefinitionService` instead. This method
468
+ should be considered legacy and exists primarily to continue to support
469
+ Adapter/Serializer APIs which expect it's return value in their method
470
+ signatures.
471
+
348
472
  The class of a model might be useful if you want to get a list of all the
349
473
  relationship names of the model, see
350
474
  [`relationshipNames`](/ember-data/release/classes/Model?anchor=relationshipNames)
@@ -367,10 +491,6 @@ class Store extends Service {
367
491
  );
368
492
  if (HAS_MODEL_PACKAGE) {
369
493
  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
494
  let maybeFactory = getModelFactory(this, this._modelFactoryCache, normalizedModelName);
375
495
 
376
496
  // for factorFor factory/class split
@@ -439,8 +559,9 @@ class Store extends Service {
439
559
  // of record-arrays via ember's run loop, not our own.
440
560
  //
441
561
  // to remove this, we would need to move to a new `async` API.
442
- return emberBackburner.join(() => {
443
- return this._backburner.join(() => {
562
+ let record!: RecordInstance;
563
+ emberBackburner.join(() => {
564
+ this._join(() => {
444
565
  let normalizedModelName = normalizeModelName(modelName);
445
566
  let properties = { ...inputProperties };
446
567
 
@@ -461,17 +582,33 @@ class Store extends Service {
461
582
 
462
583
  // Coerce ID to a string
463
584
  properties.id = coerceId(properties.id);
585
+ const resource = { type: normalizedModelName, id: properties.id };
464
586
 
465
- const factory = internalModelFactoryFor(this);
466
- const internalModel = factory.build({ type: normalizedModelName, id: properties.id });
467
- const { identifier } = internalModel;
587
+ if (resource.id) {
588
+ const identifier = this.identifierCache.peekRecordIdentifier(resource as ResourceIdentifierObject);
468
589
 
469
- this._instanceCache.getRecordData(identifier).clientDidCreate();
470
- this.recordArrayManager.recordDidChange(identifier);
590
+ assert(
591
+ `The id ${properties.id} has already been used with another '${normalizedModelName}' record.`,
592
+ !identifier
593
+ );
594
+ }
595
+
596
+ const identifier = this.identifierCache.createIdentifierForNewRecord(resource);
597
+ const recordData = this._instanceCache.getRecordData(identifier);
471
598
 
472
- return this._instanceCache.getRecord(identifier, properties);
599
+ const createOptions = normalizeProperties(
600
+ this,
601
+ identifier,
602
+ properties,
603
+ (recordData as NonSingletonRecordDataManager).managedVersion === '1'
604
+ );
605
+ const resultProps = recordData.clientDidCreate(identifier, createOptions);
606
+ this.recordArrayManager.identifierAdded(identifier);
607
+
608
+ record = this._instanceCache.getRecord(identifier, resultProps);
473
609
  });
474
610
  });
611
+ return record;
475
612
  }
476
613
 
477
614
  /**
@@ -495,13 +632,17 @@ class Store extends Service {
495
632
  if (DEBUG) {
496
633
  assertDestroyingStore(this, 'deleteRecord');
497
634
  }
498
- this._backburner.join(() => {
499
- let identifier = peekRecordIdentifier(record);
500
- if (identifier) {
501
- let internalModel = internalModelFactoryFor(this).peek(identifier);
502
- if (internalModel) {
503
- internalModel.deleteRecord();
504
- }
635
+
636
+ const identifier = peekRecordIdentifier(record);
637
+ const recordData = identifier && this._instanceCache.peek({ identifier, bucket: 'recordData' });
638
+ assert(`expected a recordData instance to exist for the record`, recordData);
639
+ this._join(() => {
640
+ recordData.setIsDeleted(identifier, true);
641
+
642
+ if (recordData.isNew(identifier)) {
643
+ run(() => {
644
+ this._instanceCache.unloadRecord(identifier);
645
+ });
505
646
  }
506
647
  });
507
648
  }
@@ -526,12 +667,9 @@ class Store extends Service {
526
667
  if (DEBUG) {
527
668
  assertDestroyingStore(this, 'unloadRecord');
528
669
  }
529
- let identifier = peekRecordIdentifier(record);
670
+ const identifier = peekRecordIdentifier(record);
530
671
  if (identifier) {
531
- let internalModel = internalModelFactoryFor(this).peek(identifier);
532
- if (internalModel) {
533
- internalModel.unloadRecord();
534
- }
672
+ this._instanceCache.unloadRecord(identifier);
535
673
  }
536
674
  }
537
675
 
@@ -745,7 +883,7 @@ class Store extends Service {
745
883
  // }
746
884
  // ]
747
885
  store.findRecord('post', 1, { reload: true }).then(function(post) {
748
- post.get('revision'); // 2
886
+ post.revision; // 2
749
887
  });
750
888
  ```
751
889
 
@@ -783,7 +921,7 @@ class Store extends Service {
783
921
  });
784
922
 
785
923
  let blogPost = store.findRecord('post', 1).then(function(post) {
786
- post.get('revision'); // 1
924
+ post.revision; // 1
787
925
  });
788
926
 
789
927
  // later, once adapter#findRecord resolved with
@@ -795,7 +933,7 @@ class Store extends Service {
795
933
  // }
796
934
  // ]
797
935
 
798
- blogPost.get('revision'); // 2
936
+ blogPost.revision; // 2
799
937
  ```
800
938
 
801
939
  If you would like to force or prevent background reloading, you can set a
@@ -983,13 +1121,12 @@ class Store extends Service {
983
1121
  resource = constructResource(type, normalizedId);
984
1122
  }
985
1123
 
986
- const internalModel = internalModelFactoryFor(this).lookup(resource);
987
- const { identifier } = internalModel;
1124
+ const identifier = this.identifierCache.getOrCreateRecordIdentifier(resource);
988
1125
  let promise;
989
1126
  options = options || {};
990
1127
 
991
1128
  // if not loaded start loading
992
- if (!internalModel.isLoaded) {
1129
+ if (!this._instanceCache.recordIsLoaded(identifier)) {
993
1130
  promise = this._instanceCache._fetchDataIfNeededForIdentifier(identifier, options);
994
1131
 
995
1132
  // Refetch if the reload option is passed
@@ -997,14 +1134,14 @@ class Store extends Service {
997
1134
  assertIdentifierHasId(identifier);
998
1135
  promise = this._fetchManager.scheduleFetch(identifier, options);
999
1136
  } else {
1000
- let snapshot = this._instanceCache.createSnapshot(identifier, options);
1137
+ let snapshot: Snapshot | null = null;
1001
1138
  let adapter = this.adapterFor(identifier.type);
1002
1139
 
1003
1140
  // Refetch the record if the adapter thinks the record is stale
1004
1141
  if (
1005
1142
  typeof options.reload === 'undefined' &&
1006
1143
  adapter.shouldReloadRecord &&
1007
- adapter.shouldReloadRecord(this, snapshot)
1144
+ adapter.shouldReloadRecord(this, (snapshot = this._instanceCache.createSnapshot(identifier, options)))
1008
1145
  ) {
1009
1146
  assertIdentifierHasId(identifier);
1010
1147
  promise = this._fetchManager.scheduleFetch(identifier, options);
@@ -1014,7 +1151,10 @@ class Store extends Service {
1014
1151
  options.backgroundReload !== false &&
1015
1152
  (options.backgroundReload ||
1016
1153
  !adapter.shouldBackgroundReloadRecord ||
1017
- adapter.shouldBackgroundReloadRecord(this, snapshot))
1154
+ adapter.shouldBackgroundReloadRecord(
1155
+ this,
1156
+ (snapshot = snapshot || this._instanceCache.createSnapshot(identifier, options))
1157
+ ))
1018
1158
  ) {
1019
1159
  assertIdentifierHasId(identifier);
1020
1160
  this._fetchManager.scheduleFetch(identifier, options);
@@ -1025,7 +1165,11 @@ class Store extends Service {
1025
1165
  }
1026
1166
  }
1027
1167
 
1028
- return promiseRecord(this, promise, `DS: Store#findRecord ${identifier}`);
1168
+ if (DEPRECATE_PROMISE_PROXIES) {
1169
+ return promiseRecord(this, promise);
1170
+ }
1171
+
1172
+ return promise.then((identifier: StableRecordIdentifier) => this.peekRecord(identifier));
1029
1173
  }
1030
1174
 
1031
1175
  /**
@@ -1144,10 +1288,10 @@ class Store extends Service {
1144
1288
  peekRecord(identifier: ResourceIdentifierObject | string, id?: string | number): RecordInstance | null {
1145
1289
  if (arguments.length === 1 && isMaybeIdentifier(identifier)) {
1146
1290
  const stableIdentifier = this.identifierCache.peekRecordIdentifier(identifier);
1147
- const internalModel = stableIdentifier && internalModelFactoryFor(this).peek(stableIdentifier);
1291
+ const isLoaded = stableIdentifier && this._instanceCache.recordIsLoaded(stableIdentifier);
1148
1292
  // TODO come up with a better mechanism for determining if we have data and could peek.
1149
1293
  // this is basically an "are we not empty" query.
1150
- return internalModel && internalModel.isLoaded ? this._instanceCache.getRecord(stableIdentifier) : null;
1294
+ return isLoaded ? this._instanceCache.getRecord(stableIdentifier) : null;
1151
1295
  }
1152
1296
 
1153
1297
  if (DEBUG) {
@@ -1164,9 +1308,9 @@ class Store extends Service {
1164
1308
  const normalizedId = ensureStringId(id);
1165
1309
  const resource = { type, id: normalizedId };
1166
1310
  const stableIdentifier = this.identifierCache.peekRecordIdentifier(resource);
1167
- const internalModel = stableIdentifier && internalModelFactoryFor(this).peek(stableIdentifier);
1311
+ const isLoaded = stableIdentifier && this._instanceCache.recordIsLoaded(stableIdentifier);
1168
1312
 
1169
- return internalModel && internalModel.isLoaded ? this._instanceCache.getRecord(stableIdentifier) : null;
1313
+ return isLoaded ? this._instanceCache.getRecord(stableIdentifier) : null;
1170
1314
  }
1171
1315
 
1172
1316
  /**
@@ -1184,6 +1328,7 @@ class Store extends Service {
1184
1328
  ```
1185
1329
 
1186
1330
  @method hasRecordForId
1331
+ @deprecated
1187
1332
  @public
1188
1333
  @param {String} modelName
1189
1334
  @param {(String|Integer)} id
@@ -1211,9 +1356,7 @@ class Store extends Service {
1211
1356
  const resource = { type, id: trueId };
1212
1357
 
1213
1358
  const identifier = this.identifierCache.peekRecordIdentifier(resource);
1214
- const internalModel = identifier && internalModelFactoryFor(this).peek(identifier);
1215
-
1216
- return !!internalModel && internalModel.isLoaded;
1359
+ return Boolean(identifier && this._instanceCache.recordIsLoaded(identifier));
1217
1360
  }
1218
1361
  assert(`store.hasRecordForId has been removed`);
1219
1362
  }
@@ -1257,8 +1400,8 @@ class Store extends Service {
1257
1400
  decoded: "/api/v1/person?ids[]=1&ids[]=2&ids[]=3"
1258
1401
  ```
1259
1402
 
1260
- This method returns a promise, which is resolved with an
1261
- [`AdapterPopulatedRecordArray`](/ember-data/release/classes/AdapterPopulatedRecordArray)
1403
+ This method returns a promise, which is resolved with a
1404
+ [`Collection`](/ember-data/release/classes/Collection)
1262
1405
  once the server returns.
1263
1406
 
1264
1407
  @since 1.13.0
@@ -1269,7 +1412,7 @@ class Store extends Service {
1269
1412
  @param {Object} options optional, may include `adapterOptions` hash which will be passed to adapter.query
1270
1413
  @return {Promise} promise
1271
1414
  */
1272
- query(modelName: string, query, options): PromiseArray<RecordInstance, AdapterPopulatedRecordArray> {
1415
+ query(modelName: string, query, options): PromiseArray<RecordInstance, Collection> | Promise<Collection> {
1273
1416
  if (DEBUG) {
1274
1417
  assertDestroyingStore(this, 'query');
1275
1418
  }
@@ -1303,9 +1446,12 @@ class Store extends Service {
1303
1446
  query,
1304
1447
  recordArray,
1305
1448
  adapterOptionsWrapper
1306
- ) as unknown as Promise<AdapterPopulatedRecordArray>;
1449
+ ) as unknown as Promise<Collection>;
1307
1450
 
1308
- return promiseArray(queryPromise);
1451
+ if (DEPRECATE_PROMISE_PROXIES) {
1452
+ return promiseArray(queryPromise);
1453
+ }
1454
+ return queryPromise;
1309
1455
  }
1310
1456
 
1311
1457
  /**
@@ -1336,8 +1482,8 @@ class Store extends Service {
1336
1482
 
1337
1483
  ```javascript
1338
1484
  store.queryRecord('user', {}).then(function(user) {
1339
- let username = user.get('username');
1340
- console.log(`Currently logged in as ${username}`);
1485
+ let username = user.username;
1486
+ // do thing
1341
1487
  });
1342
1488
  ```
1343
1489
 
@@ -1374,9 +1520,9 @@ class Store extends Service {
1374
1520
 
1375
1521
  ```javascript
1376
1522
  store.query('user', { username: 'unique' }).then(function(users) {
1377
- return users.get('firstObject');
1523
+ return users.firstObject;
1378
1524
  }).then(function(user) {
1379
- let id = user.get('id');
1525
+ let id = user.id;
1380
1526
  });
1381
1527
  ```
1382
1528
 
@@ -1394,7 +1540,7 @@ class Store extends Service {
1394
1540
 
1395
1541
  ```javascript
1396
1542
  store.queryRecord('user', { username: 'unique' }).then(function(user) {
1397
- console.log(user); // null
1543
+ // user is null
1398
1544
  });
1399
1545
  ```
1400
1546
 
@@ -1406,7 +1552,11 @@ class Store extends Service {
1406
1552
  @param {Object} options optional, may include `adapterOptions` hash which will be passed to adapter.queryRecord
1407
1553
  @return {Promise} promise which resolves with the found record or `null`
1408
1554
  */
1409
- queryRecord(modelName: string, query, options?): PromiseObject<RecordInstance | null> {
1555
+ queryRecord(
1556
+ modelName: string,
1557
+ query,
1558
+ options?
1559
+ ): PromiseObject<RecordInstance | null> | Promise<RecordInstance | null> {
1410
1560
  if (DEBUG) {
1411
1561
  assertDestroyingStore(this, 'queryRecord');
1412
1562
  }
@@ -1439,7 +1589,10 @@ class Store extends Service {
1439
1589
  adapterOptionsWrapper
1440
1590
  ) as Promise<StableRecordIdentifier | null>;
1441
1591
 
1442
- return promiseObject(promise.then((identifier) => identifier && this.peekRecord(identifier)));
1592
+ if (DEPRECATE_PROMISE_PROXIES) {
1593
+ return promiseObject(promise.then((identifier) => identifier && this.peekRecord(identifier)));
1594
+ }
1595
+ return promise.then((identifier) => identifier && this.peekRecord(identifier));
1443
1596
  }
1444
1597
 
1445
1598
  /**
@@ -1633,7 +1786,7 @@ class Store extends Service {
1633
1786
  findAll(
1634
1787
  modelName: string,
1635
1788
  options: { reload?: boolean; backgroundReload?: boolean } = {}
1636
- ): PromiseArray<RecordInstance, RecordArray> {
1789
+ ): PromiseArray<RecordInstance, IdentifierArray> {
1637
1790
  if (DEBUG) {
1638
1791
  assertDestroyingStore(this, 'findAll');
1639
1792
  }
@@ -1659,7 +1812,7 @@ class Store extends Service {
1659
1812
  array.isUpdating = true;
1660
1813
  fetch = _findAll(adapter, this, normalizedModelName, options);
1661
1814
  } else {
1662
- let snapshotArray = array._createSnapshot(options);
1815
+ let snapshotArray = new SnapshotRecordArray(this, array, options);
1663
1816
 
1664
1817
  if (options.reload !== false) {
1665
1818
  if (
@@ -1667,7 +1820,7 @@ class Store extends Service {
1667
1820
  (!adapter.shouldReloadAll && snapshotArray.length === 0)
1668
1821
  ) {
1669
1822
  array.isUpdating = true;
1670
- fetch = _findAll(adapter, this, modelName, options);
1823
+ fetch = _findAll(adapter, this, modelName, options, snapshotArray);
1671
1824
  }
1672
1825
  }
1673
1826
 
@@ -1680,14 +1833,17 @@ class Store extends Service {
1680
1833
  adapter.shouldBackgroundReloadAll(this, snapshotArray)
1681
1834
  ) {
1682
1835
  array.isUpdating = true;
1683
- _findAll(adapter, this, modelName, options);
1836
+ _findAll(adapter, this, modelName, options, snapshotArray);
1684
1837
  }
1685
1838
 
1686
1839
  fetch = resolve(array);
1687
1840
  }
1688
1841
  }
1689
1842
 
1690
- return promiseArray(fetch);
1843
+ if (DEPRECATE_PROMISE_PROXIES) {
1844
+ return promiseArray(fetch);
1845
+ }
1846
+ return fetch;
1691
1847
  }
1692
1848
 
1693
1849
  /**
@@ -1715,7 +1871,7 @@ class Store extends Service {
1715
1871
  @param {String} modelName
1716
1872
  @return {RecordArray}
1717
1873
  */
1718
- peekAll(modelName) {
1874
+ peekAll(modelName: string): IdentifierArray {
1719
1875
  if (DEBUG) {
1720
1876
  assertDestroyingStore(this, 'peekAll');
1721
1877
  }
@@ -1724,8 +1880,9 @@ class Store extends Service {
1724
1880
  `Passing classes to store methods has been removed. Please pass a dasherized string instead of ${modelName}`,
1725
1881
  typeof modelName === 'string'
1726
1882
  );
1727
- let normalizedModelName = normalizeModelName(modelName);
1728
- return this.recordArrayManager.liveRecordArrayFor(normalizedModelName);
1883
+
1884
+ let type = normalizeModelName(modelName);
1885
+ return this.recordArrayManager.liveArrayFor(type);
1729
1886
  }
1730
1887
 
1731
1888
  /**
@@ -1752,45 +1909,29 @@ class Store extends Service {
1752
1909
  !modelName || typeof modelName === 'string'
1753
1910
  );
1754
1911
 
1755
- const factory = internalModelFactoryFor(this);
1756
-
1757
- if (modelName === undefined) {
1758
- factory.clear();
1759
- } else {
1760
- let normalizedModelName = normalizeModelName(modelName);
1761
- factory.clear(normalizedModelName);
1762
- }
1763
- }
1764
-
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' },
1912
+ this._join(() => {
1913
+ if (modelName === undefined) {
1914
+ // destroy the graph before unloadAll
1915
+ // since then we avoid churning relationships
1916
+ // during unload
1917
+ if (HAS_RECORD_DATA_PACKAGE) {
1918
+ const peekGraph = (
1919
+ importSync('@ember-data/record-data/-private') as typeof import('@ember-data/record-data/-private')
1920
+ ).peekGraph;
1921
+ let graph = peekGraph(this);
1922
+ if (graph) {
1923
+ graph.identifiers.clear();
1924
+ }
1786
1925
  }
1787
- );
1788
- if (DEBUG) {
1789
- assertDestroyingStore(this, 'recordWasInvalid');
1926
+ this._notificationManager.destroy();
1927
+
1928
+ this.recordArrayManager.clear();
1929
+ this._instanceCache.clear();
1930
+ } else {
1931
+ let normalizedModelName = normalizeModelName(modelName);
1932
+ this._instanceCache.clear(normalizedModelName);
1790
1933
  }
1791
- internalModel.adapterDidInvalidate(parsedErrors, error);
1792
- }
1793
- assert(`store.recordWasInvalid has been removed`);
1934
+ });
1794
1935
  }
1795
1936
 
1796
1937
  /**
@@ -1972,19 +2113,20 @@ class Store extends Service {
1972
2113
  @method _push
1973
2114
  @private
1974
2115
  @param {Object} jsonApiDoc
1975
- @return {InternalModel|Array<InternalModel>} pushed InternalModel(s)
2116
+ @return {StableRecordIdentifier|Array<StableRecordIdentifier>} identifiers for the primary records that had data loaded
1976
2117
  */
1977
2118
  _push(jsonApiDoc): StableExistingRecordIdentifier | StableExistingRecordIdentifier[] | null {
1978
2119
  if (DEBUG) {
1979
2120
  assertDestroyingStore(this, '_push');
1980
2121
  }
1981
- let identifiers = this._backburner.join(() => {
2122
+ let ret;
2123
+ this._join(() => {
1982
2124
  let included = jsonApiDoc.included;
1983
2125
  let i, length;
1984
2126
 
1985
2127
  if (included) {
1986
2128
  for (i = 0, length = included.length; i < length; i++) {
1987
- this._instanceCache._load(included[i]);
2129
+ this._instanceCache.loadData(included[i]);
1988
2130
  }
1989
2131
  }
1990
2132
 
@@ -1993,13 +2135,15 @@ class Store extends Service {
1993
2135
  let identifiers = new Array(length);
1994
2136
 
1995
2137
  for (i = 0; i < length; i++) {
1996
- identifiers[i] = this._instanceCache._load(jsonApiDoc.data[i]);
2138
+ identifiers[i] = this._instanceCache.loadData(jsonApiDoc.data[i]);
1997
2139
  }
1998
- return identifiers;
2140
+ ret = identifiers;
2141
+ return;
1999
2142
  }
2000
2143
 
2001
2144
  if (jsonApiDoc.data === null) {
2002
- return null;
2145
+ ret = null;
2146
+ return;
2003
2147
  }
2004
2148
 
2005
2149
  assert(
@@ -2009,11 +2153,11 @@ class Store extends Service {
2009
2153
  typeof jsonApiDoc.data === 'object'
2010
2154
  );
2011
2155
 
2012
- return this._instanceCache._load(jsonApiDoc.data);
2156
+ ret = this._instanceCache.loadData(jsonApiDoc.data);
2157
+ return;
2013
2158
  });
2014
2159
 
2015
- // this typecast is necessary because `backburner.join` is mistyped to return void
2016
- return identifiers;
2160
+ return ret;
2017
2161
  }
2018
2162
 
2019
2163
  /**
@@ -2108,41 +2252,47 @@ class Store extends Service {
2108
2252
  return this._instanceCache.createSnapshot(recordIdentifierFor(record)).serialize(options);
2109
2253
  }
2110
2254
 
2111
- // todo @runspired this should likely be publicly @documented for custom records
2255
+ /**
2256
+ * Trigger a save for a Record.
2257
+ *
2258
+ * @method saveRecord
2259
+ * @public
2260
+ * @param {RecordInstance} record
2261
+ * @param options
2262
+ * @returns {Promise<RecordInstance>}
2263
+ */
2112
2264
  saveRecord(record: RecordInstance, options: Dict<unknown> = {}): Promise<RecordInstance> {
2113
2265
  assert(`Unable to initate save for a record in a disconnected state`, storeFor(record));
2114
2266
  let identifier = recordIdentifierFor(record);
2115
- let internalModel = identifier && internalModelFactoryFor(this).peek(identifier)!;
2267
+ let recordData = identifier && this._instanceCache.peek({ identifier, bucket: 'recordData' });
2116
2268
 
2117
- if (!internalModel) {
2269
+ if (!recordData) {
2118
2270
  // this commonly means we're disconnected
2119
2271
  // but just in case we reject here to prevent bad things.
2120
2272
  return reject(`Record Is Disconnected`);
2121
2273
  }
2122
2274
  // TODO we used to check if the record was destroyed here
2123
- // Casting can be removed once REQUEST_SERVICE ff is turned on
2124
- // because a `Record` is provided there will always be a matching internalModel
2125
-
2126
2275
  assert(
2127
2276
  `Cannot initiate a save request for an unloaded record: ${identifier}`,
2128
- !internalModel.isEmpty && !internalModel.isDestroyed
2277
+ recordData && this._instanceCache.recordIsLoaded(identifier)
2129
2278
  );
2130
- if (internalModel._isRecordFullyDeleted()) {
2279
+ if (recordDataIsFullyDeleted(this._instanceCache, identifier)) {
2131
2280
  return resolve(record);
2132
2281
  }
2133
2282
 
2134
- internalModel.adapterWillCommit();
2283
+ recordData.willCommit(identifier);
2284
+ if (isDSModel(record)) {
2285
+ record.errors.clear();
2286
+ }
2135
2287
 
2136
2288
  if (!options) {
2137
2289
  options = {};
2138
2290
  }
2139
- let recordData = this._instanceCache.getRecordData(identifier);
2140
2291
  let operation: 'createRecord' | 'deleteRecord' | 'updateRecord' = 'updateRecord';
2141
2292
 
2142
- // TODO handle missing isNew
2143
- if (recordData.isNew && recordData.isNew()) {
2293
+ if (recordData.isNew(identifier)) {
2144
2294
  operation = 'createRecord';
2145
- } else if (recordData.isDeleted && recordData.isDeleted()) {
2295
+ } else if (recordData.isDeleted(identifier)) {
2146
2296
  operation = 'deleteRecord';
2147
2297
  }
2148
2298
 
@@ -2162,7 +2312,7 @@ class Store extends Service {
2162
2312
  have an outer run loop available still from the first
2163
2313
  call to `store._push`;
2164
2314
  */
2165
- this._backburner.join(() => {
2315
+ this._join(() => {
2166
2316
  if (DEBUG) {
2167
2317
  assertDestroyingStore(this, 'saveRecord');
2168
2318
  }
@@ -2170,19 +2320,24 @@ class Store extends Service {
2170
2320
  let data = payload && payload.data;
2171
2321
  if (!data) {
2172
2322
  assert(
2173
- `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.`,
2174
- internalModel.id
2323
+ `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.`,
2324
+ identifier.id
2175
2325
  );
2176
2326
  }
2177
2327
 
2178
2328
  const cache = this.identifierCache;
2329
+ let actualIdentifier = identifier;
2179
2330
  if (operation !== 'deleteRecord' && data) {
2180
- cache.updateRecordIdentifier(identifier, data);
2331
+ actualIdentifier = cache.updateRecordIdentifier(identifier, data);
2181
2332
  }
2182
2333
 
2183
2334
  //We first make sure the primary data has been updated
2184
- //TODO try to move notification to the user to the end of the runloop
2185
- internalModel.adapterDidCommit(data);
2335
+ const recordData = this._instanceCache.getRecordData(actualIdentifier);
2336
+ recordData.didCommit(identifier, data);
2337
+
2338
+ if (operation === 'deleteRecord') {
2339
+ this.recordArrayManager.identifierRemoved(actualIdentifier);
2340
+ }
2186
2341
 
2187
2342
  if (payload && payload.included) {
2188
2343
  this._push({ data: null, included: payload.included });
@@ -2191,12 +2346,14 @@ class Store extends Service {
2191
2346
  return record;
2192
2347
  },
2193
2348
  (e) => {
2194
- if (typeof e === 'string') {
2195
- throw e;
2349
+ let err = e;
2350
+ if (!e) {
2351
+ err = new Error(`Unknown Error Occurred During Request`);
2352
+ } else if (typeof e === 'string') {
2353
+ err = new Error(e);
2196
2354
  }
2197
- const { error, parsedErrors } = e;
2198
- internalModel.adapterDidInvalidate(parsedErrors, error);
2199
- throw error;
2355
+ adapterDidInvalidate(this, identifier, err);
2356
+ throw err;
2200
2357
  }
2201
2358
  );
2202
2359
  }
@@ -2205,19 +2362,15 @@ class Store extends Service {
2205
2362
  * Instantiation hook allowing applications or addons to configure the store
2206
2363
  * to utilize a custom RecordData implementation.
2207
2364
  *
2208
- * @method createRecordDataFor
2365
+ * @method createRecordDataFor (hook)
2209
2366
  * @public
2210
- * @param modelName
2211
- * @param id
2212
- * @param clientId
2367
+ * @param identifier
2213
2368
  * @param storeWrapper
2214
2369
  */
2215
2370
  createRecordDataFor(
2216
- modelName: string,
2217
- id: string | null,
2218
- clientId: string,
2371
+ identifier: StableRecordIdentifier,
2219
2372
  storeWrapper: RecordDataStoreWrapper
2220
- ): RecordData {
2373
+ ): RecordData | RecordDataV1 {
2221
2374
  if (HAS_RECORD_DATA_PACKAGE) {
2222
2375
  // we can't greedily use require as this causes
2223
2376
  // a cycle we can't easily fix (or clearly pin point) at present.
@@ -2227,15 +2380,33 @@ class Store extends Service {
2227
2380
  if (_RecordData === undefined) {
2228
2381
  _RecordData = (
2229
2382
  importSync('@ember-data/record-data/-private') as typeof import('@ember-data/record-data/-private')
2230
- ).RecordData as RecordDataConstruct;
2383
+ ).RecordData;
2231
2384
  }
2232
2385
 
2233
- let identifier = this.identifierCache.getOrCreateRecordIdentifier({
2234
- type: modelName,
2235
- id,
2236
- lid: clientId,
2237
- });
2238
- return new _RecordData(identifier, storeWrapper);
2386
+ if (DEPRECATE_V1CACHE_STORE_APIS && arguments.length === 4) {
2387
+ deprecate(
2388
+ `Store.createRecordDataFor(<type>, <id>, <lid>, <storeWrapper>) has been deprecated in favor of Store.createRecordDataFor(<identifier>, <storeWrapper>)`,
2389
+ false,
2390
+ {
2391
+ id: 'ember-data:deprecate-v1cache-store-apis',
2392
+ for: 'ember-data',
2393
+ until: '5.0',
2394
+ since: { enabled: '4.8', available: '4.8' },
2395
+ }
2396
+ );
2397
+ identifier = this.identifierCache.getOrCreateRecordIdentifier({
2398
+ type: arguments[0],
2399
+ id: arguments[1],
2400
+ lid: arguments[2],
2401
+ });
2402
+ storeWrapper = arguments[3];
2403
+ }
2404
+
2405
+ this.__private_singleton_recordData = this.__private_singleton_recordData || new _RecordData(storeWrapper);
2406
+ (
2407
+ this.__private_singleton_recordData as RecordData & { createCache(identifier: StableRecordIdentifier): void }
2408
+ ).createCache(identifier);
2409
+ return this.__private_singleton_recordData;
2239
2410
  }
2240
2411
 
2241
2412
  assert(`Expected store.createRecordDataFor to be implemented but it wasn't`);
@@ -2440,22 +2611,8 @@ class Store extends Service {
2440
2611
  willDestroy() {
2441
2612
  super.willDestroy();
2442
2613
  this.recordArrayManager.destroy();
2443
-
2444
2614
  this.identifierCache.destroy();
2445
2615
 
2446
- // destroy the graph before unloadAll
2447
- // since then we avoid churning relationships
2448
- // during unload
2449
- if (HAS_RECORD_DATA_PACKAGE) {
2450
- const peekGraph = (
2451
- importSync('@ember-data/record-data/-private') as typeof import('@ember-data/record-data/-private')
2452
- ).peekGraph;
2453
- let graph = peekGraph(this);
2454
- if (graph) {
2455
- graph.willDestroy();
2456
- }
2457
- }
2458
-
2459
2616
  this.unloadAll();
2460
2617
 
2461
2618
  if (DEBUG) {
@@ -2509,3 +2666,205 @@ export function assertIdentifierHasId(
2509
2666
  ): asserts identifier is StableExistingRecordIdentifier {
2510
2667
  assert(`Attempted to schedule a fetch for a record without an id.`, identifier.id !== null);
2511
2668
  }
2669
+
2670
+ function isDSModel(record: RecordInstance | null): record is DSModel {
2671
+ return (
2672
+ HAS_MODEL_PACKAGE &&
2673
+ !!record &&
2674
+ 'constructor' in record &&
2675
+ 'isModel' in record.constructor &&
2676
+ record.constructor.isModel === true
2677
+ );
2678
+ }
2679
+
2680
+ type AdapterErrors = Error & { errors?: unknown[]; isAdapterError?: true; code?: string };
2681
+ type SerializerWithParseErrors = MinimumSerializerInterface & {
2682
+ extractErrors?(store: Store, modelClass: ShimModelClass, error: AdapterErrors, recordId: string | null): any;
2683
+ };
2684
+
2685
+ function adapterDidInvalidate(
2686
+ store: Store,
2687
+ identifier: StableRecordIdentifier,
2688
+ error: Error & { errors?: JsonApiValidationError[]; isAdapterError?: true; code?: string }
2689
+ ) {
2690
+ if (error && error.isAdapterError === true && error.code === 'InvalidError') {
2691
+ let serializer = store.serializerFor(identifier.type) as SerializerWithParseErrors;
2692
+
2693
+ // TODO @deprecate extractErrors being called
2694
+ // TODO remove extractErrors from the default serializers.
2695
+ if (serializer && typeof serializer.extractErrors === 'function') {
2696
+ let errorsHash = serializer.extractErrors(store, store.modelFor(identifier.type), error, identifier.id);
2697
+ error.errors = errorsHashToArray(errorsHash);
2698
+ }
2699
+ }
2700
+ const recordData = store._instanceCache.getRecordData(identifier);
2701
+
2702
+ if (error.errors) {
2703
+ assert(
2704
+ `Expected the RecordData implementation for ${identifier} to have a getErrors(identifier) method for retreiving errors.`,
2705
+ typeof recordData.getErrors === 'function'
2706
+ );
2707
+
2708
+ let jsonApiErrors: JsonApiValidationError[] = error.errors;
2709
+ if (jsonApiErrors.length === 0) {
2710
+ jsonApiErrors = [{ title: 'Invalid Error', detail: '', source: { pointer: '/data' } }];
2711
+ }
2712
+ recordData.commitWasRejected(identifier, jsonApiErrors);
2713
+ } else {
2714
+ recordData.commitWasRejected(identifier);
2715
+ }
2716
+ }
2717
+
2718
+ function makeArray(value) {
2719
+ return Array.isArray(value) ? value : [value];
2720
+ }
2721
+
2722
+ const PRIMARY_ATTRIBUTE_KEY = 'base';
2723
+
2724
+ function errorsHashToArray(errors): JsonApiValidationError[] {
2725
+ const out: JsonApiValidationError[] = [];
2726
+
2727
+ if (errors) {
2728
+ Object.keys(errors).forEach((key) => {
2729
+ let messages = makeArray(errors[key]);
2730
+ for (let i = 0; i < messages.length; i++) {
2731
+ let title = 'Invalid Attribute';
2732
+ let pointer = `/data/attributes/${key}`;
2733
+ if (key === PRIMARY_ATTRIBUTE_KEY) {
2734
+ title = 'Invalid Document';
2735
+ pointer = `/data`;
2736
+ }
2737
+ out.push({
2738
+ title: title,
2739
+ detail: messages[i],
2740
+ source: {
2741
+ pointer: pointer,
2742
+ },
2743
+ });
2744
+ }
2745
+ });
2746
+ }
2747
+
2748
+ return out;
2749
+ }
2750
+
2751
+ function normalizeProperties(
2752
+ store: Store,
2753
+ identifier: StableRecordIdentifier,
2754
+ properties?: { [key: string]: unknown },
2755
+ isForV1: boolean = false
2756
+ ): { [key: string]: unknown } | undefined {
2757
+ // assert here
2758
+ if (properties !== undefined) {
2759
+ if ('id' in properties) {
2760
+ assert(`expected id to be a string or null`, properties.id !== undefined);
2761
+ }
2762
+ assert(
2763
+ `You passed '${typeof properties}' as properties for record creation instead of an object.`,
2764
+ typeof properties === 'object' && properties !== null
2765
+ );
2766
+
2767
+ const { type } = identifier;
2768
+
2769
+ // convert relationship Records to RecordDatas before passing to RecordData
2770
+ let defs = store.getSchemaDefinitionService().relationshipsDefinitionFor({ type });
2771
+
2772
+ if (defs !== null) {
2773
+ let keys = Object.keys(properties);
2774
+ let relationshipValue;
2775
+
2776
+ for (let i = 0; i < keys.length; i++) {
2777
+ let prop = keys[i];
2778
+ let def = defs[prop];
2779
+
2780
+ if (def !== undefined) {
2781
+ if (def.kind === 'hasMany') {
2782
+ if (DEBUG) {
2783
+ assertRecordsPassedToHasMany(properties[prop] as RecordInstance[]);
2784
+ }
2785
+ relationshipValue = extractIdentifiersFromRecords(properties[prop] as RecordInstance[], isForV1);
2786
+ } else {
2787
+ relationshipValue = extractIdentifierFromRecord(properties[prop] as RecordInstance, isForV1);
2788
+ }
2789
+
2790
+ properties[prop] = relationshipValue;
2791
+ }
2792
+ }
2793
+ }
2794
+ }
2795
+ return properties;
2796
+ }
2797
+
2798
+ function assertRecordsPassedToHasMany(records: RecordInstance[]) {
2799
+ assert(`You must pass an array of records to set a hasMany relationship`, Array.isArray(records));
2800
+ assert(
2801
+ `All elements of a hasMany relationship must be instances of Model, you passed ${records
2802
+ .map((r) => `${typeof r}`)
2803
+ .join(', ')}`,
2804
+ (function () {
2805
+ return records.every((record) => {
2806
+ try {
2807
+ recordIdentifierFor(record);
2808
+ return true;
2809
+ } catch {
2810
+ return false;
2811
+ }
2812
+ });
2813
+ })()
2814
+ );
2815
+ }
2816
+
2817
+ function extractIdentifiersFromRecords(records: RecordInstance[], isForV1: boolean = false): StableRecordIdentifier[] {
2818
+ return records.map((record) => extractIdentifierFromRecord(record, isForV1)) as StableRecordIdentifier[];
2819
+ }
2820
+
2821
+ type PromiseProxyRecord = { then(): void; content: RecordInstance | null | undefined };
2822
+
2823
+ function extractIdentifierFromRecord(
2824
+ recordOrPromiseRecord: PromiseProxyRecord | RecordInstance | null,
2825
+ isForV1: boolean = false
2826
+ ) {
2827
+ if (!recordOrPromiseRecord) {
2828
+ return null;
2829
+ }
2830
+ const extract = isForV1 ? recordDataFor : recordIdentifierFor;
2831
+
2832
+ if (DEPRECATE_PROMISE_PROXIES && isPromiseRecord(recordOrPromiseRecord)) {
2833
+ let content = recordOrPromiseRecord.content;
2834
+ assert(
2835
+ '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.',
2836
+ content !== undefined
2837
+ );
2838
+ deprecate(
2839
+ `You passed in a PromiseProxy to a Relationship API that now expects a resolved value. await the value before setting it.`,
2840
+ false,
2841
+ {
2842
+ id: 'ember-data:deprecate-promise-proxies',
2843
+ until: '5.0',
2844
+ since: {
2845
+ enabled: '4.8',
2846
+ available: '4.8',
2847
+ },
2848
+ for: 'ember-data',
2849
+ }
2850
+ );
2851
+ return content ? extract(content) : null;
2852
+ }
2853
+
2854
+ return extract(recordOrPromiseRecord);
2855
+ }
2856
+
2857
+ function isPromiseRecord(record: PromiseProxyRecord | RecordInstance): record is PromiseProxyRecord {
2858
+ return !!record.then;
2859
+ }
2860
+
2861
+ function secretInit(
2862
+ record: RecordInstance,
2863
+ recordData: RecordData,
2864
+ identifier: StableRecordIdentifier,
2865
+ store: Store
2866
+ ): void {
2867
+ setRecordIdentifier(record, identifier);
2868
+ StoreMap.set(record, store);
2869
+ setRecordDataFor(record, recordData);
2870
+ }