@ember-data/store 4.5.0-beta.0 → 4.5.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 (64) hide show
  1. package/addon/-private/{system/backburner.js → backburner.js} +0 -0
  2. package/addon/-private/{system/coerce-id.ts → coerce-id.ts} +0 -0
  3. package/addon/-private/{system/store/common.js → common.js} +0 -0
  4. package/addon/-private/{system/core-store.ts → core-store.ts} +467 -1253
  5. package/addon/-private/{system/errors-utils.js → errors-utils.js} +7 -6
  6. package/addon/-private/{system/fetch-manager.ts → fetch-manager.ts} +72 -42
  7. package/addon/-private/finders.js +107 -0
  8. package/addon/-private/identifer-debug-consts.ts +3 -0
  9. package/addon/-private/{identifiers/cache.ts → identifier-cache.ts} +26 -14
  10. package/addon/-private/{system/identity-map.ts → identity-map.ts} +2 -1
  11. package/addon/-private/index.ts +17 -17
  12. package/addon/-private/instance-cache.ts +387 -0
  13. package/addon/-private/{system/store/internal-model-factory.ts → internal-model-factory.ts} +25 -19
  14. package/addon/-private/{system/internal-model-map.ts → internal-model-map.ts} +9 -5
  15. package/addon/-private/model/internal-model.ts +602 -0
  16. package/addon/-private/{system/references/record.ts → model/record-reference.ts} +23 -36
  17. package/addon/-private/{system/model → model}/shim-model-class.ts +19 -14
  18. package/addon/-private/{system/normalize-model-name.ts → normalize-model-name.ts} +0 -0
  19. package/addon/-private/{system/promise-proxies.ts → promise-proxies.ts} +12 -5
  20. package/addon/-private/{system/promise-proxy-base.js → promise-proxy-base.js} +0 -0
  21. package/addon/-private/{system/record-array-manager.ts → record-array-manager.ts} +19 -18
  22. package/addon/-private/{system/record-arrays → record-arrays}/adapter-populated-record-array.ts +11 -10
  23. package/addon/-private/{system/record-arrays → record-arrays}/record-array.ts +37 -19
  24. package/addon/-private/record-data-for.ts +39 -0
  25. package/addon/-private/{system/store/record-data-store-wrapper.ts → record-data-store-wrapper.ts} +21 -26
  26. package/addon/-private/{system/record-notification-manager.ts → record-notification-manager.ts} +8 -3
  27. package/addon/-private/{system/request-cache.ts → request-cache.ts} +5 -6
  28. package/addon/-private/{system/schema-definition-service.ts → schema-definition-service.ts} +30 -14
  29. package/addon/-private/{system/store/serializer-response.ts → serializer-response.ts} +7 -6
  30. package/addon/-private/{system/snapshot-record-array.ts → snapshot-record-array.ts} +27 -8
  31. package/addon/-private/{system/snapshot.ts → snapshot.ts} +54 -39
  32. package/addon/-private/utils/construct-resource.ts +7 -3
  33. package/addon/-private/utils/promise-record.ts +9 -18
  34. package/addon/-private/{system/weak-cache.ts → weak-cache.ts} +2 -2
  35. package/addon/index.ts +1 -0
  36. package/package.json +21 -20
  37. package/addon/-private/identifiers/is-stable-identifier.ts +0 -18
  38. package/addon/-private/identifiers/utils/uuid-v4.ts +0 -80
  39. package/addon/-private/system/ds-model-store.ts +0 -136
  40. package/addon/-private/system/model/internal-model.ts +0 -1303
  41. package/addon/-private/system/model/states.js +0 -736
  42. package/addon/-private/system/record-arrays.ts +0 -8
  43. package/addon/-private/system/record-data-for.ts +0 -54
  44. package/addon/-private/system/references/belongs-to.ts +0 -406
  45. package/addon/-private/system/references/has-many.ts +0 -487
  46. package/addon/-private/system/references/reference.ts +0 -205
  47. package/addon/-private/system/references.js +0 -9
  48. package/addon/-private/system/store/finders.js +0 -412
  49. package/addon/-private/ts-interfaces/ds-model.ts +0 -50
  50. package/addon/-private/ts-interfaces/ember-data-json-api.ts +0 -145
  51. package/addon/-private/ts-interfaces/fetch-manager.ts +0 -44
  52. package/addon/-private/ts-interfaces/identifier.ts +0 -246
  53. package/addon/-private/ts-interfaces/minimum-adapter-interface.ts +0 -584
  54. package/addon/-private/ts-interfaces/minimum-serializer-interface.ts +0 -257
  55. package/addon/-private/ts-interfaces/promise-proxies.ts +0 -3
  56. package/addon/-private/ts-interfaces/record-data-json-api.ts +0 -29
  57. package/addon/-private/ts-interfaces/record-data-record-wrapper.ts +0 -46
  58. package/addon/-private/ts-interfaces/record-data-schemas.ts +0 -45
  59. package/addon/-private/ts-interfaces/record-data-store-wrapper.ts +0 -56
  60. package/addon/-private/ts-interfaces/record-data.ts +0 -72
  61. package/addon/-private/ts-interfaces/record-instance.ts +0 -18
  62. package/addon/-private/ts-interfaces/schema-definition-service.ts +0 -12
  63. package/addon/-private/ts-interfaces/store.ts +0 -10
  64. package/addon/-private/ts-interfaces/utils.ts +0 -6
@@ -1,90 +1,79 @@
1
1
  /**
2
2
  @module @ember-data/store
3
3
  */
4
- import { getOwner } from '@ember/application';
5
- import { A } from '@ember/array';
6
- import { assert, inspect, warn } from '@ember/debug';
7
- import { set } from '@ember/object';
4
+ import { getOwner, setOwner } from '@ember/application';
5
+ import { assert, deprecate } from '@ember/debug';
8
6
  import { _backburner as emberBackburner } from '@ember/runloop';
9
7
  import type { Backburner } from '@ember/runloop/-private/backburner';
10
8
  import Service from '@ember/service';
11
9
  import { registerWaiter, unregisterWaiter } from '@ember/test';
12
- import { isNone, isPresent, typeOf } from '@ember/utils';
13
10
  import { DEBUG } from '@glimmer/env';
14
- import Ember from 'ember';
15
11
 
16
12
  import { importSync } from '@embroider/macros';
17
- import { all, default as RSVP, resolve } from 'rsvp';
13
+ import { reject, resolve } from 'rsvp';
18
14
 
19
- import { HAS_RECORD_DATA_PACKAGE } from '@ember-data/private-build-infra';
20
- import type { ManyRelationship, RecordData as RecordDataClass } from '@ember-data/record-data/-private';
21
- import type { RelationshipState } from '@ember-data/record-data/-private/graph/-state';
22
-
23
- import { IdentifierCache } from '../identifiers/cache';
15
+ import type DSModelClass from '@ember-data/model';
16
+ import { HAS_MODEL_PACKAGE, HAS_RECORD_DATA_PACKAGE } from '@ember-data/private-build-infra';
17
+ import {
18
+ DEPRECATE_HAS_RECORD,
19
+ DEPRECATE_JSON_API_FALLBACK,
20
+ DEPRECATE_RECORD_WAS_INVALID,
21
+ DEPRECATE_STORE_FIND,
22
+ } from '@ember-data/private-build-infra/deprecations';
23
+ import type { RecordData as RecordDataClass } from '@ember-data/record-data/-private';
24
+ import type { DSModel } from '@ember-data/types/q/ds-model';
24
25
  import type {
25
26
  CollectionResourceDocument,
26
27
  EmptyResourceDocument,
27
- ExistingResourceObject,
28
28
  JsonApiDocument,
29
29
  ResourceIdentifierObject,
30
30
  SingleResourceDocument,
31
- } from '../ts-interfaces/ember-data-json-api';
32
- import type {
33
- RecordIdentifier,
34
- StableExistingRecordIdentifier,
35
- StableRecordIdentifier,
36
- } from '../ts-interfaces/identifier';
37
- import { MinimumAdapterInterface } from '../ts-interfaces/minimum-adapter-interface';
38
- import type { MinimumSerializerInterface } from '../ts-interfaces/minimum-serializer-interface';
39
- import type { RecordData } from '../ts-interfaces/record-data';
40
- import type { JsonApiRelationship } from '../ts-interfaces/record-data-json-api';
41
- import type { RecordDataRecordWrapper } from '../ts-interfaces/record-data-record-wrapper';
42
- import type { AttributesSchema, RelationshipsSchema } from '../ts-interfaces/record-data-schemas';
43
- import type { RecordInstance } from '../ts-interfaces/record-instance';
44
- import type { SchemaDefinitionService } from '../ts-interfaces/schema-definition-service';
45
- import type { FindOptions } from '../ts-interfaces/store';
46
- import type { Dict } from '../ts-interfaces/utils';
47
- import constructResource from '../utils/construct-resource';
48
- import promiseRecord from '../utils/promise-record';
31
+ } from '@ember-data/types/q/ember-data-json-api';
32
+ import type { StableExistingRecordIdentifier, StableRecordIdentifier } from '@ember-data/types/q/identifier';
33
+ import type { MinimumAdapterInterface } from '@ember-data/types/q/minimum-adapter-interface';
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';
37
+ import type { RecordInstance } from '@ember-data/types/q/record-instance';
38
+ import type { SchemaDefinitionService } from '@ember-data/types/q/schema-definition-service';
39
+ import type { FindOptions } from '@ember-data/types/q/store';
40
+ import type { Dict } from '@ember-data/types/q/utils';
41
+
49
42
  import edBackburner from './backburner';
50
43
  import coerceId, { ensureStringId } from './coerce-id';
51
44
  import FetchManager, { SaveOp } from './fetch-manager';
52
- import type InternalModel from './model/internal-model';
45
+ import { _findAll, _query, _queryRecord } from './finders';
46
+ import { IdentifierCache } from './identifier-cache';
47
+ import { InstanceCache, storeFor, StoreMap } from './instance-cache';
53
48
  import {
54
- assertRecordsPassedToHasMany,
55
- extractRecordDataFromRecord,
56
- extractRecordDatasFromRecords,
57
- } from './model/internal-model';
49
+ internalModelFactoryFor,
50
+ peekRecordIdentifier,
51
+ recordIdentifierFor,
52
+ setRecordIdentifier,
53
+ } from './internal-model-factory';
54
+ import RecordReference from './model/record-reference';
58
55
  import type ShimModelClass from './model/shim-model-class';
59
56
  import { getShimClass } from './model/shim-model-class';
60
57
  import normalizeModelName from './normalize-model-name';
61
- import type { PromiseArray, PromiseObject } from './promise-proxies';
62
- import { promiseArray, promiseObject } from './promise-proxies';
58
+ import { PromiseArray, promiseArray, PromiseObject, promiseObject } from './promise-proxies';
63
59
  import RecordArrayManager from './record-array-manager';
64
- import { AdapterPopulatedRecordArray, RecordArray } from './record-arrays';
60
+ import AdapterPopulatedRecordArray from './record-arrays/adapter-populated-record-array';
61
+ import RecordArray from './record-arrays/record-array';
65
62
  import { setRecordDataFor } from './record-data-for';
63
+ import RecordDataStoreWrapper from './record-data-store-wrapper';
66
64
  import NotificationManager from './record-notification-manager';
67
- import type { BelongsToReference, HasManyReference } from './references';
68
- import { RecordReference } from './references';
69
65
  import type RequestCache from './request-cache';
70
- import { _findAll, _findBelongsTo, _findHasMany, _query, _queryRecord } from './store/finders';
71
- import {
72
- internalModelFactoryFor,
73
- peekRecordIdentifier,
74
- recordIdentifierFor,
75
- setRecordIdentifier,
76
- } from './store/internal-model-factory';
77
- import RecordDataStoreWrapper from './store/record-data-store-wrapper';
78
- import WeakCache from './weak-cache';
66
+ import { DSModelSchemaDefinitionService, getModelFactory } from './schema-definition-service';
67
+ import constructResource from './utils/construct-resource';
68
+ import promiseRecord from './utils/promise-record';
69
+
70
+ export { storeFor };
79
71
 
80
72
  type RecordDataConstruct = typeof RecordDataClass;
81
73
  let _RecordData: RecordDataConstruct | undefined;
82
74
 
83
- const { ENV } = Ember;
84
75
  type AsyncTrackingToken = Readonly<{ label: string; trace: Error | string }>;
85
76
 
86
- const RECORD_REFERENCES = new WeakCache<StableRecordIdentifier, RecordReference>(DEBUG ? 'reference' : '');
87
-
88
77
  function freeze<T>(obj: T): T {
89
78
  if (typeof Object.freeze === 'function') {
90
79
  return Object.freeze(obj);
@@ -172,7 +161,7 @@ export interface CreateRecordProperties {
172
161
  @extends Ember.Service
173
162
  */
174
163
 
175
- abstract class CoreStore extends Service {
164
+ class Store extends Service {
176
165
  /**
177
166
  * Ember Data uses several specialized micro-queues for organizing
178
167
  and coalescing similar async work.
@@ -189,79 +178,26 @@ abstract class CoreStore extends Service {
189
178
 
190
179
  declare _notificationManager: NotificationManager;
191
180
  declare identifierCache: IdentifierCache;
192
- declare _adapterCache: Dict<MinimumAdapterInterface & { store: CoreStore }>;
193
- declare _serializerCache: Dict<MinimumSerializerInterface & { store: CoreStore }>;
194
- declare _storeWrapper: RecordDataStoreWrapper;
181
+ declare _adapterCache: Dict<MinimumAdapterInterface & { store: Store }>;
182
+ declare _serializerCache: Dict<MinimumSerializerInterface & { store: Store }>;
183
+ declare _modelFactoryCache: Dict<unknown>;
195
184
  declare _fetchManager: FetchManager;
196
185
  declare _schemaDefinitionService: SchemaDefinitionService;
186
+ declare _instanceCache: InstanceCache;
197
187
 
198
188
  // DEBUG-only properties
199
189
  declare _trackedAsyncRequests: AsyncTrackingToken[];
200
- declare shouldTrackAsyncRequests: boolean;
201
190
  declare generateStackTracesForTrackedRequests: boolean;
202
191
  declare _trackAsyncRequestStart: (str: string) => void;
203
192
  declare _trackAsyncRequestEnd: (token: AsyncTrackingToken) => void;
204
193
  declare __asyncWaiter: () => boolean;
205
194
 
206
- /**
207
- The default adapter to use to communicate to a backend server or
208
- other persistence layer. This will be overridden by an application
209
- adapter if present.
210
-
211
- If you want to specify `app/adapters/custom.js` as a string, do:
212
-
213
- ```js
214
- import Store from '@ember-data/store';
215
-
216
- export default Store.extend({
217
- constructor() {
218
- super(...arguments);
219
- this.adapter = 'custom';
220
- }
221
- }
222
- ```
223
-
224
- @property adapter
225
- @public
226
- @default '-json-api'
227
- @type {String}
228
- */
229
-
230
- /**
231
- This property returns the adapter, after resolving a possible
232
- string key.
233
-
234
- If the supplied `adapter` was a class, or a String property
235
- path resolved to a class, this property will instantiate the
236
- class.
237
-
238
- This property is cacheable, so the same instance of a specified
239
- adapter class should be used for the lifetime of the store.
240
-
241
- @property defaultAdapter
242
- @private
243
- @return Adapter
244
- */
245
-
246
195
  /**
247
196
  @method init
248
197
  @private
249
198
  */
250
199
  constructor() {
251
200
  super(...arguments);
252
- this._adapterCache = Object.create(null);
253
- this._serializerCache = Object.create(null);
254
- this._storeWrapper = new RecordDataStoreWrapper(this);
255
- this._backburner = edBackburner;
256
- this.recordArrayManager = new RecordArrayManager({ store: this });
257
-
258
- RECORD_REFERENCES._generator = (identifier) => {
259
- return new RecordReference(this, identifier);
260
- };
261
-
262
- this._fetchManager = new FetchManager(this);
263
- this._notificationManager = new NotificationManager(this);
264
- this.__recordDataFor = this.__recordDataFor.bind(this);
265
201
 
266
202
  /**
267
203
  * Provides access to the IdentifierCache instance
@@ -275,10 +211,24 @@ abstract class CoreStore extends Service {
275
211
  */
276
212
  this.identifierCache = new IdentifierCache();
277
213
 
214
+ // private but maybe useful to be here, somewhat intimate
215
+ this.recordArrayManager = new RecordArrayManager({ store: this });
216
+
217
+ // private, TODO consider taking public as the instance is public to instantiateRecord anyway
218
+ this._notificationManager = new NotificationManager(this);
219
+
220
+ // private
221
+ this._fetchManager = new FetchManager(this);
222
+ this._instanceCache = new InstanceCache(this);
223
+ this._adapterCache = Object.create(null);
224
+ this._serializerCache = Object.create(null);
225
+ this._modelFactoryCache = Object.create(null);
226
+
227
+ // private
228
+ // TODO we should find a path to something simpler than backburner
229
+ this._backburner = edBackburner;
230
+
278
231
  if (DEBUG) {
279
- if (this.shouldTrackAsyncRequests === undefined) {
280
- this.shouldTrackAsyncRequests = false;
281
- }
282
232
  if (this.generateStackTracesForTrackedRequests === undefined) {
283
233
  this.generateStackTracesForTrackedRequests = false;
284
234
  }
@@ -317,11 +267,8 @@ abstract class CoreStore extends Service {
317
267
  };
318
268
 
319
269
  this.__asyncWaiter = () => {
320
- let shouldTrack = this.shouldTrackAsyncRequests;
321
270
  let tracked = this._trackedAsyncRequests;
322
- let isSettled = tracked.length === 0;
323
-
324
- return shouldTrack !== true || isSettled;
271
+ return tracked.length === 0;
325
272
  };
326
273
 
327
274
  registerWaiter(this.__asyncWaiter);
@@ -332,85 +279,66 @@ abstract class CoreStore extends Service {
332
279
  return this._fetchManager.requestCache;
333
280
  }
334
281
 
335
- _instantiateRecord(
336
- internalModel: InternalModel,
337
- modelName: string,
338
- recordData: RecordData,
282
+ instantiateRecord(
339
283
  identifier: StableRecordIdentifier,
340
- properties?: { [key: string]: any }
341
- ) {
342
- // assert here
343
- if (properties !== undefined) {
344
- assert(
345
- `You passed '${properties}' as properties for record creation instead of an object.`,
346
- typeof properties === 'object' && properties !== null
347
- );
348
-
349
- if ('id' in properties) {
350
- internalModel.setId(properties.id);
351
- }
352
-
353
- // convert relationship Records to RecordDatas before passing to RecordData
354
- let defs = this._relationshipsDefinitionFor({ type: modelName });
355
-
356
- if (defs !== null) {
357
- let keys = Object.keys(properties);
358
- let relationshipValue;
359
-
360
- for (let i = 0; i < keys.length; i++) {
361
- let prop = keys[i];
362
- let def = defs[prop];
363
-
364
- if (def !== undefined) {
365
- if (def.kind === 'hasMany') {
366
- if (DEBUG) {
367
- assertRecordsPassedToHasMany(properties[prop]);
368
- }
369
- relationshipValue = extractRecordDatasFromRecords(properties[prop]);
370
- } else {
371
- relationshipValue = extractRecordDataFromRecord(properties[prop]);
372
- }
284
+ createRecordArgs: { [key: string]: unknown },
285
+ recordDataFor: (identifier: StableRecordIdentifier) => RecordDataRecordWrapper,
286
+ notificationManager: NotificationManager
287
+ ): DSModel | RecordInstance {
288
+ if (HAS_MODEL_PACKAGE) {
289
+ let modelName = identifier.type;
290
+ let store = this;
291
+
292
+ let internalModel = this._instanceCache._internalModelForResource(identifier);
293
+ let createOptions: any = {
294
+ _internalModel: internalModel,
295
+ // TODO deprecate allowing unknown args setting
296
+ _createProps: createRecordArgs,
297
+ // 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);
302
+ },
303
+ container: null, // necessary hack for setOwner?
304
+ };
373
305
 
374
- properties[prop] = relationshipValue;
375
- }
376
- }
377
- }
306
+ // ensure that `getOwner(this)` works inside a model instance
307
+ 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);
378
311
  }
379
-
380
- // TODO guard against initRecordOptions no being there
381
- let createOptions = recordData._initRecordCreateOptions(properties);
382
- //TODO Igor pass a wrapper instead of RD
383
- let record = this.instantiateRecord(identifier, createOptions, this.__recordDataFor, this._notificationManager);
384
- setRecordIdentifier(record, identifier);
385
- //recordToInternalModelMap.set(record, internalModel);
386
- return record;
312
+ assert(`You must implement the store's instantiateRecord hook for your custom model class.`);
387
313
  }
388
314
 
389
- abstract instantiateRecord(
390
- identifier: StableRecordIdentifier,
391
- createRecordArgs: { [key: string]: unknown }, // args passed in to store.createRecord() and processed by recordData to be set on creation
392
- recordDataFor: (identifier: RecordIdentifier) => RecordDataRecordWrapper,
393
- notificationManager: NotificationManager
394
- ): RecordInstance;
395
- abstract teardownRecord(record: RecordInstance): void;
396
- abstract getSchemaDefinitionService(): SchemaDefinitionService;
397
-
398
- _attributesDefinitionFor(identifier: RecordIdentifier | { type: string }): AttributesSchema {
399
- return this.getSchemaDefinitionService().attributesDefinitionFor(identifier);
315
+ teardownRecord(record: DSModel | RecordInstance): void {
316
+ if (HAS_MODEL_PACKAGE) {
317
+ assert(
318
+ `expected to receive an instance of DSModel. If using a custom model make sure you implement teardownRecord`,
319
+ 'destroy' in record
320
+ );
321
+ (record as DSModel).destroy();
322
+ } else {
323
+ assert(`You must implement the store's teardownRecord hook for your custom models`);
324
+ }
400
325
  }
401
326
 
402
- _relationshipsDefinitionFor(identifier: RecordIdentifier | { type: string }): RelationshipsSchema {
403
- return this.getSchemaDefinitionService().relationshipsDefinitionFor(identifier);
327
+ getSchemaDefinitionService(): SchemaDefinitionService {
328
+ if (HAS_MODEL_PACKAGE && !this._schemaDefinitionService) {
329
+ this._schemaDefinitionService = new DSModelSchemaDefinitionService(this);
330
+ }
331
+ assert(
332
+ `You must registerSchemaDefinitionService with the store to use custom model classes`,
333
+ this._schemaDefinitionService
334
+ );
335
+ return this._schemaDefinitionService;
404
336
  }
405
337
 
406
338
  registerSchemaDefinitionService(schema: SchemaDefinitionService) {
407
339
  this._schemaDefinitionService = schema;
408
340
  }
409
341
 
410
- _relationshipMetaFor(modelName: string, id: string | null, key: string) {
411
- return this._relationshipsDefinitionFor({ type: modelName })[key];
412
- }
413
-
414
342
  /**
415
343
  Returns the schema for a particular `modelName`.
416
344
 
@@ -427,41 +355,46 @@ abstract class CoreStore extends Service {
427
355
  @param {String} modelName
428
356
  @return {subclass of Model | ShimModelClass}
429
357
  */
430
- modelFor(modelName: string): ShimModelClass {
358
+ // TODO @deprecate in favor of schema APIs, requires adapter/serializer overhaul or replacement
359
+ modelFor(modelName: string): ShimModelClass | DSModelClass {
431
360
  if (DEBUG) {
432
361
  assertDestroyedStoreOnly(this, 'modelFor');
433
362
  }
434
-
435
- return getShimClass(this, modelName);
436
- }
437
-
438
- // Feature Flagged in DSModelStore
439
- /**
440
- Returns whether a ModelClass exists for a given modelName
441
- This exists for legacy support for the RESTSerializer,
442
- which due to how it must guess whether a key is a model
443
- must query for whether a match exists.
444
-
445
- We should investigate an RFC to make this public or removing
446
- this requirement.
447
-
448
- @method _hasModelFor
449
- @private
450
- */
451
- _hasModelFor(modelName: string): boolean {
452
- assert(`You need to pass a model name to the store's hasModelFor method`, isPresent(modelName));
363
+ assert(`You need to pass a model name to the store's modelFor method`, modelName);
453
364
  assert(
454
365
  `Passing classes to store methods has been removed. Please pass a dasherized string instead of ${modelName}`,
455
366
  typeof modelName === 'string'
456
367
  );
368
+ if (HAS_MODEL_PACKAGE) {
369
+ 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
+ let maybeFactory = getModelFactory(this, this._modelFactoryCache, normalizedModelName);
375
+
376
+ // for factorFor factory/class split
377
+ let klass = maybeFactory && maybeFactory.class ? maybeFactory.class : maybeFactory;
378
+ if (!klass || !klass.isModel) {
379
+ assert(
380
+ `No model was found for '${modelName}' and no schema handles the type`,
381
+ this.getSchemaDefinitionService().doesTypeExist(modelName)
382
+ );
383
+
384
+ return getShimClass(this, modelName);
385
+ } else {
386
+ // TODO @deprecate ever returning the klass, always return the shim
387
+ return klass;
388
+ }
389
+ }
457
390
 
458
- return this.getSchemaDefinitionService().doesTypeExist(modelName);
391
+ assert(
392
+ `No model was found for '${modelName}' and no schema handles the type`,
393
+ this.getSchemaDefinitionService().doesTypeExist(modelName)
394
+ );
395
+ return getShimClass(this, modelName);
459
396
  }
460
397
 
461
- // .....................
462
- // . CREATE NEW RECORD .
463
- // .....................
464
-
465
398
  /**
466
399
  Create a new record in the current store. The properties passed
467
400
  to this method are set on the newly created record.
@@ -495,7 +428,7 @@ abstract class CoreStore extends Service {
495
428
  if (DEBUG) {
496
429
  assertDestroyingStore(this, 'createRecord');
497
430
  }
498
- assert(`You need to pass a model name to the store's createRecord method`, isPresent(modelName));
431
+ assert(`You need to pass a model name to the store's createRecord method`, modelName);
499
432
  assert(
500
433
  `Passing classes to store methods has been removed. Please pass a dasherized string instead of ${modelName}`,
501
434
  typeof modelName === 'string'
@@ -516,8 +449,14 @@ abstract class CoreStore extends Service {
516
449
  // client-side ID generators will use something like uuid.js
517
450
  // to avoid conflicts.
518
451
 
519
- if (isNone(properties.id)) {
520
- properties.id = this._generateId(normalizedModelName, properties);
452
+ if (properties.id === null || properties.id === undefined) {
453
+ let adapter = this.adapterFor(modelName);
454
+
455
+ if (adapter && adapter.generateIdForRecord) {
456
+ properties.id = adapter.generateIdForRecord(this, modelName, properties);
457
+ } else {
458
+ properties.id = null;
459
+ }
521
460
  }
522
461
 
523
462
  // Coerce ID to a string
@@ -525,40 +464,16 @@ abstract class CoreStore extends Service {
525
464
 
526
465
  const factory = internalModelFactoryFor(this);
527
466
  const internalModel = factory.build({ type: normalizedModelName, id: properties.id });
467
+ const { identifier } = internalModel;
528
468
 
529
- internalModel.send('loadedData');
530
- // TODO this exists just to proxy `isNew` to RecordData which is weird
531
- internalModel.didCreateRecord();
469
+ this._instanceCache.getRecordData(identifier).clientDidCreate();
470
+ this.recordArrayManager.recordDidChange(identifier);
532
471
 
533
- return internalModel.getRecord(properties);
472
+ return this._instanceCache.getRecord(identifier, properties);
534
473
  });
535
474
  });
536
475
  }
537
476
 
538
- /**
539
- If possible, this method asks the adapter to generate an ID for
540
- a newly created record.
541
-
542
- @method _generateId
543
- @private
544
- @param {String} modelName
545
- @param {Object} properties from the new record
546
- @return {String} if the adapter can generate one, an ID
547
- */
548
- _generateId(modelName: string, properties: CreateRecordProperties): string | null {
549
- let adapter = this.adapterFor(modelName);
550
-
551
- if (adapter && adapter.generateIdForRecord) {
552
- return adapter.generateIdForRecord(this, modelName, properties);
553
- }
554
-
555
- return null;
556
- }
557
-
558
- // .................
559
- // . DELETE RECORD .
560
- // .................
561
-
562
477
  /**
563
478
  For symmetry, a record can be deleted via the store.
564
479
 
@@ -620,16 +535,13 @@ abstract class CoreStore extends Service {
620
535
  }
621
536
  }
622
537
 
623
- // ................
624
- // . FIND RECORDS .
625
- // ................
626
-
627
538
  /**
628
539
  @method find
629
540
  @param {String} modelName
630
541
  @param {String|Integer} id
631
542
  @param {Object} options
632
543
  @return {Promise} promise
544
+ @deprecated
633
545
  @private
634
546
  */
635
547
  find(modelName: string, id: string | number, options?): PromiseObject<RecordInstance> {
@@ -661,7 +573,20 @@ abstract class CoreStore extends Service {
661
573
  typeof modelName === 'string'
662
574
  );
663
575
 
664
- return this.findRecord(modelName, id);
576
+ if (DEPRECATE_STORE_FIND) {
577
+ deprecate(
578
+ `Using store.find is deprecated, use store.findRecord instead. Likely this means you are relying on the implicit store fetching behavior of routes unknowingly.`,
579
+ false,
580
+ {
581
+ id: 'ember-data:deprecate-store-find',
582
+ since: { available: '4.5', enabled: '4.5' },
583
+ for: 'ember-data',
584
+ until: '5.0',
585
+ }
586
+ );
587
+ return this.findRecord(modelName, id);
588
+ }
589
+ assert(`store.find has been removed. Use store.findRecord instead.`);
665
590
  }
666
591
 
667
592
  /**
@@ -1044,7 +969,7 @@ abstract class CoreStore extends Service {
1044
969
 
1045
970
  assert(
1046
971
  `You need to pass a modelName or resource identifier as the first argument to the store's findRecord method`,
1047
- isPresent(resource)
972
+ resource
1048
973
  );
1049
974
  if (isMaybeIdentifier(resource)) {
1050
975
  options = id as FindOptions | undefined;
@@ -1059,155 +984,48 @@ abstract class CoreStore extends Service {
1059
984
  }
1060
985
 
1061
986
  const internalModel = internalModelFactoryFor(this).lookup(resource);
987
+ const { identifier } = internalModel;
988
+ let promise;
1062
989
  options = options || {};
1063
990
 
1064
- if (!internalModel.currentState.isLoaded) {
1065
- return promiseRecord(
1066
- this._findByInternalModel(internalModel, options),
1067
- `DS: Store#findRecord ${internalModel.identifier}`
1068
- );
1069
- }
1070
-
1071
- let fetchedInternalModel = this._findRecord(internalModel, options);
1072
-
1073
- return promiseRecord(fetchedInternalModel, `DS: Store#findRecord ${internalModel.identifier}`);
1074
- }
1075
-
1076
- _findRecord(internalModel: InternalModel, options: FindOptions): Promise<InternalModel> {
1077
- // Refetch if the reload option is passed
1078
- if (options.reload) {
1079
- return this._scheduleFetch(internalModel, options);
1080
- }
1081
-
1082
- let snapshot = internalModel.createSnapshot(options);
1083
- let adapter = this.adapterFor(internalModel.modelName);
1084
-
1085
- // Refetch the record if the adapter thinks the record is stale
1086
- if (
1087
- typeof options.reload === 'undefined' &&
1088
- adapter.shouldReloadRecord &&
1089
- adapter.shouldReloadRecord(this, snapshot)
1090
- ) {
1091
- return this._scheduleFetch(internalModel, options);
1092
- }
1093
-
1094
- if (options.backgroundReload === false) {
1095
- return resolve(internalModel);
1096
- }
1097
-
1098
- // Trigger the background refetch if backgroundReload option is passed
1099
- if (
1100
- options.backgroundReload ||
1101
- !adapter.shouldBackgroundReloadRecord ||
1102
- adapter.shouldBackgroundReloadRecord(this, snapshot)
1103
- ) {
1104
- this._scheduleFetch(internalModel, options);
1105
- }
1106
-
1107
- // Return the cached record
1108
- return resolve(internalModel);
1109
- }
1110
-
1111
- _findByInternalModel(internalModel: InternalModel, options: FindOptions = {}): Promise<InternalModel> {
1112
- if (options.preload) {
1113
- this._backburner.join(() => {
1114
- internalModel.preloadData(options.preload);
1115
- });
1116
- }
991
+ // if not loaded start loading
992
+ if (!internalModel.isLoaded) {
993
+ promise = this._instanceCache._fetchDataIfNeededForIdentifier(identifier, options);
1117
994
 
1118
- return this._findEmptyInternalModel(internalModel, options);
1119
- }
995
+ // Refetch if the reload option is passed
996
+ } else if (options.reload) {
997
+ assertIdentifierHasId(identifier);
998
+ promise = this._fetchManager.scheduleFetch(identifier, options);
999
+ } else {
1000
+ let snapshot = this._instanceCache.createSnapshot(identifier, options);
1001
+ let adapter = this.adapterFor(identifier.type);
1120
1002
 
1121
- _findEmptyInternalModel(internalModel: InternalModel, options: FindOptions): Promise<InternalModel> {
1122
- if (internalModel.currentState.isEmpty) {
1123
- return this._scheduleFetch(internalModel, options);
1124
- }
1003
+ // Refetch the record if the adapter thinks the record is stale
1004
+ if (
1005
+ typeof options.reload === 'undefined' &&
1006
+ adapter.shouldReloadRecord &&
1007
+ adapter.shouldReloadRecord(this, snapshot)
1008
+ ) {
1009
+ assertIdentifierHasId(identifier);
1010
+ promise = this._fetchManager.scheduleFetch(identifier, options);
1011
+ } else {
1012
+ // Trigger the background refetch if backgroundReload option is passed
1013
+ if (
1014
+ options.backgroundReload !== false &&
1015
+ (options.backgroundReload ||
1016
+ !adapter.shouldBackgroundReloadRecord ||
1017
+ adapter.shouldBackgroundReloadRecord(this, snapshot))
1018
+ ) {
1019
+ assertIdentifierHasId(identifier);
1020
+ this._fetchManager.scheduleFetch(identifier, options);
1021
+ }
1125
1022
 
1126
- if (internalModel.currentState.isLoading) {
1127
- let pendingRequest = this._fetchManager.getPendingFetch(internalModel.identifier, options);
1128
- if (pendingRequest) {
1129
- return pendingRequest.then(() => resolve(internalModel));
1023
+ // Return the cached record
1024
+ promise = resolve(identifier);
1130
1025
  }
1131
- return this._scheduleFetch(internalModel, options);
1132
- }
1133
-
1134
- return resolve(internalModel);
1135
- }
1136
-
1137
- /**
1138
- This method makes a series of requests to the adapter's `find` method
1139
- and returns a promise that resolves once they are all loaded.
1140
-
1141
- @private
1142
- @method findByIds
1143
- @param {String} modelName
1144
- @param {Array} ids
1145
- @return {Promise} promise
1146
- */
1147
- findByIds(modelName, ids) {
1148
- if (DEBUG) {
1149
- assertDestroyingStore(this, 'findByIds');
1150
- }
1151
- assert(`You need to pass a model name to the store's findByIds method`, isPresent(modelName));
1152
- assert(
1153
- `Passing classes to store methods has been removed. Please pass a dasherized string instead of ${modelName}`,
1154
- typeof modelName === 'string'
1155
- );
1156
-
1157
- let promises = new Array(ids.length);
1158
-
1159
- let normalizedModelName = normalizeModelName(modelName);
1160
-
1161
- for (let i = 0; i < ids.length; i++) {
1162
- promises[i] = this.findRecord(normalizedModelName, ids[i]);
1163
- }
1164
-
1165
- return promiseArray(all(promises).then(A, null, `DS: Store#findByIds of ${normalizedModelName} complete`));
1166
- }
1167
-
1168
- _scheduleFetchMany(internalModels, options) {
1169
- let fetches = new Array(internalModels.length);
1170
-
1171
- for (let i = 0; i < internalModels.length; i++) {
1172
- fetches[i] = this._scheduleFetch(internalModels[i], options);
1173
1026
  }
1174
1027
 
1175
- return all(fetches);
1176
- }
1177
-
1178
- _scheduleFetch(internalModel: InternalModel, options = {}): Promise<InternalModel> {
1179
- let generateStackTrace = this.generateStackTracesForTrackedRequests;
1180
- // TODO remove this once we don't rely on state machine
1181
- internalModel.send('loadingData');
1182
- let identifier = internalModel.identifier;
1183
-
1184
- assertIdentifierHasId(identifier);
1185
-
1186
- let promise = this._fetchManager.scheduleFetch(identifier, options, generateStackTrace);
1187
- return promise.then(
1188
- (payload) => {
1189
- // ensure that regardless of id returned we assign to the correct record
1190
- if (payload.data && !Array.isArray(payload.data)) {
1191
- payload.data.lid = identifier.lid;
1192
- }
1193
-
1194
- // Returning this._push here, breaks typing but not any tests, investigate potential missing tests
1195
- let potentiallyNewIm = this._push(payload);
1196
- if (potentiallyNewIm && !Array.isArray(potentiallyNewIm)) {
1197
- return potentiallyNewIm;
1198
- } else {
1199
- return internalModel;
1200
- }
1201
- },
1202
- (error) => {
1203
- // TODO remove this once we don't rely on state machine
1204
- internalModel.send('notFound');
1205
- if (internalModel.currentState.isEmpty) {
1206
- internalModel.unloadRecord();
1207
- }
1208
- throw error;
1209
- }
1210
- );
1028
+ return promiseRecord(this, promise, `DS: Store#findRecord ${identifier}`);
1211
1029
  }
1212
1030
 
1213
1031
  /**
@@ -1248,6 +1066,7 @@ abstract class CoreStore extends Service {
1248
1066
  @since 2.5.0
1249
1067
  @return {RecordReference}
1250
1068
  */
1069
+ // TODO @deprecate getReference (and references generally)
1251
1070
  getReference(resource: string | ResourceIdentifierObject, id: string | number): RecordReference {
1252
1071
  if (DEBUG) {
1253
1072
  assertDestroyingStore(this, 'getReference');
@@ -1268,9 +1087,8 @@ abstract class CoreStore extends Service {
1268
1087
  );
1269
1088
 
1270
1089
  let identifier: StableRecordIdentifier = this.identifierCache.getOrCreateRecordIdentifier(resourceIdentifier);
1271
- if (identifier) {
1272
- return RECORD_REFERENCES.lookup(identifier);
1273
- }
1090
+
1091
+ return this._instanceCache.getReference(identifier);
1274
1092
  }
1275
1093
 
1276
1094
  /**
@@ -1325,18 +1143,18 @@ abstract class CoreStore extends Service {
1325
1143
  peekRecord(identifier: ResourceIdentifierObject): RecordInstance | null;
1326
1144
  peekRecord(identifier: ResourceIdentifierObject | string, id?: string | number): RecordInstance | null {
1327
1145
  if (arguments.length === 1 && isMaybeIdentifier(identifier)) {
1328
- let stableIdentifier = this.identifierCache.peekRecordIdentifier(identifier);
1329
- if (stableIdentifier) {
1330
- return internalModelFactoryFor(this).peek(stableIdentifier)?.getRecord() || null;
1331
- }
1332
- return null;
1146
+ const stableIdentifier = this.identifierCache.peekRecordIdentifier(identifier);
1147
+ const internalModel = stableIdentifier && internalModelFactoryFor(this).peek(stableIdentifier);
1148
+ // TODO come up with a better mechanism for determining if we have data and could peek.
1149
+ // this is basically an "are we not empty" query.
1150
+ return internalModel && internalModel.isLoaded ? this._instanceCache.getRecord(stableIdentifier) : null;
1333
1151
  }
1334
1152
 
1335
1153
  if (DEBUG) {
1336
1154
  assertDestroyingStore(this, 'peekRecord');
1337
1155
  }
1338
1156
 
1339
- assert(`You need to pass a model name to the store's peekRecord method`, isPresent(identifier));
1157
+ assert(`You need to pass a model name to the store's peekRecord method`, identifier);
1340
1158
  assert(
1341
1159
  `Passing classes to store methods has been removed. Please pass a dasherized string instead of ${identifier}`,
1342
1160
  typeof identifier === 'string'
@@ -1344,41 +1162,11 @@ abstract class CoreStore extends Service {
1344
1162
 
1345
1163
  const type = normalizeModelName(identifier);
1346
1164
  const normalizedId = ensureStringId(id);
1165
+ const resource = { type, id: normalizedId };
1166
+ const stableIdentifier = this.identifierCache.peekRecordIdentifier(resource);
1167
+ const internalModel = stableIdentifier && internalModelFactoryFor(this).peek(stableIdentifier);
1347
1168
 
1348
- if (this.hasRecordForId(type, normalizedId)) {
1349
- const resource = constructResource(type, normalizedId);
1350
- return internalModelFactoryFor(this).lookup(resource).getRecord();
1351
- } else {
1352
- return null;
1353
- }
1354
- }
1355
-
1356
- /**
1357
- This method is called by the record's `reload` method.
1358
-
1359
- This method calls the adapter's `find` method, which returns a promise. When
1360
- **that** promise resolves, `_reloadRecord` will resolve the promise returned
1361
- by the record's `reload`.
1362
-
1363
- @method _reloadRecord
1364
- @private
1365
- @param {Model} internalModel
1366
- @param options optional to include adapterOptions
1367
- @return {Promise} promise
1368
- */
1369
- _reloadRecord(internalModel: InternalModel, options: FindOptions): Promise<InternalModel> {
1370
- options.isReloading = true;
1371
- let { id, modelName } = internalModel;
1372
- let adapter = this.adapterFor(modelName);
1373
-
1374
- assert(`You cannot reload a record without an ID`, id);
1375
- assert(`You tried to reload a record but you have no adapter (for ${modelName})`, adapter);
1376
- assert(
1377
- `You tried to reload a record but your adapter does not implement 'findRecord'`,
1378
- typeof adapter.findRecord === 'function'
1379
- );
1380
-
1381
- return this._scheduleFetch(internalModel, options);
1169
+ return internalModel && internalModel.isLoaded ? this._instanceCache.getRecord(stableIdentifier) : null;
1382
1170
  }
1383
1171
 
1384
1172
  /**
@@ -1402,299 +1190,61 @@ abstract class CoreStore extends Service {
1402
1190
  @return {Boolean}
1403
1191
  */
1404
1192
  hasRecordForId(modelName: string, id: string | number): boolean {
1405
- if (DEBUG) {
1406
- assertDestroyingStore(this, 'hasRecordForId');
1407
- }
1408
- assert(`You need to pass a model name to the store's hasRecordForId method`, isPresent(modelName));
1409
- assert(
1410
- `Passing classes to store methods has been removed. Please pass a dasherized string instead of ${modelName}`,
1411
- typeof modelName === 'string'
1412
- );
1193
+ if (DEPRECATE_HAS_RECORD) {
1194
+ deprecate(`store.hasRecordForId has been deprecated in favor of store.peekRecord`, false, {
1195
+ id: 'ember-data:deprecate-has-record-for-id',
1196
+ since: { available: '4.5', enabled: '4.5' },
1197
+ until: '5.0',
1198
+ for: 'ember-data',
1199
+ });
1200
+ if (DEBUG) {
1201
+ assertDestroyingStore(this, 'hasRecordForId');
1202
+ }
1203
+ assert(`You need to pass a model name to the store's hasRecordForId method`, modelName);
1204
+ assert(
1205
+ `Passing classes to store methods has been removed. Please pass a dasherized string instead of ${modelName}`,
1206
+ typeof modelName === 'string'
1207
+ );
1413
1208
 
1414
- const type = normalizeModelName(modelName);
1415
- const trueId = ensureStringId(id);
1416
- const resource = { type, id: trueId };
1209
+ const type = normalizeModelName(modelName);
1210
+ const trueId = ensureStringId(id);
1211
+ const resource = { type, id: trueId };
1417
1212
 
1418
- const identifier = this.identifierCache.peekRecordIdentifier(resource);
1419
- const internalModel = identifier && internalModelFactoryFor(this).peek(identifier);
1213
+ const identifier = this.identifierCache.peekRecordIdentifier(resource);
1214
+ const internalModel = identifier && internalModelFactoryFor(this).peek(identifier);
1420
1215
 
1421
- return !!internalModel && internalModel.currentState.isLoaded;
1216
+ return !!internalModel && internalModel.isLoaded;
1217
+ }
1218
+ assert(`store.hasRecordForId has been removed`);
1422
1219
  }
1423
1220
 
1424
1221
  /**
1425
- Returns id record for a given type and ID. If one isn't already loaded,
1426
- it builds a new record and leaves it in the `empty` state.
1222
+ This method delegates a query to the adapter. This is the one place where
1223
+ adapter-level semantics are exposed to the application.
1427
1224
 
1428
- @method recordForId
1429
- @private
1430
- @param {String} modelName
1431
- @param {(String|Integer)} id
1432
- @return {Model} record
1433
- */
1434
- recordForId(modelName: string, id: string | number): RecordInstance {
1435
- if (DEBUG) {
1436
- assertDestroyingStore(this, 'recordForId');
1437
- }
1438
- assert(`You need to pass a model name to the store's recordForId method`, isPresent(modelName));
1439
- assert(
1440
- `Passing classes to store methods has been removed. Please pass a dasherized string instead of ${modelName}`,
1441
- typeof modelName === 'string'
1442
- );
1225
+ Each time this method is called a new request is made through the adapter.
1443
1226
 
1444
- const resource = constructResource(modelName, ensureStringId(id));
1227
+ Exposing queries this way seems preferable to creating an abstract query
1228
+ language for all server-side queries, and then require all adapters to
1229
+ implement them.
1445
1230
 
1446
- return internalModelFactoryFor(this).lookup(resource).getRecord();
1447
- }
1231
+ ---
1448
1232
 
1449
- /**
1450
- @method findMany
1451
- @private
1452
- @param {Array} internalModels
1453
- @return {Promise} promise
1454
- */
1455
- findMany(internalModels, options) {
1456
- if (DEBUG) {
1457
- assertDestroyingStore(this, 'findMany');
1458
- }
1459
- let finds = new Array(internalModels.length);
1233
+ If you do something like this:
1460
1234
 
1461
- for (let i = 0; i < internalModels.length; i++) {
1462
- finds[i] = this._findEmptyInternalModel(internalModels[i], options);
1463
- }
1235
+ ```javascript
1236
+ store.query('person', { page: 1 });
1237
+ ```
1464
1238
 
1465
- return all(finds);
1466
- }
1239
+ The request made to the server will look something like this:
1467
1240
 
1468
- /**
1469
- If a relationship was originally populated by the adapter as a link
1470
- (as opposed to a list of IDs), this method is called when the
1471
- relationship is fetched.
1241
+ ```
1242
+ GET "/api/v1/person?page=1"
1243
+ ```
1472
1244
 
1473
- The link (which is usually a URL) is passed through unchanged, so the
1474
- adapter can make whatever request it wants.
1245
+ ---
1475
1246
 
1476
- The usual use-case is for the server to register a URL as a link, and
1477
- then use that URL in the future to make a request for the relationship.
1478
-
1479
- @method findHasMany
1480
- @private
1481
- @param {InternalModel} internalModel
1482
- @param {any} link
1483
- @param {(Relationship)} relationship
1484
- @return {Promise} promise
1485
- */
1486
- findHasMany(internalModel, link, relationship, options) {
1487
- if (DEBUG) {
1488
- assertDestroyingStore(this, 'findHasMany');
1489
- }
1490
- let adapter = this.adapterFor(internalModel.modelName);
1491
-
1492
- assert(
1493
- `You tried to load a hasMany relationship but you have no adapter (for ${internalModel.modelName})`,
1494
- adapter
1495
- );
1496
- assert(
1497
- `You tried to load a hasMany relationship from a specified 'link' in the original payload but your adapter does not implement 'findHasMany'`,
1498
- typeof adapter.findHasMany === 'function'
1499
- );
1500
-
1501
- return _findHasMany(adapter, this, internalModel, link, relationship, options);
1502
- }
1503
-
1504
- _findHasManyByJsonApiResource(
1505
- resource,
1506
- parentInternalModel: InternalModel,
1507
- relationship: ManyRelationship,
1508
- options?: Dict<unknown>
1509
- ): Promise<void | unknown[]> {
1510
- if (HAS_RECORD_DATA_PACKAGE) {
1511
- if (!resource) {
1512
- return resolve();
1513
- }
1514
- const { definition, state } = relationship;
1515
- let adapter = this.adapterFor(definition.type);
1516
-
1517
- let { isStale, hasDematerializedInverse, hasReceivedData, isEmpty, shouldForceReload } = state;
1518
- const allInverseRecordsAreLoaded = areAllInverseRecordsLoaded(this, resource);
1519
-
1520
- let shouldFindViaLink =
1521
- resource.links &&
1522
- resource.links.related &&
1523
- (typeof adapter.findHasMany === 'function' || typeof resource.data === 'undefined') &&
1524
- (shouldForceReload || hasDematerializedInverse || isStale || (!allInverseRecordsAreLoaded && !isEmpty));
1525
-
1526
- // fetch via link
1527
- if (shouldFindViaLink) {
1528
- // findHasMany, although not public, does not need to care about our upgrade relationship definitions
1529
- // and can stick with the public definition API for now.
1530
- const relationshipMeta = this._storeWrapper.relationshipsDefinitionFor(definition.inverseType)[definition.key];
1531
- return this.findHasMany(parentInternalModel, resource.links.related, relationshipMeta, options);
1532
- }
1533
-
1534
- let preferLocalCache = hasReceivedData && !isEmpty;
1535
-
1536
- let hasLocalPartialData =
1537
- hasDematerializedInverse || (isEmpty && Array.isArray(resource.data) && resource.data.length > 0);
1538
-
1539
- // fetch using data, pulling from local cache if possible
1540
- if (!shouldForceReload && !isStale && (preferLocalCache || hasLocalPartialData)) {
1541
- let internalModels = resource.data.map((json) => this._internalModelForResource(json));
1542
-
1543
- return this.findMany(internalModels, options);
1544
- }
1545
-
1546
- let hasData = hasReceivedData && !isEmpty;
1547
-
1548
- // fetch by data
1549
- if (hasData || hasLocalPartialData) {
1550
- let internalModels = resource.data.map((json) => this._internalModelForResource(json));
1551
-
1552
- return this._scheduleFetchMany(internalModels, options);
1553
- }
1554
-
1555
- // we were explicitly told we have no data and no links.
1556
- // TODO if the relationshipIsStale, should we hit the adapter anyway?
1557
- return resolve();
1558
- }
1559
- assert(`hasMany only works with the @ember-data/record-data package`);
1560
- }
1561
-
1562
- /**
1563
- @method findBelongsTo
1564
- @private
1565
- @param {InternalModel} internalModel
1566
- @param {any} link
1567
- @param {Relationship} relationship
1568
- @return {Promise} promise
1569
- */
1570
- findBelongsTo(internalModel, link, relationship, options): Promise<InternalModel> {
1571
- if (DEBUG) {
1572
- assertDestroyingStore(this, 'findBelongsTo');
1573
- }
1574
- let adapter = this.adapterFor(internalModel.modelName);
1575
-
1576
- assert(
1577
- `You tried to load a belongsTo relationship but you have no adapter (for ${internalModel.modelName})`,
1578
- adapter
1579
- );
1580
- assert(
1581
- `You tried to load a belongsTo relationship from a specified 'link' in the original payload but your adapter does not implement 'findBelongsTo'`,
1582
- typeof adapter.findBelongsTo === 'function'
1583
- );
1584
-
1585
- return _findBelongsTo(adapter, this, internalModel, link, relationship, options);
1586
- }
1587
-
1588
- _fetchBelongsToLinkFromResource(
1589
- resource,
1590
- parentInternalModel: InternalModel,
1591
- relationshipMeta,
1592
- options
1593
- ): Promise<InternalModel | null> {
1594
- if (!resource || !resource.links || !resource.links.related) {
1595
- // should we warn here, not sure cause its an internal method
1596
- return resolve(null);
1597
- }
1598
- return this.findBelongsTo(parentInternalModel, resource.links.related, relationshipMeta, options);
1599
- }
1600
-
1601
- _findBelongsToByJsonApiResource(
1602
- resource,
1603
- parentInternalModel: InternalModel,
1604
- relationshipMeta,
1605
- options
1606
- ): Promise<InternalModel | null> {
1607
- if (!resource) {
1608
- return resolve(null);
1609
- }
1610
-
1611
- const internalModel = resource.data ? this._internalModelForResource(resource.data) : null;
1612
- let { isStale, hasDematerializedInverse, hasReceivedData, isEmpty, shouldForceReload } = resource._relationship
1613
- .state as RelationshipState;
1614
- const allInverseRecordsAreLoaded = areAllInverseRecordsLoaded(this, resource);
1615
-
1616
- let shouldFindViaLink =
1617
- resource.links &&
1618
- resource.links.related &&
1619
- (shouldForceReload || hasDematerializedInverse || isStale || (!allInverseRecordsAreLoaded && !isEmpty));
1620
-
1621
- if (internalModel) {
1622
- // short circuit if we are already loading
1623
- let pendingRequest = this._fetchManager.getPendingFetch(internalModel.identifier, options);
1624
- if (pendingRequest) {
1625
- return pendingRequest.then(() => internalModel);
1626
- }
1627
- }
1628
-
1629
- // fetch via link
1630
- if (shouldFindViaLink) {
1631
- return this._fetchBelongsToLinkFromResource(resource, parentInternalModel, relationshipMeta, options);
1632
- }
1633
-
1634
- let preferLocalCache = hasReceivedData && allInverseRecordsAreLoaded && !isEmpty;
1635
- let hasLocalPartialData = hasDematerializedInverse || (isEmpty && resource.data);
1636
- // null is explicit empty, undefined is "we don't know anything"
1637
- let localDataIsEmpty = resource.data === undefined || resource.data === null;
1638
-
1639
- // fetch using data, pulling from local cache if possible
1640
- if (!shouldForceReload && !isStale && (preferLocalCache || hasLocalPartialData)) {
1641
- /*
1642
- We have canonical data, but our local state is empty
1643
- */
1644
- if (localDataIsEmpty) {
1645
- return resolve(null);
1646
- }
1647
-
1648
- if (!internalModel) {
1649
- assert(`No InternalModel found for ${resource.lid}`, internalModel);
1650
- }
1651
-
1652
- return this._findByInternalModel(internalModel, options);
1653
- }
1654
-
1655
- let resourceIsLocal = !localDataIsEmpty && resource.data.id === null;
1656
-
1657
- if (internalModel && resourceIsLocal) {
1658
- return resolve(internalModel);
1659
- }
1660
-
1661
- // fetch by data
1662
- if (internalModel && !localDataIsEmpty) {
1663
- return this._scheduleFetch(internalModel, options);
1664
- }
1665
-
1666
- // we were explicitly told we have no data and no links.
1667
- // TODO if the relationshipIsStale, should we hit the adapter anyway?
1668
- return resolve(null);
1669
- }
1670
-
1671
- /**
1672
- This method delegates a query to the adapter. This is the one place where
1673
- adapter-level semantics are exposed to the application.
1674
-
1675
- Each time this method is called a new request is made through the adapter.
1676
-
1677
- Exposing queries this way seems preferable to creating an abstract query
1678
- language for all server-side queries, and then require all adapters to
1679
- implement them.
1680
-
1681
- ---
1682
-
1683
- If you do something like this:
1684
-
1685
- ```javascript
1686
- store.query('person', { page: 1 });
1687
- ```
1688
-
1689
- The request made to the server will look something like this:
1690
-
1691
- ```
1692
- GET "/api/v1/person?page=1"
1693
- ```
1694
-
1695
- ---
1696
-
1697
- If you do something like this:
1247
+ If you do something like this:
1698
1248
 
1699
1249
  ```javascript
1700
1250
  store.query('person', { ids: [1, 2, 3] });
@@ -1723,7 +1273,7 @@ abstract class CoreStore extends Service {
1723
1273
  if (DEBUG) {
1724
1274
  assertDestroyingStore(this, 'query');
1725
1275
  }
1726
- assert(`You need to pass a model name to the store's query method`, isPresent(modelName));
1276
+ assert(`You need to pass a model name to the store's query method`, modelName);
1727
1277
  assert(`You need to pass a query hash to the store's query method`, query);
1728
1278
  assert(
1729
1279
  `Passing classes to store methods has been removed. Please pass a dasherized string instead of ${modelName}`,
@@ -1735,20 +1285,10 @@ abstract class CoreStore extends Service {
1735
1285
  if (options && options.adapterOptions) {
1736
1286
  adapterOptionsWrapper.adapterOptions = options.adapterOptions;
1737
1287
  }
1288
+ let recordArray = options?._recordArray || null;
1738
1289
 
1739
1290
  let normalizedModelName = normalizeModelName(modelName);
1740
- return promiseArray(this._query(normalizedModelName, query, null, adapterOptionsWrapper));
1741
- }
1742
-
1743
- _query(modelName: string, query, array, options): Promise<AdapterPopulatedRecordArray> {
1744
- assert(`You need to pass a model name to the store's query method`, isPresent(modelName));
1745
- assert(`You need to pass a query hash to the store's query method`, query);
1746
- assert(
1747
- `Passing classes to store methods has been removed. Please pass a dasherized string instead of ${modelName}`,
1748
- typeof modelName === 'string'
1749
- );
1750
-
1751
- let adapter = this.adapterFor(modelName);
1291
+ let adapter = this.adapterFor(normalizedModelName);
1752
1292
 
1753
1293
  assert(`You tried to load a query but you have no adapter (for ${modelName})`, adapter);
1754
1294
  assert(
@@ -1756,7 +1296,16 @@ abstract class CoreStore extends Service {
1756
1296
  typeof adapter.query === 'function'
1757
1297
  );
1758
1298
 
1759
- return _query(adapter, this, modelName, query, array, options) as unknown as Promise<AdapterPopulatedRecordArray>;
1299
+ let queryPromise = _query(
1300
+ adapter,
1301
+ this,
1302
+ normalizedModelName,
1303
+ query,
1304
+ recordArray,
1305
+ adapterOptionsWrapper
1306
+ ) as unknown as Promise<AdapterPopulatedRecordArray>;
1307
+
1308
+ return promiseArray(queryPromise);
1760
1309
  }
1761
1310
 
1762
1311
  /**
@@ -1857,11 +1406,11 @@ abstract class CoreStore extends Service {
1857
1406
  @param {Object} options optional, may include `adapterOptions` hash which will be passed to adapter.queryRecord
1858
1407
  @return {Promise} promise which resolves with the found record or `null`
1859
1408
  */
1860
- queryRecord(modelName: string, query, options): PromiseObject<RecordInstance | null> {
1409
+ queryRecord(modelName: string, query, options?): PromiseObject<RecordInstance | null> {
1861
1410
  if (DEBUG) {
1862
1411
  assertDestroyingStore(this, 'queryRecord');
1863
1412
  }
1864
- assert(`You need to pass a model name to the store's queryRecord method`, isPresent(modelName));
1413
+ assert(`You need to pass a model name to the store's queryRecord method`, modelName);
1865
1414
  assert(`You need to pass a query hash to the store's queryRecord method`, query);
1866
1415
  assert(
1867
1416
  `Passing classes to store methods has been removed. Please pass a dasherized string instead of ${modelName}`,
@@ -1882,19 +1431,15 @@ abstract class CoreStore extends Service {
1882
1431
  typeof adapter.queryRecord === 'function'
1883
1432
  );
1884
1433
 
1885
- const promise: Promise<InternalModel | null> = _queryRecord(
1434
+ const promise: Promise<StableRecordIdentifier | null> = _queryRecord(
1886
1435
  adapter,
1887
1436
  this,
1888
1437
  normalizedModelName,
1889
1438
  query,
1890
1439
  adapterOptionsWrapper
1891
- ) as Promise<InternalModel | null>;
1440
+ ) as Promise<StableRecordIdentifier | null>;
1892
1441
 
1893
- return promiseObject(
1894
- promise.then((internalModel: InternalModel | null) => {
1895
- return internalModel ? internalModel.getRecord() : null;
1896
- })
1897
- );
1442
+ return promiseObject(promise.then((identifier) => identifier && this.peekRecord(identifier)));
1898
1443
  }
1899
1444
 
1900
1445
  /**
@@ -2092,78 +1637,57 @@ abstract class CoreStore extends Service {
2092
1637
  if (DEBUG) {
2093
1638
  assertDestroyingStore(this, 'findAll');
2094
1639
  }
2095
- assert(`You need to pass a model name to the store's findAll method`, isPresent(modelName));
1640
+ assert(`You need to pass a model name to the store's findAll method`, modelName);
2096
1641
  assert(
2097
1642
  `Passing classes to store methods has been removed. Please pass a dasherized string instead of ${modelName}`,
2098
1643
  typeof modelName === 'string'
2099
1644
  );
2100
1645
 
2101
1646
  let normalizedModelName = normalizeModelName(modelName);
2102
- let fetch = this._fetchAll(normalizedModelName, this.peekAll(normalizedModelName), options);
2103
-
2104
- return promiseArray(fetch);
2105
- }
1647
+ let array = this.peekAll(normalizedModelName);
1648
+ let fetch;
2106
1649
 
2107
- /**
2108
- @method _fetchAll
2109
- @private
2110
- @param {Model} modelName
2111
- @param {RecordArray} array
2112
- @return {Promise} promise
2113
- */
2114
- _fetchAll(
2115
- modelName: string,
2116
- array: RecordArray,
2117
- options: { reload?: boolean; backgroundReload?: boolean }
2118
- ): Promise<RecordArray> {
2119
- let adapter = this.adapterFor(modelName);
1650
+ let adapter = this.adapterFor(normalizedModelName);
2120
1651
 
2121
- assert(`You tried to load all records but you have no adapter (for ${modelName})`, adapter);
1652
+ assert(`You tried to load all records but you have no adapter (for ${normalizedModelName})`, adapter);
2122
1653
  assert(
2123
1654
  `You tried to load all records but your adapter does not implement 'findAll'`,
2124
1655
  typeof adapter.findAll === 'function'
2125
1656
  );
2126
1657
 
2127
1658
  if (options.reload) {
2128
- set(array, 'isUpdating', true);
2129
- return _findAll(adapter, this, modelName, options);
2130
- }
2131
-
2132
- let snapshotArray = array._createSnapshot(options);
2133
-
2134
- if (options.reload !== false) {
2135
- if (
2136
- (adapter.shouldReloadAll && adapter.shouldReloadAll(this, snapshotArray)) ||
2137
- (!adapter.shouldReloadAll && snapshotArray.length === 0)
2138
- ) {
2139
- set(array, 'isUpdating', true);
2140
- return _findAll(adapter, this, modelName, options);
1659
+ array.isUpdating = true;
1660
+ fetch = _findAll(adapter, this, normalizedModelName, options);
1661
+ } else {
1662
+ let snapshotArray = array._createSnapshot(options);
1663
+
1664
+ if (options.reload !== false) {
1665
+ if (
1666
+ (adapter.shouldReloadAll && adapter.shouldReloadAll(this, snapshotArray)) ||
1667
+ (!adapter.shouldReloadAll && snapshotArray.length === 0)
1668
+ ) {
1669
+ array.isUpdating = true;
1670
+ fetch = _findAll(adapter, this, modelName, options);
1671
+ }
2141
1672
  }
2142
- }
2143
1673
 
2144
- if (options.backgroundReload === false) {
2145
- return resolve(array);
2146
- }
1674
+ if (!fetch) {
1675
+ if (options.backgroundReload === false) {
1676
+ fetch = resolve(array);
1677
+ } else if (
1678
+ options.backgroundReload ||
1679
+ !adapter.shouldBackgroundReloadAll ||
1680
+ adapter.shouldBackgroundReloadAll(this, snapshotArray)
1681
+ ) {
1682
+ array.isUpdating = true;
1683
+ _findAll(adapter, this, modelName, options);
1684
+ }
2147
1685
 
2148
- if (
2149
- options.backgroundReload ||
2150
- !adapter.shouldBackgroundReloadAll ||
2151
- adapter.shouldBackgroundReloadAll(this, snapshotArray)
2152
- ) {
2153
- set(array, 'isUpdating', true);
2154
- _findAll(adapter, this, modelName, options);
1686
+ fetch = resolve(array);
1687
+ }
2155
1688
  }
2156
1689
 
2157
- return resolve(array);
2158
- }
2159
-
2160
- /**
2161
- @method _didUpdateAll
2162
- @param {String} modelName
2163
- @private
2164
- */
2165
- _didUpdateAll(modelName: string): void {
2166
- this.recordArrayManager._didUpdateAll(modelName);
1690
+ return promiseArray(fetch);
2167
1691
  }
2168
1692
 
2169
1693
  /**
@@ -2195,7 +1719,7 @@ abstract class CoreStore extends Service {
2195
1719
  if (DEBUG) {
2196
1720
  assertDestroyingStore(this, 'peekAll');
2197
1721
  }
2198
- assert(`You need to pass a model name to the store's peekAll method`, isPresent(modelName));
1722
+ assert(`You need to pass a model name to the store's peekAll method`, modelName);
2199
1723
  assert(
2200
1724
  `Passing classes to store methods has been removed. Please pass a dasherized string instead of ${modelName}`,
2201
1725
  typeof modelName === 'string'
@@ -2238,141 +1762,6 @@ abstract class CoreStore extends Service {
2238
1762
  }
2239
1763
  }
2240
1764
 
2241
- filter() {
2242
- assert(
2243
- 'The filter API has been moved to a plugin. To enable store.filter using an environment flag, or to use an alternative, you can visit the ember-data-filter addon page. https://github.com/ember-data/ember-data-filter',
2244
- false
2245
- );
2246
- }
2247
-
2248
- // ..............
2249
- // . PERSISTING .
2250
- // ..............
2251
-
2252
- /**
2253
- This method is called by `record.save`, and gets passed a
2254
- resolver for the promise that `record.save` returns.
2255
-
2256
- It schedules saving to happen at the end of the run loop.
2257
-
2258
- @method scheduleSave
2259
- @private
2260
- @param {InternalModel} internalModel
2261
- @param {Resolver} resolver
2262
- @param {Object} options
2263
- */
2264
- scheduleSave(
2265
- internalModel: InternalModel,
2266
- resolver: RSVP.Deferred<void>,
2267
- options: FindOptions
2268
- ): void | Promise<void> {
2269
- if (internalModel._isRecordFullyDeleted()) {
2270
- resolver.resolve();
2271
- return resolver.promise;
2272
- }
2273
-
2274
- internalModel.adapterWillCommit();
2275
-
2276
- if (!options) {
2277
- options = {};
2278
- }
2279
- let recordData = internalModel._recordData;
2280
- let operation: 'createRecord' | 'deleteRecord' | 'updateRecord' = 'updateRecord';
2281
-
2282
- // TODO handle missing isNew
2283
- if (recordData.isNew && recordData.isNew()) {
2284
- operation = 'createRecord';
2285
- } else if (recordData.isDeleted && recordData.isDeleted()) {
2286
- operation = 'deleteRecord';
2287
- }
2288
-
2289
- const saveOptions = Object.assign({ [SaveOp]: operation }, options);
2290
- let fetchManagerPromise = this._fetchManager.scheduleSave(internalModel.identifier, saveOptions);
2291
- let promise = fetchManagerPromise.then(
2292
- (payload) => {
2293
- /*
2294
- Note to future spelunkers hoping to optimize.
2295
- We rely on this `run` to create a run loop if needed
2296
- that `store._push` and `store.didSaveRecord` will both share.
2297
-
2298
- We use `join` because it is often the case that we
2299
- have an outer run loop available still from the first
2300
- call to `store._push`;
2301
- */
2302
- this._backburner.join(() => {
2303
- let data = payload && payload.data;
2304
- this.didSaveRecord(internalModel, { data }, operation);
2305
- if (payload && payload.included) {
2306
- this._push({ data: null, included: payload.included });
2307
- }
2308
- });
2309
- },
2310
- (e) => {
2311
- if (typeof e === 'string') {
2312
- throw e;
2313
- }
2314
- const { error, parsedErrors } = e;
2315
- this.recordWasInvalid(internalModel, parsedErrors, error);
2316
- throw error;
2317
- }
2318
- );
2319
-
2320
- return promise;
2321
- }
2322
-
2323
- /**
2324
- This method is called at the end of the run loop, and
2325
- flushes any records passed into `scheduleSave`
2326
-
2327
- @method flushPendingSave
2328
- @private
2329
- */
2330
- flushPendingSave() {
2331
- // assert here
2332
- return;
2333
- }
2334
-
2335
- /**
2336
- This method is called once the promise returned by an
2337
- adapter's `createRecord`, `updateRecord` or `deleteRecord`
2338
- is resolved.
2339
-
2340
- If the data provides a server-generated ID, it will
2341
- update the record and the store's indexes.
2342
-
2343
- @method didSaveRecord
2344
- @private
2345
- @param {InternalModel} internalModel the in-flight internal model
2346
- @param {Object} data optional data (see above)
2347
- @param {string} op the adapter operation that was committed
2348
- */
2349
- didSaveRecord(internalModel, dataArg, op: 'createRecord' | 'updateRecord' | 'deleteRecord') {
2350
- if (DEBUG) {
2351
- assertDestroyingStore(this, 'didSaveRecord');
2352
- }
2353
- let data;
2354
- if (dataArg) {
2355
- data = dataArg.data;
2356
- }
2357
- if (!data) {
2358
- assert(
2359
- `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.`,
2360
- internalModel.id
2361
- );
2362
- }
2363
-
2364
- const cache = this.identifierCache;
2365
- const identifier = internalModel.identifier;
2366
-
2367
- if (op !== 'deleteRecord' && data) {
2368
- cache.updateRecordIdentifier(identifier, data);
2369
- }
2370
-
2371
- //We first make sure the primary data has been updated
2372
- //TODO try to move notification to the user to the end of the runloop
2373
- internalModel.adapterDidCommit(data);
2374
- }
2375
-
2376
1765
  /**
2377
1766
  This method is called once the promise returned by an
2378
1767
  adapter's `createRecord`, `updateRecord` or `deleteRecord`
@@ -2380,96 +1769,28 @@ abstract class CoreStore extends Service {
2380
1769
 
2381
1770
  @method recordWasInvalid
2382
1771
  @private
1772
+ @deprecated
2383
1773
  @param {InternalModel} internalModel
2384
1774
  @param {Object} errors
2385
1775
  */
2386
1776
  recordWasInvalid(internalModel, parsedErrors, error) {
2387
- if (DEBUG) {
2388
- assertDestroyingStore(this, 'recordWasInvalid');
2389
- }
2390
- internalModel.adapterDidInvalidate(parsedErrors, error);
2391
- }
2392
-
2393
- /**
2394
- This method is called once the promise returned by an
2395
- adapter's `createRecord`, `updateRecord` or `deleteRecord`
2396
- is rejected (with anything other than a `InvalidError`).
2397
-
2398
- @method recordWasError
2399
- @private
2400
- @param {InternalModel} internalModel
2401
- @param {Error} error
2402
- */
2403
- recordWasError(internalModel, error) {
2404
- if (DEBUG) {
2405
- assertDestroyingStore(this, 'recordWasError');
2406
- }
2407
- internalModel.adapterDidError(error);
2408
- }
2409
-
2410
- /**
2411
- Sets newly received ID from the adapter's `createRecord`, `updateRecord`
2412
- or `deleteRecord`.
2413
-
2414
- @method setRecordId
2415
- @private
2416
- @param {String} modelName
2417
- @param {string} newId
2418
- @param {string} clientId
2419
- */
2420
- setRecordId(modelName: string, newId: string, clientId: string) {
2421
- if (DEBUG) {
2422
- assertDestroyingStore(this, 'setRecordId');
2423
- }
2424
- internalModelFactoryFor(this).setRecordId(modelName, newId, clientId);
2425
- }
2426
-
2427
- // ................
2428
- // . LOADING DATA .
2429
- // ................
2430
-
2431
- /**
2432
- This internal method is used by `push`.
2433
-
2434
- @method _load
2435
- @private
2436
- @param {Object} data
2437
- */
2438
- _load(data: ExistingResourceObject) {
2439
- // TODO this should determine identifier via the cache before making assumptions
2440
- const resource = constructResource(normalizeModelName(data.type), ensureStringId(data.id), coerceId(data.lid));
2441
-
2442
- let internalModel = internalModelFactoryFor(this).lookup(resource, data);
2443
-
2444
- // store.push will be from empty
2445
- // findRecord will be from root.loading
2446
- // all else will be updates
2447
- const isLoading = internalModel.currentState.stateName === 'root.loading';
2448
- const isUpdate = internalModel.currentState.isEmpty === false && !isLoading;
2449
-
2450
- // exclude store.push (root.empty) case
2451
- let identifier = internalModel.identifier;
2452
- if (isUpdate || isLoading) {
2453
- let updatedIdentifier = this.identifierCache.updateRecordIdentifier(identifier, data);
2454
-
2455
- if (updatedIdentifier !== identifier) {
2456
- // we encountered a merge of identifiers in which
2457
- // two identifiers (and likely two internalModels)
2458
- // existed for the same resource. Now that we have
2459
- // determined the correct identifier to use, make sure
2460
- // that we also use the correct internalModel.
2461
- identifier = updatedIdentifier;
2462
- internalModel = internalModelFactoryFor(this).lookup(identifier);
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');
2463
1790
  }
1791
+ internalModel.adapterDidInvalidate(parsedErrors, error);
2464
1792
  }
2465
-
2466
- internalModel.setupData(data);
2467
-
2468
- if (!isUpdate) {
2469
- this.recordArrayManager.recordDidChange(identifier);
2470
- }
2471
-
2472
- return internalModel;
1793
+ assert(`store.recordWasInvalid has been removed`);
2473
1794
  }
2474
1795
 
2475
1796
  /**
@@ -2633,7 +1954,7 @@ abstract class CoreStore extends Service {
2633
1954
  let pushed = this._push(data);
2634
1955
 
2635
1956
  if (Array.isArray(pushed)) {
2636
- let records = pushed.map((internalModel) => internalModel.getRecord());
1957
+ let records = pushed.map((identifier) => this._instanceCache.getRecord(identifier));
2637
1958
  return records;
2638
1959
  }
2639
1960
 
@@ -2641,8 +1962,7 @@ abstract class CoreStore extends Service {
2641
1962
  return null;
2642
1963
  }
2643
1964
 
2644
- let record = pushed.getRecord();
2645
- return record;
1965
+ return this._instanceCache.getRecord(pushed);
2646
1966
  }
2647
1967
 
2648
1968
  /**
@@ -2654,28 +1974,28 @@ abstract class CoreStore extends Service {
2654
1974
  @param {Object} jsonApiDoc
2655
1975
  @return {InternalModel|Array<InternalModel>} pushed InternalModel(s)
2656
1976
  */
2657
- _push(jsonApiDoc): InternalModel | InternalModel[] | null {
1977
+ _push(jsonApiDoc): StableExistingRecordIdentifier | StableExistingRecordIdentifier[] | null {
2658
1978
  if (DEBUG) {
2659
1979
  assertDestroyingStore(this, '_push');
2660
1980
  }
2661
- let internalModelOrModels = this._backburner.join(() => {
1981
+ let identifiers = this._backburner.join(() => {
2662
1982
  let included = jsonApiDoc.included;
2663
1983
  let i, length;
2664
1984
 
2665
1985
  if (included) {
2666
1986
  for (i = 0, length = included.length; i < length; i++) {
2667
- this._pushInternalModel(included[i]);
1987
+ this._instanceCache._load(included[i]);
2668
1988
  }
2669
1989
  }
2670
1990
 
2671
1991
  if (Array.isArray(jsonApiDoc.data)) {
2672
1992
  length = jsonApiDoc.data.length;
2673
- let internalModels = new Array(length);
1993
+ let identifiers = new Array(length);
2674
1994
 
2675
1995
  for (i = 0; i < length; i++) {
2676
- internalModels[i] = this._pushInternalModel(jsonApiDoc.data[i]);
1996
+ identifiers[i] = this._instanceCache._load(jsonApiDoc.data[i]);
2677
1997
  }
2678
- return internalModels;
1998
+ return identifiers;
2679
1999
  }
2680
2000
 
2681
2001
  if (jsonApiDoc.data === null) {
@@ -2683,66 +2003,17 @@ abstract class CoreStore extends Service {
2683
2003
  }
2684
2004
 
2685
2005
  assert(
2686
- `Expected an object in the 'data' property in a call to 'push' for ${jsonApiDoc.type}, but was ${typeOf(
2687
- jsonApiDoc.data
2688
- )}`,
2689
- typeOf(jsonApiDoc.data) === 'object'
2006
+ `Expected an object in the 'data' property in a call to 'push' for ${
2007
+ jsonApiDoc.type
2008
+ }, but was ${typeof jsonApiDoc.data}`,
2009
+ typeof jsonApiDoc.data === 'object'
2690
2010
  );
2691
2011
 
2692
- return this._pushInternalModel(jsonApiDoc.data);
2012
+ return this._instanceCache._load(jsonApiDoc.data);
2693
2013
  });
2694
2014
 
2695
2015
  // this typecast is necessary because `backburner.join` is mistyped to return void
2696
- return internalModelOrModels as unknown as InternalModel | InternalModel[];
2697
- }
2698
-
2699
- _pushInternalModel(data) {
2700
- // TODO type should be pulled from the identifier for debug
2701
- let modelName = data.type;
2702
- assert(
2703
- `You must include an 'id' for ${modelName} in an object passed to 'push'`,
2704
- data.id !== null && data.id !== undefined && data.id !== ''
2705
- );
2706
- assert(
2707
- `You tried to push data with a type '${modelName}' but no model could be found with that name.`,
2708
- this._hasModelFor(modelName)
2709
- );
2710
-
2711
- if (DEBUG) {
2712
- // If ENV.DS_WARN_ON_UNKNOWN_KEYS is set to true and the payload
2713
- // contains unknown attributes or relationships, log a warning.
2714
-
2715
- if (ENV.DS_WARN_ON_UNKNOWN_KEYS) {
2716
- let unknownAttributes, unknownRelationships;
2717
- let relationships = this.getSchemaDefinitionService().relationshipsDefinitionFor(modelName);
2718
- let attributes = this.getSchemaDefinitionService().attributesDefinitionFor(modelName);
2719
- // Check unknown attributes
2720
- unknownAttributes = Object.keys(data.attributes || {}).filter((key) => {
2721
- return !attributes[key];
2722
- });
2723
-
2724
- // Check unknown relationships
2725
- unknownRelationships = Object.keys(data.relationships || {}).filter((key) => {
2726
- return !relationships[key];
2727
- });
2728
- let unknownAttributesMessage = `The payload for '${modelName}' contains these unknown attributes: ${unknownAttributes}. Make sure they've been defined in your model.`;
2729
- warn(unknownAttributesMessage, unknownAttributes.length === 0, {
2730
- id: 'ds.store.unknown-keys-in-payload',
2731
- });
2732
-
2733
- let unknownRelationshipsMessage = `The payload for '${modelName}' contains these unknown relationships: ${unknownRelationships}. Make sure they've been defined in your model.`;
2734
- warn(unknownRelationshipsMessage, unknownRelationships.length === 0, {
2735
- id: 'ds.store.unknown-keys-in-payload',
2736
- });
2737
- }
2738
- }
2739
-
2740
- // Actually load the record into the store.
2741
- let internalModel = this._load(data);
2742
-
2743
- // this._setupRelationshipsForModel(internalModel, data);
2744
-
2745
- return internalModel;
2016
+ return identifiers;
2746
2017
  }
2747
2018
 
2748
2019
  /**
@@ -2801,6 +2072,7 @@ abstract class CoreStore extends Service {
2801
2072
  @param {String} modelName Optionally, a model type used to determine which serializer will be used
2802
2073
  @param {Object} inputPayload
2803
2074
  */
2075
+ // TODO @runspired @deprecate pushPayload in favor of looking up the serializer
2804
2076
  pushPayload(modelName, inputPayload) {
2805
2077
  if (DEBUG) {
2806
2078
  assertDestroyingStore(this, 'pushPayload');
@@ -2830,62 +2102,103 @@ abstract class CoreStore extends Service {
2830
2102
  serializer.pushPayload(this, payload);
2831
2103
  }
2832
2104
 
2833
- reloadBelongsTo(belongsToProxy, internalModel, key, options) {
2834
- return internalModel.reloadBelongsTo(key, options);
2835
- }
2836
-
2837
- _internalModelForResource(resource: ResourceIdentifierObject): InternalModel {
2838
- return internalModelFactoryFor(this).getByResource(resource);
2839
- }
2840
-
2841
- /**
2842
- * TODO Only needed temporarily for test support
2843
- *
2844
- * @method _internalModelForId
2845
- * @internal
2846
- */
2847
- _internalModelForId(type: string, id: string | null, lid: string | null): InternalModel {
2848
- const resource = constructResource(type, id, lid);
2849
- return internalModelFactoryFor(this).lookup(resource);
2850
- }
2851
-
2105
+ // TODO @runspired @deprecate records should implement their own serialization if desired
2852
2106
  serializeRecord(record: RecordInstance, options?: Dict<unknown>): unknown {
2853
- let identifier = recordIdentifierFor(record);
2854
- let internalModel = internalModelFactoryFor(this).peek(identifier);
2855
2107
  // TODO we used to check if the record was destroyed here
2856
- return internalModel!.createSnapshot(options).serialize(options);
2108
+ return this._instanceCache.createSnapshot(recordIdentifierFor(record)).serialize(options);
2857
2109
  }
2858
2110
 
2859
- saveRecord(record: RecordInstance, options?: Dict<unknown>): Promise<RecordInstance> {
2111
+ // todo @runspired this should likely be publicly @documented for custom records
2112
+ saveRecord(record: RecordInstance, options: Dict<unknown> = {}): Promise<RecordInstance> {
2113
+ assert(`Unable to initate save for a record in a disconnected state`, storeFor(record));
2860
2114
  let identifier = recordIdentifierFor(record);
2861
- let internalModel = internalModelFactoryFor(this).peek(identifier);
2115
+ let internalModel = identifier && internalModelFactoryFor(this).peek(identifier)!;
2116
+
2117
+ if (!internalModel) {
2118
+ // this commonly means we're disconnected
2119
+ // but just in case we reject here to prevent bad things.
2120
+ return reject(`Record Is Disconnected`);
2121
+ }
2862
2122
  // TODO we used to check if the record was destroyed here
2863
2123
  // Casting can be removed once REQUEST_SERVICE ff is turned on
2864
2124
  // because a `Record` is provided there will always be a matching internalModel
2865
- return (internalModel!.save(options) as Promise<void>).then(() => record);
2866
- }
2867
2125
 
2868
- relationshipReferenceFor(identifier: RecordIdentifier, key: string): BelongsToReference | HasManyReference {
2869
- let stableIdentifier = this.identifierCache.getOrCreateRecordIdentifier(identifier);
2870
- let internalModel = internalModelFactoryFor(this).peek(stableIdentifier);
2871
- // TODO we used to check if the record was destroyed here
2872
- return internalModel!.referenceFor(null, key);
2873
- }
2126
+ assert(
2127
+ `Cannot initiate a save request for an unloaded record: ${identifier}`,
2128
+ !internalModel.isEmpty && !internalModel.isDestroyed
2129
+ );
2130
+ if (internalModel._isRecordFullyDeleted()) {
2131
+ return resolve(record);
2132
+ }
2874
2133
 
2875
- /**
2876
- * Manages setting setting up the recordData returned by createRecordDataFor
2877
- *
2878
- * @method _createRecordData
2879
- * @internal
2880
- */
2881
- _createRecordData(identifier: StableRecordIdentifier): RecordData {
2882
- const recordData = this.createRecordDataFor(identifier.type, identifier.id, identifier.lid, this._storeWrapper);
2883
- setRecordDataFor(identifier, recordData);
2884
- // TODO this is invalid for v2 recordData but required
2885
- // for v1 recordData. Remember to remove this once the
2886
- // RecordData manager handles converting recordData to identifier
2887
- setRecordIdentifier(recordData, identifier);
2888
- return recordData;
2134
+ internalModel.adapterWillCommit();
2135
+
2136
+ if (!options) {
2137
+ options = {};
2138
+ }
2139
+ let recordData = this._instanceCache.getRecordData(identifier);
2140
+ let operation: 'createRecord' | 'deleteRecord' | 'updateRecord' = 'updateRecord';
2141
+
2142
+ // TODO handle missing isNew
2143
+ if (recordData.isNew && recordData.isNew()) {
2144
+ operation = 'createRecord';
2145
+ } else if (recordData.isDeleted && recordData.isDeleted()) {
2146
+ operation = 'deleteRecord';
2147
+ }
2148
+
2149
+ const saveOptions = Object.assign({ [SaveOp]: operation }, options);
2150
+ let fetchManagerPromise = this._fetchManager.scheduleSave(identifier, saveOptions);
2151
+ return fetchManagerPromise.then(
2152
+ (payload) => {
2153
+ /*
2154
+ // TODO @runspired re-evaluate the below claim now that
2155
+ // the save request pipeline is more streamlined.
2156
+
2157
+ Note to future spelunkers hoping to optimize.
2158
+ We rely on this `run` to create a run loop if needed
2159
+ that `store._push` and `store.saveRecord` will both share.
2160
+
2161
+ We use `join` because it is often the case that we
2162
+ have an outer run loop available still from the first
2163
+ call to `store._push`;
2164
+ */
2165
+ this._backburner.join(() => {
2166
+ if (DEBUG) {
2167
+ assertDestroyingStore(this, 'saveRecord');
2168
+ }
2169
+
2170
+ let data = payload && payload.data;
2171
+ if (!data) {
2172
+ 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
2175
+ );
2176
+ }
2177
+
2178
+ const cache = this.identifierCache;
2179
+ if (operation !== 'deleteRecord' && data) {
2180
+ cache.updateRecordIdentifier(identifier, data);
2181
+ }
2182
+
2183
+ //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);
2186
+
2187
+ if (payload && payload.included) {
2188
+ this._push({ data: null, included: payload.included });
2189
+ }
2190
+ });
2191
+ return record;
2192
+ },
2193
+ (e) => {
2194
+ if (typeof e === 'string') {
2195
+ throw e;
2196
+ }
2197
+ const { error, parsedErrors } = e;
2198
+ internalModel.adapterDidInvalidate(parsedErrors, error);
2199
+ throw error;
2200
+ }
2201
+ );
2889
2202
  }
2890
2203
 
2891
2204
  /**
@@ -2928,30 +2241,6 @@ abstract class CoreStore extends Service {
2928
2241
  assert(`Expected store.createRecordDataFor to be implemented but it wasn't`);
2929
2242
  }
2930
2243
 
2931
- /**
2932
- * @internal
2933
- */
2934
- __recordDataFor(resource: RecordIdentifier) {
2935
- const identifier = this.identifierCache.getOrCreateRecordIdentifier(resource);
2936
- return this.recordDataFor(identifier, false);
2937
- }
2938
-
2939
- /**
2940
- * @internal
2941
- */
2942
- recordDataFor(identifier: StableRecordIdentifier | { type: string }, isCreate: boolean): RecordData {
2943
- let internalModel: InternalModel;
2944
- if (isCreate === true) {
2945
- internalModel = internalModelFactoryFor(this).build({ type: identifier.type, id: null });
2946
- internalModel.send('loadedData');
2947
- internalModel.didCreateRecord();
2948
- } else {
2949
- internalModel = internalModelFactoryFor(this).lookup(identifier as StableRecordIdentifier);
2950
- }
2951
-
2952
- return internalModel._recordData;
2953
- }
2954
-
2955
2244
  /**
2956
2245
  `normalize` converts a json payload into the normalized form that
2957
2246
  [push](../methods/push?anchor=push) expects.
@@ -2972,15 +2261,14 @@ abstract class CoreStore extends Service {
2972
2261
  @param {Object} payload
2973
2262
  @return {Object} The normalized payload
2974
2263
  */
2264
+ // TODO @runspired @deprecate users should call normalize on the associated serializer directly
2975
2265
  normalize(modelName: string, payload) {
2976
2266
  if (DEBUG) {
2977
2267
  assertDestroyingStore(this, 'normalize');
2978
2268
  }
2979
- assert(`You need to pass a model name to the store's normalize method`, isPresent(modelName));
2269
+ assert(`You need to pass a model name to the store's normalize method`, modelName);
2980
2270
  assert(
2981
- `Passing classes to store methods has been removed. Please pass a dasherized string instead of ${inspect(
2982
- modelName
2983
- )}`,
2271
+ `Passing classes to store methods has been removed. Please pass a dasherized string instead of ${typeof modelName}`,
2984
2272
  typeof modelName === 'string'
2985
2273
  );
2986
2274
  let normalizedModelName = normalizeModelName(modelName);
@@ -2993,49 +2281,25 @@ abstract class CoreStore extends Service {
2993
2281
  return serializer.normalize(model, payload);
2994
2282
  }
2995
2283
 
2996
- newClientId() {
2997
- assert(`Private API Removed`, false);
2998
- }
2999
-
3000
- // ...............
3001
- // . DESTRUCTION .
3002
- // ...............
3003
-
3004
- /**
3005
- * TODO remove test usage
3006
- *
3007
- * @internal
3008
- */
3009
- _internalModelsFor(modelName: string) {
3010
- return internalModelFactoryFor(this).modelMapFor(modelName);
3011
- }
3012
-
3013
- // ......................
3014
- // . PER-TYPE ADAPTERS
3015
- // ......................
3016
-
3017
2284
  /**
3018
2285
  Returns an instance of the adapter for a given type. For
3019
2286
  example, `adapterFor('person')` will return an instance of
3020
- `App.PersonAdapter`.
2287
+ the adapter located at `app/adapters/person.js`
3021
2288
 
3022
- If no `App.PersonAdapter` is found, this method will look
3023
- for an `App.ApplicationAdapter` (the default adapter for
2289
+ If no `person` adapter is found, this method will look
2290
+ for an `application` adapter (the default adapter for
3024
2291
  your entire application).
3025
2292
 
3026
- If no `App.ApplicationAdapter` is found, it will return
3027
- the value of the `defaultAdapter`.
3028
-
3029
2293
  @method adapterFor
3030
2294
  @public
3031
2295
  @param {String} modelName
3032
2296
  @return Adapter
3033
2297
  */
3034
- adapterFor(modelName) {
2298
+ adapterFor(modelName: string) {
3035
2299
  if (DEBUG) {
3036
2300
  assertDestroyingStore(this, 'adapterFor');
3037
2301
  }
3038
- assert(`You need to pass a model name to the store's adapterFor method`, isPresent(modelName));
2302
+ assert(`You need to pass a model name to the store's adapterFor method`, modelName);
3039
2303
  assert(
3040
2304
  `Passing classes to store.adapterFor has been removed. Please pass a dasherized string instead of ${modelName}`,
3041
2305
  typeof modelName === 'string'
@@ -3048,12 +2312,11 @@ abstract class CoreStore extends Service {
3048
2312
  return adapter;
3049
2313
  }
3050
2314
 
3051
- let owner = getOwner(this);
2315
+ let owner: any = getOwner(this);
3052
2316
 
3053
2317
  // name specific adapter
3054
2318
  adapter = owner.lookup(`adapter:${normalizedModelName}`);
3055
2319
  if (adapter !== undefined) {
3056
- set(adapter, 'store', this);
3057
2320
  _adapterCache[normalizedModelName] = adapter;
3058
2321
  return adapter;
3059
2322
  }
@@ -3061,29 +2324,35 @@ abstract class CoreStore extends Service {
3061
2324
  // no adapter found for the specific name, fallback and check for application adapter
3062
2325
  adapter = _adapterCache.application || owner.lookup('adapter:application');
3063
2326
  if (adapter !== undefined) {
3064
- set(adapter, 'store', this);
3065
2327
  _adapterCache[normalizedModelName] = adapter;
3066
2328
  _adapterCache.application = adapter;
3067
2329
  return adapter;
3068
2330
  }
3069
2331
 
3070
- // final fallback, no model specific adapter, no application adapter, no
3071
- // `adapter` property on store: use json-api adapter
3072
- // TODO we should likely deprecate this?
3073
- adapter = _adapterCache['-json-api'] || owner.lookup('adapter:-json-api');
3074
- assert(
3075
- `No adapter was found for '${modelName}' and no 'application' adapter was found as a fallback.`,
3076
- adapter !== undefined
3077
- );
3078
- set(adapter, 'store', this);
3079
- _adapterCache[normalizedModelName] = adapter;
3080
- _adapterCache['-json-api'] = adapter;
3081
- return adapter;
3082
- }
2332
+ if (DEPRECATE_JSON_API_FALLBACK) {
2333
+ // final fallback, no model specific adapter, no application adapter, no
2334
+ // `adapter` property on store: use json-api adapter
2335
+ adapter = _adapterCache['-json-api'] || owner.lookup('adapter:-json-api');
2336
+ if (adapter !== undefined) {
2337
+ deprecate(
2338
+ `Your application is utilizing a deprecated hidden fallback adapter (-json-api). Please implement an application adapter to function as your fallback.`,
2339
+ false,
2340
+ {
2341
+ id: 'ember-data:deprecate-secret-adapter-fallback',
2342
+ for: 'ember-data',
2343
+ until: '5.0',
2344
+ since: { available: '4.5', enabled: '4.5' },
2345
+ }
2346
+ );
2347
+ _adapterCache[normalizedModelName] = adapter;
2348
+ _adapterCache['-json-api'] = adapter;
3083
2349
 
3084
- // ..............................
3085
- // . RECORD CHANGE NOTIFICATION .
3086
- // ..............................
2350
+ return adapter;
2351
+ }
2352
+ }
2353
+
2354
+ assert(`No adapter was found for '${modelName}' and no 'application' adapter was found as a fallback.`);
2355
+ }
3087
2356
 
3088
2357
  /**
3089
2358
  Returns an instance of the serializer for a given type. For
@@ -3106,7 +2375,7 @@ abstract class CoreStore extends Service {
3106
2375
  if (DEBUG) {
3107
2376
  assertDestroyingStore(this, 'serializerFor');
3108
2377
  }
3109
- assert(`You need to pass a model name to the store's serializerFor method`, isPresent(modelName));
2378
+ assert(`You need to pass a model name to the store's serializerFor method`, modelName);
3110
2379
  assert(
3111
2380
  `Passing classes to store.serializerFor has been removed. Please pass a dasherized string instead of ${modelName}`,
3112
2381
  typeof modelName === 'string'
@@ -3119,12 +2388,11 @@ abstract class CoreStore extends Service {
3119
2388
  return serializer;
3120
2389
  }
3121
2390
 
3122
- let owner = getOwner(this);
2391
+ let owner: any = getOwner(this);
3123
2392
 
3124
2393
  // by name
3125
2394
  serializer = owner.lookup(`serializer:${normalizedModelName}`);
3126
2395
  if (serializer !== undefined) {
3127
- set(serializer, 'store', this);
3128
2396
  _serializerCache[normalizedModelName] = serializer;
3129
2397
  return serializer;
3130
2398
  }
@@ -3132,7 +2400,6 @@ abstract class CoreStore extends Service {
3132
2400
  // no serializer found for the specific model, fallback and check for application serializer
3133
2401
  serializer = _serializerCache.application || owner.lookup('serializer:application');
3134
2402
  if (serializer !== undefined) {
3135
- set(serializer, 'store', this);
3136
2403
  _serializerCache[normalizedModelName] = serializer;
3137
2404
  _serializerCache.application = serializer;
3138
2405
  return serializer;
@@ -3193,32 +2460,20 @@ abstract class CoreStore extends Service {
3193
2460
 
3194
2461
  if (DEBUG) {
3195
2462
  unregisterWaiter(this.__asyncWaiter);
3196
- let shouldTrack = this.shouldTrackAsyncRequests;
3197
2463
  let tracked = this._trackedAsyncRequests;
3198
2464
  let isSettled = tracked.length === 0;
3199
2465
 
3200
2466
  if (!isSettled) {
3201
- if (shouldTrack) {
3202
- throw new Error(
3203
- 'Async Request leaks detected. Add a breakpoint here and set `store.generateStackTracesForTrackedRequests = true;`to inspect traces for leak origins:\n\t - ' +
3204
- tracked.map((o) => o.label).join('\n\t - ')
3205
- );
3206
- } else {
3207
- warn(
3208
- 'Async Request leaks detected. Add a breakpoint here and set `store.generateStackTracesForTrackedRequests = true;`to inspect traces for leak origins:\n\t - ' +
3209
- tracked.map((o) => o.label).join('\n\t - '),
3210
- false,
3211
- {
3212
- id: 'ds.async.leak.detected',
3213
- }
3214
- );
3215
- }
2467
+ throw new Error(
2468
+ 'Async Request leaks detected. Add a breakpoint here and set `store.generateStackTracesForTrackedRequests = true;`to inspect traces for leak origins:\n\t - ' +
2469
+ tracked.map((o) => o.label).join('\n\t - ')
2470
+ );
3216
2471
  }
3217
2472
  }
3218
2473
  }
3219
2474
  }
3220
2475
 
3221
- export default CoreStore;
2476
+ export default Store;
3222
2477
 
3223
2478
  let assertDestroyingStore: Function;
3224
2479
  let assertDestroyedStoreOnly: Function;
@@ -3238,47 +2493,6 @@ if (DEBUG) {
3238
2493
  };
3239
2494
  }
3240
2495
 
3241
- /**
3242
- * Flag indicating whether all inverse records are available
3243
- *
3244
- * true if the inverse exists and is loaded (not empty)
3245
- * true if there is no inverse
3246
- * false if the inverse exists and is not loaded (empty)
3247
- *
3248
- * @internal
3249
- * @return {boolean}
3250
- */
3251
- function areAllInverseRecordsLoaded(store: CoreStore, resource: JsonApiRelationship): boolean {
3252
- const cache = store.identifierCache;
3253
-
3254
- if (Array.isArray(resource.data)) {
3255
- // treat as collection
3256
- // check for unloaded records
3257
- let hasEmptyRecords = resource.data.reduce((hasEmptyModel, resourceIdentifier) => {
3258
- return hasEmptyModel || internalModelForRelatedResource(store, cache, resourceIdentifier).currentState.isEmpty;
3259
- }, false);
3260
-
3261
- return !hasEmptyRecords;
3262
- } else {
3263
- // treat as single resource
3264
- if (!resource.data) {
3265
- return true;
3266
- } else {
3267
- const internalModel = internalModelForRelatedResource(store, cache, resource.data);
3268
- return !internalModel.currentState.isEmpty;
3269
- }
3270
- }
3271
- }
3272
-
3273
- function internalModelForRelatedResource(
3274
- store: CoreStore,
3275
- cache: IdentifierCache,
3276
- resource: ResourceIdentifierObject
3277
- ): InternalModel {
3278
- const identifier = cache.getOrCreateRecordIdentifier(resource);
3279
- return store._internalModelForResource(identifier);
3280
- }
3281
-
3282
2496
  function isMaybeIdentifier(
3283
2497
  maybeIdentifier: string | ResourceIdentifierObject
3284
2498
  ): maybeIdentifier is ResourceIdentifierObject {
@@ -3290,7 +2504,7 @@ function isMaybeIdentifier(
3290
2504
  );
3291
2505
  }
3292
2506
 
3293
- function assertIdentifierHasId(
2507
+ export function assertIdentifierHasId(
3294
2508
  identifier: StableRecordIdentifier
3295
2509
  ): asserts identifier is StableExistingRecordIdentifier {
3296
2510
  assert(`Attempted to schedule a fetch for a record without an id.`, identifier.id !== null);