@ember-data/store 4.8.0-alpha.3 → 4.8.0-alpha.6

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 (27) hide show
  1. package/addon/-private/caches/identifier-cache.ts +65 -66
  2. package/addon/-private/caches/instance-cache.ts +173 -236
  3. package/addon/-private/caches/record-data-for.ts +1 -6
  4. package/addon/-private/index.ts +13 -10
  5. package/addon/-private/legacy-model-support/record-reference.ts +12 -10
  6. package/addon/-private/legacy-model-support/schema-definition-service.ts +9 -4
  7. package/addon/-private/legacy-model-support/shim-model-class.ts +17 -10
  8. package/addon/-private/managers/record-array-manager.ts +282 -321
  9. package/addon/-private/managers/record-data-manager.ts +822 -0
  10. package/addon/-private/managers/record-data-store-wrapper.ts +295 -91
  11. package/addon/-private/managers/record-notification-manager.ts +45 -32
  12. package/addon/-private/network/fetch-manager.ts +292 -300
  13. package/addon/-private/network/finders.js +11 -6
  14. package/addon/-private/network/request-cache.ts +20 -17
  15. package/addon/-private/network/snapshot-record-array.ts +12 -29
  16. package/addon/-private/network/snapshot.ts +25 -27
  17. package/addon/-private/proxies/promise-proxies.ts +72 -11
  18. package/addon/-private/record-arrays/identifier-array.ts +924 -0
  19. package/addon/-private/store-service.ts +380 -114
  20. package/addon/-private/utils/is-non-empty-string.ts +1 -1
  21. package/addon/-private/utils/promise-record.ts +2 -3
  22. package/addon/-private/utils/uuid-polyfill.ts +71 -0
  23. package/package.json +11 -7
  24. package/addon/-private/backburner.js +0 -25
  25. package/addon/-private/record-arrays/adapter-populated-record-array.ts +0 -128
  26. package/addon/-private/record-arrays/record-array.ts +0 -320
  27. package/addon/-private/utils/weak-cache.ts +0 -125
@@ -4,6 +4,8 @@
4
4
  import { assert, warn } from '@ember/debug';
5
5
  import { DEBUG } from '@glimmer/env';
6
6
 
7
+ import { getOwnConfig, importSync, macroCondition } from '@embroider/macros';
8
+
7
9
  import { LOG_IDENTIFIERS } from '@ember-data/private-build-infra/debugging';
8
10
  import type { ExistingResourceObject, ResourceIdentifierObject } from '@ember-data/types/q/ember-data-json-api';
9
11
  import type {
@@ -23,9 +25,8 @@ import coerceId from '../utils/coerce-id';
23
25
  import { DEBUG_CLIENT_ORIGINATED, DEBUG_IDENTIFIER_BUCKET } from '../utils/identifer-debug-consts';
24
26
  import isNonEmptyString from '../utils/is-non-empty-string';
25
27
  import normalizeModelName from '../utils/normalize-model-name';
26
- import WeakCache from '../utils/weak-cache';
27
28
 
28
- const IDENTIFIERS = new WeakSet();
29
+ const IDENTIFIERS = new Set();
29
30
 
30
31
  export function isStableIdentifier(identifier: Object): identifier is StableRecordIdentifier {
31
32
  return IDENTIFIERS.has(identifier);
@@ -34,6 +35,10 @@ export function isStableIdentifier(identifier: Object): identifier is StableReco
34
35
  const isFastBoot = typeof FastBoot !== 'undefined';
35
36
  const _crypto: Crypto = isFastBoot ? (FastBoot.require('crypto') as Crypto) : window.crypto;
36
37
 
38
+ if (macroCondition(getOwnConfig<{ polyfillUUID: boolean }>().polyfillUUID)) {
39
+ importSync('./utils/uuid-polyfill');
40
+ }
41
+
37
42
  function uuidv4(): string {
38
43
  return _crypto.randomUUID();
39
44
  }
@@ -48,10 +53,9 @@ function freeze<T>(obj: T): T {
48
53
  interface KeyOptions {
49
54
  lid: IdentifierMap;
50
55
  id: IdentifierMap;
51
- _allIdentifiers: StableRecordIdentifier[];
52
56
  }
53
57
 
54
- type IdentifierMap = ConfidentDict<StableRecordIdentifier>;
58
+ type IdentifierMap = Map<string, StableRecordIdentifier>;
55
59
  type TypeMap = ConfidentDict<KeyOptions>;
56
60
  export type MergeMethod = (
57
61
  targetIdentifier: StableRecordIdentifier,
@@ -80,12 +84,15 @@ export function setIdentifierResetMethod(method: ResetMethod | null): void {
80
84
  configuredResetMethod = method;
81
85
  }
82
86
 
87
+ type WithLid = { lid: string };
88
+ type WithId = { id: string | null; type: string };
89
+
83
90
  function defaultGenerationMethod(data: ResourceData | { type: string }, bucket: IdentifierBucket): string {
84
- if ('lid' in data && isNonEmptyString(data.lid)) {
85
- return data.lid;
91
+ if (isNonEmptyString((data as WithLid).lid)) {
92
+ return (data as WithLid).lid;
86
93
  }
87
- if ('id' in data) {
88
- let { type, id } = data;
94
+ if ((data as WithId).id !== undefined) {
95
+ let { type, id } = data as WithId;
89
96
  // TODO: add test for id not a string
90
97
  if (isNonEmptyString(coerceId(id))) {
91
98
  return `@lid:${normalizeModelName(type)}-${id}`;
@@ -98,7 +105,7 @@ function defaultEmptyCallback(...args: any[]): any {}
98
105
 
99
106
  let DEBUG_MAP;
100
107
  if (DEBUG) {
101
- DEBUG_MAP = new WeakCache<StableRecordIdentifier, StableRecordIdentifier>('identifier-proxy-target');
108
+ DEBUG_MAP = new WeakMap<StableRecordIdentifier, StableRecordIdentifier>();
102
109
  }
103
110
 
104
111
  /**
@@ -115,19 +122,16 @@ if (DEBUG) {
115
122
  @public
116
123
  */
117
124
  export class IdentifierCache {
118
- // Typescript still leaks private properties in the final
119
- // compiled class, so we may want to move these from _underscore
120
- // to a WeakMap to avoid leaking
121
- // currently we leak this for test purposes
122
125
  _cache = {
123
- lids: Object.create(null) as IdentifierMap,
126
+ lids: new Map<string, StableRecordIdentifier>(),
124
127
  types: Object.create(null) as TypeMap,
125
128
  };
126
- private _generate: GenerationMethod;
127
- private _update: UpdateMethod;
128
- private _forget: ForgetMethod;
129
- private _reset: ResetMethod;
130
- private _merge: MergeMethod;
129
+ declare _generate: GenerationMethod;
130
+ declare _update: UpdateMethod;
131
+ declare _forget: ForgetMethod;
132
+ declare _reset: ResetMethod;
133
+ declare _merge: MergeMethod;
134
+ declare _isDefaultConfig: boolean;
131
135
 
132
136
  constructor() {
133
137
  // we cache the user configuredGenerationMethod at init because it must
@@ -137,6 +141,7 @@ export class IdentifierCache {
137
141
  this._forget = configuredForgetMethod || defaultEmptyCallback;
138
142
  this._reset = configuredResetMethod || defaultEmptyCallback;
139
143
  this._merge = defaultEmptyCallback;
144
+ this._isDefaultConfig = !configuredGenerationMethod;
140
145
  }
141
146
 
142
147
  /**
@@ -156,12 +161,9 @@ export class IdentifierCache {
156
161
  * @method _getRecordIdentifier
157
162
  * @private
158
163
  */
159
- private _getRecordIdentifier(resource: ResourceIdentifierObject, shouldGenerate: true): StableRecordIdentifier;
160
- private _getRecordIdentifier(
161
- resource: ResourceIdentifierObject,
162
- shouldGenerate: false
163
- ): StableRecordIdentifier | undefined;
164
- private _getRecordIdentifier(
164
+ _getRecordIdentifier(resource: ResourceIdentifierObject, shouldGenerate: true): StableRecordIdentifier;
165
+ _getRecordIdentifier(resource: ResourceIdentifierObject, shouldGenerate: false): StableRecordIdentifier | undefined;
166
+ _getRecordIdentifier(
165
167
  resource: ResourceIdentifierObject,
166
168
  shouldGenerate: boolean = false
167
169
  ): StableRecordIdentifier | undefined {
@@ -169,7 +171,7 @@ export class IdentifierCache {
169
171
  if (isStableIdentifier(resource)) {
170
172
  if (DEBUG) {
171
173
  // TODO should we instead just treat this case as a new generation skipping the short circuit?
172
- if (!(resource.lid in this._cache.lids) || this._cache.lids[resource.lid] !== resource) {
174
+ if (!this._cache.lids.has(resource.lid) || this._cache.lids.get(resource.lid) !== resource) {
173
175
  throw new Error(`The supplied identifier ${resource} does not belong to this store instance`);
174
176
  }
175
177
  }
@@ -181,7 +183,7 @@ export class IdentifierCache {
181
183
  }
182
184
 
183
185
  let lid = coerceId(resource.lid);
184
- let identifier: StableRecordIdentifier | undefined = lid !== null ? this._cache.lids[lid] : undefined;
186
+ let identifier: StableRecordIdentifier | undefined = lid !== null ? this._cache.lids.get(lid) : undefined;
185
187
 
186
188
  if (identifier !== undefined) {
187
189
  if (LOG_IDENTIFIERS) {
@@ -197,7 +199,7 @@ export class IdentifierCache {
197
199
  }
198
200
 
199
201
  if (shouldGenerate === false) {
200
- if (!('type' in resource) || !('id' in resource) || !resource.type || !resource.id) {
202
+ if (!(resource as ExistingResourceObject).type || !(resource as ExistingResourceObject).id) {
201
203
  return;
202
204
  }
203
205
  }
@@ -212,13 +214,13 @@ export class IdentifierCache {
212
214
 
213
215
  // go straight for the stable RecordIdentifier key'd to `lid`
214
216
  if (lid !== null) {
215
- identifier = keyOptions.lid[lid];
217
+ identifier = keyOptions.lid.get(lid);
216
218
  }
217
219
 
218
220
  // we may have not seen this resource before
219
221
  // but just in case we check our own secondary lookup (`id`)
220
222
  if (identifier === undefined && id !== null) {
221
- identifier = keyOptions.id[id];
223
+ identifier = keyOptions.id.get(id);
222
224
  }
223
225
 
224
226
  if (identifier === undefined) {
@@ -235,12 +237,12 @@ export class IdentifierCache {
235
237
  // different than expected
236
238
  if (lid !== null && newLid !== lid) {
237
239
  throw new Error(`You should not change the <lid> of a RecordIdentifier`);
238
- } else if (lid === null) {
240
+ } else if (lid === null && !this._isDefaultConfig) {
239
241
  // allow configuration to tell us that we have
240
242
  // seen this `lid` before. E.g. a secondary lookup
241
243
  // connects this resource to a previously seen
242
244
  // resource.
243
- identifier = keyOptions.lid[newLid];
245
+ identifier = keyOptions.lid.get(newLid);
244
246
  }
245
247
 
246
248
  if (shouldGenerate === true) {
@@ -252,19 +254,16 @@ export class IdentifierCache {
252
254
  if (DEBUG) {
253
255
  // realistically if you hit this it means you changed `type` :/
254
256
  // TODO consider how to handle type change assertions more gracefully
255
- if (identifier.lid in this._cache.lids) {
257
+ if (this._cache.lids.has(identifier.lid)) {
256
258
  throw new Error(`You should not change the <type> of a RecordIdentifier`);
257
259
  }
258
260
  }
259
- this._cache.lids[identifier.lid] = identifier;
261
+ this._cache.lids.set(identifier.lid, identifier);
260
262
 
261
263
  // populate our primary lookup table
262
264
  // TODO consider having the `lid` cache be
263
265
  // one level up
264
- keyOptions.lid[identifier.lid] = identifier;
265
- // TODO exists temporarily to support `peekAll`
266
- // but likely to move
267
- keyOptions._allIdentifiers.push(identifier);
266
+ keyOptions.lid.set(identifier.lid, identifier);
268
267
 
269
268
  if (LOG_IDENTIFIERS && shouldGenerate) {
270
269
  // eslint-disable-next-line no-console
@@ -286,7 +285,7 @@ export class IdentifierCache {
286
285
  // because they may not match and we prefer
287
286
  // what we've set via resource data
288
287
  if (identifier.id !== null) {
289
- keyOptions.id[identifier.id] = identifier;
288
+ keyOptions.id.set(identifier.id, identifier);
290
289
 
291
290
  // TODO allow filling out of `id` here
292
291
  // for the `username` non-client created
@@ -360,17 +359,17 @@ export class IdentifierCache {
360
359
 
361
360
  // populate our unique table
362
361
  if (DEBUG) {
363
- if (identifier.lid in this._cache.lids) {
362
+ if (this._cache.lids.has(identifier.lid)) {
364
363
  throw new Error(`The lid generated for the new record is not unique as it matches an existing identifier`);
365
364
  }
366
365
  }
367
- this._cache.lids[identifier.lid] = identifier;
366
+ this._cache.lids.set(identifier.lid, identifier);
368
367
 
369
368
  // populate the type+lid cache
370
- keyOptions.lid[newLid] = identifier;
371
- // ensure a peekAll sees our new identifier too
372
- // TODO move this outta here?
373
- keyOptions._allIdentifiers.push(identifier);
369
+ keyOptions.lid.set(newLid, identifier);
370
+ if (data.id) {
371
+ keyOptions.id.set(data.id, identifier);
372
+ }
374
373
 
375
374
  if (LOG_IDENTIFIERS) {
376
375
  // eslint-disable-next-line no-console
@@ -405,13 +404,17 @@ export class IdentifierCache {
405
404
  updateRecordIdentifier(identifierObject: RecordIdentifier, data: ResourceData): StableRecordIdentifier {
406
405
  let identifier = this.getOrCreateRecordIdentifier(identifierObject);
407
406
 
408
- let newId = 'id' in data ? coerceId(data.id) : null;
407
+ let newId =
408
+ (data as ExistingResourceObject).id !== undefined ? coerceId((data as ExistingResourceObject).id) : null;
409
409
  let existingIdentifier = detectMerge(this._cache.types, identifier, data, newId, this._cache.lids);
410
410
 
411
411
  if (!existingIdentifier) {
412
412
  // If the incoming type does not match the identifier type, we need to create an identifier for the incoming
413
413
  // data so we can merge the incoming data with the existing identifier, see #7325 and #7363
414
- if ('type' in data && data.type && identifier.type !== normalizeModelName(data.type)) {
414
+ if (
415
+ (data as ExistingResourceObject).type &&
416
+ identifier.type !== normalizeModelName((data as ExistingResourceObject).type)
417
+ ) {
415
418
  let incomingDataResource = { ...data };
416
419
  // Need to strip the lid from the incomingData in order force a new identifier creation
417
420
  delete incomingDataResource.lid;
@@ -452,10 +455,10 @@ export class IdentifierCache {
452
455
  );
453
456
  }
454
457
  let keyOptions = getTypeIndex(this._cache.types, identifier.type);
455
- keyOptions.id[newId] = identifier;
458
+ keyOptions.id.set(newId, identifier);
456
459
 
457
460
  if (id !== null) {
458
- delete keyOptions.id[id];
461
+ keyOptions.id.delete(id);
459
462
  }
460
463
  } else if (LOG_IDENTIFIERS) {
461
464
  // eslint-disable-next-line no-console
@@ -484,10 +487,10 @@ export class IdentifierCache {
484
487
  this.forgetRecordIdentifier(abandoned);
485
488
 
486
489
  // ensure a secondary cache entry for this id for the identifier we do keep
487
- keyOptions.id[newId] = kept;
490
+ keyOptions.id.set(newId, kept);
488
491
  // ensure a secondary cache entry for this id for the abandoned identifier's type we do keep
489
492
  let baseKeyOptions = getTypeIndex(this._cache.types, existingIdentifier.type);
490
- baseKeyOptions.id[newId] = kept;
493
+ baseKeyOptions.id.set(newId, kept);
491
494
 
492
495
  // make sure that the `lid` on the data we are processing matches the lid we kept
493
496
  data.lid = kept.lid;
@@ -511,13 +514,10 @@ export class IdentifierCache {
511
514
  let identifier = this.getOrCreateRecordIdentifier(identifierObject);
512
515
  let keyOptions = getTypeIndex(this._cache.types, identifier.type);
513
516
  if (identifier.id !== null) {
514
- delete keyOptions.id[identifier.id];
517
+ keyOptions.id.delete(identifier.id);
515
518
  }
516
- delete this._cache.lids[identifier.lid];
517
- delete keyOptions.lid[identifier.lid];
518
-
519
- let index = keyOptions._allIdentifiers.indexOf(identifier);
520
- keyOptions._allIdentifiers.splice(index, 1);
519
+ this._cache.lids.delete(identifier.lid);
520
+ keyOptions.lid.delete(identifier.lid);
521
521
 
522
522
  IDENTIFIERS.delete(identifierObject);
523
523
  this._forget(identifier, 'record');
@@ -537,9 +537,8 @@ function getTypeIndex(typeMap: TypeMap, type: string): KeyOptions {
537
537
 
538
538
  if (typeIndex === undefined) {
539
539
  typeIndex = {
540
- lid: Object.create(null),
541
- id: Object.create(null),
542
- _allIdentifiers: [],
540
+ lid: new Map(),
541
+ id: new Map(),
543
542
  };
544
543
  typeMap[type] = typeIndex;
545
544
  }
@@ -643,8 +642,8 @@ function performRecordIdentifierUpdate(identifier: StableRecordIdentifier, data:
643
642
  // for the multiple-cache-key scenario we "could"
644
643
  // use a heuristic to guess the best id for display
645
644
  // (usually when `data.id` is available and `data.attributes` is not)
646
- if ('id' in data && data.id !== undefined) {
647
- identifier.id = coerceId(data.id);
645
+ if ((data as ExistingResourceObject).id !== undefined) {
646
+ identifier.id = coerceId((data as ExistingResourceObject).id);
648
647
  }
649
648
  }
650
649
 
@@ -658,20 +657,20 @@ function detectMerge(
658
657
  const { id, type, lid } = identifier;
659
658
  if (id !== null && id !== newId && newId !== null) {
660
659
  let keyOptions = getTypeIndex(typesCache, identifier.type);
661
- let existingIdentifier = keyOptions.id[newId];
660
+ let existingIdentifier = keyOptions.id.get(newId);
662
661
 
663
662
  return existingIdentifier !== undefined ? existingIdentifier : false;
664
663
  } else {
665
- let newType = 'type' in data && data.type && normalizeModelName(data.type);
664
+ let newType = (data as ExistingResourceObject).type && normalizeModelName((data as ExistingResourceObject).type);
666
665
 
667
666
  // If the ids and type are the same but lid is not the same, we should trigger a merge of the identifiers
668
667
  if (id !== null && id === newId && newType === type && data.lid && data.lid !== lid) {
669
- let existingIdentifier = lids[data.lid];
668
+ let existingIdentifier = lids.get(data.lid);
670
669
  return existingIdentifier !== undefined ? existingIdentifier : false;
671
670
  // If the lids are the same, and ids are the same, but types are different we should trigger a merge of the identifiers
672
671
  } else if (id !== null && id === newId && newType && newType !== type && data.lid && data.lid === lid) {
673
672
  let keyOptions = getTypeIndex(typesCache, newType);
674
- let existingIdentifier = keyOptions.id[id];
673
+ let existingIdentifier = keyOptions.id.get(id);
675
674
  return existingIdentifier !== undefined ? existingIdentifier : false;
676
675
  }
677
676
  }