@ember-data/store 4.6.1 → 4.7.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (45) hide show
  1. package/addon/-debug/index.js +35 -13
  2. package/addon/-private/{identifier-cache.ts → caches/identifier-cache.ts} +148 -73
  3. package/addon/-private/caches/instance-cache.ts +690 -0
  4. package/addon/-private/{record-data-for.ts → caches/record-data-for.ts} +2 -7
  5. package/addon/-private/index.ts +44 -24
  6. package/addon/-private/{model → legacy-model-support}/record-reference.ts +15 -13
  7. package/addon/-private/{schema-definition-service.ts → legacy-model-support/schema-definition-service.ts} +13 -9
  8. package/addon/-private/{model → legacy-model-support}/shim-model-class.ts +18 -11
  9. package/addon/-private/managers/record-array-manager.ts +377 -0
  10. package/addon/-private/managers/record-data-manager.ts +845 -0
  11. package/addon/-private/managers/record-data-store-wrapper.ts +421 -0
  12. package/addon/-private/managers/record-notification-manager.ts +109 -0
  13. package/addon/-private/network/fetch-manager.ts +567 -0
  14. package/addon/-private/{finders.js → network/finders.js} +14 -17
  15. package/addon/-private/{request-cache.ts → network/request-cache.ts} +21 -18
  16. package/addon/-private/{snapshot-record-array.ts → network/snapshot-record-array.ts} +14 -31
  17. package/addon/-private/{snapshot.ts → network/snapshot.ts} +40 -49
  18. package/addon/-private/{promise-proxies.ts → proxies/promise-proxies.ts} +76 -15
  19. package/addon/-private/{promise-proxy-base.js → proxies/promise-proxy-base.js} +0 -0
  20. package/addon/-private/record-arrays/identifier-array.ts +924 -0
  21. package/addon/-private/{core-store.ts → store-service.ts} +574 -215
  22. package/addon/-private/{coerce-id.ts → utils/coerce-id.ts} +1 -1
  23. package/addon/-private/{common.js → utils/common.js} +1 -2
  24. package/addon/-private/utils/construct-resource.ts +2 -2
  25. package/addon/-private/{identifer-debug-consts.ts → utils/identifer-debug-consts.ts} +0 -0
  26. package/addon/-private/utils/is-non-empty-string.ts +1 -1
  27. package/addon/-private/{normalize-model-name.ts → utils/normalize-model-name.ts} +1 -3
  28. package/addon/-private/utils/promise-record.ts +5 -6
  29. package/addon/-private/{serializer-response.ts → utils/serializer-response.ts} +2 -2
  30. package/addon/-private/utils/uuid-polyfill.ts +73 -0
  31. package/package.json +12 -8
  32. package/addon/-private/backburner.js +0 -25
  33. package/addon/-private/errors-utils.js +0 -146
  34. package/addon/-private/fetch-manager.ts +0 -597
  35. package/addon/-private/identity-map.ts +0 -54
  36. package/addon/-private/instance-cache.ts +0 -387
  37. package/addon/-private/internal-model-factory.ts +0 -359
  38. package/addon/-private/internal-model-map.ts +0 -121
  39. package/addon/-private/model/internal-model.ts +0 -602
  40. package/addon/-private/record-array-manager.ts +0 -444
  41. package/addon/-private/record-arrays/adapter-populated-record-array.ts +0 -130
  42. package/addon/-private/record-arrays/record-array.ts +0 -318
  43. package/addon/-private/record-data-store-wrapper.ts +0 -243
  44. package/addon/-private/record-notification-manager.ts +0 -73
  45. package/addon/-private/weak-cache.ts +0 -125
@@ -1,359 +0,0 @@
1
- import { assert, warn } from '@ember/debug';
2
- import { DEBUG } from '@glimmer/env';
3
-
4
- import type {
5
- ExistingResourceObject,
6
- NewResourceIdentifierObject,
7
- ResourceIdentifierObject,
8
- } from '@ember-data/types/q/ember-data-json-api';
9
- import type { StableRecordIdentifier } from '@ember-data/types/q/identifier';
10
- import type { RecordData } from '@ember-data/types/q/record-data';
11
- import type { RecordInstance } from '@ember-data/types/q/record-instance';
12
-
13
- import type Store from './core-store';
14
- import type { IdentifierCache } from './identifier-cache';
15
- import IdentityMap from './identity-map';
16
- import type InternalModelMap from './internal-model-map';
17
- import InternalModel from './model/internal-model';
18
- import constructResource from './utils/construct-resource';
19
- import WeakCache from './weak-cache';
20
-
21
- /**
22
- @module @ember-data/store
23
- */
24
- const FactoryCache = new WeakCache<Store, InternalModelFactory>(DEBUG ? 'internal-model-factory' : '');
25
- FactoryCache._generator = (store: Store) => {
26
- return new InternalModelFactory(store);
27
- };
28
- type NewResourceInfo = { type: string; id: string | null };
29
-
30
- const RecordCache = new WeakCache<RecordInstance | RecordData, StableRecordIdentifier>(DEBUG ? 'identifier' : '');
31
- if (DEBUG) {
32
- RecordCache._expectMsg = (key: RecordInstance | RecordData) =>
33
- `${String(key)} is not a record instantiated by @ember-data/store`;
34
- }
35
-
36
- export function peekRecordIdentifier(record: RecordInstance | RecordData): StableRecordIdentifier | undefined {
37
- return RecordCache.get(record);
38
- }
39
-
40
- /**
41
- Retrieves the unique referentially-stable [RecordIdentifier](/ember-data/release/classes/StableRecordIdentifier)
42
- assigned to the given record instance.
43
-
44
- ```js
45
- import { recordIdentifierFor } from "@ember-data/store";
46
-
47
- // ... gain access to a record, for instance with peekRecord or findRecord
48
- const record = store.peekRecord("user", "1");
49
-
50
- // get the identifier for the record (see docs for StableRecordIdentifier)
51
- const identifier = recordIdentifierFor(record);
52
-
53
- // access the identifier's properties.
54
- const { id, type, lid } = identifier;
55
- ```
56
-
57
- @method recordIdentifierFor
58
- @public
59
- @static
60
- @for @ember-data/store
61
- @param {Object} record a record instance previously obstained from the store.
62
- @returns {StableRecordIdentifier}
63
- */
64
- export function recordIdentifierFor(record: RecordInstance | RecordData): StableRecordIdentifier {
65
- return RecordCache.getWithError(record);
66
- }
67
-
68
- export function setRecordIdentifier(record: RecordInstance | RecordData, identifier: StableRecordIdentifier): void {
69
- if (DEBUG && RecordCache.has(record) && RecordCache.get(record) !== identifier) {
70
- throw new Error(`${record} was already assigned an identifier`);
71
- }
72
-
73
- /*
74
- It would be nice to do a reverse check here that an identifier has not
75
- previously been assigned a record; however, unload + rematerialization
76
- prevents us from having a great way of doing so when CustomRecordClasses
77
- don't necessarily give us access to a `isDestroyed` for dematerialized
78
- instance.
79
- */
80
-
81
- RecordCache.set(record, identifier);
82
- }
83
-
84
- export function internalModelFactoryFor(store: Store): InternalModelFactory {
85
- return FactoryCache.lookup(store);
86
- }
87
-
88
- /**
89
- * The InternalModelFactory handles the lifecyle of
90
- * instantiating, caching, and destroying InternalModel
91
- * instances.
92
- *
93
- * @class InternalModelFactory
94
- * @internal
95
- */
96
- export default class InternalModelFactory {
97
- declare _identityMap: IdentityMap;
98
- declare identifierCache: IdentifierCache;
99
- declare store: Store;
100
-
101
- constructor(store: Store) {
102
- this.store = store;
103
- this.identifierCache = store.identifierCache;
104
- this.identifierCache.__configureMerge((identifier, matchedIdentifier, resourceData) => {
105
- let intendedIdentifier = identifier;
106
- if (identifier.id !== matchedIdentifier.id) {
107
- intendedIdentifier = 'id' in resourceData && identifier.id === resourceData.id ? identifier : matchedIdentifier;
108
- } else if (identifier.type !== matchedIdentifier.type) {
109
- intendedIdentifier =
110
- 'type' in resourceData && identifier.type === resourceData.type ? identifier : matchedIdentifier;
111
- }
112
- let altIdentifier = identifier === intendedIdentifier ? matchedIdentifier : identifier;
113
-
114
- // check for duplicate InternalModel's
115
- const map = this.modelMapFor(identifier.type);
116
- let im = map.get(intendedIdentifier.lid);
117
- let otherIm = map.get(altIdentifier.lid);
118
-
119
- // we cannot merge internalModels when both have records
120
- // (this may not be strictly true, we could probably swap the internalModel the record points at)
121
- if (im && otherIm && im.hasRecord && otherIm.hasRecord) {
122
- // TODO we probably don't need to throw these errors anymore
123
- // once InternalModel is fully removed, as we can just "swap"
124
- // what data source the abandoned record points at so long as
125
- // it itself is not retained by the store in any way.
126
- if ('id' in resourceData) {
127
- throw new Error(
128
- `Failed to update the 'id' for the RecordIdentifier '${identifier.type}:${identifier.id} (${identifier.lid})' to '${resourceData.id}', because that id is already in use by '${matchedIdentifier.type}:${matchedIdentifier.id} (${matchedIdentifier.lid})'`
129
- );
130
- }
131
- // TODO @runspired determine when this is even possible
132
- assert(
133
- `Failed to update the RecordIdentifier '${identifier.type}:${identifier.id} (${identifier.lid})' to merge with the detected duplicate identifier '${matchedIdentifier.type}:${matchedIdentifier.id} (${matchedIdentifier.lid})'`
134
- );
135
- }
136
-
137
- // remove otherIm from cache
138
- if (otherIm) {
139
- map.remove(otherIm, altIdentifier.lid);
140
- }
141
-
142
- if (im === null && otherIm === null) {
143
- // nothing more to do
144
- return intendedIdentifier;
145
-
146
- // only the other has an InternalModel
147
- // OR only the other has a Record
148
- } else if ((im === null && otherIm !== null) || (im && !im.hasRecord && otherIm && otherIm.hasRecord)) {
149
- if (im) {
150
- // TODO check if we are retained in any async relationships
151
- map.remove(im, intendedIdentifier.lid);
152
- // im.destroy();
153
- }
154
- im = otherIm;
155
- // TODO do we need to notify the id change?
156
- im._id = intendedIdentifier.id;
157
- im.identifier = intendedIdentifier;
158
- map.add(im, intendedIdentifier.lid);
159
-
160
- // just use im
161
- } else {
162
- // otherIm.destroy();
163
- }
164
-
165
- /*
166
- TODO @runspired consider adding this to make polymorphism even nicer
167
- if (HAS_RECORD_DATA_PACKAGE) {
168
- if (identifier.type !== matchedIdentifier.type) {
169
- const graphFor = importSync('@ember-data/record-data/-private').graphFor;
170
- graphFor(this).registerPolymorphicType(identifier.type, matchedIdentifier.type);
171
- }
172
- }
173
- */
174
-
175
- return intendedIdentifier;
176
- });
177
- this._identityMap = new IdentityMap();
178
- }
179
-
180
- /**
181
- * Retrieve the InternalModel for a given { type, id, lid }.
182
- *
183
- * If an InternalModel does not exist, it instantiates one.
184
- *
185
- * If an InternalModel does exist bus has a scheduled destroy,
186
- * the scheduled destroy will be cancelled.
187
- *
188
- * @method lookup
189
- * @private
190
- */
191
- lookup(resource: ResourceIdentifierObject, data?: ExistingResourceObject): InternalModel {
192
- if (data !== undefined) {
193
- // if we've been given data associated with this lookup
194
- // we must first give secondary-caches for LIDs the
195
- // opportunity to populate based on it
196
- this.identifierCache.getOrCreateRecordIdentifier(data);
197
- }
198
-
199
- const identifier = this.identifierCache.getOrCreateRecordIdentifier(resource);
200
- const internalModel = this.peek(identifier);
201
-
202
- if (internalModel) {
203
- // unloadRecord is async, if one attempts to unload + then sync push,
204
- // we must ensure the unload is canceled before continuing
205
- // The createRecord path will take _existingInternalModelForId()
206
- // which will call `destroySync` instead for this unload + then
207
- // sync createRecord scenario. Once we have true client-side
208
- // delete signaling, we should never call destroySync
209
- if (internalModel.hasScheduledDestroy()) {
210
- internalModel.cancelDestroy();
211
- }
212
-
213
- return internalModel;
214
- }
215
-
216
- return this._build(identifier, false);
217
- }
218
-
219
- /**
220
- * Peek the InternalModel for a given { type, id, lid }.
221
- *
222
- * If an InternalModel does not exist, return `null`.
223
- *
224
- * @method peek
225
- * @private
226
- */
227
- peek(identifier: StableRecordIdentifier): InternalModel | null {
228
- return this.modelMapFor(identifier.type).get(identifier.lid);
229
- }
230
-
231
- getByResource(resource: ResourceIdentifierObject): InternalModel {
232
- const normalizedResource = constructResource(resource);
233
-
234
- return this.lookup(normalizedResource);
235
- }
236
-
237
- setRecordId(type: string, id: string, lid: string) {
238
- const resource: NewResourceIdentifierObject = { type, id: null, lid };
239
- const identifier = this.identifierCache.getOrCreateRecordIdentifier(resource);
240
- const internalModel = this.peek(identifier);
241
-
242
- if (internalModel === null) {
243
- throw new Error(`Cannot set the id ${id} on the record ${type}:${lid} as there is no such record in the cache.`);
244
- }
245
-
246
- let oldId = internalModel.id;
247
- let modelName = internalModel.modelName;
248
-
249
- // ID absolutely can't be missing if the oldID is empty (missing Id in response for a new record)
250
- assert(
251
- `'${modelName}' was saved to the server, but the response does not have an id and your record does not either.`,
252
- !(id === null && oldId === null)
253
- );
254
-
255
- // ID absolutely can't be different than oldID if oldID is not null
256
- // TODO this assertion and restriction may not strictly be needed in the identifiers world
257
- assert(
258
- `Cannot update the id for '${modelName}:${lid}' from '${oldId}' to '${id}'.`,
259
- !(oldId !== null && id !== oldId)
260
- );
261
-
262
- // ID can be null if oldID is not null (altered ID in response for a record)
263
- // however, this is more than likely a developer error.
264
- if (oldId !== null && id === null) {
265
- warn(
266
- `Your ${modelName} record was saved to the server, but the response does not have an id.`,
267
- !(oldId !== null && id === null)
268
- );
269
- return;
270
- }
271
-
272
- let existingInternalModel = this.peekById(modelName, id);
273
-
274
- assert(
275
- `'${modelName}' was saved to the server, but the response returned the new id '${id}', which has already been used with another record.'`,
276
- !existingInternalModel || existingInternalModel === internalModel
277
- );
278
-
279
- if (identifier.id === null) {
280
- // TODO potentially this needs to handle merged result
281
- this.identifierCache.updateRecordIdentifier(identifier, { type, id });
282
- }
283
-
284
- internalModel.setId(id, true);
285
- }
286
-
287
- peekById(type: string, id: string): InternalModel | null {
288
- const identifier = this.identifierCache.peekRecordIdentifier({ type, id });
289
- let internalModel = identifier ? this.modelMapFor(type).get(identifier.lid) : null;
290
-
291
- if (internalModel && internalModel.hasScheduledDestroy()) {
292
- // unloadRecord is async, if one attempts to unload + then sync create,
293
- // we must ensure the unload is complete before starting the create
294
- // The push path will take this.lookup()
295
- // which will call `cancelDestroy` instead for this unload + then
296
- // sync push scenario. Once we have true client-side
297
- // delete signaling, we should never call destroySync
298
- internalModel.destroySync();
299
- internalModel = null;
300
- }
301
- return internalModel;
302
- }
303
-
304
- build(newResourceInfo: NewResourceInfo): InternalModel {
305
- return this._build(newResourceInfo, true);
306
- }
307
-
308
- _build(resource: StableRecordIdentifier, isCreate: false): InternalModel;
309
- _build(resource: NewResourceInfo, isCreate: true): InternalModel;
310
- _build(resource: StableRecordIdentifier | NewResourceInfo, isCreate: boolean = false): InternalModel {
311
- if (isCreate === true && resource.id) {
312
- let existingInternalModel = this.peekById(resource.type, resource.id);
313
-
314
- assert(
315
- `The id ${resource.id} has already been used with another '${resource.type}' record.`,
316
- !existingInternalModel
317
- );
318
- }
319
-
320
- const { identifierCache } = this;
321
- let identifier: StableRecordIdentifier;
322
-
323
- if (isCreate === true) {
324
- identifier = identifierCache.createIdentifierForNewRecord(resource);
325
- } else {
326
- identifier = resource as StableRecordIdentifier;
327
- }
328
-
329
- // lookupFactory should really return an object that creates
330
- // instances with the injections applied
331
- let internalModel = new InternalModel(this.store, identifier);
332
-
333
- this.modelMapFor(resource.type).add(internalModel, identifier.lid);
334
-
335
- return internalModel;
336
- }
337
-
338
- remove(internalModel: InternalModel): void {
339
- let recordMap = this.modelMapFor(internalModel.modelName);
340
- let clientId = internalModel.identifier.lid;
341
-
342
- recordMap.remove(internalModel, clientId);
343
-
344
- const { identifier } = internalModel;
345
- this.identifierCache.forgetRecordIdentifier(identifier);
346
- }
347
-
348
- modelMapFor(type: string): InternalModelMap {
349
- return this._identityMap.retrieve(type);
350
- }
351
-
352
- clear(type?: string) {
353
- if (type === undefined) {
354
- this._identityMap.clear();
355
- } else {
356
- this.modelMapFor(type).clear();
357
- }
358
- }
359
- }
@@ -1,121 +0,0 @@
1
- import { assert } from '@ember/debug';
2
-
3
- import type { StableRecordIdentifier } from '@ember-data/types/q/identifier';
4
- import type { ConfidentDict } from '@ember-data/types/q/utils';
5
-
6
- import InternalModel from './model/internal-model';
7
-
8
- /**
9
- @module @ember-data/store
10
- */
11
-
12
- /**
13
- `InternalModelMap` is a custom storage map for internalModels of a given modelName
14
- used by `IdentityMap`.
15
-
16
- It was extracted from an implicit pojo based "internalModel map" and preserves
17
- that interface while we work towards a more official API.
18
-
19
- @class InternalModelMap
20
- @internal
21
- */
22
- export default class InternalModelMap {
23
- _idToModel: ConfidentDict<InternalModel> = Object.create(null);
24
- _models: InternalModel[] = [];
25
- modelName: string;
26
-
27
- constructor(modelName: string) {
28
- this.modelName = modelName;
29
- }
30
-
31
- get(id: string): InternalModel | null {
32
- return this._idToModel[id] || null;
33
- }
34
-
35
- has(id: string): boolean {
36
- return !!this._idToModel[id];
37
- }
38
-
39
- get length(): number {
40
- return this._models.length;
41
- }
42
-
43
- get recordIdentifiers(): StableRecordIdentifier[] {
44
- return this._models.map((m) => m.identifier);
45
- }
46
-
47
- set(id: string, internalModel: InternalModel): void {
48
- assert(`You cannot index an internalModel by an empty id'`, typeof id === 'string' && id.length > 0);
49
- assert(
50
- `You cannot set an index for an internalModel to something other than an internalModel`,
51
- internalModel instanceof InternalModel
52
- );
53
- assert(
54
- `You cannot set an index for an internalModel that is not in the InternalModelMap`,
55
- this.contains(internalModel)
56
- );
57
- assert(
58
- `You cannot update the id index of an InternalModel once set. Attempted to update ${id}.`,
59
- !this.has(id) || this.get(id) === internalModel
60
- );
61
-
62
- this._idToModel[id] = internalModel;
63
- }
64
-
65
- add(internalModel: InternalModel, id: string | null): void {
66
- assert(
67
- `You cannot re-add an already present InternalModel to the InternalModelMap.`,
68
- !this.contains(internalModel)
69
- );
70
-
71
- if (id) {
72
- assert(
73
- `Duplicate InternalModel for ${this.modelName}:${id} detected.`,
74
- !this.has(id) || this.get(id) === internalModel
75
- );
76
-
77
- this._idToModel[id] = internalModel;
78
- }
79
-
80
- this._models.push(internalModel);
81
- }
82
-
83
- remove(internalModel: InternalModel, id: string): void {
84
- delete this._idToModel[id];
85
-
86
- let loc = this._models.indexOf(internalModel);
87
-
88
- if (loc !== -1) {
89
- this._models.splice(loc, 1);
90
- }
91
- }
92
-
93
- contains(internalModel: InternalModel): boolean {
94
- return this._models.indexOf(internalModel) !== -1;
95
- }
96
-
97
- /**
98
- An array of all models of this modelName
99
- @property models
100
- @internal
101
- @type Array
102
- */
103
- get models(): InternalModel[] {
104
- return this._models;
105
- }
106
-
107
- /**
108
- Destroy all models in the map
109
-
110
- @internal
111
- */
112
- clear(): void {
113
- let internalModels = this._models;
114
- this._models = [];
115
-
116
- for (let i = 0; i < internalModels.length; i++) {
117
- let internalModel = internalModels[i];
118
- internalModel.unloadRecord();
119
- }
120
- }
121
- }