@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,387 +0,0 @@
1
- import { assert } from '@ember/debug';
2
- import { DEBUG } from '@glimmer/env';
3
-
4
- import { resolve } from 'rsvp';
5
-
6
- import type { ExistingResourceObject, ResourceIdentifierObject } from '@ember-data/types/q/ember-data-json-api';
7
- import type {
8
- RecordIdentifier,
9
- StableExistingRecordIdentifier,
10
- StableRecordIdentifier,
11
- } from '@ember-data/types/q/identifier';
12
- import type { RecordData } from '@ember-data/types/q/record-data';
13
- import type { RecordInstance } from '@ember-data/types/q/record-instance';
14
- import type { FindOptions } from '@ember-data/types/q/store';
15
-
16
- import coerceId, { ensureStringId } from './coerce-id';
17
- import type { CreateRecordProperties } from './core-store';
18
- import type Store from './core-store';
19
- import { assertIdentifierHasId } from './core-store';
20
- import { internalModelFactoryFor, setRecordIdentifier } from './internal-model-factory';
21
- import InternalModel from './model/internal-model';
22
- import RecordReference from './model/record-reference';
23
- import normalizeModelName from './normalize-model-name';
24
- import recordDataFor, { setRecordDataFor } from './record-data-for';
25
- import RecordDataStoreWrapper from './record-data-store-wrapper';
26
- import Snapshot from './snapshot';
27
- import constructResource from './utils/construct-resource';
28
- import WeakCache from './weak-cache';
29
-
30
- const RECORD_REFERENCES = new WeakCache<StableRecordIdentifier, RecordReference>(DEBUG ? 'reference' : '');
31
- export const StoreMap = new WeakCache<RecordInstance, Store>(DEBUG ? 'store' : '');
32
-
33
- export function storeFor(record: RecordInstance): Store | undefined {
34
- const store = StoreMap.get(record);
35
-
36
- assert(
37
- `A record in a disconnected state cannot utilize the store. This typically means the record has been destroyed, most commonly by unloading it.`,
38
- store
39
- );
40
- return store;
41
- }
42
-
43
- type Caches = {
44
- record: WeakMap<StableRecordIdentifier, RecordInstance>;
45
- recordData: WeakMap<StableRecordIdentifier, RecordData>;
46
- };
47
- export class InstanceCache {
48
- declare store: Store;
49
- declare _storeWrapper: RecordDataStoreWrapper;
50
-
51
- #instances: Caches = {
52
- record: new WeakMap<StableRecordIdentifier, RecordInstance>(),
53
- recordData: new WeakMap<StableRecordIdentifier, RecordData>(),
54
- };
55
-
56
- constructor(store: Store) {
57
- this.store = store;
58
-
59
- this._storeWrapper = new RecordDataStoreWrapper(this.store);
60
- this.__recordDataFor = this.__recordDataFor.bind(this);
61
-
62
- RECORD_REFERENCES._generator = (identifier) => {
63
- return new RecordReference(this.store, identifier);
64
- };
65
- }
66
- peek({ identifier, bucket }: { identifier: StableRecordIdentifier; bucket: 'record' }): RecordInstance | undefined;
67
- peek({ identifier, bucket }: { identifier: StableRecordIdentifier; bucket: 'recordData' }): RecordData | undefined;
68
- peek({
69
- identifier,
70
- bucket,
71
- }: {
72
- identifier: StableRecordIdentifier;
73
- bucket: 'record' | 'recordData';
74
- }): RecordData | RecordInstance | undefined {
75
- return this.#instances[bucket]?.get(identifier);
76
- }
77
- set({
78
- identifier,
79
- bucket,
80
- value,
81
- }: {
82
- identifier: StableRecordIdentifier;
83
- bucket: 'record';
84
- value: RecordInstance;
85
- }): void {
86
- this.#instances[bucket].set(identifier, value);
87
- }
88
-
89
- getRecord(identifier: StableRecordIdentifier, properties?: CreateRecordProperties): RecordInstance {
90
- let record = this.peek({ identifier, bucket: 'record' });
91
-
92
- if (!record) {
93
- // TODO store this state somewhere better
94
- const internalModel = this.getInternalModel(identifier);
95
-
96
- if (internalModel._isDematerializing) {
97
- // TODO this should be an assertion, this likely means
98
- // we have a bug to find wherein our own store is calling this
99
- // with an identifier that should have already been disconnected.
100
- // the destroy + fetch again case is likely either preserving the
101
- // identifier for re-use or failing to unload it.
102
- return null as unknown as RecordInstance;
103
- }
104
-
105
- // TODO store this state somewhere better
106
- internalModel.hasRecord = true;
107
-
108
- if (properties && 'id' in properties) {
109
- assert(`expected id to be a string or null`, properties.id !== undefined);
110
- internalModel.setId(properties.id);
111
- }
112
-
113
- record = this._instantiateRecord(this.getRecordData(identifier), identifier, properties);
114
- this.set({ identifier, bucket: 'record', value: record });
115
- }
116
-
117
- return record;
118
- }
119
-
120
- getReference(identifier: StableRecordIdentifier) {
121
- return RECORD_REFERENCES.lookup(identifier);
122
- }
123
-
124
- _fetchDataIfNeededForIdentifier(
125
- identifier: StableRecordIdentifier,
126
- options: FindOptions = {}
127
- ): Promise<StableRecordIdentifier> {
128
- const internalModel = this.getInternalModel(identifier);
129
-
130
- // pre-loading will change the isEmpty value
131
- // TODO stpre this state somewhere other than InternalModel
132
- const { isEmpty, isLoading } = internalModel;
133
-
134
- if (options.preload) {
135
- this.store._backburner.join(() => {
136
- internalModel.preloadData(options.preload);
137
- });
138
- }
139
-
140
- let promise: Promise<StableRecordIdentifier>;
141
- if (isEmpty) {
142
- assertIdentifierHasId(identifier);
143
-
144
- promise = this.store._fetchManager.scheduleFetch(identifier, options);
145
- } else if (isLoading) {
146
- promise = this.store._fetchManager.getPendingFetch(identifier, options)!;
147
- assert(`Expected to find a pending request for a record in the loading state, but found none`, promise);
148
- } else {
149
- promise = resolve(identifier);
150
- }
151
-
152
- return promise;
153
- }
154
-
155
- _instantiateRecord(
156
- recordData: RecordData,
157
- identifier: StableRecordIdentifier,
158
- properties?: { [key: string]: unknown }
159
- ) {
160
- // assert here
161
- if (properties !== undefined) {
162
- assert(
163
- `You passed '${typeof properties}' as properties for record creation instead of an object.`,
164
- typeof properties === 'object' && properties !== null
165
- );
166
-
167
- const { type } = identifier;
168
-
169
- // convert relationship Records to RecordDatas before passing to RecordData
170
- let defs = this.store.getSchemaDefinitionService().relationshipsDefinitionFor({ type });
171
-
172
- if (defs !== null) {
173
- let keys = Object.keys(properties);
174
- let relationshipValue;
175
-
176
- for (let i = 0; i < keys.length; i++) {
177
- let prop = keys[i];
178
- let def = defs[prop];
179
-
180
- if (def !== undefined) {
181
- if (def.kind === 'hasMany') {
182
- if (DEBUG) {
183
- assertRecordsPassedToHasMany(properties[prop] as RecordInstance[]);
184
- }
185
- relationshipValue = extractRecordDatasFromRecords(properties[prop] as RecordInstance[]);
186
- } else {
187
- relationshipValue = extractRecordDataFromRecord(properties[prop] as RecordInstance);
188
- }
189
-
190
- properties[prop] = relationshipValue;
191
- }
192
- }
193
- }
194
- }
195
-
196
- // TODO guard against initRecordOptions no being there
197
- let createOptions = recordData._initRecordCreateOptions(properties);
198
- //TODO Igor pass a wrapper instead of RD
199
- let record = this.store.instantiateRecord(
200
- identifier,
201
- createOptions,
202
- // eslint-disable-next-line @typescript-eslint/unbound-method
203
- this.__recordDataFor,
204
- this.store._notificationManager
205
- );
206
- setRecordIdentifier(record, identifier);
207
- setRecordDataFor(record, recordData);
208
- StoreMap.set(record, this.store);
209
- return record;
210
- }
211
-
212
- _teardownRecord(record: RecordInstance) {
213
- StoreMap.delete(record);
214
- // TODO remove identifier:record cache link
215
- this.store.teardownRecord(record);
216
- }
217
-
218
- removeRecord(identifier: StableRecordIdentifier): boolean {
219
- let record = this.peek({ identifier, bucket: 'record' });
220
-
221
- if (record) {
222
- this.#instances.record.delete(identifier);
223
- this._teardownRecord(record);
224
- }
225
-
226
- return !!record;
227
- }
228
-
229
- // TODO move RecordData Cache into InstanceCache
230
- getRecordData(identifier: StableRecordIdentifier) {
231
- let recordData = this.peek({ identifier, bucket: 'recordData' });
232
-
233
- if (!recordData) {
234
- recordData = this._createRecordData(identifier);
235
- this.#instances.recordData.set(identifier, recordData);
236
- this.getInternalModel(identifier).hasRecordData = true;
237
- }
238
-
239
- return recordData;
240
- }
241
-
242
- // TODO move InternalModel cache into InstanceCache
243
- getInternalModel(identifier: StableRecordIdentifier) {
244
- return this._internalModelForResource(identifier);
245
- }
246
-
247
- createSnapshot(identifier: StableRecordIdentifier, options: FindOptions = {}): Snapshot {
248
- return new Snapshot(options, identifier, this.store);
249
- }
250
-
251
- __recordDataFor(resource: RecordIdentifier) {
252
- const identifier = this.store.identifierCache.getOrCreateRecordIdentifier(resource);
253
- return this.recordDataFor(identifier, false);
254
- }
255
-
256
- // TODO move this to InstanceCache
257
- _createRecordData(identifier: StableRecordIdentifier): RecordData {
258
- const recordData = this.store.createRecordDataFor(
259
- identifier.type,
260
- identifier.id,
261
- identifier.lid,
262
- this._storeWrapper
263
- );
264
- setRecordDataFor(identifier, recordData);
265
- // TODO this is invalid for v2 recordData but required
266
- // for v1 recordData. Remember to remove this once the
267
- // RecordData manager handles converting recordData to identifier
268
- setRecordIdentifier(recordData, identifier);
269
- return recordData;
270
- }
271
-
272
- // TODO string candidate for early elimination
273
- _internalModelForResource(resource: ResourceIdentifierObject): InternalModel {
274
- return internalModelFactoryFor(this.store).getByResource(resource);
275
- }
276
-
277
- setRecordId(modelName: string, newId: string, clientId: string) {
278
- internalModelFactoryFor(this.store).setRecordId(modelName, newId, clientId);
279
- }
280
-
281
- _load(data: ExistingResourceObject): StableExistingRecordIdentifier {
282
- // TODO type should be pulled from the identifier for debug
283
- let modelName = data.type;
284
- assert(
285
- `You must include an 'id' for ${modelName} in an object passed to 'push'`,
286
- data.id !== null && data.id !== undefined && data.id !== ''
287
- );
288
- assert(
289
- `You tried to push data with a type '${modelName}' but no model could be found with that name.`,
290
- this.store.getSchemaDefinitionService().doesTypeExist(modelName)
291
- );
292
-
293
- // TODO this should determine identifier via the cache before making assumptions
294
- const resource = constructResource(normalizeModelName(data.type), ensureStringId(data.id), coerceId(data.lid));
295
- const maybeIdentifier = this.store.identifierCache.peekRecordIdentifier(resource);
296
-
297
- let internalModel = internalModelFactoryFor(this.store).lookup(resource, data);
298
-
299
- // store.push will be from empty
300
- // findRecord will be from root.loading
301
- // this cannot be loading state if we do not already have an identifier
302
- // all else will be updates
303
- const isLoading = internalModel.isLoading || (!internalModel.isLoaded && maybeIdentifier);
304
- const isUpdate = internalModel.isEmpty === false && !isLoading;
305
-
306
- // exclude store.push (root.empty) case
307
- let identifier = internalModel.identifier;
308
- if (isUpdate || isLoading) {
309
- let updatedIdentifier = this.store.identifierCache.updateRecordIdentifier(identifier, data);
310
-
311
- if (updatedIdentifier !== identifier) {
312
- // we encountered a merge of identifiers in which
313
- // two identifiers (and likely two internalModels)
314
- // existed for the same resource. Now that we have
315
- // determined the correct identifier to use, make sure
316
- // that we also use the correct internalModel.
317
- identifier = updatedIdentifier;
318
- internalModel = internalModelFactoryFor(this.store).lookup(identifier);
319
- }
320
- }
321
-
322
- internalModel.setupData(data);
323
-
324
- if (!isUpdate) {
325
- this.store.recordArrayManager.recordDidChange(identifier);
326
- }
327
-
328
- return identifier as StableExistingRecordIdentifier;
329
- }
330
-
331
- recordDataFor(identifier: StableRecordIdentifier | { type: string }, isCreate: boolean): RecordData {
332
- let recordData: RecordData;
333
- if (isCreate === true) {
334
- // TODO remove once InternalModel is no longer essential to internal state
335
- // and just build a new identifier directly
336
- let internalModel = internalModelFactoryFor(this.store).build({ type: identifier.type, id: null });
337
- let stableIdentifier = internalModel.identifier;
338
- recordData = this.getRecordData(stableIdentifier);
339
- recordData.clientDidCreate();
340
- this.store.recordArrayManager.recordDidChange(stableIdentifier);
341
- } else {
342
- // TODO remove once InternalModel is no longer essential to internal state
343
- internalModelFactoryFor(this.store).lookup(identifier as StableRecordIdentifier);
344
- recordData = this.getRecordData(identifier as StableRecordIdentifier);
345
- }
346
-
347
- return recordData;
348
- }
349
- }
350
-
351
- function assertRecordsPassedToHasMany(records: RecordInstance[]) {
352
- assert(`You must pass an array of records to set a hasMany relationship`, Array.isArray(records));
353
- assert(
354
- `All elements of a hasMany relationship must be instances of Model, you passed ${records
355
- .map((r) => `${typeof r}`)
356
- .join(', ')}`,
357
- (function () {
358
- return records.every((record) => Object.prototype.hasOwnProperty.call(record, '_internalModel') === true);
359
- })()
360
- );
361
- }
362
-
363
- function extractRecordDatasFromRecords(records: RecordInstance[]): RecordData[] {
364
- return records.map(extractRecordDataFromRecord) as RecordData[];
365
- }
366
- type PromiseProxyRecord = { then(): void; get(str: 'content'): RecordInstance | null | undefined };
367
-
368
- function extractRecordDataFromRecord(recordOrPromiseRecord: PromiseProxyRecord | RecordInstance | null) {
369
- if (!recordOrPromiseRecord) {
370
- return null;
371
- }
372
-
373
- if (isPromiseRecord(recordOrPromiseRecord)) {
374
- let content = recordOrPromiseRecord.get && recordOrPromiseRecord.get('content');
375
- assert(
376
- '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.',
377
- content !== undefined
378
- );
379
- return content ? recordDataFor(content) : null;
380
- }
381
-
382
- return recordDataFor(recordOrPromiseRecord);
383
- }
384
-
385
- function isPromiseRecord(record: PromiseProxyRecord | RecordInstance): record is PromiseProxyRecord {
386
- return !!record.then;
387
- }