@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.
- package/addon/-private/{system/backburner.js → backburner.js} +0 -0
- package/addon/-private/{system/coerce-id.ts → coerce-id.ts} +0 -0
- package/addon/-private/{system/store/common.js → common.js} +0 -0
- package/addon/-private/{system/core-store.ts → core-store.ts} +467 -1253
- package/addon/-private/{system/errors-utils.js → errors-utils.js} +7 -6
- package/addon/-private/{system/fetch-manager.ts → fetch-manager.ts} +72 -42
- package/addon/-private/finders.js +107 -0
- package/addon/-private/identifer-debug-consts.ts +3 -0
- package/addon/-private/{identifiers/cache.ts → identifier-cache.ts} +26 -14
- package/addon/-private/{system/identity-map.ts → identity-map.ts} +2 -1
- package/addon/-private/index.ts +17 -17
- package/addon/-private/instance-cache.ts +387 -0
- package/addon/-private/{system/store/internal-model-factory.ts → internal-model-factory.ts} +25 -19
- package/addon/-private/{system/internal-model-map.ts → internal-model-map.ts} +9 -5
- package/addon/-private/model/internal-model.ts +602 -0
- package/addon/-private/{system/references/record.ts → model/record-reference.ts} +23 -36
- package/addon/-private/{system/model → model}/shim-model-class.ts +19 -14
- package/addon/-private/{system/normalize-model-name.ts → normalize-model-name.ts} +0 -0
- package/addon/-private/{system/promise-proxies.ts → promise-proxies.ts} +12 -5
- package/addon/-private/{system/promise-proxy-base.js → promise-proxy-base.js} +0 -0
- package/addon/-private/{system/record-array-manager.ts → record-array-manager.ts} +19 -18
- package/addon/-private/{system/record-arrays → record-arrays}/adapter-populated-record-array.ts +11 -10
- package/addon/-private/{system/record-arrays → record-arrays}/record-array.ts +37 -19
- package/addon/-private/record-data-for.ts +39 -0
- package/addon/-private/{system/store/record-data-store-wrapper.ts → record-data-store-wrapper.ts} +21 -26
- package/addon/-private/{system/record-notification-manager.ts → record-notification-manager.ts} +8 -3
- package/addon/-private/{system/request-cache.ts → request-cache.ts} +5 -6
- package/addon/-private/{system/schema-definition-service.ts → schema-definition-service.ts} +30 -14
- package/addon/-private/{system/store/serializer-response.ts → serializer-response.ts} +7 -6
- package/addon/-private/{system/snapshot-record-array.ts → snapshot-record-array.ts} +27 -8
- package/addon/-private/{system/snapshot.ts → snapshot.ts} +54 -39
- package/addon/-private/utils/construct-resource.ts +7 -3
- package/addon/-private/utils/promise-record.ts +9 -18
- package/addon/-private/{system/weak-cache.ts → weak-cache.ts} +2 -2
- package/addon/index.ts +1 -0
- package/package.json +21 -20
- package/addon/-private/identifiers/is-stable-identifier.ts +0 -18
- package/addon/-private/identifiers/utils/uuid-v4.ts +0 -80
- package/addon/-private/system/ds-model-store.ts +0 -136
- package/addon/-private/system/model/internal-model.ts +0 -1303
- package/addon/-private/system/model/states.js +0 -736
- package/addon/-private/system/record-arrays.ts +0 -8
- package/addon/-private/system/record-data-for.ts +0 -54
- package/addon/-private/system/references/belongs-to.ts +0 -406
- package/addon/-private/system/references/has-many.ts +0 -487
- package/addon/-private/system/references/reference.ts +0 -205
- package/addon/-private/system/references.js +0 -9
- package/addon/-private/system/store/finders.js +0 -412
- package/addon/-private/ts-interfaces/ds-model.ts +0 -50
- package/addon/-private/ts-interfaces/ember-data-json-api.ts +0 -145
- package/addon/-private/ts-interfaces/fetch-manager.ts +0 -44
- package/addon/-private/ts-interfaces/identifier.ts +0 -246
- package/addon/-private/ts-interfaces/minimum-adapter-interface.ts +0 -584
- package/addon/-private/ts-interfaces/minimum-serializer-interface.ts +0 -257
- package/addon/-private/ts-interfaces/promise-proxies.ts +0 -3
- package/addon/-private/ts-interfaces/record-data-json-api.ts +0 -29
- package/addon/-private/ts-interfaces/record-data-record-wrapper.ts +0 -46
- package/addon/-private/ts-interfaces/record-data-schemas.ts +0 -45
- package/addon/-private/ts-interfaces/record-data-store-wrapper.ts +0 -56
- package/addon/-private/ts-interfaces/record-data.ts +0 -72
- package/addon/-private/ts-interfaces/record-instance.ts +0 -18
- package/addon/-private/ts-interfaces/schema-definition-service.ts +0 -12
- package/addon/-private/ts-interfaces/store.ts +0 -10
- 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 {
|
|
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 {
|
|
13
|
+
import { reject, resolve } from 'rsvp';
|
|
18
14
|
|
|
19
|
-
import
|
|
20
|
-
import
|
|
21
|
-
import
|
|
22
|
-
|
|
23
|
-
|
|
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 '
|
|
32
|
-
import type {
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
} from '
|
|
37
|
-
import {
|
|
38
|
-
import type {
|
|
39
|
-
import type {
|
|
40
|
-
import type {
|
|
41
|
-
|
|
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
|
|
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
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
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
|
|
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
|
|
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 {
|
|
71
|
-
import
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
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
|
-
|
|
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:
|
|
193
|
-
declare _serializerCache: Dict<MinimumSerializerInterface & { store:
|
|
194
|
-
declare
|
|
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
|
-
|
|
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
|
-
|
|
336
|
-
internalModel: InternalModel,
|
|
337
|
-
modelName: string,
|
|
338
|
-
recordData: RecordData,
|
|
282
|
+
instantiateRecord(
|
|
339
283
|
identifier: StableRecordIdentifier,
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
internalModel
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
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
|
-
|
|
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
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
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
|
-
|
|
403
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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`,
|
|
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 (
|
|
520
|
-
|
|
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
|
-
|
|
530
|
-
|
|
531
|
-
internalModel.didCreateRecord();
|
|
469
|
+
this._instanceCache.getRecordData(identifier).clientDidCreate();
|
|
470
|
+
this.recordArrayManager.recordDidChange(identifier);
|
|
532
471
|
|
|
533
|
-
return
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
1065
|
-
|
|
1066
|
-
|
|
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
|
-
|
|
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
|
-
|
|
1122
|
-
|
|
1123
|
-
|
|
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
|
-
|
|
1127
|
-
|
|
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
|
|
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
|
-
|
|
1272
|
-
|
|
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
|
-
|
|
1329
|
-
|
|
1330
|
-
|
|
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`,
|
|
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
|
-
|
|
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 (
|
|
1406
|
-
|
|
1407
|
-
|
|
1408
|
-
|
|
1409
|
-
|
|
1410
|
-
|
|
1411
|
-
|
|
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
|
-
|
|
1415
|
-
|
|
1416
|
-
|
|
1209
|
+
const type = normalizeModelName(modelName);
|
|
1210
|
+
const trueId = ensureStringId(id);
|
|
1211
|
+
const resource = { type, id: trueId };
|
|
1417
1212
|
|
|
1418
|
-
|
|
1419
|
-
|
|
1213
|
+
const identifier = this.identifierCache.peekRecordIdentifier(resource);
|
|
1214
|
+
const internalModel = identifier && internalModelFactoryFor(this).peek(identifier);
|
|
1420
1215
|
|
|
1421
|
-
|
|
1216
|
+
return !!internalModel && internalModel.isLoaded;
|
|
1217
|
+
}
|
|
1218
|
+
assert(`store.hasRecordForId has been removed`);
|
|
1422
1219
|
}
|
|
1423
1220
|
|
|
1424
1221
|
/**
|
|
1425
|
-
|
|
1426
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
1462
|
-
|
|
1463
|
-
|
|
1235
|
+
```javascript
|
|
1236
|
+
store.query('person', { page: 1 });
|
|
1237
|
+
```
|
|
1464
1238
|
|
|
1465
|
-
|
|
1466
|
-
}
|
|
1239
|
+
The request made to the server will look something like this:
|
|
1467
1240
|
|
|
1468
|
-
|
|
1469
|
-
|
|
1470
|
-
|
|
1471
|
-
relationship is fetched.
|
|
1241
|
+
```
|
|
1242
|
+
GET "/api/v1/person?page=1"
|
|
1243
|
+
```
|
|
1472
1244
|
|
|
1473
|
-
|
|
1474
|
-
adapter can make whatever request it wants.
|
|
1245
|
+
---
|
|
1475
1246
|
|
|
1476
|
-
|
|
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`,
|
|
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
|
-
|
|
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
|
-
|
|
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`,
|
|
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<
|
|
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<
|
|
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`,
|
|
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
|
|
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 ${
|
|
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
|
-
|
|
2129
|
-
|
|
2130
|
-
}
|
|
2131
|
-
|
|
2132
|
-
|
|
2133
|
-
|
|
2134
|
-
|
|
2135
|
-
|
|
2136
|
-
|
|
2137
|
-
|
|
2138
|
-
|
|
2139
|
-
|
|
2140
|
-
|
|
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
|
-
|
|
2145
|
-
|
|
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
|
-
|
|
2149
|
-
|
|
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
|
|
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`,
|
|
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 (
|
|
2388
|
-
|
|
2389
|
-
|
|
2390
|
-
|
|
2391
|
-
|
|
2392
|
-
|
|
2393
|
-
|
|
2394
|
-
|
|
2395
|
-
|
|
2396
|
-
|
|
2397
|
-
|
|
2398
|
-
|
|
2399
|
-
|
|
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((
|
|
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
|
-
|
|
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):
|
|
1977
|
+
_push(jsonApiDoc): StableExistingRecordIdentifier | StableExistingRecordIdentifier[] | null {
|
|
2658
1978
|
if (DEBUG) {
|
|
2659
1979
|
assertDestroyingStore(this, '_push');
|
|
2660
1980
|
}
|
|
2661
|
-
let
|
|
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.
|
|
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
|
|
1993
|
+
let identifiers = new Array(length);
|
|
2674
1994
|
|
|
2675
1995
|
for (i = 0; i < length; i++) {
|
|
2676
|
-
|
|
1996
|
+
identifiers[i] = this._instanceCache._load(jsonApiDoc.data[i]);
|
|
2677
1997
|
}
|
|
2678
|
-
return
|
|
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 ${
|
|
2687
|
-
jsonApiDoc.
|
|
2688
|
-
|
|
2689
|
-
|
|
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.
|
|
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
|
|
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
|
-
|
|
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
|
|
2108
|
+
return this._instanceCache.createSnapshot(recordIdentifierFor(record)).serialize(options);
|
|
2857
2109
|
}
|
|
2858
2110
|
|
|
2859
|
-
|
|
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
|
-
|
|
2869
|
-
|
|
2870
|
-
|
|
2871
|
-
|
|
2872
|
-
|
|
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
|
-
|
|
2877
|
-
|
|
2878
|
-
|
|
2879
|
-
|
|
2880
|
-
|
|
2881
|
-
|
|
2882
|
-
|
|
2883
|
-
|
|
2884
|
-
|
|
2885
|
-
|
|
2886
|
-
|
|
2887
|
-
|
|
2888
|
-
|
|
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`,
|
|
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 ${
|
|
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
|
-
`
|
|
2287
|
+
the adapter located at `app/adapters/person.js`
|
|
3021
2288
|
|
|
3022
|
-
If no `
|
|
3023
|
-
for an `
|
|
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`,
|
|
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
|
-
|
|
3071
|
-
|
|
3072
|
-
|
|
3073
|
-
|
|
3074
|
-
|
|
3075
|
-
|
|
3076
|
-
|
|
3077
|
-
|
|
3078
|
-
|
|
3079
|
-
|
|
3080
|
-
|
|
3081
|
-
|
|
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
|
-
|
|
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`,
|
|
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
|
-
|
|
3202
|
-
|
|
3203
|
-
|
|
3204
|
-
|
|
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
|
|
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);
|