@ember-data/store 5.4.0-alpha.2 → 5.4.0-alpha.21

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.
@@ -1,365 +1,17 @@
1
- import { macroCondition, getOwnConfig } from '@embroider/macros';
2
- import { getOwner } from '@ember/application';
3
- import { assert, deprecate, warn } from '@ember/debug';
1
+ import { deprecate, assert, warn } from '@ember/debug';
4
2
  import EmberObject from '@ember/object';
5
- import { _backburner } from '@ember/runloop';
6
- import { tracked } from '@glimmer/tracking';
3
+ import { SkipCache, EnableHydration } from '@warp-drive/core-types/request';
4
+ import { macroCondition, getOwnConfig } from '@embroider/macros';
5
+ import { CACHE_OWNER, DEBUG_STALE_CACHE_OWNER, DEBUG_CLIENT_ORIGINATED, DEBUG_IDENTIFIER_BUCKET } from '@warp-drive/core-types/identifier';
7
6
  import { dasherize } from '@ember/string';
8
- import { addToTransaction, subscribe, addTransactionCB } from '@ember-data/tracking/-private';
9
- import { tagForProperty } from '@ember/-internals/metal';
10
- import { dependentKeyCompat } from '@ember/object/compat';
11
- import { dirtyTag } from '@glimmer/validator';
12
- import Ember from 'ember';
13
- function _initializerDefineProperty(target, property, descriptor, context) {
14
- if (!descriptor) return;
15
- Object.defineProperty(target, property, {
16
- enumerable: descriptor.enumerable,
17
- configurable: descriptor.configurable,
18
- writable: descriptor.writable,
19
- value: descriptor.initializer ? descriptor.initializer.call(context) : void 0
20
- });
21
- }
22
- function _classPrivateFieldBase(receiver, privateKey) {
23
- if (!Object.prototype.hasOwnProperty.call(receiver, privateKey)) {
24
- throw new TypeError("attempted to use private field on non-instance");
25
- }
26
- return receiver;
27
- }
28
- var id = 0;
29
- function _classPrivateFieldKey(name) {
30
- return "__private_" + id++ + "_" + name;
31
- }
32
- function _applyDecoratedDescriptor(target, property, decorators, descriptor, context) {
33
- var desc = {};
34
- Object.keys(descriptor).forEach(function (key) {
35
- desc[key] = descriptor[key];
36
- });
37
- desc.enumerable = !!desc.enumerable;
38
- desc.configurable = !!desc.configurable;
39
- if ('value' in desc || desc.initializer) {
40
- desc.writable = true;
41
- }
42
- desc = decorators.slice().reverse().reduce(function (desc, decorator) {
43
- return decorator(target, property, desc) || desc;
44
- }, desc);
45
- if (context && desc.initializer !== void 0) {
46
- desc.value = desc.initializer ? desc.initializer.call(context) : void 0;
47
- desc.initializer = undefined;
48
- }
49
- if (desc.initializer === void 0) {
50
- Object.defineProperty(target, property, desc);
51
- desc = null;
52
- }
53
- return desc;
54
- }
55
- var _class$2, _descriptor$2, _descriptor2$1, _descriptor3, _descriptor4, _store, _request;
56
- function urlFromLink(link) {
57
- if (typeof link === 'string') return link;
58
- return link.href;
59
- }
60
- let Document = (_class$2 = (_store = /*#__PURE__*/_classPrivateFieldKey("store"), _request = /*#__PURE__*/_classPrivateFieldKey("request"), class Document {
61
- constructor(store, identifier) {
62
- Object.defineProperty(this, _request, {
63
- value: _request2
64
- });
65
- _initializerDefineProperty(this, "links", _descriptor$2, this);
66
- _initializerDefineProperty(this, "data", _descriptor2$1, this);
67
- _initializerDefineProperty(this, "errors", _descriptor3, this);
68
- _initializerDefineProperty(this, "meta", _descriptor4, this);
69
- Object.defineProperty(this, _store, {
70
- writable: true,
71
- value: void 0
72
- });
73
- _classPrivateFieldBase(this, _store)[_store] = store;
74
- this.identifier = identifier;
75
- }
76
- fetch(options = {}) {
77
- assert(`No self link`, this.links?.self);
78
- options.cacheOptions = options.cacheOptions || {};
79
- options.cacheOptions.key = this.identifier?.lid;
80
- return _classPrivateFieldBase(this, _request)[_request]('self', options);
81
- }
82
- next(options) {
83
- return _classPrivateFieldBase(this, _request)[_request]('next', options);
84
- }
85
- prev(options) {
86
- return _classPrivateFieldBase(this, _request)[_request]('prev', options);
87
- }
88
- first(options) {
89
- return _classPrivateFieldBase(this, _request)[_request]('first', options);
90
- }
91
- last(options) {
92
- return _classPrivateFieldBase(this, _request)[_request]('last', options);
93
- }
94
- toJSON() {
95
- const data = {};
96
- data.identifier = this.identifier;
97
- if (this.data !== undefined) {
98
- data.data = this.data;
99
- }
100
- if (this.links !== undefined) {
101
- data.links = this.links;
102
- }
103
- if (this.errors !== undefined) {
104
- data.errors = this.errors;
105
- }
106
- if (this.meta !== undefined) {
107
- data.meta = this.meta;
108
- }
109
- return data;
110
- }
111
- }), (_descriptor$2 = _applyDecoratedDescriptor(_class$2.prototype, "links", [tracked], {
112
- configurable: true,
113
- enumerable: true,
114
- writable: true,
115
- initializer: null
116
- }), _descriptor2$1 = _applyDecoratedDescriptor(_class$2.prototype, "data", [tracked], {
117
- configurable: true,
118
- enumerable: true,
119
- writable: true,
120
- initializer: null
121
- }), _descriptor3 = _applyDecoratedDescriptor(_class$2.prototype, "errors", [tracked], {
122
- configurable: true,
123
- enumerable: true,
124
- writable: true,
125
- initializer: null
126
- }), _descriptor4 = _applyDecoratedDescriptor(_class$2.prototype, "meta", [tracked], {
127
- configurable: true,
128
- enumerable: true,
129
- writable: true,
130
- initializer: null
131
- })), _class$2);
132
- async function _request2(link, options = {}) {
133
- const href = this.links?.[link];
134
- if (!href) {
135
- return null;
136
- }
137
- const response = await _classPrivateFieldBase(this, _store)[_store].request(Object.assign(options, {
138
- url: urlFromLink(href)
139
- }));
140
- return response.content;
141
- }
142
- function isErrorDocument(document) {
143
- return 'errors' in document;
144
- }
145
- function maybeUpdateUiObjects(store, request, options, document, isFromCache) {
146
- const {
147
- identifier
148
- } = options;
149
- if (isErrorDocument(document)) {
150
- if (!identifier && !options.shouldHydrate) {
151
- return document;
152
- }
153
- let doc;
154
- if (identifier) {
155
- doc = store._documentCache.get(identifier);
156
- }
157
- if (!doc) {
158
- doc = new Document(store, identifier);
159
- copyDocumentProperties(doc, document);
160
- if (identifier) {
161
- store._documentCache.set(identifier, doc);
162
- }
163
- } else if (!isFromCache) {
164
- doc.data = undefined;
165
- copyDocumentProperties(doc, document);
166
- }
167
- return options.shouldHydrate ? doc : document;
168
- }
169
- if (Array.isArray(document.data)) {
170
- const {
171
- recordArrayManager
172
- } = store;
173
- if (!identifier) {
174
- if (!options.shouldHydrate) {
175
- return document;
176
- }
177
- const data = recordArrayManager.createArray({
178
- type: request.url,
179
- identifiers: document.data,
180
- doc: document,
181
- query: request
182
- });
183
- const doc = new Document(store, null);
184
- doc.data = data;
185
- doc.meta = document.meta;
186
- doc.links = document.links;
187
- return doc;
188
- }
189
- let managed = recordArrayManager._keyedArrays.get(identifier.lid);
190
- if (!managed) {
191
- managed = recordArrayManager.createArray({
192
- type: identifier.lid,
193
- identifiers: document.data,
194
- doc: document
195
- });
196
- recordArrayManager._keyedArrays.set(identifier.lid, managed);
197
- const doc = new Document(store, identifier);
198
- doc.data = managed;
199
- doc.meta = document.meta;
200
- doc.links = document.links;
201
- store._documentCache.set(identifier, doc);
202
- return options.shouldHydrate ? doc : document;
203
- } else {
204
- const doc = store._documentCache.get(identifier);
205
- if (!isFromCache) {
206
- recordArrayManager.populateManagedArray(managed, document.data, document);
207
- doc.data = managed;
208
- doc.meta = document.meta;
209
- doc.links = document.links;
210
- }
211
- return options.shouldHydrate ? doc : document;
212
- }
213
- } else {
214
- if (!identifier && !options.shouldHydrate) {
215
- return document;
216
- }
217
- const data = document.data ? store.peekRecord(document.data) : null;
218
- let doc;
219
- if (identifier) {
220
- doc = store._documentCache.get(identifier);
221
- }
222
- if (!doc) {
223
- doc = new Document(store, identifier);
224
- doc.data = data;
225
- copyDocumentProperties(doc, document);
226
- if (identifier) {
227
- store._documentCache.set(identifier, doc);
228
- }
229
- } else if (!isFromCache) {
230
- doc.data = data;
231
- copyDocumentProperties(doc, document);
232
- }
233
- return options.shouldHydrate ? doc : document;
234
- }
235
- }
236
- function calcShouldFetch(store, request, hasCachedValue, identifier) {
237
- const {
238
- cacheOptions
239
- } = request;
240
- return cacheOptions?.reload || !hasCachedValue || (store.lifetimes && identifier ? store.lifetimes.isHardExpired(identifier) : false);
241
- }
242
- function calcShouldBackgroundFetch(store, request, willFetch, identifier) {
243
- const {
244
- cacheOptions
245
- } = request;
246
- return !willFetch && (cacheOptions?.backgroundReload || (store.lifetimes && identifier ? store.lifetimes.isSoftExpired(identifier) : false));
247
- }
248
- function fetchContentAndHydrate(next, context, identifier, shouldFetch, shouldBackgroundFetch) {
249
- const {
250
- store
251
- } = context.request;
252
- const shouldHydrate = context.request[Symbol.for('ember-data:enable-hydration')] || false;
253
- return next(context.request).then(document => {
254
- store.requestManager._pending.delete(context.id);
255
- store._enableAsyncFlush = true;
256
- let response;
257
- store._join(() => {
258
- response = store.cache.put(document);
259
- response = maybeUpdateUiObjects(store, context.request, {
260
- shouldHydrate,
261
- shouldFetch,
262
- shouldBackgroundFetch,
263
- identifier
264
- }, response, false);
265
- });
266
- store._enableAsyncFlush = null;
267
- if (shouldFetch) {
268
- return response;
269
- } else if (shouldBackgroundFetch) {
270
- store.notifications._flush();
271
- }
272
- }, error => {
273
- store.requestManager._pending.delete(context.id);
274
- if (context.request.signal?.aborted) {
275
- throw error;
276
- }
277
- store.requestManager._pending.delete(context.id);
278
- store._enableAsyncFlush = true;
279
- let response;
280
- store._join(() => {
281
- response = store.cache.put(error);
282
- response = maybeUpdateUiObjects(store, context.request, {
283
- shouldHydrate,
284
- shouldFetch,
285
- shouldBackgroundFetch,
286
- identifier
287
- }, response, false);
288
- });
289
- store._enableAsyncFlush = null;
290
- if (!shouldBackgroundFetch) {
291
- const newError = cloneError(error);
292
- newError.content = response;
293
- throw newError;
294
- } else {
295
- store.notifications._flush();
296
- }
297
- });
298
- }
299
- function cloneError(error) {
300
- const cloned = new Error(error.message);
301
- cloned.stack = error.stack;
302
- cloned.error = error.error;
303
- return cloned;
304
- }
305
- const SkipCache = Symbol.for('ember-data:skip-cache');
306
- const EnableHydration = Symbol.for('ember-data:enable-hydration');
307
- const CacheHandler = {
308
- request(context, next) {
309
- // if we have no cache or no cache-key skip cache handling
310
- if (!context.request.store || context.request.cacheOptions?.[SkipCache]) {
311
- return next(context.request);
312
- }
313
- const {
314
- store
315
- } = context.request;
316
- const identifier = store.identifierCache.getOrCreateDocumentIdentifier(context.request);
317
- const peeked = identifier ? store.cache.peekRequest(identifier) : null;
318
-
319
- // determine if we should skip cache
320
- if (calcShouldFetch(store, context.request, !!peeked, identifier)) {
321
- return fetchContentAndHydrate(next, context, identifier, true, false);
322
- }
7
+ import { defineSignal, addToTransaction, createSignal, subscribe, createArrayTags, addTransactionCB } from '@ember-data/tracking/-private';
8
+ import { _backburner } from '@ember/runloop';
9
+ import { compat } from '@ember-data/tracking';
323
10
 
324
- // if we have not skipped cache, determine if we should update behind the scenes
325
- if (calcShouldBackgroundFetch(store, context.request, false, identifier)) {
326
- let promise = fetchContentAndHydrate(next, context, identifier, false, true);
327
- store.requestManager._pending.set(context.id, promise);
328
- }
329
- const shouldHydrate = context.request[EnableHydration] || false;
330
- if ('error' in peeked) {
331
- const content = shouldHydrate ? maybeUpdateUiObjects(store, context.request, {
332
- shouldHydrate,
333
- identifier
334
- }, peeked.content, true) : peeked.content;
335
- const newError = cloneError(peeked);
336
- newError.content = content;
337
- throw newError;
338
- }
339
- return Promise.resolve(shouldHydrate ? maybeUpdateUiObjects(store, context.request, {
340
- shouldHydrate,
341
- identifier
342
- }, peeked.content, true) : peeked.content);
343
- }
344
- };
345
- function copyDocumentProperties(target, source) {
346
- if ('links' in source) {
347
- target.links = source.links;
348
- }
349
- if ('meta' in source) {
350
- target.meta = source.meta;
351
- }
352
- if ('errors' in source) {
353
- target.errors = source.errors;
354
- }
355
- }
11
+ /**
12
+ @module @ember-data/store
13
+ */
356
14
 
357
- // Used by the store to normalize IDs entering the store. Despite the fact
358
- // that developers may provide IDs as numbers (e.g., `store.findRecord('person', 1)`),
359
- // it is important that internally we use strings, since IDs may be serialized
360
- // and lose type information. For example, Ember's router may put a record's
361
- // ID into the URL, and if we later try to deserialize that URL and find the
362
- // corresponding record, we will not know if it is a string or a number.
363
15
  function coerceId(id) {
364
16
  if (macroCondition(getOwnConfig().deprecations.DEPRECATE_NON_STRICT_ID)) {
365
17
  let normalized;
@@ -392,10 +44,6 @@ function ensureStringId(id) {
392
44
  assert(`Expected id to be a string or number, received ${String(id)}`, normalized !== null);
393
45
  return normalized;
394
46
  }
395
-
396
- // provided for additional debuggability
397
- const DEBUG_CLIENT_ORIGINATED = Symbol('record-originated-on-client');
398
- const DEBUG_IDENTIFIER_BUCKET = Symbol('identifier-bucket');
399
47
  function normalizeModelName(type) {
400
48
  if (macroCondition(getOwnConfig().deprecations.DEPRECATE_NON_STRICT_TYPES)) {
401
49
  const result = dasherize(type);
@@ -424,7 +72,7 @@ function installPolyfill() {
424
72
  // we might be able to optimize this by requesting more bytes than we need at a time
425
73
  const rng = function () {
426
74
  // WHATWG crypto RNG - http://wiki.whatwg.org/wiki/Crypto
427
- let rnds8 = new Uint8Array(16);
75
+ const rnds8 = new Uint8Array(16);
428
76
  if (!CRYPTO.getRandomValues && !isFastBoot) {
429
77
  throw new Error(`Unable to generate bytes for UUID`);
430
78
  }
@@ -440,12 +88,12 @@ function installPolyfill() {
440
88
  byteToHex[i] = (i + 0x100).toString(16).substr(1);
441
89
  }
442
90
  const bytesToUuid = function (buf) {
443
- let bth = byteToHex;
91
+ const bth = byteToHex;
444
92
  // join used to fix memory issue caused by concatenation: https://bugs.chromium.org/p/v8/issues/detail?id=3175#c4
445
93
  return [bth[buf[0]], bth[buf[1]], bth[buf[2]], bth[buf[3]], '-', bth[buf[4]], bth[buf[5]], '-', bth[buf[6]], bth[buf[7]], '-', bth[buf[8]], bth[buf[9]], '-', bth[buf[10]], bth[buf[11]], bth[buf[12]], bth[buf[13]], bth[buf[14]], bth[buf[15]]].join('');
446
94
  };
447
95
  CRYPTO.randomUUID = function uuidv4() {
448
- let rnds = rng();
96
+ const rnds = rng();
449
97
 
450
98
  // Per 4.4, set bits for version and `clock_seq_hi_and_reserved`
451
99
  rnds[6] = rnds[6] & 0x0f | 0x40;
@@ -476,7 +124,7 @@ function hasType(resource) {
476
124
  const IDENTIFIERS = new Set();
477
125
  const DOCUMENTS = new Set();
478
126
  function isStableIdentifier(identifier) {
479
- return IDENTIFIERS.has(identifier);
127
+ return identifier[CACHE_OWNER] !== undefined || IDENTIFIERS.has(identifier);
480
128
  }
481
129
  function isDocumentIdentifier(identifier) {
482
130
  return DOCUMENTS.has(identifier);
@@ -499,6 +147,7 @@ function freeze(obj) {
499
147
 
500
148
  // type IdentifierTypeLookup = { all: Set<StableRecordIdentifier>; id: Map<string, StableRecordIdentifier> };
501
149
  // type IdentifiersByType = Map<string, IdentifierTypeLookup>;
150
+
502
151
  let configuredForgetMethod;
503
152
  let configuredGenerationMethod;
504
153
  let configuredResetMethod;
@@ -519,6 +168,7 @@ function setIdentifierResetMethod(method) {
519
168
  // Map<type, Map<id, lid>>
520
169
 
521
170
  const NEW_IDENTIFIERS = new Map();
171
+ let IDENTIFIER_CACHE_ID = 0;
522
172
  function updateTypeIdMapping(typeMap, identifier, id) {
523
173
  let idMap = typeMap.get(identifier.type);
524
174
  if (!idMap) {
@@ -601,10 +251,12 @@ class IdentifierCache {
601
251
  this._merge = defaultMergeMethod;
602
252
  this._keyInfoForResource = defaultKeyInfoMethod;
603
253
  this._isDefaultConfig = !configuredGenerationMethod;
254
+ this._id = IDENTIFIER_CACHE_ID++;
604
255
  this._cache = {
605
256
  resources: new Map(),
606
257
  resourcesByType: Object.create(null),
607
- documents: new Map()
258
+ documents: new Map(),
259
+ polymorphicLidBackMap: new Map()
608
260
  };
609
261
  }
610
262
 
@@ -658,7 +310,7 @@ class IdentifierCache {
658
310
  console.log(`Identifiers: ${lid ? 'no ' : ''}lid ${lid ? lid + ' ' : ''}determined for resource`, resource);
659
311
  }
660
312
  let identifier = /*#__NOINLINE__*/getIdentifierFromLid(this._cache, lid, resource);
661
- if (identifier !== undefined) {
313
+ if (identifier !== null) {
662
314
  if (macroCondition(getOwnConfig().debug.LOG_IDENTIFIERS)) {
663
315
  // eslint-disable-next-line no-console
664
316
  console.groupEnd();
@@ -676,11 +328,13 @@ class IdentifierCache {
676
328
  // if we still don't have an identifier, time to generate one
677
329
  if (shouldGenerate === 2) {
678
330
  resource.lid = lid;
331
+ resource[CACHE_OWNER] = this._id;
679
332
  identifier = /*#__NOINLINE__*/makeStableRecordIdentifier(resource, 'record', false);
680
333
  } else {
681
334
  // we lie a bit here as a memory optimization
682
335
  const keyInfo = this._keyInfoForResource(resource, null);
683
336
  keyInfo.lid = lid;
337
+ keyInfo[CACHE_OWNER] = this._id;
684
338
  identifier = /*#__NOINLINE__*/makeStableRecordIdentifier(keyInfo, 'record', false);
685
339
  }
686
340
  addResourceToCache(this._cache, identifier);
@@ -698,7 +352,7 @@ class IdentifierCache {
698
352
  *
699
353
  * @method peekRecordIdentifier
700
354
  * @param resource
701
- * @returns {StableRecordIdentifier | undefined}
355
+ * @return {StableRecordIdentifier | undefined}
702
356
  * @private
703
357
  */
704
358
  peekRecordIdentifier(resource) {
@@ -710,7 +364,7 @@ class IdentifierCache {
710
364
  Returns `null` if the request does not have a `cacheKey` or `url`.
711
365
  @method getOrCreateDocumentIdentifier
712
366
  @param request
713
- @returns {StableDocumentIdentifier | null}
367
+ @return {StableDocumentIdentifier | null}
714
368
  @public
715
369
  */
716
370
  getOrCreateDocumentIdentifier(request) {
@@ -744,7 +398,7 @@ class IdentifierCache {
744
398
  - this referential stability of the object itself is guaranteed
745
399
  @method getOrCreateRecordIdentifier
746
400
  @param resource
747
- @returns {StableRecordIdentifier}
401
+ @return {StableRecordIdentifier}
748
402
  @public
749
403
  */
750
404
  getOrCreateRecordIdentifier(resource) {
@@ -759,15 +413,16 @@ class IdentifierCache {
759
413
  with the signature `generateMethod({ type }, 'record')`.
760
414
  @method createIdentifierForNewRecord
761
415
  @param data
762
- @returns {StableRecordIdentifier}
416
+ @return {StableRecordIdentifier}
763
417
  @public
764
418
  */
765
419
  createIdentifierForNewRecord(data) {
766
- let newLid = this._generate(data, 'record');
767
- let identifier = /*#__NOINLINE__*/makeStableRecordIdentifier({
420
+ const newLid = this._generate(data, 'record');
421
+ const identifier = /*#__NOINLINE__*/makeStableRecordIdentifier({
768
422
  id: data.id || null,
769
423
  type: data.type,
770
- lid: newLid
424
+ lid: newLid,
425
+ [CACHE_OWNER]: this._id
771
426
  }, 'record', true);
772
427
 
773
428
  // populate our unique table
@@ -801,7 +456,7 @@ class IdentifierCache {
801
456
  @method updateRecordIdentifier
802
457
  @param identifierObject
803
458
  @param data
804
- @returns {StableRecordIdentifier}
459
+ @return {StableRecordIdentifier}
805
460
  @public
806
461
  */
807
462
  updateRecordIdentifier(identifierObject, data) {
@@ -821,7 +476,7 @@ class IdentifierCache {
821
476
  }
822
477
  }
823
478
  if (existingIdentifier) {
824
- let generatedIdentifier = identifier;
479
+ const generatedIdentifier = identifier;
825
480
  identifier = this._mergeRecordIdentifiers(keyInfo, generatedIdentifier, existingIdentifier, data);
826
481
 
827
482
  // make sure that the `lid` on the data we are processing matches the lid we kept
@@ -833,7 +488,7 @@ class IdentifierCache {
833
488
  console.log(`Identifiers: merged identifiers ${generatedIdentifier.lid} and ${existingIdentifier.lid} for resource into ${identifier.lid}`, data);
834
489
  }
835
490
  }
836
- let id = identifier.id;
491
+ const id = identifier.id;
837
492
  /*#__NOINLINE__*/
838
493
  performRecordIdentifierUpdate(identifier, keyInfo, data, this._update);
839
494
  const newId = identifier.id;
@@ -867,16 +522,32 @@ class IdentifierCache {
867
522
  const kept = this._merge(identifier, existingIdentifier, data);
868
523
  const abandoned = kept === identifier ? existingIdentifier : identifier;
869
524
 
525
+ // get any backreferences before forgetting this identifier, as it will be removed from the cache
526
+ // and we will no longer be able to find them
527
+ const abandonedBackReferences = this._cache.polymorphicLidBackMap.get(abandoned.lid);
528
+ // delete the backreferences for the abandoned identifier so that forgetRecordIdentifier
529
+ // does not try to remove them.
530
+ if (abandonedBackReferences) this._cache.polymorphicLidBackMap.delete(abandoned.lid);
531
+
870
532
  // cleanup the identifier we no longer need
871
533
  this.forgetRecordIdentifier(abandoned);
872
534
 
873
- // ensure a secondary cache entry for this id for the identifier we do keep
874
- // keyOptions.id.set(newId, kept);
535
+ // ensure a secondary cache entry for the original lid for the abandoned identifier
536
+ this._cache.resources.set(abandoned.lid, kept);
875
537
 
876
- // ensure a secondary cache entry for this id for the abandoned identifier's type we do keep
877
- // let baseKeyOptions = getTypeIndex(this._cache.resourcesByType, existingIdentifier.type);
878
- // baseKeyOptions.id.set(newId, kept);
538
+ // backReferences let us know which other identifiers are pointing at this identifier
539
+ // so we can delete them later if we forget this identifier
540
+ const keptBackReferences = this._cache.polymorphicLidBackMap.get(kept.lid) ?? [];
541
+ keptBackReferences.push(abandoned.lid);
879
542
 
543
+ // update the backreferences from the abandoned identifier to be for the kept identifier
544
+ if (abandonedBackReferences) {
545
+ abandonedBackReferences.forEach(lid => {
546
+ keptBackReferences.push(lid);
547
+ this._cache.resources.set(lid, kept);
548
+ });
549
+ }
550
+ this._cache.polymorphicLidBackMap.set(kept.lid, keptBackReferences);
880
551
  return kept;
881
552
  }
882
553
 
@@ -899,6 +570,17 @@ class IdentifierCache {
899
570
  }
900
571
  this._cache.resources.delete(identifier.lid);
901
572
  typeSet.lid.delete(identifier.lid);
573
+ const backReferences = this._cache.polymorphicLidBackMap.get(identifier.lid);
574
+ if (backReferences) {
575
+ backReferences.forEach(lid => {
576
+ this._cache.resources.delete(lid);
577
+ });
578
+ this._cache.polymorphicLidBackMap.delete(identifier.lid);
579
+ }
580
+ if (macroCondition(getOwnConfig().env.DEBUG)) {
581
+ identifier[DEBUG_STALE_CACHE_OWNER] = identifier[CACHE_OWNER];
582
+ }
583
+ identifier[CACHE_OWNER] = undefined;
902
584
  IDENTIFIERS.delete(identifier);
903
585
  this._forget(identifier, 'record');
904
586
  if (macroCondition(getOwnConfig().debug.LOG_IDENTIFIERS)) {
@@ -929,15 +611,33 @@ function makeStableRecordIdentifier(recordIdentifier, bucket, clientOriginated)
929
611
  get type() {
930
612
  return recordIdentifier.type;
931
613
  },
932
- toString() {
614
+ get [CACHE_OWNER]() {
615
+ return recordIdentifier[CACHE_OWNER];
616
+ },
617
+ set [CACHE_OWNER](value) {
618
+ recordIdentifier[CACHE_OWNER] = value;
619
+ },
620
+ get [DEBUG_STALE_CACHE_OWNER]() {
621
+ return recordIdentifier[DEBUG_STALE_CACHE_OWNER];
622
+ },
623
+ set [DEBUG_STALE_CACHE_OWNER](value) {
624
+ recordIdentifier[DEBUG_STALE_CACHE_OWNER] = value;
625
+ }
626
+ };
627
+ Object.defineProperty(wrapper, 'toString', {
628
+ enumerable: false,
629
+ value: () => {
933
630
  const {
934
631
  type,
935
632
  id,
936
633
  lid
937
634
  } = recordIdentifier;
938
635
  return `${clientOriginated ? '[CLIENT_ORIGINATED] ' : ''}${String(type)}:${String(id)} (${lid})`;
939
- },
940
- toJSON() {
636
+ }
637
+ });
638
+ Object.defineProperty(wrapper, 'toJSON', {
639
+ enumerable: false,
640
+ value: () => {
941
641
  const {
942
642
  type,
943
643
  id,
@@ -949,7 +649,7 @@ function makeStableRecordIdentifier(recordIdentifier, bucket, clientOriginated)
949
649
  lid
950
650
  };
951
651
  }
952
- };
652
+ });
953
653
  wrapper[DEBUG_CLIENT_ORIGINATED] = clientOriginated;
954
654
  wrapper[DEBUG_IDENTIFIER_BUCKET] = bucket;
955
655
  IDENTIFIERS.add(wrapper);
@@ -967,7 +667,7 @@ function performRecordIdentifierUpdate(identifier, keyInfo, data, updateFn) {
967
667
  } = keyInfo;
968
668
 
969
669
  // get the mutable instance behind our proxy wrapper
970
- let wrapper = identifier;
670
+ const wrapper = identifier;
971
671
  identifier = DEBUG_MAP.get(wrapper);
972
672
  if (hasLid(data)) {
973
673
  const lid = data.lid;
@@ -1022,7 +722,7 @@ function detectMerge(cache, keyInfo, identifier, data) {
1022
722
  // we trigger a merge of the identifiers
1023
723
  // though probably we should just throw an error here
1024
724
  if (id !== null && id === newId && newType === type && hasLid(data) && data.lid !== lid) {
1025
- return cache.resources.get(data.lid) || false;
725
+ return getIdentifierFromLid(cache, data.lid, data) || false;
1026
726
 
1027
727
  // If the lids are the same, and ids are the same, but types are different we should trigger a merge of the identifiers
1028
728
  } else if (id !== null && id === newId && newType && newType !== type && hasLid(data) && data.lid === lid) {
@@ -1039,7 +739,7 @@ function getIdentifierFromLid(cache, lid, resource) {
1039
739
  // eslint-disable-next-line no-console
1040
740
  console.log(`Identifiers: cache ${identifier ? 'HIT' : 'MISS'} - Non-Stable ${lid}`, resource);
1041
741
  }
1042
- return identifier;
742
+ return identifier || null;
1043
743
  }
1044
744
  function addResourceToCache(cache, identifier) {
1045
745
  cache.resources.set(identifier.lid, identifier);
@@ -1056,28 +756,27 @@ function addResourceToCache(cache, identifier) {
1056
756
  typeSet.id.set(identifier.id, identifier);
1057
757
  }
1058
758
  }
1059
- var _class$1, _descriptor$1;
1060
759
 
1061
760
  /**
1062
761
  @module @ember-data/store
1063
762
  */
763
+
1064
764
  /**
1065
765
  @module @ember-data/store
1066
766
  */
767
+
1067
768
  /**
1068
769
  A `RecordReference` is a low-level API that allows users and
1069
770
  addon authors to perform meta-operations on a record.
1070
771
 
1071
772
  @class RecordReference
1072
773
  @public
1073
- @extends Reference
1074
774
  */
1075
- let RecordReference = (_class$1 = class RecordReference {
775
+ class RecordReference {
1076
776
  constructor(store, identifier) {
1077
777
  // unsubscribe token given to us by the notification manager
1078
778
  this.___token = void 0;
1079
779
  this.___identifier = void 0;
1080
- _initializerDefineProperty(this, "_ref", _descriptor$1, this);
1081
780
  this.store = store;
1082
781
  this.___identifier = identifier;
1083
782
  this.___token = store.notifications.subscribe(identifier, (_, bucket, notifiedKey) => {
@@ -1245,14 +944,8 @@ let RecordReference = (_class$1 = class RecordReference {
1245
944
  }
1246
945
  assert(`Unable to fetch record of type ${this.type} without an id`);
1247
946
  }
1248
- }, _descriptor$1 = _applyDecoratedDescriptor(_class$1.prototype, "_ref", [tracked], {
1249
- configurable: true,
1250
- enumerable: true,
1251
- writable: true,
1252
- initializer: function () {
1253
- return 0;
1254
- }
1255
- }), _class$1);
947
+ }
948
+ defineSignal(RecordReference.prototype, '_ref');
1256
949
 
1257
950
  /**
1258
951
  @module @ember-data/store
@@ -1283,6 +976,8 @@ class CacheCapabilitiesManager {
1283
976
  if (this._store._cbs) {
1284
977
  this._store._schedule('notify', () => this._flushNotifications());
1285
978
  } else {
979
+ // TODO @runspired determine if relationship mutations should schedule
980
+ // into join/run vs immediate flush
1286
981
  this._flushNotifications();
1287
982
  }
1288
983
  }
@@ -1290,7 +985,7 @@ class CacheCapabilitiesManager {
1290
985
  if (this._willNotify === false) {
1291
986
  return;
1292
987
  }
1293
- let pending = this._pendingNotifies;
988
+ const pending = this._pendingNotifies;
1294
989
  this._pendingNotifies = new Map();
1295
990
  this._willNotify = false;
1296
991
  pending.forEach((set, identifier) => {
@@ -1314,6 +1009,9 @@ class CacheCapabilitiesManager {
1314
1009
  getSchemaDefinitionService() {
1315
1010
  return this._store.getSchemaDefinitionService();
1316
1011
  }
1012
+ get schema() {
1013
+ return this._store.schema;
1014
+ }
1317
1015
  setRecordId(identifier, id) {
1318
1016
  assert(`Expected a stable identifier`, isStableIdentifier(identifier));
1319
1017
  this._store._instanceCache.setRecordId(identifier, id);
@@ -1347,6 +1045,9 @@ function peekCache(instance) {
1347
1045
  }
1348
1046
  return null;
1349
1047
  }
1048
+ function isDestroyable(record) {
1049
+ return Boolean(record && typeof record === 'object' && typeof record.destroy === 'function');
1050
+ }
1350
1051
 
1351
1052
  /**
1352
1053
  @module @ember-data/store
@@ -1374,7 +1075,7 @@ function peekRecordIdentifier(record) {
1374
1075
  @static
1375
1076
  @for @ember-data/store
1376
1077
  @param {Object} record a record instance previously obstained from the store.
1377
- @returns {StableRecordIdentifier}
1078
+ @return {StableRecordIdentifier}
1378
1079
  */
1379
1080
  function recordIdentifierFor(record) {
1380
1081
  assert(`${String(record)} is not a record instantiated by @ember-data/store`, RecordCache.has(record));
@@ -1421,11 +1122,11 @@ class InstanceCache {
1421
1122
  // @ts-expect-error TODO this needs to be fixed
1422
1123
  'type' in resourceData && identifier.type === resourceData.type ? identifier : matchedIdentifier;
1423
1124
  }
1424
- let staleIdentifier = identifier === keptIdentifier ? matchedIdentifier : identifier;
1125
+ const staleIdentifier = identifier === keptIdentifier ? matchedIdentifier : identifier;
1425
1126
 
1426
1127
  // check for duplicate entities
1427
- let keptHasRecord = this.__instances.record.has(keptIdentifier);
1428
- let staleHasRecord = this.__instances.record.has(staleIdentifier);
1128
+ const keptHasRecord = this.__instances.record.has(keptIdentifier);
1129
+ const staleHasRecord = this.__instances.record.has(staleIdentifier);
1429
1130
 
1430
1131
  // we cannot merge entities when both have records
1431
1132
  // (this may not be strictly true, we could probably swap the cache data the record points at)
@@ -1479,7 +1180,7 @@ class InstanceCache {
1479
1180
  return record;
1480
1181
  }
1481
1182
  getReference(identifier) {
1482
- let cache = this.__instances.reference;
1183
+ const cache = this.__instances.reference;
1483
1184
  let reference = cache.get(identifier);
1484
1185
  if (!reference) {
1485
1186
  reference = new RecordReference(this.store, identifier);
@@ -1510,7 +1211,7 @@ class InstanceCache {
1510
1211
  }
1511
1212
  disconnect(identifier) {
1512
1213
  const record = this.__instances.record.get(identifier);
1513
- assert('Cannot destroy record while it is still materialized', !record || record.isDestroyed || record.isDestroying);
1214
+ assert('Cannot destroy record while it is still materialized', !isDestroyable(record) || record.isDestroyed || record.isDestroying);
1514
1215
  this.store._graph?.remove(identifier);
1515
1216
  this.store.identifierCache.forgetRecordIdentifier(identifier);
1516
1217
  removeRecordDataFor(identifier);
@@ -1578,7 +1279,7 @@ class InstanceCache {
1578
1279
  });
1579
1280
  } else {
1580
1281
  const typeCache = cache.resourcesByType;
1581
- let identifiers = typeCache[type]?.lid;
1282
+ const identifiers = typeCache[type]?.lid;
1582
1283
  if (identifiers) {
1583
1284
  identifiers.forEach(identifier => {
1584
1285
  // if (rds.has(identifier)) {
@@ -1596,7 +1297,7 @@ class InstanceCache {
1596
1297
  type,
1597
1298
  lid
1598
1299
  } = identifier;
1599
- let oldId = identifier.id;
1300
+ const oldId = identifier.id;
1600
1301
 
1601
1302
  // ID absolutely can't be missing if the oldID is empty (missing Id in response for a new record)
1602
1303
  assert(`'${type}' was saved to the server, but the response does not have an id and your record does not either.`, !(id === null && oldId === null));
@@ -1615,7 +1316,7 @@ class InstanceCache {
1615
1316
  // eslint-disable-next-line no-console
1616
1317
  console.log(`InstanceCache: updating id to '${id}' for record ${String(identifier)}`);
1617
1318
  }
1618
- let existingIdentifier = this.store.identifierCache.peekRecordIdentifier({
1319
+ const existingIdentifier = this.store.identifierCache.peekRecordIdentifier({
1619
1320
  type,
1620
1321
  id
1621
1322
  });
@@ -1654,13 +1355,13 @@ function resourceIsFullyDeleted(instanceCache, identifier) {
1654
1355
  */
1655
1356
 
1656
1357
  function preloadData(store, identifier, preload) {
1657
- let jsonPayload = {};
1358
+ const jsonPayload = {};
1658
1359
  //TODO(Igor) consider the polymorphic case
1659
1360
  const schemas = store.getSchemaDefinitionService();
1660
1361
  const relationships = schemas.relationshipsDefinitionFor(identifier);
1661
1362
  Object.keys(preload).forEach(key => {
1662
- let preloadValue = preload[key];
1663
- let relationshipMeta = relationships[key];
1363
+ const preloadValue = preload[key];
1364
+ const relationshipMeta = relationships[key];
1664
1365
  if (relationshipMeta) {
1665
1366
  if (!jsonPayload.relationships) {
1666
1367
  jsonPayload.relationships = {};
@@ -1729,7 +1430,7 @@ function getShimClass(store, modelName) {
1729
1430
  }
1730
1431
  function mapFromHash(hash) {
1731
1432
  const map = new Map();
1732
- for (let i in hash) {
1433
+ for (const i in hash) {
1733
1434
  if (Object.prototype.hasOwnProperty.call(hash, i)) {
1734
1435
  map.set(i, hash[i]);
1735
1436
  }
@@ -1744,31 +1445,31 @@ class ShimModelClass {
1744
1445
  this.modelName = modelName;
1745
1446
  }
1746
1447
  get fields() {
1747
- let attrs = this.__store.getSchemaDefinitionService().attributesDefinitionFor({
1448
+ const attrs = this.__store.getSchemaDefinitionService().attributesDefinitionFor({
1748
1449
  type: this.modelName
1749
1450
  });
1750
- let relationships = this.__store.getSchemaDefinitionService().relationshipsDefinitionFor({
1451
+ const relationships = this.__store.getSchemaDefinitionService().relationshipsDefinitionFor({
1751
1452
  type: this.modelName
1752
1453
  });
1753
- let fields = new Map();
1454
+ const fields = new Map();
1754
1455
  Object.keys(attrs).forEach(key => fields.set(key, 'attribute'));
1755
1456
  Object.keys(relationships).forEach(key => fields.set(key, relationships[key].kind));
1756
1457
  return fields;
1757
1458
  }
1758
1459
  get attributes() {
1759
- let attrs = this.__store.getSchemaDefinitionService().attributesDefinitionFor({
1460
+ const attrs = this.__store.getSchemaDefinitionService().attributesDefinitionFor({
1760
1461
  type: this.modelName
1761
1462
  });
1762
1463
  return mapFromHash(attrs);
1763
1464
  }
1764
1465
  get relationshipsByName() {
1765
- let relationships = this.__store.getSchemaDefinitionService().relationshipsDefinitionFor({
1466
+ const relationships = this.__store.getSchemaDefinitionService().relationshipsDefinitionFor({
1766
1467
  type: this.modelName
1767
1468
  });
1768
1469
  return mapFromHash(relationships);
1769
1470
  }
1770
1471
  eachAttribute(callback, binding) {
1771
- let attrDefs = this.__store.getSchemaDefinitionService().attributesDefinitionFor({
1472
+ const attrDefs = this.__store.getSchemaDefinitionService().attributesDefinitionFor({
1772
1473
  type: this.modelName
1773
1474
  });
1774
1475
  Object.keys(attrDefs).forEach(key => {
@@ -1776,7 +1477,7 @@ class ShimModelClass {
1776
1477
  });
1777
1478
  }
1778
1479
  eachRelationship(callback, binding) {
1779
- let relationshipDefs = this.__store.getSchemaDefinitionService().relationshipsDefinitionFor({
1480
+ const relationshipDefs = this.__store.getSchemaDefinitionService().relationshipsDefinitionFor({
1780
1481
  type: this.modelName
1781
1482
  });
1782
1483
  Object.keys(relationshipDefs).forEach(key => {
@@ -1794,6 +1495,16 @@ class ShimModelClass {
1794
1495
  });
1795
1496
  }
1796
1497
  }
1498
+ function _classPrivateFieldBase(receiver, privateKey) {
1499
+ if (!Object.prototype.hasOwnProperty.call(receiver, privateKey)) {
1500
+ throw new TypeError("attempted to use private field on non-instance");
1501
+ }
1502
+ return receiver;
1503
+ }
1504
+ var id = 0;
1505
+ function _classPrivateFieldKey(name) {
1506
+ return "__private_" + id++ + "_" + name;
1507
+ }
1797
1508
  var _cache = /*#__PURE__*/_classPrivateFieldKey("cache");
1798
1509
  /**
1799
1510
  * The CacheManager wraps a Cache enforcing that only
@@ -1820,16 +1531,6 @@ class CacheManager {
1820
1531
  writable: true,
1821
1532
  value: void 0
1822
1533
  });
1823
- /**
1824
- * Query the cache for whether a given resource has been deleted and that deletion
1825
- * has also been persisted.
1826
- *
1827
- * @method isDeletionCommitted
1828
- * @public
1829
- * @param identifier
1830
- * @returns {boolean}
1831
- */
1832
- this.isDel = void 0;
1833
1534
  _classPrivateFieldBase(this, _cache)[_cache] = cache;
1834
1535
  }
1835
1536
 
@@ -1843,7 +1544,7 @@ class CacheManager {
1843
1544
  * semantics, `put` has `replace` semantics similar to
1844
1545
  * the `http` method `PUT`
1845
1546
  *
1846
- * the individually cacheabl
1547
+ * the individually cacheable
1847
1548
  * e resource data it may contain
1848
1549
  * should upsert, but the document data surrounding it should
1849
1550
  * fully replace any existing information
@@ -1856,7 +1557,7 @@ class CacheManager {
1856
1557
  *
1857
1558
  * @method put
1858
1559
  * @param {StructuredDocument} doc
1859
- * @returns {ResourceDocument}
1560
+ * @return {ResourceDocument}
1860
1561
  * @public
1861
1562
  */
1862
1563
  put(doc) {
@@ -1872,7 +1573,7 @@ class CacheManager {
1872
1573
  * @method patch
1873
1574
  * @public
1874
1575
  * @param op the operation to perform
1875
- * @returns {void}
1576
+ * @return {void}
1876
1577
  */
1877
1578
  patch(op) {
1878
1579
  _classPrivateFieldBase(this, _cache)[_cache].patch(op);
@@ -1907,7 +1608,7 @@ class CacheManager {
1907
1608
  * An implementation might want to do this because
1908
1609
  * de-referencing records which read from their own
1909
1610
  * blob is generally safer because the record does
1910
- * not require retainining connections to the Store
1611
+ * not require retaining connections to the Store
1911
1612
  * and Cache to present data on a per-field basis.
1912
1613
  *
1913
1614
  * This generally takes the place of `getAttr` as
@@ -1920,7 +1621,7 @@ class CacheManager {
1920
1621
  * @method peek
1921
1622
  * @public
1922
1623
  * @param {StableRecordIdentifier | StableDocumentIdentifier} identifier
1923
- * @returns {ResourceDocument | ResourceBlob | null} the known resource data
1624
+ * @return {ResourceDocument | ResourceBlob | null} the known resource data
1924
1625
  */
1925
1626
 
1926
1627
  peek(identifier) {
@@ -1933,7 +1634,7 @@ class CacheManager {
1933
1634
  *
1934
1635
  * @method peekRequest
1935
1636
  * @param {StableDocumentIdentifier}
1936
- * @returns {StableDocumentIdentifier | null}
1637
+ * @return {StableDocumentIdentifier | null}
1937
1638
  * @public
1938
1639
  */
1939
1640
  peekRequest(identifier) {
@@ -1948,7 +1649,7 @@ class CacheManager {
1948
1649
  * @param identifier
1949
1650
  * @param data
1950
1651
  * @param hasRecord
1951
- * @returns {void | string[]} if `hasRecord` is true then calculated key changes should be returned
1652
+ * @return {void | string[]} if `hasRecord` is true then calculated key changes should be returned
1952
1653
  */
1953
1654
  upsert(identifier, data, hasRecord) {
1954
1655
  return _classPrivateFieldBase(this, _cache)[_cache].upsert(identifier, data, hasRecord);
@@ -1966,7 +1667,7 @@ class CacheManager {
1966
1667
  *
1967
1668
  * @method fork
1968
1669
  * @public
1969
- * @returns Promise<Cache>
1670
+ * @return Promise<Cache>
1970
1671
  */
1971
1672
  fork() {
1972
1673
  return _classPrivateFieldBase(this, _cache)[_cache].fork();
@@ -1982,7 +1683,7 @@ class CacheManager {
1982
1683
  * @method merge
1983
1684
  * @param {Cache} cache
1984
1685
  * @public
1985
- * @returns Promise<void>
1686
+ * @return Promise<void>
1986
1687
  */
1987
1688
  merge(cache) {
1988
1689
  return _classPrivateFieldBase(this, _cache)[_cache].merge(cache);
@@ -2034,7 +1735,7 @@ class CacheManager {
2034
1735
  * via `cache.hydrate`.
2035
1736
  *
2036
1737
  * @method dump
2037
- * @returns {Promise<ReadableStream>}
1738
+ * @return {Promise<ReadableStream>}
2038
1739
  * @public
2039
1740
  */
2040
1741
  dump() {
@@ -2055,7 +1756,7 @@ class CacheManager {
2055
1756
  *
2056
1757
  * @method hydrate
2057
1758
  * @param {ReadableStream} stream
2058
- * @returns {Promise<void>}
1759
+ * @return {Promise<void>}
2059
1760
  * @public
2060
1761
  */
2061
1762
  hydrate(stream) {
@@ -2069,7 +1770,7 @@ class CacheManager {
2069
1770
  // ================
2070
1771
 
2071
1772
  /**
2072
- * [LIFECYLCE] Signal to the cache that a new record has been instantiated on the client
1773
+ * [LIFECYCLE] Signal to the cache that a new record has been instantiated on the client
2073
1774
  *
2074
1775
  * It returns properties from options that should be set on the record during the create
2075
1776
  * process. This return value behavior is deprecated.
@@ -2143,7 +1844,7 @@ class CacheManager {
2143
1844
  * @public
2144
1845
  * @param identifier
2145
1846
  * @param propertyName
2146
- * @returns {unknown}
1847
+ * @return {unknown}
2147
1848
  */
2148
1849
  getAttr(identifier, propertyName) {
2149
1850
  return _classPrivateFieldBase(this, _cache)[_cache].getAttr(identifier, propertyName);
@@ -2168,7 +1869,7 @@ class CacheManager {
2168
1869
  * @method changedAttrs
2169
1870
  * @public
2170
1871
  * @param identifier
2171
- * @returns
1872
+ * @return
2172
1873
  */
2173
1874
  changedAttrs(identifier) {
2174
1875
  return _classPrivateFieldBase(this, _cache)[_cache].changedAttrs(identifier);
@@ -2180,7 +1881,7 @@ class CacheManager {
2180
1881
  * @method hasChangedAttrs
2181
1882
  * @public
2182
1883
  * @param identifier
2183
- * @returns {boolean}
1884
+ * @return {boolean}
2184
1885
  */
2185
1886
  hasChangedAttrs(identifier) {
2186
1887
  return _classPrivateFieldBase(this, _cache)[_cache].hasChangedAttrs(identifier);
@@ -2192,7 +1893,7 @@ class CacheManager {
2192
1893
  * @method rollbackAttrs
2193
1894
  * @public
2194
1895
  * @param identifier
2195
- * @returns the names of attributes that were restored
1896
+ * @return the names of attributes that were restored
2196
1897
  */
2197
1898
  rollbackAttrs(identifier) {
2198
1899
  return _classPrivateFieldBase(this, _cache)[_cache].rollbackAttrs(identifier);
@@ -2201,6 +1902,65 @@ class CacheManager {
2201
1902
  // Relationships
2202
1903
  // =============
2203
1904
 
1905
+ /**
1906
+ * Query the cache for the changes to relationships of a resource.
1907
+ *
1908
+ * Returns a map of relationship names to RelationshipDiff objects.
1909
+ *
1910
+ * ```ts
1911
+ * type RelationshipDiff =
1912
+ | {
1913
+ kind: 'collection';
1914
+ remoteState: StableRecordIdentifier[];
1915
+ additions: Set<StableRecordIdentifier>;
1916
+ removals: Set<StableRecordIdentifier>;
1917
+ localState: StableRecordIdentifier[];
1918
+ reordered: boolean;
1919
+ }
1920
+ | {
1921
+ kind: 'resource';
1922
+ remoteState: StableRecordIdentifier | null;
1923
+ localState: StableRecordIdentifier | null;
1924
+ };
1925
+ ```
1926
+ *
1927
+ * @method changedRelationships
1928
+ * @public
1929
+ * @param {StableRecordIdentifier} identifier
1930
+ * @return {Map<string, RelationshipDiff>}
1931
+ */
1932
+ changedRelationships(identifier) {
1933
+ return _classPrivateFieldBase(this, _cache)[_cache].changedRelationships(identifier);
1934
+ }
1935
+
1936
+ /**
1937
+ * Query the cache for whether any mutated attributes exist
1938
+ *
1939
+ * @method hasChangedRelationships
1940
+ * @public
1941
+ * @param {StableRecordIdentifier} identifier
1942
+ * @return {boolean}
1943
+ */
1944
+ hasChangedRelationships(identifier) {
1945
+ return _classPrivateFieldBase(this, _cache)[_cache].hasChangedRelationships(identifier);
1946
+ }
1947
+
1948
+ /**
1949
+ * Tell the cache to discard any uncommitted mutations to relationships.
1950
+ *
1951
+ * This will also discard the change on any appropriate inverses.
1952
+ *
1953
+ * This method is a candidate to become a mutation
1954
+ *
1955
+ * @method rollbackRelationships
1956
+ * @public
1957
+ * @param {StableRecordIdentifier} identifier
1958
+ * @return {string[]} the names of relationships that were restored
1959
+ */
1960
+ rollbackRelationships(identifier) {
1961
+ return _classPrivateFieldBase(this, _cache)[_cache].rollbackRelationships(identifier);
1962
+ }
1963
+
2204
1964
  /**
2205
1965
  * Query the cache for the current state of a relationship property
2206
1966
  *
@@ -2208,7 +1968,7 @@ class CacheManager {
2208
1968
  * @public
2209
1969
  * @param identifier
2210
1970
  * @param propertyName
2211
- * @returns resource relationship object
1971
+ * @return resource relationship object
2212
1972
  */
2213
1973
  getRelationship(identifier, propertyName) {
2214
1974
  return _classPrivateFieldBase(this, _cache)[_cache].getRelationship(identifier, propertyName);
@@ -2236,7 +1996,7 @@ class CacheManager {
2236
1996
  * @method getErrors
2237
1997
  * @public
2238
1998
  * @param identifier
2239
- * @returns
1999
+ * @return
2240
2000
  */
2241
2001
  getErrors(identifier) {
2242
2002
  return _classPrivateFieldBase(this, _cache)[_cache].getErrors(identifier);
@@ -2248,7 +2008,7 @@ class CacheManager {
2248
2008
  * @method isEmpty
2249
2009
  * @public
2250
2010
  * @param identifier
2251
- * @returns {boolean}
2011
+ * @return {boolean}
2252
2012
  */
2253
2013
  isEmpty(identifier) {
2254
2014
  return _classPrivateFieldBase(this, _cache)[_cache].isEmpty(identifier);
@@ -2261,7 +2021,7 @@ class CacheManager {
2261
2021
  * @method isNew
2262
2022
  * @public
2263
2023
  * @param identifier
2264
- * @returns {boolean}
2024
+ * @return {boolean}
2265
2025
  */
2266
2026
  isNew(identifier) {
2267
2027
  return _classPrivateFieldBase(this, _cache)[_cache].isNew(identifier);
@@ -2274,15 +2034,29 @@ class CacheManager {
2274
2034
  * @method isDeleted
2275
2035
  * @public
2276
2036
  * @param identifier
2277
- * @returns {boolean}
2037
+ * @return {boolean}
2278
2038
  */
2279
2039
  isDeleted(identifier) {
2280
2040
  return _classPrivateFieldBase(this, _cache)[_cache].isDeleted(identifier);
2281
2041
  }
2042
+
2043
+ /**
2044
+ * Query the cache for whether a given resource has been deleted and that deletion
2045
+ * has also been persisted.
2046
+ *
2047
+ * @method isDeletionCommitted
2048
+ * @public
2049
+ * @param identifier
2050
+ * @return {boolean}
2051
+ */
2282
2052
  isDeletionCommitted(identifier) {
2283
2053
  return _classPrivateFieldBase(this, _cache)[_cache].isDeletionCommitted(identifier);
2284
2054
  }
2285
2055
  }
2056
+
2057
+ /**
2058
+ * @module @ember-data/store
2059
+ */
2286
2060
  let tokenId = 0;
2287
2061
  const CacheOperations = new Set(['added', 'removed', 'state', 'updated']);
2288
2062
  function isCacheOperationValue(value) {
@@ -2293,7 +2067,7 @@ function runLoopIsFlushing() {
2293
2067
  return !!_backburner.currentInstance && _backburner._autorun !== true;
2294
2068
  }
2295
2069
  function _unsubscribe(tokens, token, cache) {
2296
- let identifier = tokens.get(token);
2070
+ const identifier = tokens.get(token);
2297
2071
  if (macroCondition(getOwnConfig().debug.LOG_NOTIFICATIONS)) {
2298
2072
  if (!identifier) {
2299
2073
  // eslint-disable-next-line no-console
@@ -2352,7 +2126,7 @@ class NotificationManager {
2352
2126
  * @public
2353
2127
  * @param {StableDocumentIdentifier | StableRecordIdentifier | 'resource' | 'document'} identifier
2354
2128
  * @param {NotificationCallback | ResourceOperationCallback | DocumentOperationCallback} callback
2355
- * @returns {UnsubscribeToken} an opaque token to be used with unsubscribe
2129
+ * @return {UnsubscribeToken} an opaque token to be used with unsubscribe
2356
2130
  */
2357
2131
 
2358
2132
  subscribe(identifier, callback) {
@@ -2362,7 +2136,7 @@ class NotificationManager {
2362
2136
  map = new Map();
2363
2137
  this._cache.set(identifier, map);
2364
2138
  }
2365
- let unsubToken = macroCondition(getOwnConfig().env.DEBUG) ? {
2139
+ const unsubToken = macroCondition(getOwnConfig().env.DEBUG) ? {
2366
2140
  _tokenRef: tokenId++
2367
2141
  } : {};
2368
2142
  map.set(unsubToken, callback);
@@ -2415,7 +2189,7 @@ class NotificationManager {
2415
2189
  this._buffered.set(identifier, buffer);
2416
2190
  }
2417
2191
  buffer.push([value, key]);
2418
- void this._scheduleNotify();
2192
+ this._scheduleNotify();
2419
2193
  }
2420
2194
  return hasSubscribers;
2421
2195
  }
@@ -2457,14 +2231,14 @@ class NotificationManager {
2457
2231
 
2458
2232
  // TODO for documents this will need to switch based on Identifier kind
2459
2233
  if (isCacheOperationValue(value)) {
2460
- let callbackMap = this._cache.get(isDocumentIdentifier(identifier) ? 'document' : 'resource');
2234
+ const callbackMap = this._cache.get(isDocumentIdentifier(identifier) ? 'document' : 'resource');
2461
2235
  if (callbackMap) {
2462
2236
  callbackMap.forEach(cb => {
2463
2237
  cb(identifier, value);
2464
2238
  });
2465
2239
  }
2466
2240
  }
2467
- let callbackMap = this._cache.get(identifier);
2241
+ const callbackMap = this._cache.get(identifier);
2468
2242
  if (!callbackMap || !callbackMap.size) {
2469
2243
  return false;
2470
2244
  }
@@ -2474,13 +2248,36 @@ class NotificationManager {
2474
2248
  });
2475
2249
  return true;
2476
2250
  }
2477
- destroy() {
2478
- this.isDestroyed = true;
2479
- this._tokens.clear();
2480
- this._cache.clear();
2251
+ destroy() {
2252
+ this.isDestroyed = true;
2253
+ this._tokens.clear();
2254
+ this._cache.clear();
2255
+ }
2256
+ }
2257
+ function _applyDecoratedDescriptor(target, property, decorators, descriptor, context) {
2258
+ var desc = {};
2259
+ Object.keys(descriptor).forEach(function (key) {
2260
+ desc[key] = descriptor[key];
2261
+ });
2262
+ desc.enumerable = !!desc.enumerable;
2263
+ desc.configurable = !!desc.configurable;
2264
+ if ('value' in desc || desc.initializer) {
2265
+ desc.writable = true;
2266
+ }
2267
+ desc = decorators.slice().reverse().reduce(function (desc, decorator) {
2268
+ return decorator(target, property, desc) || desc;
2269
+ }, desc);
2270
+ if (context && desc.initializer !== void 0) {
2271
+ desc.value = desc.initializer ? desc.initializer.call(context) : void 0;
2272
+ desc.initializer = undefined;
2273
+ }
2274
+ if (desc.initializer === void 0) {
2275
+ Object.defineProperty(target, property, desc);
2276
+ desc = null;
2481
2277
  }
2278
+ return desc;
2482
2279
  }
2483
- var _class, _descriptor, _class3, _descriptor2;
2280
+ var _class;
2484
2281
  const ARRAY_GETTER_METHODS = new Set([Symbol.iterator, 'concat', 'entries', 'every', 'fill', 'filter', 'find', 'findIndex', 'flat', 'flatMap', 'forEach', 'includes', 'indexOf', 'join', 'keys', 'lastIndexOf', 'map', 'reduce', 'reduceRight', 'slice', 'some', 'values']);
2485
2282
  const ARRAY_SETTER_METHODS = new Set(['push', 'pop', 'unshift', 'shift', 'splice', 'sort']);
2486
2283
  const SYNC_PROPS = new Set(['[]', 'length', 'links', 'meta']);
@@ -2490,19 +2287,16 @@ function isArrayGetter(prop) {
2490
2287
  function isArraySetter(prop) {
2491
2288
  return ARRAY_SETTER_METHODS.has(prop);
2492
2289
  }
2493
- const IDENTIFIER_ARRAY_TAG = Symbol('#tag');
2290
+ function isSelfProp(self, prop) {
2291
+ return prop in self;
2292
+ }
2293
+ const ARRAY_SIGNAL = Symbol('#signal');
2494
2294
  const SOURCE = Symbol('#source');
2495
2295
  const MUTATE = Symbol('#update');
2496
2296
  const NOTIFY = Symbol('#notify');
2497
2297
  const IS_COLLECTION = Symbol.for('Collection');
2498
2298
  function notifyArray(arr) {
2499
- addToTransaction(arr[IDENTIFIER_ARRAY_TAG]);
2500
- if (macroCondition(getOwnConfig().deprecations.DEPRECATE_COMPUTED_CHAINS)) {
2501
- // eslint-disable-next-line
2502
- dirtyTag(tagForProperty(arr, 'length'));
2503
- // eslint-disable-next-line
2504
- dirtyTag(tagForProperty(arr, '[]'));
2505
- }
2299
+ addToTransaction(arr[ARRAY_SIGNAL]);
2506
2300
  }
2507
2301
  function convertToInt(prop) {
2508
2302
  if (typeof prop === 'symbol') return null;
@@ -2510,29 +2304,6 @@ function convertToInt(prop) {
2510
2304
  if (isNaN(num)) return null;
2511
2305
  return num % 1 === 0 ? num : null;
2512
2306
  }
2513
- let Tag = (_class = class Tag {
2514
- /*
2515
- * whether this was part of a transaction when last mutated
2516
- */
2517
-
2518
- constructor() {
2519
- _initializerDefineProperty(this, "ref", _descriptor, this);
2520
- if (macroCondition(getOwnConfig().env.DEBUG)) {
2521
- const [arr, prop] = arguments;
2522
- this._debug_base = arr.constructor.name + ':' + String(arr.modelName);
2523
- this._debug_prop = prop;
2524
- }
2525
- this.shouldReset = false;
2526
- this.t = false;
2527
- }
2528
- }, _descriptor = _applyDecoratedDescriptor(_class.prototype, "ref", [tracked], {
2529
- configurable: true,
2530
- enumerable: true,
2531
- writable: true,
2532
- initializer: function () {
2533
- return null;
2534
- }
2535
- }), _class);
2536
2307
  function safeForEach(instance, arr, store, callback, target) {
2537
2308
  if (target === undefined) {
2538
2309
  target = null;
@@ -2565,7 +2336,7 @@ function safeForEach(instance, arr, store, callback, target) {
2565
2336
  @class RecordArray
2566
2337
  @public
2567
2338
  */
2568
- let IdentifierArray = (_class3 = class IdentifierArray {
2339
+ let IdentifierArray = (_class = class IdentifierArray {
2569
2340
  [NOTIFY]() {
2570
2341
  notifyArray(this);
2571
2342
  }
@@ -2593,14 +2364,6 @@ let IdentifierArray = (_class3 = class IdentifierArray {
2593
2364
  set length(value) {
2594
2365
  this[SOURCE].length = value;
2595
2366
  }
2596
-
2597
- // here to support computed chains
2598
- // and {{#each}}
2599
- get '[]'() {
2600
- if (macroCondition(getOwnConfig().deprecations.DEPRECATE_COMPUTED_CHAINS)) {
2601
- return this;
2602
- }
2603
- }
2604
2367
  constructor(options) {
2605
2368
  /**
2606
2369
  The flag to signal a `RecordArray` is currently loading data.
@@ -2615,7 +2378,6 @@ let IdentifierArray = (_class3 = class IdentifierArray {
2615
2378
  @public
2616
2379
  @type Boolean
2617
2380
  */
2618
- _initializerDefineProperty(this, "isUpdating", _descriptor2, this);
2619
2381
  this.isLoaded = true;
2620
2382
  this.isDestroying = false;
2621
2383
  this.isDestroyed = false;
@@ -2623,16 +2385,15 @@ let IdentifierArray = (_class3 = class IdentifierArray {
2623
2385
  this[IS_COLLECTION] = true;
2624
2386
  this[SOURCE] = void 0;
2625
2387
  // eslint-disable-next-line @typescript-eslint/no-this-alias
2626
- let self = this;
2388
+ const self = this;
2627
2389
  this.modelName = options.type;
2628
2390
  this.store = options.store;
2629
2391
  this._manager = options.manager;
2630
2392
  this[SOURCE] = options.identifiers;
2631
- // @ts-expect-error
2632
- this[IDENTIFIER_ARRAY_TAG] = macroCondition(getOwnConfig().env.DEBUG) ? new Tag(this, 'length') : new Tag();
2393
+ this[ARRAY_SIGNAL] = createSignal(this, 'length');
2633
2394
  const store = options.store;
2634
2395
  const boundFns = new Map();
2635
- const _TAG = this[IDENTIFIER_ARRAY_TAG];
2396
+ const _SIGNAL = this[ARRAY_SIGNAL];
2636
2397
  const PrivateState = {
2637
2398
  links: options.links || null,
2638
2399
  meta: options.meta || null
@@ -2645,40 +2406,40 @@ let IdentifierArray = (_class3 = class IdentifierArray {
2645
2406
 
2646
2407
  const proxy = new Proxy(this[SOURCE], {
2647
2408
  get(target, prop, receiver) {
2648
- let index = convertToInt(prop);
2649
- if (_TAG.shouldReset && (index !== null || SYNC_PROPS.has(prop) || isArrayGetter(prop))) {
2409
+ const index = convertToInt(prop);
2410
+ if (_SIGNAL.shouldReset && (index !== null || SYNC_PROPS.has(prop) || isArrayGetter(prop))) {
2650
2411
  options.manager._syncArray(receiver);
2651
- _TAG.t = false;
2652
- _TAG.shouldReset = false;
2412
+ _SIGNAL.t = false;
2413
+ _SIGNAL.shouldReset = false;
2653
2414
  }
2654
2415
  if (index !== null) {
2655
2416
  const identifier = target[index];
2656
2417
  if (!transaction) {
2657
- subscribe(_TAG);
2418
+ subscribe(_SIGNAL);
2658
2419
  }
2659
2420
  return identifier && store._instanceCache.getRecord(identifier);
2660
2421
  }
2661
- if (prop === 'meta') return subscribe(_TAG), PrivateState.meta;
2662
- if (prop === 'links') return subscribe(_TAG), PrivateState.links;
2663
- if (prop === '[]') return subscribe(_TAG), receiver;
2422
+ if (prop === 'meta') return subscribe(_SIGNAL), PrivateState.meta;
2423
+ if (prop === 'links') return subscribe(_SIGNAL), PrivateState.links;
2424
+ if (prop === '[]') return subscribe(_SIGNAL), receiver;
2664
2425
  if (isArrayGetter(prop)) {
2665
2426
  let fn = boundFns.get(prop);
2666
2427
  if (fn === undefined) {
2667
2428
  if (prop === 'forEach') {
2668
2429
  fn = function () {
2669
- subscribe(_TAG);
2430
+ subscribe(_SIGNAL);
2670
2431
  transaction = true;
2671
- let result = safeForEach(receiver, target, store, arguments[0], arguments[1]);
2432
+ const result = safeForEach(receiver, target, store, arguments[0], arguments[1]);
2672
2433
  transaction = false;
2673
2434
  return result;
2674
2435
  };
2675
2436
  } else {
2676
2437
  fn = function () {
2677
- subscribe(_TAG);
2438
+ subscribe(_SIGNAL);
2678
2439
  // array functions must run through Reflect to work properly
2679
2440
  // binding via other means will not work.
2680
2441
  transaction = true;
2681
- let result = Reflect.apply(target[prop], receiver, arguments);
2442
+ const result = Reflect.apply(target[prop], receiver, arguments);
2682
2443
  transaction = false;
2683
2444
  return result;
2684
2445
  };
@@ -2700,10 +2461,7 @@ let IdentifierArray = (_class3 = class IdentifierArray {
2700
2461
  const args = Array.prototype.slice.call(arguments);
2701
2462
  assert(`Cannot start a new array transaction while a previous transaction is underway`, !transaction);
2702
2463
  transaction = true;
2703
- let result = Reflect.apply(target[prop], receiver, args);
2704
- self[MUTATE](prop, args, result);
2705
- addToTransaction(_TAG);
2706
- // TODO handle cache updates
2464
+ const result = self[MUTATE](target, receiver, prop, args, _SIGNAL);
2707
2465
  transaction = false;
2708
2466
  return result;
2709
2467
  };
@@ -2711,16 +2469,16 @@ let IdentifierArray = (_class3 = class IdentifierArray {
2711
2469
  }
2712
2470
  return fn;
2713
2471
  }
2714
- if (prop in self) {
2715
- if (prop === NOTIFY || prop === IDENTIFIER_ARRAY_TAG || prop === SOURCE) {
2472
+ if (isSelfProp(self, prop)) {
2473
+ if (prop === NOTIFY || prop === ARRAY_SIGNAL || prop === SOURCE) {
2716
2474
  return self[prop];
2717
2475
  }
2718
2476
  let fn = boundFns.get(prop);
2719
2477
  if (fn) return fn;
2720
- let outcome = self[prop];
2478
+ const outcome = self[prop];
2721
2479
  if (typeof outcome === 'function') {
2722
2480
  fn = function () {
2723
- subscribe(_TAG);
2481
+ subscribe(_SIGNAL);
2724
2482
  // array functions must run through Reflect to work properly
2725
2483
  // binding via other means will not work.
2726
2484
  return Reflect.apply(outcome, receiver, arguments);
@@ -2728,17 +2486,16 @@ let IdentifierArray = (_class3 = class IdentifierArray {
2728
2486
  boundFns.set(prop, fn);
2729
2487
  return fn;
2730
2488
  }
2731
- return subscribe(_TAG), outcome;
2489
+ return subscribe(_SIGNAL), outcome;
2732
2490
  }
2733
2491
  return target[prop];
2734
2492
  },
2735
- set(target, prop, value) {
2493
+ // FIXME: Should this get a generic like get above?
2494
+ set(target, prop, value, receiver) {
2736
2495
  if (prop === 'length') {
2737
2496
  if (!transaction && value === 0) {
2738
2497
  transaction = true;
2739
- addToTransaction(_TAG);
2740
- Reflect.set(target, prop, value);
2741
- self[MUTATE]('length 0', []);
2498
+ self[MUTATE](target, receiver, 'length 0', [], _SIGNAL);
2742
2499
  transaction = false;
2743
2500
  return true;
2744
2501
  } else if (transaction) {
@@ -2755,9 +2512,22 @@ let IdentifierArray = (_class3 = class IdentifierArray {
2755
2512
  PrivateState.meta = value || null;
2756
2513
  return true;
2757
2514
  }
2758
- let index = convertToInt(prop);
2515
+ const index = convertToInt(prop);
2516
+
2517
+ // we do not allow "holey" arrays and so if the index is
2518
+ // greater than length then we will disallow setting it.
2519
+ // however, there is a special case for "unshift" with more than
2520
+ // one item being inserted since current items will be moved to the
2521
+ // new indices first.
2522
+ // we "loosely" detect this by just checking whether we are in
2523
+ // a transaction.
2759
2524
  if (index === null || index > target.length) {
2760
- if (prop in self) {
2525
+ if (index !== null && transaction) {
2526
+ const identifier = recordIdentifierFor(value);
2527
+ assert(`Cannot set index ${index} past the end of the array.`, isStableIdentifier(identifier));
2528
+ target[index] = identifier;
2529
+ return true;
2530
+ } else if (isSelfProp(self, prop)) {
2761
2531
  self[prop] = value;
2762
2532
  return true;
2763
2533
  }
@@ -2767,12 +2537,30 @@ let IdentifierArray = (_class3 = class IdentifierArray {
2767
2537
  assert(`Mutating ${String(prop)} on this RecordArray is not allowed.`, options.allowMutation);
2768
2538
  return false;
2769
2539
  }
2770
- let original = target[index];
2771
- let newIdentifier = extractIdentifierFromRecord$1(value);
2540
+ const original = target[index];
2541
+ const newIdentifier = extractIdentifierFromRecord$1(value);
2772
2542
  target[index] = newIdentifier;
2543
+ assert(`Expected a record`, isStableIdentifier(newIdentifier));
2544
+ // We generate "transactions" whenever a setter method on the array
2545
+ // is called and might bulk update multiple array cells. Fundamentally,
2546
+ // all array operations decompose into individual cell replacements.
2547
+ // e.g. a push is really a "replace cell at next index with new value"
2548
+ // or a splice is "shift all values left/right by X and set out of new
2549
+ // bounds cells to undefined"
2550
+ //
2551
+ // so, if we are in a transaction, then this is not a user generated change
2552
+ // but one generated by a setter method. In this case we want to only apply
2553
+ // the change to the target array and not call the MUTATE method.
2554
+ // If there is no transaction though, then this means the user themselves has
2555
+ // directly changed the value of a specific index and we need to thus generate
2556
+ // a mutation for that change.
2557
+ // e.g. "arr.push(newVal)" is handled by a "addToRelatedRecords" mutation within
2558
+ // a transaction.
2559
+ // while "arr[arr.length] = newVal;" is handled by this replace cell code path.
2773
2560
  if (!transaction) {
2774
- self[MUTATE]('replace cell', [index, original, newIdentifier]);
2775
- addToTransaction(_TAG);
2561
+ self[MUTATE](target, receiver, 'replace cell', [index, original, newIdentifier], _SIGNAL);
2562
+ } else {
2563
+ target[index] = newIdentifier;
2776
2564
  }
2777
2565
  return true;
2778
2566
  },
@@ -2787,12 +2575,7 @@ let IdentifierArray = (_class3 = class IdentifierArray {
2787
2575
  return IdentifierArray.prototype;
2788
2576
  }
2789
2577
  });
2790
- if (macroCondition(getOwnConfig().env.DEBUG)) {
2791
- const meta = Ember.meta(this);
2792
- meta.hasMixin = mixin => {
2793
- assert(`Do not call A() on EmberData RecordArrays`);
2794
- };
2795
- }
2578
+ createArrayTags(proxy, _SIGNAL);
2796
2579
  this[NOTIFY] = this[NOTIFY].bind(proxy);
2797
2580
  return proxy;
2798
2581
  }
@@ -2817,8 +2600,8 @@ let IdentifierArray = (_class3 = class IdentifierArray {
2817
2600
  return this._updatingPromise;
2818
2601
  }
2819
2602
  this.isUpdating = true;
2820
- let updatingPromise = this._update();
2821
- updatingPromise.finally(() => {
2603
+ const updatingPromise = this._update();
2604
+ void updatingPromise.finally(() => {
2822
2605
  this._updatingPromise = null;
2823
2606
  if (this.isDestroying || this.isDestroyed) {
2824
2607
  return;
@@ -2856,17 +2639,23 @@ let IdentifierArray = (_class3 = class IdentifierArray {
2856
2639
  @return {Promise<IdentifierArray>} promise
2857
2640
  */
2858
2641
  save() {
2859
- let promise = Promise.all(this.map(record => this.store.saveRecord(record))).then(() => this);
2642
+ const promise = Promise.all(this.map(record => this.store.saveRecord(record))).then(() => this);
2860
2643
  return promise;
2861
2644
  }
2862
- }, (_descriptor2 = _applyDecoratedDescriptor(_class3.prototype, "isUpdating", [tracked], {
2863
- configurable: true,
2645
+ }, _applyDecoratedDescriptor(_class.prototype, "length", [compat], Object.getOwnPropertyDescriptor(_class.prototype, "length"), _class.prototype), _class); // this will error if someone tries to call
2646
+ // A(identifierArray) since it is not configurable
2647
+ // which is preferable to the `meta` override we used
2648
+ // before which required importing all of Ember
2649
+ const desc = {
2864
2650
  enumerable: true,
2865
- writable: true,
2866
- initializer: function () {
2867
- return false;
2651
+ configurable: false,
2652
+ get: function () {
2653
+ return this;
2868
2654
  }
2869
- }), _applyDecoratedDescriptor(_class3.prototype, "length", [dependentKeyCompat], Object.getOwnPropertyDescriptor(_class3.prototype, "length"), _class3.prototype)), _class3);
2655
+ };
2656
+ compat(desc);
2657
+ Object.defineProperty(IdentifierArray.prototype, '[]', desc);
2658
+ defineSignal(IdentifierArray.prototype, 'isUpdating', false);
2870
2659
  class Collection extends IdentifierArray {
2871
2660
  constructor(options) {
2872
2661
  super(options);
@@ -2898,7 +2687,8 @@ class Collection extends IdentifierArray {
2898
2687
  Collection.prototype.query = null;
2899
2688
 
2900
2689
  // Ensure instanceof works correctly
2901
- //Object.setPrototypeOf(IdentifierArray.prototype, Array.prototype);
2690
+ // Object.setPrototypeOf(IdentifierArray.prototype, Array.prototype);
2691
+
2902
2692
  function assertRecordPassedToHasMany(record) {
2903
2693
  assert(`All elements of a hasMany relationship must be instances of Model, you passed $${typeof record}`, function () {
2904
2694
  try {
@@ -2920,7 +2710,6 @@ function extractIdentifierFromRecord$1(record) {
2920
2710
  /**
2921
2711
  @module @ember-data/store
2922
2712
  */
2923
-
2924
2713
  const FAKE_ARR = {};
2925
2714
  const SLICE_BATCH_SIZE = 1200;
2926
2715
  /**
@@ -2963,7 +2752,7 @@ const SLICE_BATCH_SIZE = 1200;
2963
2752
  */
2964
2753
  function fastPush(target, source) {
2965
2754
  let startLength = 0;
2966
- let newLength = source.length;
2755
+ const newLength = source.length;
2967
2756
  while (newLength - startLength > SLICE_BATCH_SIZE) {
2968
2757
  // eslint-disable-next-line prefer-spread
2969
2758
  target.push.apply(target, source.slice(startLength, startLength + SLICE_BATCH_SIZE));
@@ -3020,8 +2809,8 @@ class RecordArrayManager {
3020
2809
  */
3021
2810
  liveArrayFor(type) {
3022
2811
  let array = this._live.get(type);
3023
- let identifiers = [];
3024
- let staged = this._staged.get(type);
2812
+ const identifiers = [];
2813
+ const staged = this._staged.get(type);
3025
2814
  if (staged) {
3026
2815
  staged.forEach((value, key) => {
3027
2816
  if (value === 'add') {
@@ -3044,7 +2833,7 @@ class RecordArrayManager {
3044
2833
  return array;
3045
2834
  }
3046
2835
  createArray(config) {
3047
- let options = {
2836
+ const options = {
3048
2837
  type: config.type,
3049
2838
  links: config.doc?.links || null,
3050
2839
  meta: config.doc?.meta || null,
@@ -3055,7 +2844,7 @@ class RecordArrayManager {
3055
2844
  store: this.store,
3056
2845
  manager: this
3057
2846
  };
3058
- let array = new Collection(options);
2847
+ const array = new Collection(options);
3059
2848
  this._managed.add(array);
3060
2849
  this._set.set(array, new Set(options.identifiers || []));
3061
2850
  if (config.identifiers) {
@@ -3067,7 +2856,7 @@ class RecordArrayManager {
3067
2856
  if (array === FAKE_ARR) {
3068
2857
  return;
3069
2858
  }
3070
- let tag = array[IDENTIFIER_ARRAY_TAG];
2859
+ const tag = array[ARRAY_SIGNAL];
3071
2860
  if (!tag.shouldReset) {
3072
2861
  tag.shouldReset = true;
3073
2862
  addTransactionCB(array[NOTIFY]);
@@ -3079,11 +2868,11 @@ class RecordArrayManager {
3079
2868
  if (this.isDestroying || this.isDestroyed) {
3080
2869
  return;
3081
2870
  }
3082
- let liveArray = this._live.get(identifier.type);
2871
+ const liveArray = this._live.get(identifier.type);
3083
2872
  const allPending = this._pending;
3084
- let pending = new Map();
2873
+ const pending = new Map();
3085
2874
  if (includeManaged) {
3086
- let managed = this._identifiers.get(identifier);
2875
+ const managed = this._identifiers.get(identifier);
3087
2876
  if (managed) {
3088
2877
  managed.forEach(arr => {
3089
2878
  let changes = allPending.get(arr);
@@ -3138,10 +2927,10 @@ class RecordArrayManager {
3138
2927
  associate(this._identifiers, array, identifiers);
3139
2928
  }
3140
2929
  identifierAdded(identifier) {
3141
- let changeSets = this._getPendingFor(identifier, false);
2930
+ const changeSets = this._getPendingFor(identifier, false);
3142
2931
  if (changeSets) {
3143
2932
  changeSets.forEach((changes, array) => {
3144
- let existing = changes.get(identifier);
2933
+ const existing = changes.get(identifier);
3145
2934
  if (existing === 'del') {
3146
2935
  changes.delete(identifier);
3147
2936
  } else {
@@ -3152,10 +2941,10 @@ class RecordArrayManager {
3152
2941
  }
3153
2942
  }
3154
2943
  identifierRemoved(identifier) {
3155
- let changeSets = this._getPendingFor(identifier, true, true);
2944
+ const changeSets = this._getPendingFor(identifier, true, true);
3156
2945
  if (changeSets) {
3157
2946
  changeSets.forEach((changes, array) => {
3158
- let existing = changes.get(identifier);
2947
+ const existing = changes.get(identifier);
3159
2948
  if (existing === 'add') {
3160
2949
  changes.delete(identifier);
3161
2950
  } else {
@@ -3166,7 +2955,7 @@ class RecordArrayManager {
3166
2955
  }
3167
2956
  }
3168
2957
  identifierChanged(identifier) {
3169
- let newState = this.store._instanceCache.recordIsLoaded(identifier, true);
2958
+ const newState = this.store._instanceCache.recordIsLoaded(identifier, true);
3170
2959
 
3171
2960
  // if the change matches the most recent direct added/removed
3172
2961
  // state, then we can ignore it
@@ -3193,13 +2982,12 @@ class RecordArrayManager {
3193
2982
  this.clear(false);
3194
2983
  this._live.clear();
3195
2984
  this.isDestroyed = true;
3196
- // eslint-disable-next-line @typescript-eslint/no-unsafe-argument
3197
2985
  this.store.notifications.unsubscribe(this._subscription);
3198
2986
  }
3199
2987
  }
3200
2988
  function associate(ArraysCache, array, identifiers) {
3201
2989
  for (let i = 0; i < identifiers.length; i++) {
3202
- let identifier = identifiers[i];
2990
+ const identifier = identifiers[i];
3203
2991
  let cache = ArraysCache.get(identifier);
3204
2992
  if (!cache) {
3205
2993
  cache = new Set();
@@ -3214,13 +3002,13 @@ function disassociate(ArraysCache, array, identifiers) {
3214
3002
  }
3215
3003
  }
3216
3004
  function disassociateIdentifier(ArraysCache, array, identifier) {
3217
- let cache = ArraysCache.get(identifier);
3005
+ const cache = ArraysCache.get(identifier);
3218
3006
  if (cache) {
3219
3007
  cache.delete(array);
3220
3008
  }
3221
3009
  }
3222
3010
  function sync(array, changes, arraySet) {
3223
- let state = array[SOURCE];
3011
+ const state = array[SOURCE];
3224
3012
  const adds = [];
3225
3013
  const removes = [];
3226
3014
  changes.forEach((value, key) => {
@@ -3234,13 +3022,13 @@ function sync(array, changes, arraySet) {
3234
3022
  } else {
3235
3023
  if (arraySet.has(key)) {
3236
3024
  removes.push(key);
3025
+ arraySet.delete(key);
3237
3026
  }
3238
3027
  }
3239
3028
  });
3240
3029
  if (removes.length) {
3241
3030
  if (removes.length === state.length) {
3242
3031
  state.length = 0;
3243
- arraySet.clear();
3244
3032
  // changing the reference breaks the Proxy
3245
3033
  // state = array[SOURCE] = [];
3246
3034
  } else {
@@ -3267,6 +3055,9 @@ function sync(array, changes, arraySet) {
3267
3055
  }
3268
3056
  }
3269
3057
 
3058
+ /**
3059
+ * @module @ember-data/store
3060
+ */
3270
3061
  const Touching = Symbol('touching');
3271
3062
  const RequestPromise = Symbol('promise');
3272
3063
  const EMPTY_ARR = macroCondition(getOwnConfig().env.DEBUG) ? Object.freeze([]) : [];
@@ -3294,14 +3085,14 @@ class RequestStateService {
3294
3085
  this._done.delete(identifier);
3295
3086
  }
3296
3087
  _enqueue(promise, queryRequest) {
3297
- let query = queryRequest.data[0];
3088
+ const query = queryRequest.data[0];
3298
3089
  if (hasRecordIdentifier(query)) {
3299
3090
  const identifier = query.recordIdentifier;
3300
- let type = query.op === 'saveRecord' ? 'mutation' : 'query';
3091
+ const type = query.op === 'saveRecord' ? 'mutation' : 'query';
3301
3092
  if (!this._pending.has(identifier)) {
3302
3093
  this._pending.set(identifier, []);
3303
3094
  }
3304
- let request = {
3095
+ const request = {
3305
3096
  state: 'pending',
3306
3097
  request: queryRequest,
3307
3098
  type
@@ -3312,7 +3103,7 @@ class RequestStateService {
3312
3103
  this._triggerSubscriptions(request);
3313
3104
  return promise.then(result => {
3314
3105
  this._dequeue(identifier, request);
3315
- let finalizedRequest = {
3106
+ const finalizedRequest = {
3316
3107
  state: 'fulfilled',
3317
3108
  request: queryRequest,
3318
3109
  type,
@@ -3326,7 +3117,7 @@ class RequestStateService {
3326
3117
  return result;
3327
3118
  }, error => {
3328
3119
  this._dequeue(identifier, request);
3329
- let finalizedRequest = {
3120
+ const finalizedRequest = {
3330
3121
  state: 'rejected',
3331
3122
  request: queryRequest,
3332
3123
  type,
@@ -3375,7 +3166,7 @@ class RequestStateService {
3375
3166
  _addDone(request) {
3376
3167
  request[Touching].forEach(identifier => {
3377
3168
  // TODO add support for multiple
3378
- let requestDataOp = request.request.data[0].op;
3169
+ const requestDataOp = request.request.data[0].op;
3379
3170
  let requests = this._done.get(identifier);
3380
3171
  if (requests) {
3381
3172
  requests = requests.filter(req => {
@@ -3439,7 +3230,7 @@ class RequestStateService {
3439
3230
  * @method getPendingRequestsForRecord
3440
3231
  * @public
3441
3232
  * @param {StableRecordIdentifier} identifier
3442
- * @returns {RequestState[]} an array of request states for any pending requests for the given identifier
3233
+ * @return {RequestState[]} an array of request states for any pending requests for the given identifier
3443
3234
  */
3444
3235
  getPendingRequestsForRecord(identifier) {
3445
3236
  return this._pending.get(identifier) || EMPTY_ARR;
@@ -3451,10 +3242,10 @@ class RequestStateService {
3451
3242
  * @method getLastRequestForRecord
3452
3243
  * @public
3453
3244
  * @param {StableRecordIdentifier} identifier
3454
- * @returns {RequestState | null} the state of the most recent request for the given identifier
3245
+ * @return {RequestState | null} the state of the most recent request for the given identifier
3455
3246
  */
3456
3247
  getLastRequestForRecord(identifier) {
3457
- let requests = this._done.get(identifier);
3248
+ const requests = this._done.get(identifier);
3458
3249
  if (requests) {
3459
3250
  return requests[requests.length - 1];
3460
3251
  }
@@ -3466,7 +3257,7 @@ function isNonEmptyString(str) {
3466
3257
  }
3467
3258
  function constructResource(type, id, lid) {
3468
3259
  if (typeof type === 'object' && type !== null) {
3469
- let resource = type;
3260
+ const resource = type;
3470
3261
  if (isStableIdentifier(resource)) {
3471
3262
  return resource;
3472
3263
  }
@@ -3501,6 +3292,11 @@ function constructResource(type, id, lid) {
3501
3292
  }
3502
3293
  }
3503
3294
 
3295
+ /**
3296
+ @module @ember-data/store
3297
+ */
3298
+ // this import location is deprecated but breaks in 4.8 and older
3299
+
3504
3300
  /**
3505
3301
  * A Store coordinates interaction between your application, a [Cache](https://api.emberjs.com/ember-data/release/classes/%3CInterface%3E%20Cache),
3506
3302
  * and sources of data (such as your API or a local persistence layer)
@@ -3519,7 +3315,10 @@ function constructResource(type, id, lid) {
3519
3315
 
3520
3316
  @class Store
3521
3317
  @public
3522
- */ // @ts-expect-error
3318
+ */
3319
+
3320
+ // @ts-expect-error
3321
+
3523
3322
  class Store extends EmberObject {
3524
3323
  /**
3525
3324
  * Provides access to the NotificationManager associated
@@ -3651,8 +3450,6 @@ class Store extends EmberObject {
3651
3450
  // private
3652
3451
  this._requestCache = new RequestStateService(this);
3653
3452
  this._instanceCache = new InstanceCache(this);
3654
- this._adapterCache = Object.create(null);
3655
- this._serializerCache = Object.create(null);
3656
3453
  this._documentCache = new Map();
3657
3454
  this.isDestroying = false;
3658
3455
  this.isDestroyed = false;
@@ -3710,7 +3507,7 @@ class Store extends EmberObject {
3710
3507
  * that have been initiated for a given identifier.
3711
3508
  *
3712
3509
  * @method getRequestStateService
3713
- * @returns {RequestStateService}
3510
+ * @return {RequestStateService}
3714
3511
  * @public
3715
3512
  */
3716
3513
  getRequestStateService() {
@@ -3735,10 +3532,11 @@ class Store extends EmberObject {
3735
3532
  * inserting the response into the cache and handing
3736
3533
  * back a Future which resolves to a ResponseDocument
3737
3534
  *
3738
- * Resource data is always updated in the cache.
3535
+ * ## Cache Keys
3739
3536
  *
3740
- * Only GET requests have the request result and document
3741
- * cached by default when a cache key is present.
3537
+ * Only GET requests with a url or requests with an explicit
3538
+ * cache key (`cacheOptions.key`) will have the request result
3539
+ * and document cached.
3742
3540
  *
3743
3541
  * The cache key used is `requestConfig.cacheOptions.key`
3744
3542
  * if present, falling back to `requestconfig.url`.
@@ -3749,16 +3547,44 @@ class Store extends EmberObject {
3749
3547
  * via the `POST` method `requestConfig.cacheOptions.key`
3750
3548
  * MUST be supplied for the document to be cached.
3751
3549
  *
3550
+ * ## Requesting Without a Cache Key
3551
+ *
3552
+ * Resource data within the request is always updated in the cache,
3553
+ * regardless of whether a cache key is present for the request.
3554
+ *
3555
+ * ## Fulfilling From Cache
3556
+ *
3557
+ * When a cache-key is determined, the request may fulfill
3558
+ * from cache provided the cache is not stale.
3559
+ *
3560
+ * Cache staleness is determined by the configured LifetimesService
3561
+ * with priority given to the `cacheOptions.reload` and
3562
+ * `cacheOptions.backgroundReload` on the request if present.
3563
+ *
3564
+ * If the cache data has soft expired or the request asks for a background
3565
+ * reload, the request will fulfill from cache if possible and
3566
+ * make a non-blocking request in the background to update the cache.
3567
+ *
3568
+ * If the cache data has hard expired or the request asks for a reload,
3569
+ * the request will not fulfill from cache and will make a blocking
3570
+ * request to update the cache.
3571
+ *
3572
+ * ## The Response
3573
+ *
3574
+ * The primary difference between `requestManager.request` and `store.request`
3575
+ * is that `store.request` will attempt to hydrate the response content into
3576
+ * a response Document containing RecordInstances.
3577
+ *
3752
3578
  * @method request
3753
3579
  * @param {StoreRequestInput} requestConfig
3754
- * @returns {Future}
3580
+ * @return {Future}
3755
3581
  * @public
3756
3582
  */
3757
3583
  request(requestConfig) {
3758
3584
  // we lazily set the cache handler when we issue the first request
3759
3585
  // because constructor doesn't allow for this to run after
3760
3586
  // the user has had the chance to set the prop.
3761
- let opts = {
3587
+ const opts = {
3762
3588
  store: this,
3763
3589
  [EnableHydration]: true
3764
3590
  };
@@ -3810,7 +3636,7 @@ class Store extends EmberObject {
3810
3636
  * @param createRecordArgs
3811
3637
  * @param recordDataFor deprecated use this.cache
3812
3638
  * @param notificationManager deprecated use this.notifications
3813
- * @returns A record instance
3639
+ * @return A record instance
3814
3640
  * @public
3815
3641
  */
3816
3642
 
@@ -4018,43 +3844,41 @@ class Store extends EmberObject {
4018
3844
  //
4019
3845
  // to remove this, we would need to move to a new `async` API.
4020
3846
  let record;
4021
- _backburner.join(() => {
4022
- this._join(() => {
4023
- let normalizedModelName = normalizeModelName(modelName);
4024
- let properties = {
4025
- ...inputProperties
4026
- };
3847
+ this._join(() => {
3848
+ const normalizedModelName = normalizeModelName(modelName);
3849
+ const properties = {
3850
+ ...inputProperties
3851
+ };
4027
3852
 
4028
- // If the passed properties do not include a primary key,
4029
- // give the adapter an opportunity to generate one. Typically,
4030
- // client-side ID generators will use something like uuid.js
4031
- // to avoid conflicts.
3853
+ // If the passed properties do not include a primary key,
3854
+ // give the adapter an opportunity to generate one. Typically,
3855
+ // client-side ID generators will use something like uuid.js
3856
+ // to avoid conflicts.
4032
3857
 
4033
- if (properties.id === null || properties.id === undefined) {
4034
- let adapter = this.adapterFor(modelName);
4035
- if (adapter && adapter.generateIdForRecord) {
4036
- properties.id = adapter.generateIdForRecord(this, modelName, properties);
4037
- } else {
4038
- properties.id = null;
4039
- }
3858
+ if (properties.id === null || properties.id === undefined) {
3859
+ const adapter = this.adapterFor?.(modelName, true);
3860
+ if (adapter && adapter.generateIdForRecord) {
3861
+ properties.id = adapter.generateIdForRecord(this, modelName, properties);
3862
+ } else {
3863
+ properties.id = null;
4040
3864
  }
3865
+ }
4041
3866
 
4042
- // Coerce ID to a string
4043
- properties.id = coerceId(properties.id);
4044
- const resource = {
4045
- type: normalizedModelName,
4046
- id: properties.id
4047
- };
4048
- if (resource.id) {
4049
- const identifier = this.identifierCache.peekRecordIdentifier(resource);
4050
- assert(`The id ${String(properties.id)} has already been used with another '${normalizedModelName}' record.`, !identifier);
4051
- }
4052
- const identifier = this.identifierCache.createIdentifierForNewRecord(resource);
4053
- const cache = this.cache;
4054
- const createOptions = normalizeProperties(this, identifier, properties);
4055
- const resultProps = cache.clientDidCreate(identifier, createOptions);
4056
- record = this._instanceCache.getRecord(identifier, resultProps);
4057
- });
3867
+ // Coerce ID to a string
3868
+ properties.id = coerceId(properties.id);
3869
+ const resource = {
3870
+ type: normalizedModelName,
3871
+ id: properties.id
3872
+ };
3873
+ if (resource.id) {
3874
+ const identifier = this.identifierCache.peekRecordIdentifier(resource);
3875
+ assert(`The id ${String(properties.id)} has already been used with another '${normalizedModelName}' record.`, !identifier);
3876
+ }
3877
+ const identifier = this.identifierCache.createIdentifierForNewRecord(resource);
3878
+ const cache = this.cache;
3879
+ const createOptions = normalizeProperties(this, identifier, properties);
3880
+ const resultProps = cache.clientDidCreate(identifier, createOptions);
3881
+ record = this._instanceCache.getRecord(identifier, resultProps);
4058
3882
  });
4059
3883
  return record;
4060
3884
  }
@@ -4082,9 +3906,7 @@ class Store extends EmberObject {
4082
3906
  this._join(() => {
4083
3907
  cache.setIsDeleted(identifier, true);
4084
3908
  if (cache.isNew(identifier)) {
4085
- _backburner.join(() => {
4086
- this._instanceCache.unloadRecord(identifier);
4087
- });
3909
+ this._instanceCache.unloadRecord(identifier);
4088
3910
  }
4089
3911
  });
4090
3912
  }
@@ -4166,8 +3988,7 @@ class Store extends EmberObject {
4166
3988
  In your adapter you can then access this id without triggering a network request via the
4167
3989
  snapshot:
4168
3990
  ```app/adapters/application.js
4169
- import EmberObject from '@ember/object';
4170
- export default class Adapter extends EmberObject {
3991
+ export default class Adapter {
4171
3992
  findRecord(store, schema, id, snapshot) {
4172
3993
  let type = schema.modelName;
4173
3994
  if (type === 'comment')
@@ -4176,6 +3997,9 @@ class Store extends EmberObject {
4176
3997
  .then(response => response.json())
4177
3998
  }
4178
3999
  }
4000
+ static create() {
4001
+ return new this();
4002
+ }
4179
4003
  }
4180
4004
  ```
4181
4005
  This could also be achieved by supplying the post id to the adapter via the adapterOptions
@@ -4189,9 +4013,8 @@ class Store extends EmberObject {
4189
4013
  }
4190
4014
  ```
4191
4015
  ```app/adapters/application.js
4192
- import EmberObject from '@ember/object';
4193
- export default class Adapter extends EmberObject {
4194
- findRecord(store, schema, id, snapshot) {
4016
+ export default class Adapter {
4017
+ findRecord(store, schema, id, snapshot) {
4195
4018
  let type = schema.modelName;
4196
4019
  if (type === 'comment')
4197
4020
  let postId = snapshot.adapterOptions.post;
@@ -4199,6 +4022,9 @@ class Store extends EmberObject {
4199
4022
  .then(response => response.json())
4200
4023
  }
4201
4024
  }
4025
+ static create() {
4026
+ return new this();
4027
+ }
4202
4028
  }
4203
4029
  ```
4204
4030
  If you have access to the post model you can also pass the model itself to preload:
@@ -4325,9 +4151,8 @@ class Store extends EmberObject {
4325
4151
  }
4326
4152
  ```
4327
4153
  ```app/adapters/application.js
4328
- import EmberObject from '@ember/object';
4329
- export default class Adapter extends EmberObject {
4330
- findRecord(store, schema, id, snapshot) {
4154
+ export default class Adapter {
4155
+ findRecord(store, schema, id, snapshot) {
4331
4156
  let type = schema.modelName;
4332
4157
  if (type === 'post')
4333
4158
  let includes = snapshot.adapterOptions.include;
@@ -4335,6 +4160,9 @@ class Store extends EmberObject {
4335
4160
  .then(response => response.json())
4336
4161
  }
4337
4162
  }
4163
+ static create() {
4164
+ return new this();
4165
+ }
4338
4166
  }
4339
4167
  ```
4340
4168
  In this case, the post's comments would then be available in your template as
@@ -4478,7 +4306,7 @@ class Store extends EmberObject {
4478
4306
  resourceIdentifier = constructResource(type, normalizedId);
4479
4307
  }
4480
4308
  assert('getReference expected to receive either a resource identifier or type and id as arguments', isMaybeIdentifier(resourceIdentifier));
4481
- let identifier = this.identifierCache.getOrCreateRecordIdentifier(resourceIdentifier);
4309
+ const identifier = this.identifierCache.getOrCreateRecordIdentifier(resourceIdentifier);
4482
4310
  return this._instanceCache.getReference(identifier);
4483
4311
  }
4484
4312
 
@@ -4897,7 +4725,7 @@ class Store extends EmberObject {
4897
4725
  }
4898
4726
  assert(`You need to pass a model name to the store's peekAll method`, modelName);
4899
4727
  assert(`Passing classes to store methods has been removed. Please pass a dasherized string instead of ${modelName}`, typeof modelName === 'string');
4900
- let type = normalizeModelName(modelName);
4728
+ const type = normalizeModelName(modelName);
4901
4729
  return this.recordArrayManager.liveArrayFor(type);
4902
4730
  }
4903
4731
 
@@ -4927,7 +4755,7 @@ class Store extends EmberObject {
4927
4755
  this.recordArrayManager.clear();
4928
4756
  this._instanceCache.clear();
4929
4757
  } else {
4930
- let normalizedModelName = normalizeModelName(modelName);
4758
+ const normalizedModelName = normalizeModelName(modelName);
4931
4759
  this._instanceCache.clear(normalizedModelName);
4932
4760
  }
4933
4761
  });
@@ -5068,10 +4896,9 @@ class Store extends EmberObject {
5068
4896
  if (macroCondition(getOwnConfig().env.DEBUG)) {
5069
4897
  assertDestroyingStore(this, 'push');
5070
4898
  }
5071
- let pushed = this._push(data, false);
4899
+ const pushed = this._push(data, false);
5072
4900
  if (Array.isArray(pushed)) {
5073
- let records = pushed.map(identifier => this._instanceCache.getRecord(identifier));
5074
- return records;
4901
+ return pushed.map(identifier => this._instanceCache.getRecord(identifier));
5075
4902
  }
5076
4903
  if (pushed === null) {
5077
4904
  return null;
@@ -5093,396 +4920,688 @@ class Store extends EmberObject {
5093
4920
  }
5094
4921
  if (macroCondition(getOwnConfig().debug.LOG_PAYLOADS)) {
5095
4922
  try {
5096
- let data = JSON.parse(JSON.stringify(jsonApiDoc));
5097
- // eslint-disable-next-line no-console
5098
- console.log('EmberData | Payload - push', data);
5099
- } catch (e) {
5100
- // eslint-disable-next-line no-console
5101
- console.log('EmberData | Payload - push', jsonApiDoc);
4923
+ const data = JSON.parse(JSON.stringify(jsonApiDoc));
4924
+ // eslint-disable-next-line no-console
4925
+ console.log('EmberData | Payload - push', data);
4926
+ } catch (e) {
4927
+ // eslint-disable-next-line no-console
4928
+ console.log('EmberData | Payload - push', jsonApiDoc);
4929
+ }
4930
+ }
4931
+ if (asyncFlush) {
4932
+ this._enableAsyncFlush = true;
4933
+ }
4934
+ let ret;
4935
+ this._join(() => {
4936
+ ret = this.cache.put({
4937
+ content: jsonApiDoc
4938
+ });
4939
+ });
4940
+ this._enableAsyncFlush = null;
4941
+ return 'data' in ret ? ret.data : null;
4942
+ }
4943
+
4944
+ /**
4945
+ * Trigger a save for a Record.
4946
+ *
4947
+ * @method saveRecord
4948
+ * @public
4949
+ * @param {RecordInstance} record
4950
+ * @param options
4951
+ * @return {Promise<RecordInstance>}
4952
+ */
4953
+ saveRecord(record, options = {}) {
4954
+ if (macroCondition(getOwnConfig().env.DEBUG)) {
4955
+ assertDestroyingStore(this, 'saveRecord');
4956
+ }
4957
+ assert(`Unable to initate save for a record in a disconnected state`, storeFor(record));
4958
+ const identifier = recordIdentifierFor(record);
4959
+ const cache = this.cache;
4960
+ if (!identifier) {
4961
+ // this commonly means we're disconnected
4962
+ // but just in case we reject here to prevent bad things.
4963
+ return Promise.reject(new Error(`Record Is Disconnected`));
4964
+ }
4965
+ // TODO we used to check if the record was destroyed here
4966
+ assert(`Cannot initiate a save request for an unloaded record: ${identifier.lid}`, this._instanceCache.recordIsLoaded(identifier));
4967
+ if (resourceIsFullyDeleted(this._instanceCache, identifier)) {
4968
+ return Promise.resolve(record);
4969
+ }
4970
+ if (!options) {
4971
+ options = {};
4972
+ }
4973
+ let operation = 'updateRecord';
4974
+ if (cache.isNew(identifier)) {
4975
+ operation = 'createRecord';
4976
+ } else if (cache.isDeleted(identifier)) {
4977
+ operation = 'deleteRecord';
4978
+ }
4979
+ const request = {
4980
+ op: operation,
4981
+ data: {
4982
+ options,
4983
+ record: identifier
4984
+ },
4985
+ records: [identifier],
4986
+ cacheOptions: {
4987
+ [SkipCache]: true
4988
+ }
4989
+ };
4990
+
4991
+ // we lie here on the type because legacy doesn't have enough context
4992
+ cache.willCommit(identifier, {
4993
+ request
4994
+ });
4995
+ return this.request(request).then(document => document.content);
4996
+ }
4997
+
4998
+ /**
4999
+ * Instantiation hook allowing applications or addons to configure the store
5000
+ * to utilize a custom Cache implementation.
5001
+ *
5002
+ * This hook should not be called directly by consuming applications or libraries.
5003
+ * Use `Store.cache` to access the Cache instance.
5004
+ *
5005
+ * @method createCache (hook)
5006
+ * @public
5007
+ * @param storeWrapper
5008
+ * @return {Cache}
5009
+ */
5010
+
5011
+ /**
5012
+ * Returns the cache instance associated to this Store, instantiates the Cache
5013
+ * if necessary via `Store.createCache`
5014
+ *
5015
+ * @property {Cache} cache
5016
+ * @public
5017
+ */
5018
+ get cache() {
5019
+ let {
5020
+ cache
5021
+ } = this._instanceCache;
5022
+ if (!cache) {
5023
+ cache = this._instanceCache.cache = this.createCache(this._instanceCache._storeWrapper);
5024
+ if (macroCondition(getOwnConfig().env.DEBUG)) {
5025
+ cache = new CacheManager(cache);
5026
+ }
5027
+ }
5028
+ return cache;
5029
+ }
5030
+
5031
+ // @ts-expect-error
5032
+ destroy() {
5033
+ if (this.isDestroyed) {
5034
+ // @ember/test-helpers will call destroy multiple times
5035
+ return;
5036
+ }
5037
+ this.isDestroying = true;
5038
+ this._graph?.destroy();
5039
+ this._graph = undefined;
5040
+ this.notifications.destroy();
5041
+ this.recordArrayManager.destroy();
5042
+ this.identifierCache.destroy();
5043
+ this.unloadAll();
5044
+ this.isDestroyed = true;
5045
+ }
5046
+ static create(args) {
5047
+ return new this(args);
5048
+ }
5049
+ }
5050
+ let assertDestroyingStore;
5051
+ let assertDestroyedStoreOnly;
5052
+ if (macroCondition(getOwnConfig().env.DEBUG)) {
5053
+ // eslint-disable-next-line @typescript-eslint/no-shadow
5054
+ assertDestroyingStore = function assertDestroyingStore(store, method) {
5055
+ assert(`Attempted to call store.${method}(), but the store instance has already been destroyed.`, !(store.isDestroying || store.isDestroyed));
5056
+ };
5057
+ // eslint-disable-next-line @typescript-eslint/no-shadow
5058
+ assertDestroyedStoreOnly = function assertDestroyedStoreOnly(store, method) {
5059
+ assert(`Attempted to call store.${method}(), but the store instance has already been destroyed.`, !store.isDestroyed);
5060
+ };
5061
+ }
5062
+ function isMaybeIdentifier(maybeIdentifier) {
5063
+ return Boolean(maybeIdentifier !== null && typeof maybeIdentifier === 'object' && ('id' in maybeIdentifier && 'type' in maybeIdentifier && maybeIdentifier.id && maybeIdentifier.type || maybeIdentifier.lid));
5064
+ }
5065
+ function normalizeProperties(store, identifier, properties) {
5066
+ // assert here
5067
+ if (properties !== undefined) {
5068
+ if ('id' in properties) {
5069
+ assert(`expected id to be a string or null`, properties.id !== undefined);
5070
+ }
5071
+ assert(`You passed '${typeof properties}' as properties for record creation instead of an object.`, typeof properties === 'object' && properties !== null);
5072
+ const {
5073
+ type
5074
+ } = identifier;
5075
+
5076
+ // convert relationship Records to RecordDatas before passing to RecordData
5077
+ const defs = store.getSchemaDefinitionService().relationshipsDefinitionFor({
5078
+ type
5079
+ });
5080
+ if (defs !== null) {
5081
+ const keys = Object.keys(properties);
5082
+ let relationshipValue;
5083
+ for (let i = 0; i < keys.length; i++) {
5084
+ const prop = keys[i];
5085
+ const def = defs[prop];
5086
+ if (def !== undefined) {
5087
+ if (def.kind === 'hasMany') {
5088
+ if (macroCondition(getOwnConfig().env.DEBUG)) {
5089
+ assertRecordsPassedToHasMany(properties[prop]);
5090
+ }
5091
+ relationshipValue = extractIdentifiersFromRecords(properties[prop]);
5092
+ } else {
5093
+ relationshipValue = extractIdentifierFromRecord(properties[prop]);
5094
+ }
5095
+ properties[prop] = relationshipValue;
5096
+ }
5097
+ }
5098
+ }
5099
+ }
5100
+ return properties;
5101
+ }
5102
+ function assertRecordsPassedToHasMany(records) {
5103
+ assert(`You must pass an array of records to set a hasMany relationship`, Array.isArray(records));
5104
+ assert(`All elements of a hasMany relationship must be instances of Model, you passed ${records.map(r => `${typeof r}`).join(', ')}`, function () {
5105
+ return records.every(record => {
5106
+ try {
5107
+ recordIdentifierFor(record);
5108
+ return true;
5109
+ } catch {
5110
+ return false;
5102
5111
  }
5103
- }
5104
- if (asyncFlush) {
5105
- this._enableAsyncFlush = true;
5106
- }
5107
- let ret;
5108
- this._join(() => {
5109
- ret = this.cache.put({
5110
- content: jsonApiDoc
5111
- });
5112
5112
  });
5113
- this._enableAsyncFlush = null;
5114
- return 'data' in ret ? ret.data : null;
5113
+ }());
5114
+ }
5115
+ function extractIdentifiersFromRecords(records) {
5116
+ return records.map(record => extractIdentifierFromRecord(record));
5117
+ }
5118
+ function extractIdentifierFromRecord(recordOrPromiseRecord) {
5119
+ if (!recordOrPromiseRecord) {
5120
+ return null;
5115
5121
  }
5122
+ const extract = recordIdentifierFor;
5123
+ return extract(recordOrPromiseRecord);
5124
+ }
5125
+ function urlFromLink(link) {
5126
+ if (typeof link === 'string') return link;
5127
+ return link.href;
5128
+ }
5116
5129
 
5130
+ /**
5131
+ * A Document is a class that wraps the response content from a request to the API
5132
+ * returned by `Cache.put` or `Cache.peek`, converting resource-identifiers into
5133
+ * record instances.
5134
+ *
5135
+ * It is not directly instantiated by the user, and its properties should not
5136
+ * be directly modified. Whether individual properties are mutable or not is
5137
+ * determined by the record instance itself.
5138
+ *
5139
+ * @public
5140
+ * @class Document
5141
+ */
5142
+ var _store = /*#__PURE__*/_classPrivateFieldKey("store");
5143
+ var _request = /*#__PURE__*/_classPrivateFieldKey("request");
5144
+ class Document {
5145
+ constructor(store, identifier) {
5146
+ Object.defineProperty(this, _request, {
5147
+ value: _request2
5148
+ });
5149
+ /**
5150
+ * The links object for this document, if any
5151
+ *
5152
+ * e.g.
5153
+ *
5154
+ * ```
5155
+ * {
5156
+ * self: '/articles?page[number]=3',
5157
+ * }
5158
+ * ```
5159
+ *
5160
+ * @property links
5161
+ * @type {object|undefined} - a links object
5162
+ * @public
5163
+ */
5164
+ /**
5165
+ * The primary data for this document, if any.
5166
+ *
5167
+ * If this document has no primary data (e.g. because it is an error document)
5168
+ * this property will be `undefined`.
5169
+ *
5170
+ * For collections this will be an array of record instances,
5171
+ * for single resource requests it will be a single record instance or null.
5172
+ *
5173
+ * @property data
5174
+ * @public
5175
+ * @type {object|Array<object>|null|undefined} - a data object
5176
+ */
5177
+ /**
5178
+ * The errors returned by the API for this request, if any
5179
+ *
5180
+ * @property errors
5181
+ * @public
5182
+ * @type {object|undefined} - an errors object
5183
+ */
5184
+ /**
5185
+ * The meta object for this document, if any
5186
+ *
5187
+ * @property meta
5188
+ * @public
5189
+ * @type {object|undefined} - a meta object
5190
+ */
5191
+ /**
5192
+ * The identifier associated with this document, if any
5193
+ *
5194
+ * @property identifier
5195
+ * @public
5196
+ * @type {StableDocumentIdentifier|null}
5197
+ */
5198
+ Object.defineProperty(this, _store, {
5199
+ writable: true,
5200
+ value: void 0
5201
+ });
5202
+ _classPrivateFieldBase(this, _store)[_store] = store;
5203
+ this.identifier = identifier;
5204
+ }
5117
5205
  /**
5118
- Push some raw data into the store.
5119
- This method can be used both to push in brand new
5120
- records, as well as to update existing records. You
5121
- can push in more than one type of object at once.
5122
- All objects should be in the format expected by the
5123
- serializer.
5124
- ```app/serializers/application.js
5125
- import RESTSerializer from '@ember-data/serializer/rest';
5126
- export default class ApplicationSerializer extends RESTSerializer;
5127
- ```
5128
- ```js
5129
- let pushData = {
5130
- posts: [
5131
- { id: 1, postTitle: "Great post", commentIds: [2] }
5132
- ],
5133
- comments: [
5134
- { id: 2, commentBody: "Insightful comment" }
5135
- ]
5136
- }
5137
- store.pushPayload(pushData);
5138
- ```
5139
- By default, the data will be deserialized using a default
5140
- serializer (the application serializer if it exists).
5141
- Alternatively, `pushPayload` will accept a model type which
5142
- will determine which serializer will process the payload.
5143
- ```app/serializers/application.js
5144
- import RESTSerializer from '@ember-data/serializer/rest';
5145
- export default class ApplicationSerializer extends RESTSerializer;
5146
- ```
5147
- ```app/serializers/post.js
5148
- import JSONSerializer from '@ember-data/serializer/json';
5149
- export default JSONSerializer;
5150
- ```
5151
- ```js
5152
- store.pushPayload(pushData); // Will use the application serializer
5153
- store.pushPayload('post', pushData); // Will use the post serializer
5154
- ```
5155
- @method pushPayload
5156
- @public
5157
- @param {String} modelName Optionally, a model type used to determine which serializer will be used
5158
- @param {Object} inputPayload
5159
- */
5160
- // TODO @runspired @deprecate pushPayload in favor of looking up the serializer
5161
- pushPayload(modelName, inputPayload) {
5162
- if (macroCondition(getOwnConfig().env.DEBUG)) {
5163
- assertDestroyingStore(this, 'pushPayload');
5164
- }
5165
- const payload = inputPayload || modelName;
5166
- const normalizedModelName = inputPayload ? normalizeModelName(modelName) : 'application';
5167
- const serializer = this.serializerFor(normalizedModelName);
5168
- assert(`You cannot use 'store.pushPayload(<type>, <payload>)' unless the serializer for '${normalizedModelName}' defines 'pushPayload'`, serializer && typeof serializer.pushPayload === 'function');
5169
- serializer.pushPayload(this, payload);
5206
+ * Fetches the related link for this document, returning a promise that resolves
5207
+ * with the document when the request completes. If no related link is present,
5208
+ * will fallback to the self link if present
5209
+ *
5210
+ * @method fetch
5211
+ * @public
5212
+ * @param {object} options
5213
+ * @return Promise<Document>
5214
+ */
5215
+ fetch(options = {}) {
5216
+ assert(`No self or related link`, this.links?.related || this.links?.self);
5217
+ options.cacheOptions = options.cacheOptions || {};
5218
+ options.cacheOptions.key = this.identifier?.lid;
5219
+ return _classPrivateFieldBase(this, _request)[_request](this.links.related ? 'related' : 'self', options);
5170
5220
  }
5171
5221
 
5172
5222
  /**
5173
- * Trigger a save for a Record.
5223
+ * Fetches the next link for this document, returning a promise that resolves
5224
+ * with the new document when the request completes, or null if there is no
5225
+ * next link.
5174
5226
  *
5175
- * @method saveRecord
5227
+ * @method next
5176
5228
  * @public
5177
- * @param {RecordInstance} record
5178
- * @param options
5179
- * @returns {Promise<RecordInstance>}
5229
+ * @param {object} options
5230
+ * @return Promise<Document | null>
5180
5231
  */
5181
- saveRecord(record, options = {}) {
5182
- if (macroCondition(getOwnConfig().env.DEBUG)) {
5183
- assertDestroyingStore(this, 'saveRecord');
5184
- }
5185
- assert(`Unable to initate save for a record in a disconnected state`, storeFor(record));
5186
- let identifier = recordIdentifierFor(record);
5187
- const cache = this.cache;
5188
- if (!identifier) {
5189
- // this commonly means we're disconnected
5190
- // but just in case we reject here to prevent bad things.
5191
- return Promise.reject(`Record Is Disconnected`);
5192
- }
5193
- // TODO we used to check if the record was destroyed here
5194
- assert(`Cannot initiate a save request for an unloaded record: ${identifier.lid}`, this._instanceCache.recordIsLoaded(identifier));
5195
- if (resourceIsFullyDeleted(this._instanceCache, identifier)) {
5196
- return Promise.resolve(record);
5197
- }
5198
- if (!options) {
5199
- options = {};
5200
- }
5201
- let operation = 'updateRecord';
5202
- if (cache.isNew(identifier)) {
5203
- operation = 'createRecord';
5204
- } else if (cache.isDeleted(identifier)) {
5205
- operation = 'deleteRecord';
5206
- }
5207
- const request = {
5208
- op: operation,
5209
- data: {
5210
- options,
5211
- record: identifier
5212
- },
5213
- cacheOptions: {
5214
- [SkipCache]: true
5215
- }
5216
- };
5232
+ next(options = {}) {
5233
+ return _classPrivateFieldBase(this, _request)[_request]('next', options);
5234
+ }
5217
5235
 
5218
- // we lie here on the type because legacy doesn't have enough context
5219
- cache.willCommit(identifier, {
5220
- request
5221
- });
5222
- return this.request(request).then(document => document.content);
5236
+ /**
5237
+ * Fetches the prev link for this document, returning a promise that resolves
5238
+ * with the new document when the request completes, or null if there is no
5239
+ * prev link.
5240
+ *
5241
+ * @method prev
5242
+ * @public
5243
+ * @param {object} options
5244
+ * @return Promise<Document | null>
5245
+ */
5246
+ prev(options = {}) {
5247
+ return _classPrivateFieldBase(this, _request)[_request]('prev', options);
5223
5248
  }
5224
5249
 
5225
5250
  /**
5226
- * Instantiation hook allowing applications or addons to configure the store
5227
- * to utilize a custom Cache implementation.
5251
+ * Fetches the first link for this document, returning a promise that resolves
5252
+ * with the new document when the request completes, or null if there is no
5253
+ * first link.
5228
5254
  *
5229
- * This hook should not be called directly by consuming applications or libraries.
5230
- * Use `Store.cache` to access the Cache instance.
5255
+ * @method first
5256
+ * @public
5257
+ * @param {object} options
5258
+ * @return Promise<Document | null>
5259
+ */
5260
+ first(options = {}) {
5261
+ return _classPrivateFieldBase(this, _request)[_request]('first', options);
5262
+ }
5263
+
5264
+ /**
5265
+ * Fetches the last link for this document, returning a promise that resolves
5266
+ * with the new document when the request completes, or null if there is no
5267
+ * last link.
5231
5268
  *
5232
- * @method createCache (hook)
5269
+ * @method last
5233
5270
  * @public
5234
- * @param storeWrapper
5235
- * @returns {Cache}
5271
+ * @param {object} options
5272
+ * @return Promise<Document | null>
5236
5273
  */
5274
+ last(options = {}) {
5275
+ return _classPrivateFieldBase(this, _request)[_request]('last', options);
5276
+ }
5237
5277
 
5238
5278
  /**
5239
- * Returns the cache instance associated to this Store, instantiates the Cache
5240
- * if necessary via `Store.createCache`
5279
+ * Implemented for `JSON.stringify` support.
5241
5280
  *
5242
- * @property {Cache} cache
5281
+ * Returns the JSON representation of the document wrapper.
5282
+ *
5283
+ * This is a shallow serialization, it does not deeply serialize
5284
+ * the document's contents, leaving that to the individual record
5285
+ * instances to determine how to do, if at all.
5286
+ *
5287
+ * @method toJSON
5243
5288
  * @public
5289
+ * @return
5244
5290
  */
5245
- get cache() {
5246
- let {
5247
- cache
5248
- } = this._instanceCache;
5249
- if (!cache) {
5250
- cache = this._instanceCache.cache = this.createCache(this._instanceCache._storeWrapper);
5251
- if (macroCondition(getOwnConfig().env.DEBUG)) {
5252
- cache = new CacheManager(cache);
5253
- }
5291
+ toJSON() {
5292
+ const data = {};
5293
+ data.identifier = this.identifier;
5294
+ if (this.data !== undefined) {
5295
+ data.data = this.data;
5254
5296
  }
5255
- return cache;
5297
+ if (this.links !== undefined) {
5298
+ data.links = this.links;
5299
+ }
5300
+ if (this.errors !== undefined) {
5301
+ data.errors = this.errors;
5302
+ }
5303
+ if (this.meta !== undefined) {
5304
+ data.meta = this.meta;
5305
+ }
5306
+ return data;
5307
+ }
5308
+ }
5309
+ async function _request2(link, options) {
5310
+ const href = this.links?.[link];
5311
+ if (!href) {
5312
+ return null;
5256
5313
  }
5314
+ options.method = options.method || 'GET';
5315
+ const response = await _classPrivateFieldBase(this, _store)[_store].request(Object.assign(options, {
5316
+ url: urlFromLink(href)
5317
+ }));
5318
+ return response.content;
5319
+ }
5320
+ defineSignal(Document.prototype, 'data');
5321
+ defineSignal(Document.prototype, 'links');
5322
+ defineSignal(Document.prototype, 'errors');
5323
+ defineSignal(Document.prototype, 'meta');
5257
5324
 
5258
- /**
5259
- `normalize` converts a json payload into the normalized form that
5260
- [push](../methods/push?anchor=push) expects.
5261
- Example
5262
- ```js
5263
- socket.on('message', function(message) {
5264
- let modelName = message.model;
5265
- let data = message.data;
5266
- store.push(store.normalize(modelName, data));
5267
- });
5268
- ```
5269
- @method normalize
5270
- @public
5271
- @param {String} modelName The name of the model type for this payload
5272
- @param {Object} payload
5273
- @return {Object} The normalized payload
5274
- */
5275
- // TODO @runspired @deprecate users should call normalize on the associated serializer directly
5276
- normalize(modelName, payload) {
5277
- if (macroCondition(getOwnConfig().env.DEBUG)) {
5278
- assertDestroyingStore(this, 'normalize');
5325
+ /**
5326
+ * @module @ember-data/store
5327
+ */
5328
+
5329
+ /**
5330
+ * A service which an application may provide to the store via
5331
+ * the store's `lifetimes` property to configure the behavior
5332
+ * of the CacheHandler.
5333
+ *
5334
+ * The default behavior for request lifetimes is to never expire
5335
+ * unless manually refreshed via `cacheOptions.reload` or `cacheOptions.backgroundReload`.
5336
+ *
5337
+ * Implementing this service allows you to programatically define
5338
+ * when a request should be considered expired.
5339
+ *
5340
+ * @class <Interface> LifetimesService
5341
+ * @public
5342
+ */
5343
+
5344
+ const MUTATION_OPS = new Set(['createRecord', 'updateRecord', 'deleteRecord']);
5345
+ function isErrorDocument(document) {
5346
+ return 'errors' in document;
5347
+ }
5348
+ function maybeUpdateUiObjects(store, request, options, document, isFromCache) {
5349
+ const {
5350
+ identifier
5351
+ } = options;
5352
+ if (isErrorDocument(document)) {
5353
+ if (!identifier && !options.shouldHydrate) {
5354
+ return document;
5355
+ }
5356
+ let doc;
5357
+ if (identifier) {
5358
+ doc = store._documentCache.get(identifier);
5359
+ }
5360
+ if (!doc) {
5361
+ doc = new Document(store, identifier);
5362
+ copyDocumentProperties(doc, document);
5363
+ if (identifier) {
5364
+ store._documentCache.set(identifier, doc);
5365
+ }
5366
+ } else if (!isFromCache) {
5367
+ doc.data = undefined;
5368
+ copyDocumentProperties(doc, document);
5369
+ }
5370
+ return options.shouldHydrate ? doc : document;
5371
+ }
5372
+ if (Array.isArray(document.data)) {
5373
+ const {
5374
+ recordArrayManager
5375
+ } = store;
5376
+ if (!identifier) {
5377
+ if (!options.shouldHydrate) {
5378
+ return document;
5379
+ }
5380
+ const data = recordArrayManager.createArray({
5381
+ type: request.url,
5382
+ identifiers: document.data,
5383
+ doc: document,
5384
+ query: request
5385
+ });
5386
+ const doc = new Document(store, null);
5387
+ doc.data = data;
5388
+ doc.meta = document.meta;
5389
+ doc.links = document.links;
5390
+ return doc;
5279
5391
  }
5280
- assert(`You need to pass a model name to the store's normalize method`, modelName);
5281
- assert(`Passing classes to store methods has been removed. Please pass a dasherized string instead of ${typeof modelName}`, typeof modelName === 'string');
5282
- const normalizedModelName = normalizeModelName(modelName);
5283
- const serializer = this.serializerFor(normalizedModelName);
5284
- const schema = this.modelFor(normalizedModelName);
5285
- assert(`You must define a normalize method in your serializer in order to call store.normalize`, typeof serializer?.normalize === 'function');
5286
- return serializer.normalize(schema, payload);
5287
- }
5288
-
5289
- /**
5290
- Returns an instance of the adapter for a given type. For
5291
- example, `adapterFor('person')` will return an instance of
5292
- the adapter located at `app/adapters/person.js`
5293
- If no `person` adapter is found, this method will look
5294
- for an `application` adapter (the default adapter for
5295
- your entire application).
5296
- @method adapterFor
5297
- @public
5298
- @param {String} modelName
5299
- @return Adapter
5300
- */
5301
- adapterFor(modelName) {
5302
- if (macroCondition(getOwnConfig().env.DEBUG)) {
5303
- assertDestroyingStore(this, 'adapterFor');
5392
+ let managed = recordArrayManager._keyedArrays.get(identifier.lid);
5393
+ if (!managed) {
5394
+ managed = recordArrayManager.createArray({
5395
+ type: identifier.lid,
5396
+ identifiers: document.data,
5397
+ doc: document
5398
+ });
5399
+ recordArrayManager._keyedArrays.set(identifier.lid, managed);
5400
+ const doc = new Document(store, identifier);
5401
+ doc.data = managed;
5402
+ doc.meta = document.meta;
5403
+ doc.links = document.links;
5404
+ store._documentCache.set(identifier, doc);
5405
+ return options.shouldHydrate ? doc : document;
5406
+ } else {
5407
+ const doc = store._documentCache.get(identifier);
5408
+ if (!isFromCache) {
5409
+ recordArrayManager.populateManagedArray(managed, document.data, document);
5410
+ doc.data = managed;
5411
+ doc.meta = document.meta;
5412
+ doc.links = document.links;
5413
+ }
5414
+ return options.shouldHydrate ? doc : document;
5304
5415
  }
5305
- assert(`You need to pass a model name to the store's adapterFor method`, modelName);
5306
- assert(`Passing classes to store.adapterFor has been removed. Please pass a dasherized string instead of ${modelName}`, typeof modelName === 'string');
5307
- let normalizedModelName = normalizeModelName(modelName);
5308
- let {
5309
- _adapterCache
5310
- } = this;
5311
- let adapter = _adapterCache[normalizedModelName];
5312
- if (adapter) {
5313
- return adapter;
5416
+ } else {
5417
+ if (!identifier && !options.shouldHydrate) {
5418
+ return document;
5314
5419
  }
5315
- const owner = getOwner(this);
5316
-
5317
- // name specific adapter
5318
- adapter = owner.lookup(`adapter:${normalizedModelName}`);
5319
- if (adapter !== undefined) {
5320
- _adapterCache[normalizedModelName] = adapter;
5321
- return adapter;
5420
+ const data = document.data ? store.peekRecord(document.data) : null;
5421
+ let doc;
5422
+ if (identifier) {
5423
+ doc = store._documentCache.get(identifier);
5322
5424
  }
5323
-
5324
- // no adapter found for the specific name, fallback and check for application adapter
5325
- adapter = _adapterCache.application || owner.lookup('adapter:application');
5326
- if (adapter !== undefined) {
5327
- _adapterCache[normalizedModelName] = adapter;
5328
- _adapterCache.application = adapter;
5329
- return adapter;
5425
+ if (!doc) {
5426
+ doc = new Document(store, identifier);
5427
+ doc.data = data;
5428
+ copyDocumentProperties(doc, document);
5429
+ if (identifier) {
5430
+ store._documentCache.set(identifier, doc);
5431
+ }
5432
+ } else if (!isFromCache) {
5433
+ doc.data = data;
5434
+ copyDocumentProperties(doc, document);
5330
5435
  }
5331
- assert(`No adapter was found for '${modelName}' and no 'application' adapter was found as a fallback.`);
5436
+ return options.shouldHydrate ? doc : document;
5332
5437
  }
5333
-
5334
- /**
5335
- Returns an instance of the serializer for a given type. For
5336
- example, `serializerFor('person')` will return an instance of
5337
- `App.PersonSerializer`.
5338
- If no `App.PersonSerializer` is found, this method will look
5339
- for an `App.ApplicationSerializer` (the default serializer for
5340
- your entire application).
5341
- If a serializer cannot be found on the adapter, it will fall back
5342
- to an instance of `JSONSerializer`.
5343
- @method serializerFor
5344
- @public
5345
- @param {String} modelName the record to serialize
5346
- @return {Serializer}
5347
- */
5348
- serializerFor(modelName) {
5349
- if (macroCondition(getOwnConfig().env.DEBUG)) {
5350
- assertDestroyingStore(this, 'serializerFor');
5351
- }
5352
- assert(`You need to pass a model name to the store's serializerFor method`, modelName);
5353
- assert(`Passing classes to store.serializerFor has been removed. Please pass a dasherized string instead of ${modelName}`, typeof modelName === 'string');
5354
- let normalizedModelName = normalizeModelName(modelName);
5355
- let {
5356
- _serializerCache
5357
- } = this;
5358
- let serializer = _serializerCache[normalizedModelName];
5359
- if (serializer) {
5360
- return serializer;
5361
- }
5362
-
5363
- // by name
5364
- const owner = getOwner(this);
5365
- serializer = owner.lookup(`serializer:${normalizedModelName}`);
5366
- if (serializer !== undefined) {
5367
- _serializerCache[normalizedModelName] = serializer;
5368
- return serializer;
5438
+ }
5439
+ function calcShouldFetch(store, request, hasCachedValue, identifier) {
5440
+ const {
5441
+ cacheOptions
5442
+ } = request;
5443
+ return request.op && MUTATION_OPS.has(request.op) || cacheOptions?.reload || !hasCachedValue || (store.lifetimes && identifier ? store.lifetimes.isHardExpired(identifier, store) : false);
5444
+ }
5445
+ function calcShouldBackgroundFetch(store, request, willFetch, identifier) {
5446
+ const {
5447
+ cacheOptions
5448
+ } = request;
5449
+ return !willFetch && (cacheOptions?.backgroundReload || (store.lifetimes && identifier ? store.lifetimes.isSoftExpired(identifier, store) : false));
5450
+ }
5451
+ function isMutation(request) {
5452
+ return Boolean(request.op && MUTATION_OPS.has(request.op));
5453
+ }
5454
+ function fetchContentAndHydrate(next, context, identifier, shouldFetch, shouldBackgroundFetch) {
5455
+ const {
5456
+ store
5457
+ } = context.request;
5458
+ const shouldHydrate = context.request[EnableHydration] || false;
5459
+ let isMut = false;
5460
+ if (isMutation(context.request)) {
5461
+ isMut = true;
5462
+ // TODO should we handle multiple records in request.records by iteratively calling willCommit for each
5463
+ const record = context.request.data?.record || context.request.records?.[0];
5464
+ assert(`Expected to receive a list of records included in the ${context.request.op} request`, record);
5465
+ store.cache.willCommit(record, context);
5466
+ }
5467
+ if (store.lifetimes?.willRequest) {
5468
+ store.lifetimes.willRequest(context.request, identifier, store);
5469
+ }
5470
+ const promise = next(context.request).then(document => {
5471
+ store.requestManager._pending.delete(context.id);
5472
+ store._enableAsyncFlush = true;
5473
+ let response;
5474
+ store._join(() => {
5475
+ if (isMutation(context.request)) {
5476
+ const record = context.request.data?.record || context.request.records?.[0];
5477
+ response = store.cache.didCommit(record, document);
5478
+ } else {
5479
+ response = store.cache.put(document);
5480
+ }
5481
+ response = maybeUpdateUiObjects(store, context.request, {
5482
+ shouldHydrate,
5483
+ shouldFetch,
5484
+ shouldBackgroundFetch,
5485
+ identifier
5486
+ }, response, false);
5487
+ });
5488
+ store._enableAsyncFlush = null;
5489
+ if (store.lifetimes?.didRequest) {
5490
+ store.lifetimes.didRequest(context.request, document.response, identifier, store);
5369
5491
  }
5370
-
5371
- // no serializer found for the specific model, fallback and check for application serializer
5372
- serializer = _serializerCache.application || owner.lookup('serializer:application');
5373
- if (serializer !== undefined) {
5374
- _serializerCache[normalizedModelName] = serializer;
5375
- _serializerCache.application = serializer;
5376
- return serializer;
5492
+ if (shouldFetch) {
5493
+ return response;
5494
+ } else if (shouldBackgroundFetch) {
5495
+ store.notifications._flush();
5377
5496
  }
5378
- return null;
5379
- }
5380
-
5381
- // @ts-expect-error
5382
- destroy() {
5383
- if (this.isDestroyed) {
5384
- // @ember/test-helpers will call destroy multiple times
5385
- return;
5497
+ }, error => {
5498
+ store.requestManager._pending.delete(context.id);
5499
+ if (context.request.signal?.aborted) {
5500
+ throw error;
5386
5501
  }
5387
- this.isDestroying = true;
5388
- // enqueue destruction of any adapters/serializers we have created
5389
- for (let adapterName in this._adapterCache) {
5390
- let adapter = this._adapterCache[adapterName];
5391
- if (typeof adapter.destroy === 'function') {
5392
- adapter.destroy();
5502
+ store.requestManager._pending.delete(context.id);
5503
+ store._enableAsyncFlush = true;
5504
+ let response;
5505
+ store._join(() => {
5506
+ if (isMutation(context.request)) {
5507
+ // TODO similar to didCommit we should spec this to be similar to cache.put for handling full response
5508
+ // currently we let the response remain undefiend.
5509
+ const errors = error && error.content && typeof error.content === 'object' && 'errors' in error.content && Array.isArray(error.content.errors) ? error.content.errors : undefined;
5510
+ const record = context.request.data?.record || context.request.records?.[0];
5511
+ store.cache.commitWasRejected(record, errors);
5512
+ // re-throw the original error to preserve `errors` property.
5513
+ throw error;
5514
+ } else {
5515
+ response = store.cache.put(error);
5516
+ response = maybeUpdateUiObjects(store, context.request, {
5517
+ shouldHydrate,
5518
+ shouldFetch,
5519
+ shouldBackgroundFetch,
5520
+ identifier
5521
+ }, response, false);
5393
5522
  }
5523
+ });
5524
+ store._enableAsyncFlush = null;
5525
+ if (identifier && store.lifetimes?.didRequest) {
5526
+ store.lifetimes.didRequest(context.request, error.response, identifier, store);
5394
5527
  }
5395
- for (let serializerName in this._serializerCache) {
5396
- let serializer = this._serializerCache[serializerName];
5397
- if (typeof serializer.destroy === 'function') {
5398
- serializer.destroy();
5399
- }
5528
+ if (!shouldBackgroundFetch) {
5529
+ const newError = cloneError(error);
5530
+ newError.content = response;
5531
+ throw newError;
5532
+ } else {
5533
+ store.notifications._flush();
5400
5534
  }
5401
- this._graph?.destroy();
5402
- this._graph = undefined;
5403
- this.notifications.destroy();
5404
- this.recordArrayManager.destroy();
5405
- this.identifierCache.destroy();
5406
- this.unloadAll();
5407
- this.isDestroyed = true;
5408
- }
5409
- static create(args) {
5410
- return new this(args);
5535
+ });
5536
+ if (!isMut) {
5537
+ return promise;
5411
5538
  }
5539
+ assert(`Expected a mutation`, isMutation(context.request));
5540
+
5541
+ // for mutations we need to enqueue the promise with the requestStateService
5542
+ // TODO should we enque a request per record in records?
5543
+ const record = context.request.data?.record || context.request.records?.[0];
5544
+ return store._requestCache._enqueue(promise, {
5545
+ data: [{
5546
+ op: 'saveRecord',
5547
+ recordIdentifier: record,
5548
+ options: undefined
5549
+ }]
5550
+ });
5412
5551
  }
5413
- let assertDestroyingStore;
5414
- let assertDestroyedStoreOnly;
5415
- if (macroCondition(getOwnConfig().env.DEBUG)) {
5416
- // eslint-disable-next-line @typescript-eslint/no-shadow
5417
- assertDestroyingStore = function assertDestroyingStore(store, method) {
5418
- assert(`Attempted to call store.${method}(), but the store instance has already been destroyed.`, !(store.isDestroying || store.isDestroyed));
5419
- };
5420
- // eslint-disable-next-line @typescript-eslint/no-shadow
5421
- assertDestroyedStoreOnly = function assertDestroyedStoreOnly(store, method) {
5422
- assert(`Attempted to call store.${method}(), but the store instance has already been destroyed.`, !store.isDestroyed);
5423
- };
5424
- }
5425
- function isMaybeIdentifier(maybeIdentifier) {
5426
- return Boolean(maybeIdentifier !== null && typeof maybeIdentifier === 'object' && ('id' in maybeIdentifier && 'type' in maybeIdentifier && maybeIdentifier.id && maybeIdentifier.type || maybeIdentifier.lid));
5552
+ function cloneError(error) {
5553
+ const cloned = new Error(error.message);
5554
+ cloned.stack = error.stack;
5555
+ cloned.error = error.error;
5556
+ return cloned;
5427
5557
  }
5428
- function normalizeProperties(store, identifier, properties) {
5429
- // assert here
5430
- if (properties !== undefined) {
5431
- if ('id' in properties) {
5432
- assert(`expected id to be a string or null`, properties.id !== undefined);
5558
+ const CacheHandler = {
5559
+ request(context, next) {
5560
+ // if we have no cache or no cache-key skip cache handling
5561
+ if (!context.request.store || context.request.cacheOptions?.[SkipCache]) {
5562
+ return next(context.request);
5433
5563
  }
5434
- assert(`You passed '${typeof properties}' as properties for record creation instead of an object.`, typeof properties === 'object' && properties !== null);
5435
5564
  const {
5436
- type
5437
- } = identifier;
5565
+ store
5566
+ } = context.request;
5567
+ const identifier = store.identifierCache.getOrCreateDocumentIdentifier(context.request);
5568
+ const peeked = identifier ? store.cache.peekRequest(identifier) : null;
5438
5569
 
5439
- // convert relationship Records to RecordDatas before passing to RecordData
5440
- let defs = store.getSchemaDefinitionService().relationshipsDefinitionFor({
5441
- type
5442
- });
5443
- if (defs !== null) {
5444
- let keys = Object.keys(properties);
5445
- let relationshipValue;
5446
- for (let i = 0; i < keys.length; i++) {
5447
- let prop = keys[i];
5448
- let def = defs[prop];
5449
- if (def !== undefined) {
5450
- if (def.kind === 'hasMany') {
5451
- if (macroCondition(getOwnConfig().env.DEBUG)) {
5452
- assertRecordsPassedToHasMany(properties[prop]);
5453
- }
5454
- relationshipValue = extractIdentifiersFromRecords(properties[prop]);
5455
- } else {
5456
- relationshipValue = extractIdentifierFromRecord(properties[prop]);
5457
- }
5458
- properties[prop] = relationshipValue;
5459
- }
5460
- }
5570
+ // determine if we should skip cache
5571
+ if (calcShouldFetch(store, context.request, !!peeked, identifier)) {
5572
+ return fetchContentAndHydrate(next, context, identifier, true, false);
5573
+ }
5574
+
5575
+ // if we have not skipped cache, determine if we should update behind the scenes
5576
+ if (calcShouldBackgroundFetch(store, context.request, false, identifier)) {
5577
+ const promise = fetchContentAndHydrate(next, context, identifier, false, true);
5578
+ store.requestManager._pending.set(context.id, promise);
5579
+ }
5580
+ const shouldHydrate = context.request[EnableHydration] || false;
5581
+ if ('error' in peeked) {
5582
+ const content = shouldHydrate ? maybeUpdateUiObjects(store, context.request, {
5583
+ shouldHydrate,
5584
+ identifier
5585
+ }, peeked.content, true) : peeked.content;
5586
+ const newError = cloneError(peeked);
5587
+ newError.content = content;
5588
+ throw newError;
5461
5589
  }
5590
+ return Promise.resolve(shouldHydrate ? maybeUpdateUiObjects(store, context.request, {
5591
+ shouldHydrate,
5592
+ identifier
5593
+ }, peeked.content, true) : peeked.content);
5462
5594
  }
5463
- return properties;
5464
- }
5465
- function assertRecordsPassedToHasMany(records) {
5466
- assert(`You must pass an array of records to set a hasMany relationship`, Array.isArray(records));
5467
- assert(`All elements of a hasMany relationship must be instances of Model, you passed ${records.map(r => `${typeof r}`).join(', ')}`, function () {
5468
- return records.every(record => {
5469
- try {
5470
- recordIdentifierFor(record);
5471
- return true;
5472
- } catch {
5473
- return false;
5474
- }
5475
- });
5476
- }());
5477
- }
5478
- function extractIdentifiersFromRecords(records) {
5479
- return records.map(record => extractIdentifierFromRecord(record));
5480
- }
5481
- function extractIdentifierFromRecord(recordOrPromiseRecord) {
5482
- if (!recordOrPromiseRecord) {
5483
- return null;
5595
+ };
5596
+ function copyDocumentProperties(target, source) {
5597
+ if ('links' in source) {
5598
+ target.links = source.links;
5599
+ }
5600
+ if ('meta' in source) {
5601
+ target.meta = source.meta;
5602
+ }
5603
+ if ('errors' in source) {
5604
+ target.errors = source.errors;
5484
5605
  }
5485
- const extract = recordIdentifierFor;
5486
- return extract(recordOrPromiseRecord);
5487
5606
  }
5488
- export { CacheHandler as C, IdentifierArray as I, MUTATE as M, RecordArrayManager as R, Store as S, _clearCaches as _, setIdentifierGenerationMethod as a, setIdentifierUpdateMethod as b, setIdentifierForgetMethod as c, setIdentifierResetMethod as d, coerceId as e, Collection as f, SOURCE as g, IDENTIFIER_ARRAY_TAG as h, isStableIdentifier as i, fastPush as j, removeRecordDataFor as k, setRecordIdentifier as l, StoreMap as m, notifyArray as n, setCacheFor as o, peekCache as p, recordIdentifierFor as r, storeFor as s };
5607
+ export { ARRAY_SIGNAL as A, CacheHandler as C, IdentifierArray as I, MUTATE as M, RecordArrayManager as R, Store as S, _clearCaches as _, setIdentifierGenerationMethod as a, setIdentifierUpdateMethod as b, setIdentifierForgetMethod as c, setIdentifierResetMethod as d, coerceId as e, Collection as f, SOURCE as g, fastPush as h, isStableIdentifier as i, removeRecordDataFor as j, setRecordIdentifier as k, StoreMap as l, setCacheFor as m, notifyArray as n, normalizeModelName as o, peekCache as p, recordIdentifierFor as r, storeFor as s };