@ember-data/store 4.7.0-beta.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,6 +1,8 @@
1
1
  import { assert } from '@ember/debug';
2
2
  import { DEBUG } from '@glimmer/env';
3
3
 
4
+ import { DEPRECATE_NON_EXPLICIT_POLYMORPHISM } from '@ember-data/private-build-infra/deprecations';
5
+
4
6
  /*
5
7
  Assert that `addedRecord` has a valid type so it can be added to the
6
8
  relationship of the `record`.
@@ -9,7 +11,7 @@ import { DEBUG } from '@glimmer/env';
9
11
  relationship (specified via `relationshipMeta`) of the `record`.
10
12
 
11
13
  This utility should only be used internally, as both record parameters must
12
- be an InternalModel and the `relationshipMeta` needs to be the meta
14
+ be stable record identifiers and the `relationshipMeta` needs to be the meta
13
15
  information about the relationship, retrieved via
14
16
  `record.relationshipFor(key)`.
15
17
  */
@@ -29,18 +31,38 @@ if (DEBUG) {
29
31
  };
30
32
 
31
33
  assertPolymorphicType = function assertPolymorphicType(parentIdentifier, parentDefinition, addedIdentifier, store) {
32
- store = store._store ? store._store : store; // allow usage with storeWrapper
33
- let addedModelName = addedIdentifier.type;
34
- let parentModelName = parentIdentifier.type;
35
- let key = parentDefinition.key;
36
- let relationshipModelName = parentDefinition.type;
37
- let relationshipClass = store.modelFor(relationshipModelName);
38
- let addedClass = store.modelFor(addedModelName);
39
-
40
- let assertionMessage = `The '${addedModelName}' type does not implement '${relationshipModelName}' and thus cannot be assigned to the '${key}' relationship in '${parentModelName}'. Make it a descendant of '${relationshipModelName}' or use a mixin of the same name.`;
41
- let isPolymorphic = checkPolymorphic(relationshipClass, addedClass);
42
-
43
- assert(assertionMessage, isPolymorphic);
34
+ let asserted = false;
35
+
36
+ if (parentDefinition.inverseIsImplicit) {
37
+ return;
38
+ }
39
+ if (parentDefinition.isPolymorphic) {
40
+ let meta = store.getSchemaDefinitionService().relationshipsDefinitionFor(addedIdentifier)[
41
+ parentDefinition.inverseKey
42
+ ];
43
+ if (meta?.options?.as) {
44
+ asserted = true;
45
+ assert(
46
+ `The schema for the relationship '${parentDefinition.inverseKey}' on '${addedIdentifier.type}' type does not implement '${parentDefinition.type}' and thus cannot be assigned to the '${parentDefinition.key}' relationship in '${parentIdentifier.type}'. The definition should specify 'as: "${parentDefinition.type}"' in options.`,
47
+ meta.options.as === parentDefinition.type
48
+ );
49
+ }
50
+ }
51
+
52
+ if (DEPRECATE_NON_EXPLICIT_POLYMORPHISM && !asserted) {
53
+ store = store._store ? store._store : store; // allow usage with storeWrapper
54
+ let addedModelName = addedIdentifier.type;
55
+ let parentModelName = parentIdentifier.type;
56
+ let key = parentDefinition.key;
57
+ let relationshipModelName = parentDefinition.type;
58
+ let relationshipClass = store.modelFor(relationshipModelName);
59
+ let addedClass = store.modelFor(addedModelName);
60
+
61
+ let assertionMessage = `The '${addedModelName}' type does not implement '${relationshipModelName}' and thus cannot be assigned to the '${key}' relationship in '${parentModelName}'. Make it a descendant of '${relationshipModelName}' or use a mixin of the same name.`;
62
+ let isPolymorphic = checkPolymorphic(relationshipClass, addedClass);
63
+
64
+ assert(assertionMessage, isPolymorphic);
65
+ }
44
66
  };
45
67
  }
46
68
 
@@ -4,6 +4,9 @@
4
4
  import { assert, warn } from '@ember/debug';
5
5
  import { DEBUG } from '@glimmer/env';
6
6
 
7
+ import { getOwnConfig, macroCondition } from '@embroider/macros';
8
+
9
+ import { LOG_IDENTIFIERS } from '@ember-data/private-build-infra/debugging';
7
10
  import type { ExistingResourceObject, ResourceIdentifierObject } from '@ember-data/types/q/ember-data-json-api';
8
11
  import type {
9
12
  ForgetMethod,
@@ -18,13 +21,13 @@ import type {
18
21
  } from '@ember-data/types/q/identifier';
19
22
  import type { ConfidentDict } from '@ember-data/types/q/utils';
20
23
 
21
- import coerceId from './coerce-id';
22
- import { DEBUG_CLIENT_ORIGINATED, DEBUG_IDENTIFIER_BUCKET } from './identifer-debug-consts';
23
- import normalizeModelName from './normalize-model-name';
24
- import isNonEmptyString from './utils/is-non-empty-string';
25
- import WeakCache from './weak-cache';
24
+ import coerceId from '../utils/coerce-id';
25
+ import { DEBUG_CLIENT_ORIGINATED, DEBUG_IDENTIFIER_BUCKET } from '../utils/identifer-debug-consts';
26
+ import isNonEmptyString from '../utils/is-non-empty-string';
27
+ import normalizeModelName from '../utils/normalize-model-name';
28
+ import installPolyfill from '../utils/uuid-polyfill';
26
29
 
27
- const IDENTIFIERS = new WeakSet();
30
+ const IDENTIFIERS = new Set();
28
31
 
29
32
  export function isStableIdentifier(identifier: Object): identifier is StableRecordIdentifier {
30
33
  return IDENTIFIERS.has(identifier);
@@ -33,6 +36,10 @@ export function isStableIdentifier(identifier: Object): identifier is StableReco
33
36
  const isFastBoot = typeof FastBoot !== 'undefined';
34
37
  const _crypto: Crypto = isFastBoot ? (FastBoot.require('crypto') as Crypto) : window.crypto;
35
38
 
39
+ if (macroCondition(getOwnConfig<{ polyfillUUID: boolean }>().polyfillUUID)) {
40
+ installPolyfill();
41
+ }
42
+
36
43
  function uuidv4(): string {
37
44
  return _crypto.randomUUID();
38
45
  }
@@ -47,10 +54,9 @@ function freeze<T>(obj: T): T {
47
54
  interface KeyOptions {
48
55
  lid: IdentifierMap;
49
56
  id: IdentifierMap;
50
- _allIdentifiers: StableRecordIdentifier[];
51
57
  }
52
58
 
53
- type IdentifierMap = ConfidentDict<StableRecordIdentifier>;
59
+ type IdentifierMap = Map<string, StableRecordIdentifier>;
54
60
  type TypeMap = ConfidentDict<KeyOptions>;
55
61
  export type MergeMethod = (
56
62
  targetIdentifier: StableRecordIdentifier,
@@ -79,12 +85,15 @@ export function setIdentifierResetMethod(method: ResetMethod | null): void {
79
85
  configuredResetMethod = method;
80
86
  }
81
87
 
88
+ type WithLid = { lid: string };
89
+ type WithId = { id: string | null; type: string };
90
+
82
91
  function defaultGenerationMethod(data: ResourceData | { type: string }, bucket: IdentifierBucket): string {
83
- if ('lid' in data && isNonEmptyString(data.lid)) {
84
- return data.lid;
92
+ if (isNonEmptyString((data as WithLid).lid)) {
93
+ return (data as WithLid).lid;
85
94
  }
86
- if ('id' in data) {
87
- let { type, id } = data;
95
+ if ((data as WithId).id !== undefined) {
96
+ let { type, id } = data as WithId;
88
97
  // TODO: add test for id not a string
89
98
  if (isNonEmptyString(coerceId(id))) {
90
99
  return `@lid:${normalizeModelName(type)}-${id}`;
@@ -97,7 +106,7 @@ function defaultEmptyCallback(...args: any[]): any {}
97
106
 
98
107
  let DEBUG_MAP;
99
108
  if (DEBUG) {
100
- DEBUG_MAP = new WeakCache<StableRecordIdentifier, StableRecordIdentifier>('identifier-proxy-target');
109
+ DEBUG_MAP = new WeakMap<StableRecordIdentifier, StableRecordIdentifier>();
101
110
  }
102
111
 
103
112
  /**
@@ -114,19 +123,16 @@ if (DEBUG) {
114
123
  @public
115
124
  */
116
125
  export class IdentifierCache {
117
- // Typescript still leaks private properties in the final
118
- // compiled class, so we may want to move these from _underscore
119
- // to a WeakMap to avoid leaking
120
- // currently we leak this for test purposes
121
126
  _cache = {
122
- lids: Object.create(null) as IdentifierMap,
127
+ lids: new Map<string, StableRecordIdentifier>(),
123
128
  types: Object.create(null) as TypeMap,
124
129
  };
125
- private _generate: GenerationMethod;
126
- private _update: UpdateMethod;
127
- private _forget: ForgetMethod;
128
- private _reset: ResetMethod;
129
- private _merge: MergeMethod;
130
+ declare _generate: GenerationMethod;
131
+ declare _update: UpdateMethod;
132
+ declare _forget: ForgetMethod;
133
+ declare _reset: ResetMethod;
134
+ declare _merge: MergeMethod;
135
+ declare _isDefaultConfig: boolean;
130
136
 
131
137
  constructor() {
132
138
  // we cache the user configuredGenerationMethod at init because it must
@@ -136,13 +142,14 @@ export class IdentifierCache {
136
142
  this._forget = configuredForgetMethod || defaultEmptyCallback;
137
143
  this._reset = configuredResetMethod || defaultEmptyCallback;
138
144
  this._merge = defaultEmptyCallback;
145
+ this._isDefaultConfig = !configuredGenerationMethod;
139
146
  }
140
147
 
141
148
  /**
142
149
  * Internal hook to allow management of merge conflicts with identifiers.
143
150
  *
144
- * we allow late binding of this private internal merge so that `internalModelFactory`
145
- * can insert itself here to handle elimination of duplicates
151
+ * we allow late binding of this private internal merge so that
152
+ * the cache can insert itself here to handle elimination of duplicates
146
153
  *
147
154
  * @method __configureMerge
148
155
  * @private
@@ -155,12 +162,9 @@ export class IdentifierCache {
155
162
  * @method _getRecordIdentifier
156
163
  * @private
157
164
  */
158
- private _getRecordIdentifier(resource: ResourceIdentifierObject, shouldGenerate: true): StableRecordIdentifier;
159
- private _getRecordIdentifier(
160
- resource: ResourceIdentifierObject,
161
- shouldGenerate: false
162
- ): StableRecordIdentifier | undefined;
163
- private _getRecordIdentifier(
165
+ _getRecordIdentifier(resource: ResourceIdentifierObject, shouldGenerate: true): StableRecordIdentifier;
166
+ _getRecordIdentifier(resource: ResourceIdentifierObject, shouldGenerate: false): StableRecordIdentifier | undefined;
167
+ _getRecordIdentifier(
164
168
  resource: ResourceIdentifierObject,
165
169
  shouldGenerate: boolean = false
166
170
  ): StableRecordIdentifier | undefined {
@@ -168,22 +172,35 @@ export class IdentifierCache {
168
172
  if (isStableIdentifier(resource)) {
169
173
  if (DEBUG) {
170
174
  // TODO should we instead just treat this case as a new generation skipping the short circuit?
171
- if (!(resource.lid in this._cache.lids) || this._cache.lids[resource.lid] !== resource) {
175
+ if (!this._cache.lids.has(resource.lid) || this._cache.lids.get(resource.lid) !== resource) {
172
176
  throw new Error(`The supplied identifier ${resource} does not belong to this store instance`);
173
177
  }
174
178
  }
179
+ if (LOG_IDENTIFIERS) {
180
+ // eslint-disable-next-line no-console
181
+ console.log(`Identifiers: Peeked Identifier was already Stable ${String(resource)}`);
182
+ }
175
183
  return resource;
176
184
  }
177
185
 
178
186
  let lid = coerceId(resource.lid);
179
- let identifier: StableRecordIdentifier | undefined = lid !== null ? this._cache.lids[lid] : undefined;
187
+ let identifier: StableRecordIdentifier | undefined = lid !== null ? this._cache.lids.get(lid) : undefined;
180
188
 
181
189
  if (identifier !== undefined) {
190
+ if (LOG_IDENTIFIERS) {
191
+ // eslint-disable-next-line no-console
192
+ console.log(`Identifiers: cache HIT ${identifier}`, resource);
193
+ }
182
194
  return identifier;
183
195
  }
184
196
 
197
+ if (LOG_IDENTIFIERS) {
198
+ // eslint-disable-next-line no-console
199
+ console.groupCollapsed(`Identifiers: ${shouldGenerate ? 'Generating' : 'Peeking'} Identifier`, resource);
200
+ }
201
+
185
202
  if (shouldGenerate === false) {
186
- if (!('type' in resource) || !('id' in resource) || !resource.type || !resource.id) {
203
+ if (!(resource as ExistingResourceObject).type || !(resource as ExistingResourceObject).id) {
187
204
  return;
188
205
  }
189
206
  }
@@ -198,31 +215,35 @@ export class IdentifierCache {
198
215
 
199
216
  // go straight for the stable RecordIdentifier key'd to `lid`
200
217
  if (lid !== null) {
201
- identifier = keyOptions.lid[lid];
218
+ identifier = keyOptions.lid.get(lid);
202
219
  }
203
220
 
204
221
  // we may have not seen this resource before
205
222
  // but just in case we check our own secondary lookup (`id`)
206
223
  if (identifier === undefined && id !== null) {
207
- identifier = keyOptions.id[id];
224
+ identifier = keyOptions.id.get(id);
208
225
  }
209
226
 
210
227
  if (identifier === undefined) {
211
228
  // we have definitely not seen this resource before
212
229
  // so we allow the user configured `GenerationMethod` to tell us
213
230
  let newLid = this._generate(resource, 'record');
231
+ if (LOG_IDENTIFIERS) {
232
+ // eslint-disable-next-line no-console
233
+ console.log(`Identifiers: lid ${newLid} determined for resource`, resource);
234
+ }
214
235
 
215
236
  // we do this _even_ when `lid` is present because secondary lookups
216
237
  // may need to be populated, but we enforce not giving us something
217
238
  // different than expected
218
239
  if (lid !== null && newLid !== lid) {
219
240
  throw new Error(`You should not change the <lid> of a RecordIdentifier`);
220
- } else if (lid === null) {
241
+ } else if (lid === null && !this._isDefaultConfig) {
221
242
  // allow configuration to tell us that we have
222
243
  // seen this `lid` before. E.g. a secondary lookup
223
244
  // connects this resource to a previously seen
224
245
  // resource.
225
- identifier = keyOptions.lid[newLid];
246
+ identifier = keyOptions.lid.get(newLid);
226
247
  }
227
248
 
228
249
  if (shouldGenerate === true) {
@@ -234,19 +255,27 @@ export class IdentifierCache {
234
255
  if (DEBUG) {
235
256
  // realistically if you hit this it means you changed `type` :/
236
257
  // TODO consider how to handle type change assertions more gracefully
237
- if (identifier.lid in this._cache.lids) {
258
+ if (this._cache.lids.has(identifier.lid)) {
238
259
  throw new Error(`You should not change the <type> of a RecordIdentifier`);
239
260
  }
240
261
  }
241
- this._cache.lids[identifier.lid] = identifier;
262
+ this._cache.lids.set(identifier.lid, identifier);
242
263
 
243
264
  // populate our primary lookup table
244
265
  // TODO consider having the `lid` cache be
245
266
  // one level up
246
- keyOptions.lid[identifier.lid] = identifier;
247
- // TODO exists temporarily to support `peekAll`
248
- // but likely to move
249
- keyOptions._allIdentifiers.push(identifier);
267
+ keyOptions.lid.set(identifier.lid, identifier);
268
+
269
+ if (LOG_IDENTIFIERS && shouldGenerate) {
270
+ // eslint-disable-next-line no-console
271
+ console.log(`Identifiers: generated ${String(identifier)} for`, resource);
272
+ if (resource[DEBUG_IDENTIFIER_BUCKET]) {
273
+ // eslint-disable-next-line no-console
274
+ console.trace(
275
+ `[WARNING] Identifiers: generated a new identifier from a previously used identifier. This is likely a bug.`
276
+ );
277
+ }
278
+ }
250
279
  }
251
280
 
252
281
  // populate our own secondary lookup table
@@ -257,7 +286,7 @@ export class IdentifierCache {
257
286
  // because they may not match and we prefer
258
287
  // what we've set via resource data
259
288
  if (identifier.id !== null) {
260
- keyOptions.id[identifier.id] = identifier;
289
+ keyOptions.id.set(identifier.id, identifier);
261
290
 
262
291
  // TODO allow filling out of `id` here
263
292
  // for the `username` non-client created
@@ -266,6 +295,15 @@ export class IdentifierCache {
266
295
  }
267
296
  }
268
297
 
298
+ if (LOG_IDENTIFIERS) {
299
+ if (!identifier && !shouldGenerate) {
300
+ // eslint-disable-next-line no-console
301
+ console.log(`Identifiers: cache MISS`, resource);
302
+ }
303
+ // eslint-disable-next-line no-console
304
+ console.groupEnd();
305
+ }
306
+
269
307
  return identifier;
270
308
  }
271
309
 
@@ -322,17 +360,22 @@ export class IdentifierCache {
322
360
 
323
361
  // populate our unique table
324
362
  if (DEBUG) {
325
- if (identifier.lid in this._cache.lids) {
363
+ if (this._cache.lids.has(identifier.lid)) {
326
364
  throw new Error(`The lid generated for the new record is not unique as it matches an existing identifier`);
327
365
  }
328
366
  }
329
- this._cache.lids[identifier.lid] = identifier;
367
+ this._cache.lids.set(identifier.lid, identifier);
330
368
 
331
369
  // populate the type+lid cache
332
- keyOptions.lid[newLid] = identifier;
333
- // ensure a peekAll sees our new identifier too
334
- // TODO move this outta here?
335
- keyOptions._allIdentifiers.push(identifier);
370
+ keyOptions.lid.set(newLid, identifier);
371
+ if (data.id) {
372
+ keyOptions.id.set(data.id, identifier);
373
+ }
374
+
375
+ if (LOG_IDENTIFIERS) {
376
+ // eslint-disable-next-line no-console
377
+ console.log(`Identifiers: createded identifier ${String(identifier)} for newly generated resource`, data);
378
+ }
336
379
 
337
380
  return identifier;
338
381
  }
@@ -362,13 +405,17 @@ export class IdentifierCache {
362
405
  updateRecordIdentifier(identifierObject: RecordIdentifier, data: ResourceData): StableRecordIdentifier {
363
406
  let identifier = this.getOrCreateRecordIdentifier(identifierObject);
364
407
 
365
- let newId = 'id' in data ? coerceId(data.id) : null;
408
+ let newId =
409
+ (data as ExistingResourceObject).id !== undefined ? coerceId((data as ExistingResourceObject).id) : null;
366
410
  let existingIdentifier = detectMerge(this._cache.types, identifier, data, newId, this._cache.lids);
367
411
 
368
412
  if (!existingIdentifier) {
369
413
  // If the incoming type does not match the identifier type, we need to create an identifier for the incoming
370
414
  // data so we can merge the incoming data with the existing identifier, see #7325 and #7363
371
- if ('type' in data && data.type && identifier.type !== normalizeModelName(data.type)) {
415
+ if (
416
+ (data as ExistingResourceObject).type &&
417
+ identifier.type !== normalizeModelName((data as ExistingResourceObject).type)
418
+ ) {
372
419
  let incomingDataResource = { ...data };
373
420
  // Need to strip the lid from the incomingData in order force a new identifier creation
374
421
  delete incomingDataResource.lid;
@@ -378,7 +425,21 @@ export class IdentifierCache {
378
425
 
379
426
  if (existingIdentifier) {
380
427
  let keyOptions = getTypeIndex(this._cache.types, identifier.type);
381
- identifier = this._mergeRecordIdentifiers(keyOptions, identifier, existingIdentifier, data, newId as string);
428
+ let generatedIdentifier = identifier;
429
+ identifier = this._mergeRecordIdentifiers(
430
+ keyOptions,
431
+ generatedIdentifier,
432
+ existingIdentifier,
433
+ data,
434
+ newId as string
435
+ );
436
+ if (LOG_IDENTIFIERS) {
437
+ // eslint-disable-next-line no-console
438
+ console.log(
439
+ `Identifiers: merged identifiers ${generatedIdentifier.lid} and ${existingIdentifier.lid} for resource into ${identifier.lid}`,
440
+ data
441
+ );
442
+ }
382
443
  }
383
444
 
384
445
  let id = identifier.id;
@@ -387,12 +448,22 @@ export class IdentifierCache {
387
448
 
388
449
  // add to our own secondary lookup table
389
450
  if (id !== newId && newId !== null) {
451
+ if (LOG_IDENTIFIERS) {
452
+ // eslint-disable-next-line no-console
453
+ console.log(
454
+ `Identifiers: updated id for identifier ${identifier.lid} from '${id}' to '${newId}' for resource`,
455
+ data
456
+ );
457
+ }
390
458
  let keyOptions = getTypeIndex(this._cache.types, identifier.type);
391
- keyOptions.id[newId] = identifier;
459
+ keyOptions.id.set(newId, identifier);
392
460
 
393
461
  if (id !== null) {
394
- delete keyOptions.id[id];
462
+ keyOptions.id.delete(id);
395
463
  }
464
+ } else if (LOG_IDENTIFIERS) {
465
+ // eslint-disable-next-line no-console
466
+ console.log(`Identifiers: updated identifier ${identifier.lid} resource`, data);
396
467
  }
397
468
 
398
469
  return identifier;
@@ -417,10 +488,10 @@ export class IdentifierCache {
417
488
  this.forgetRecordIdentifier(abandoned);
418
489
 
419
490
  // ensure a secondary cache entry for this id for the identifier we do keep
420
- keyOptions.id[newId] = kept;
491
+ keyOptions.id.set(newId, kept);
421
492
  // ensure a secondary cache entry for this id for the abandoned identifier's type we do keep
422
493
  let baseKeyOptions = getTypeIndex(this._cache.types, existingIdentifier.type);
423
- baseKeyOptions.id[newId] = kept;
494
+ baseKeyOptions.id.set(newId, kept);
424
495
 
425
496
  // make sure that the `lid` on the data we are processing matches the lid we kept
426
497
  data.lid = kept.lid;
@@ -444,16 +515,17 @@ export class IdentifierCache {
444
515
  let identifier = this.getOrCreateRecordIdentifier(identifierObject);
445
516
  let keyOptions = getTypeIndex(this._cache.types, identifier.type);
446
517
  if (identifier.id !== null) {
447
- delete keyOptions.id[identifier.id];
518
+ keyOptions.id.delete(identifier.id);
448
519
  }
449
- delete this._cache.lids[identifier.lid];
450
- delete keyOptions.lid[identifier.lid];
451
-
452
- let index = keyOptions._allIdentifiers.indexOf(identifier);
453
- keyOptions._allIdentifiers.splice(index, 1);
520
+ this._cache.lids.delete(identifier.lid);
521
+ keyOptions.lid.delete(identifier.lid);
454
522
 
455
523
  IDENTIFIERS.delete(identifierObject);
456
524
  this._forget(identifier, 'record');
525
+ if (LOG_IDENTIFIERS) {
526
+ // eslint-disable-next-line no-console
527
+ console.log(`Identifiers: released identifier ${identifierObject.lid}`);
528
+ }
457
529
  }
458
530
 
459
531
  destroy() {
@@ -466,9 +538,8 @@ function getTypeIndex(typeMap: TypeMap, type: string): KeyOptions {
466
538
 
467
539
  if (typeIndex === undefined) {
468
540
  typeIndex = {
469
- lid: Object.create(null),
470
- id: Object.create(null),
471
- _allIdentifiers: [],
541
+ lid: new Map(),
542
+ id: new Map(),
472
543
  };
473
544
  typeMap[type] = typeIndex;
474
545
  }
@@ -507,6 +578,10 @@ function makeStableRecordIdentifier(
507
578
  let { type, id, lid } = recordIdentifier;
508
579
  return `${clientOriginated ? '[CLIENT_ORIGINATED] ' : ''}${type}:${id} (${lid})`;
509
580
  },
581
+ toJSON() {
582
+ let { type, id, lid } = recordIdentifier;
583
+ return { type, id, lid };
584
+ },
510
585
  };
511
586
  wrapper[DEBUG_CLIENT_ORIGINATED] = clientOriginated;
512
587
  wrapper[DEBUG_IDENTIFIER_BUCKET] = bucket;
@@ -568,8 +643,8 @@ function performRecordIdentifierUpdate(identifier: StableRecordIdentifier, data:
568
643
  // for the multiple-cache-key scenario we "could"
569
644
  // use a heuristic to guess the best id for display
570
645
  // (usually when `data.id` is available and `data.attributes` is not)
571
- if ('id' in data && data.id !== undefined) {
572
- identifier.id = coerceId(data.id);
646
+ if ((data as ExistingResourceObject).id !== undefined) {
647
+ identifier.id = coerceId((data as ExistingResourceObject).id);
573
648
  }
574
649
  }
575
650
 
@@ -583,20 +658,20 @@ function detectMerge(
583
658
  const { id, type, lid } = identifier;
584
659
  if (id !== null && id !== newId && newId !== null) {
585
660
  let keyOptions = getTypeIndex(typesCache, identifier.type);
586
- let existingIdentifier = keyOptions.id[newId];
661
+ let existingIdentifier = keyOptions.id.get(newId);
587
662
 
588
663
  return existingIdentifier !== undefined ? existingIdentifier : false;
589
664
  } else {
590
- let newType = 'type' in data && data.type && normalizeModelName(data.type);
665
+ let newType = (data as ExistingResourceObject).type && normalizeModelName((data as ExistingResourceObject).type);
591
666
 
592
667
  // If the ids and type are the same but lid is not the same, we should trigger a merge of the identifiers
593
668
  if (id !== null && id === newId && newType === type && data.lid && data.lid !== lid) {
594
- let existingIdentifier = lids[data.lid];
669
+ let existingIdentifier = lids.get(data.lid);
595
670
  return existingIdentifier !== undefined ? existingIdentifier : false;
596
671
  // If the lids are the same, and ids are the same, but types are different we should trigger a merge of the identifiers
597
672
  } else if (id !== null && id === newId && newType && newType !== type && data.lid && data.lid === lid) {
598
673
  let keyOptions = getTypeIndex(typesCache, newType);
599
- let existingIdentifier = keyOptions.id[id];
674
+ let existingIdentifier = keyOptions.id.get(id);
600
675
  return existingIdentifier !== undefined ? existingIdentifier : false;
601
676
  }
602
677
  }