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