@ember-data/store 4.12.0-beta.8 → 4.12.0-beta.9

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.
@@ -35,36 +35,225 @@ import ObjectProxy from '@ember/object/proxy';
35
35
  function normalizeModelName$1(modelName) {
36
36
  return dasherize(modelName);
37
37
  }
38
- function getHydratedContent(store, request, document) {
38
+ function _initializerDefineProperty(target, property, descriptor, context) {
39
+ if (!descriptor) return;
40
+ Object.defineProperty(target, property, {
41
+ enumerable: descriptor.enumerable,
42
+ configurable: descriptor.configurable,
43
+ writable: descriptor.writable,
44
+ value: descriptor.initializer ? descriptor.initializer.call(context) : void 0
45
+ });
46
+ }
47
+ function _classPrivateFieldBase(receiver, privateKey) {
48
+ if (!Object.prototype.hasOwnProperty.call(receiver, privateKey)) {
49
+ throw new TypeError("attempted to use private field on non-instance");
50
+ }
51
+ return receiver;
52
+ }
53
+ var id = 0;
54
+ function _classPrivateFieldKey(name) {
55
+ return "__private_" + id++ + "_" + name;
56
+ }
57
+ function _applyDecoratedDescriptor(target, property, decorators, descriptor, context) {
58
+ var desc = {};
59
+ Object.keys(descriptor).forEach(function (key) {
60
+ desc[key] = descriptor[key];
61
+ });
62
+ desc.enumerable = !!desc.enumerable;
63
+ desc.configurable = !!desc.configurable;
64
+ if ('value' in desc || desc.initializer) {
65
+ desc.writable = true;
66
+ }
67
+ desc = decorators.slice().reverse().reduce(function (desc, decorator) {
68
+ return decorator(target, property, desc) || desc;
69
+ }, desc);
70
+ if (context && desc.initializer !== void 0) {
71
+ desc.value = desc.initializer ? desc.initializer.call(context) : void 0;
72
+ desc.initializer = undefined;
73
+ }
74
+ if (desc.initializer === void 0) {
75
+ Object.defineProperty(target, property, desc);
76
+ desc = null;
77
+ }
78
+ return desc;
79
+ }
80
+ var _class$3, _descriptor$3, _descriptor2$1, _descriptor3, _descriptor4, _store$1, _request;
81
+ function urlFromLink(link) {
82
+ if (typeof link === 'string') return link;
83
+ return link.href;
84
+ }
85
+ let Document = (_class$3 = (_store$1 = /*#__PURE__*/_classPrivateFieldKey("store"), _request = /*#__PURE__*/_classPrivateFieldKey("request"), class Document {
86
+ constructor(store, identifier) {
87
+ Object.defineProperty(this, _request, {
88
+ value: _request2
89
+ });
90
+ _initializerDefineProperty(this, "links", _descriptor$3, this);
91
+ _initializerDefineProperty(this, "data", _descriptor2$1, this);
92
+ _initializerDefineProperty(this, "errors", _descriptor3, this);
93
+ _initializerDefineProperty(this, "meta", _descriptor4, this);
94
+ Object.defineProperty(this, _store$1, {
95
+ writable: true,
96
+ value: void 0
97
+ });
98
+ _classPrivateFieldBase(this, _store$1)[_store$1] = store;
99
+ this.identifier = identifier;
100
+ }
101
+ fetch(options = {}) {
102
+ assert(`No self link`, this.links?.self);
103
+ options.cacheOptions = options.cacheOptions || {};
104
+ options.cacheOptions.key = this.identifier?.lid;
105
+ return _classPrivateFieldBase(this, _request)[_request]('self', options);
106
+ }
107
+ next(options) {
108
+ return _classPrivateFieldBase(this, _request)[_request]('next', options);
109
+ }
110
+ prev(options) {
111
+ return _classPrivateFieldBase(this, _request)[_request]('prev', options);
112
+ }
113
+ first(options) {
114
+ return _classPrivateFieldBase(this, _request)[_request]('first', options);
115
+ }
116
+ last(options) {
117
+ return _classPrivateFieldBase(this, _request)[_request]('last', options);
118
+ }
119
+ toJSON() {
120
+ const data = {};
121
+ data.identifier = this.identifier;
122
+ if (this.data !== undefined) {
123
+ data.data = this.data;
124
+ }
125
+ if (this.links !== undefined) {
126
+ data.links = this.links;
127
+ }
128
+ if (this.errors !== undefined) {
129
+ data.errors = this.errors;
130
+ }
131
+ if (this.meta !== undefined) {
132
+ data.meta = this.meta;
133
+ }
134
+ return data;
135
+ }
136
+ }), (_descriptor$3 = _applyDecoratedDescriptor(_class$3.prototype, "links", [tracked], {
137
+ configurable: true,
138
+ enumerable: true,
139
+ writable: true,
140
+ initializer: null
141
+ }), _descriptor2$1 = _applyDecoratedDescriptor(_class$3.prototype, "data", [tracked], {
142
+ configurable: true,
143
+ enumerable: true,
144
+ writable: true,
145
+ initializer: null
146
+ }), _descriptor3 = _applyDecoratedDescriptor(_class$3.prototype, "errors", [tracked], {
147
+ configurable: true,
148
+ enumerable: true,
149
+ writable: true,
150
+ initializer: null
151
+ }), _descriptor4 = _applyDecoratedDescriptor(_class$3.prototype, "meta", [tracked], {
152
+ configurable: true,
153
+ enumerable: true,
154
+ writable: true,
155
+ initializer: null
156
+ })), _class$3);
157
+ async function _request2(link, options = {}) {
158
+ const href = this.links?.[link];
159
+ if (!href) {
160
+ return null;
161
+ }
162
+ const response = await _classPrivateFieldBase(this, _store$1)[_store$1].request(Object.assign(options, {
163
+ url: urlFromLink(href)
164
+ }));
165
+ return response.content;
166
+ }
167
+ function isErrorDocument(document) {
168
+ return 'errors' in document;
169
+ }
170
+ function maybeUpdateUiObjects(store, request, options, document, isFromCache) {
171
+ const {
172
+ identifier
173
+ } = options;
174
+ if (isErrorDocument(document)) {
175
+ if (!identifier && !options.shouldHydrate) {
176
+ return document;
177
+ }
178
+ let doc;
179
+ if (identifier) {
180
+ doc = store._documentCache.get(identifier);
181
+ }
182
+ if (!doc) {
183
+ doc = new Document(store, identifier);
184
+ copyDocumentProperties(doc, document);
185
+ if (identifier) {
186
+ store._documentCache.set(identifier, doc);
187
+ }
188
+ } else if (!isFromCache) {
189
+ doc.data = undefined;
190
+ copyDocumentProperties(doc, document);
191
+ }
192
+ return options.shouldHydrate ? doc : document;
193
+ }
39
194
  if (Array.isArray(document.data)) {
40
- const {
41
- lid
42
- } = document;
43
195
  const {
44
196
  recordArrayManager
45
197
  } = store;
46
- if (!lid) {
47
- return recordArrayManager.createArray({
198
+ if (!identifier) {
199
+ if (!options.shouldHydrate) {
200
+ return document;
201
+ }
202
+ const data = recordArrayManager.createArray({
48
203
  identifiers: document.data,
49
204
  doc: document,
50
205
  query: request
51
206
  });
207
+ const doc = new Document(store, null);
208
+ doc.data = data;
209
+ doc.meta = document.meta;
210
+ doc.links = document.links;
211
+ return doc;
52
212
  }
53
- let managed = recordArrayManager._keyedArrays.get(lid);
213
+ let managed = recordArrayManager._keyedArrays.get(identifier.lid);
54
214
  if (!managed) {
55
215
  managed = recordArrayManager.createArray({
56
216
  identifiers: document.data,
57
217
  doc: document
58
218
  });
59
- recordArrayManager._keyedArrays.set(lid, managed);
219
+ recordArrayManager._keyedArrays.set(identifier.lid, managed);
220
+ const doc = new Document(store, identifier);
221
+ doc.data = managed;
222
+ doc.meta = document.meta;
223
+ doc.links = document.links;
224
+ store._documentCache.set(identifier, doc);
225
+ return options.shouldHydrate ? doc : document;
60
226
  } else {
61
- recordArrayManager.populateManagedArray(managed, document.data, document);
227
+ const doc = store._documentCache.get(identifier);
228
+ if (!isFromCache) {
229
+ recordArrayManager.populateManagedArray(managed, document.data, document);
230
+ doc.data = managed;
231
+ doc.meta = document.meta;
232
+ doc.links = document.links;
233
+ }
234
+ return options.shouldHydrate ? doc : document;
62
235
  }
63
- return managed;
64
236
  } else {
65
- return Object.assign({}, document, {
66
- data: document.data ? store.peekRecord(document.data) : null
67
- });
237
+ if (!identifier && !options.shouldHydrate) {
238
+ return document;
239
+ }
240
+ const data = document.data ? store.peekRecord(document.data) : null;
241
+ let doc;
242
+ if (identifier) {
243
+ doc = store._documentCache.get(identifier);
244
+ }
245
+ if (!doc) {
246
+ doc = new Document(store, identifier);
247
+ doc.data = data;
248
+ copyDocumentProperties(doc, document);
249
+ if (identifier) {
250
+ store._documentCache.set(identifier, doc);
251
+ }
252
+ } else if (!isFromCache) {
253
+ doc.data = data;
254
+ copyDocumentProperties(doc, document);
255
+ }
256
+ return options.shouldHydrate ? doc : document;
68
257
  }
69
258
  }
70
259
  function calcShouldFetch(store, request, hasCachedValue, identifier) {
@@ -79,37 +268,63 @@ function calcShouldBackgroundFetch(store, request, willFetch, identifier) {
79
268
  } = request;
80
269
  return !willFetch && (cacheOptions?.backgroundReload || (store.lifetimes && identifier ? store.lifetimes.isSoftExpired(identifier) : false));
81
270
  }
82
- function fetchContentAndHydrate(next, context, shouldFetch, shouldBackgroundFetch) {
271
+ function fetchContentAndHydrate(next, context, identifier, shouldFetch, shouldBackgroundFetch) {
83
272
  const {
84
273
  store
85
274
  } = context.request;
86
275
  const shouldHydrate = context.request[Symbol.for('ember-data:enable-hydration')] || false;
87
276
  return next(context.request).then(document => {
277
+ store.requestManager._pending.delete(context.id);
88
278
  store._enableAsyncFlush = true;
89
279
  let response;
90
280
  store._join(() => {
91
281
  response = store.cache.put(document);
92
- if (shouldFetch && shouldHydrate) {
93
- response = getHydratedContent(store, context.request, response);
94
- }
282
+ response = maybeUpdateUiObjects(store, context.request, {
283
+ shouldHydrate,
284
+ shouldFetch,
285
+ shouldBackgroundFetch,
286
+ identifier
287
+ }, response, false);
95
288
  });
96
289
  store._enableAsyncFlush = null;
97
290
  if (shouldFetch) {
98
291
  return response;
292
+ } else if (shouldBackgroundFetch) {
293
+ store.notifications._flush();
99
294
  }
100
295
  }, error => {
296
+ store.requestManager._pending.delete(context.id);
297
+ if (context.request.signal?.aborted) {
298
+ throw error;
299
+ }
300
+ store.requestManager._pending.delete(context.id);
101
301
  store._enableAsyncFlush = true;
302
+ let response;
102
303
  store._join(() => {
103
- store.cache.put(error);
304
+ response = store.cache.put(error);
305
+ response = maybeUpdateUiObjects(store, context.request, {
306
+ shouldHydrate,
307
+ shouldFetch,
308
+ shouldBackgroundFetch,
309
+ identifier
310
+ }, response, false);
104
311
  });
105
312
  store._enableAsyncFlush = null;
106
-
107
- // TODO @runspired this is probably not the right thing to throw so make sure we add a test
108
313
  if (!shouldBackgroundFetch) {
109
- throw error;
314
+ const newError = cloneError(error);
315
+ newError.content = response;
316
+ throw newError;
317
+ } else {
318
+ store.notifications._flush();
110
319
  }
111
320
  });
112
321
  }
322
+ function cloneError(error) {
323
+ const cloned = new Error(error.message);
324
+ cloned.stack = error.stack;
325
+ cloned.error = error.error;
326
+ return cloned;
327
+ }
113
328
  const SkipCache = Symbol.for('ember-data:skip-cache');
114
329
  const EnableHydration = Symbol.for('ember-data:enable-hydration');
115
330
  const CacheHandler = {
@@ -126,20 +341,41 @@ const CacheHandler = {
126
341
 
127
342
  // determine if we should skip cache
128
343
  if (calcShouldFetch(store, context.request, !!peeked, identifier)) {
129
- return fetchContentAndHydrate(next, context, true, false);
344
+ return fetchContentAndHydrate(next, context, identifier, true, false);
130
345
  }
131
346
 
132
347
  // if we have not skipped cache, determine if we should update behind the scenes
133
348
  if (calcShouldBackgroundFetch(store, context.request, false, identifier)) {
134
- void fetchContentAndHydrate(next, context, false, true);
135
- }
136
- if ('error' in peeked) {
137
- throw peeked.error;
349
+ let promise = fetchContentAndHydrate(next, context, identifier, false, true);
350
+ store.requestManager._pending.set(context.id, promise);
138
351
  }
139
352
  const shouldHydrate = context.request[EnableHydration] || false;
140
- return Promise.resolve(shouldHydrate ? getHydratedContent(store, context.request, peeked.content) : peeked.content);
353
+ if ('error' in peeked) {
354
+ const content = shouldHydrate ? maybeUpdateUiObjects(store, context.request, {
355
+ shouldHydrate,
356
+ identifier
357
+ }, peeked.content, true) : peeked.content;
358
+ const newError = cloneError(peeked);
359
+ newError.content = content;
360
+ throw newError;
361
+ }
362
+ return Promise.resolve(shouldHydrate ? maybeUpdateUiObjects(store, context.request, {
363
+ shouldHydrate,
364
+ identifier
365
+ }, peeked.content, true) : peeked.content);
141
366
  }
142
367
  };
368
+ function copyDocumentProperties(target, source) {
369
+ if ('links' in source) {
370
+ target.links = source.links;
371
+ }
372
+ if ('meta' in source) {
373
+ target.meta = source.meta;
374
+ }
375
+ if ('errors' in source) {
376
+ target.errors = source.errors;
377
+ }
378
+ }
143
379
 
144
380
  /*
145
381
  * Returns the Cache instance associated with a given
@@ -848,38 +1084,6 @@ function detectMerge(typesCache, identifier, data, newId, lids) {
848
1084
  }
849
1085
  return false;
850
1086
  }
851
- function _initializerDefineProperty(target, property, descriptor, context) {
852
- if (!descriptor) return;
853
- Object.defineProperty(target, property, {
854
- enumerable: descriptor.enumerable,
855
- configurable: descriptor.configurable,
856
- writable: descriptor.writable,
857
- value: descriptor.initializer ? descriptor.initializer.call(context) : void 0
858
- });
859
- }
860
- function _applyDecoratedDescriptor(target, property, decorators, descriptor, context) {
861
- var desc = {};
862
- Object.keys(descriptor).forEach(function (key) {
863
- desc[key] = descriptor[key];
864
- });
865
- desc.enumerable = !!desc.enumerable;
866
- desc.configurable = !!desc.configurable;
867
- if ('value' in desc || desc.initializer) {
868
- desc.writable = true;
869
- }
870
- desc = decorators.slice().reverse().reduce(function (desc, decorator) {
871
- return decorator(target, property, desc) || desc;
872
- }, desc);
873
- if (context && desc.initializer !== void 0) {
874
- desc.value = desc.initializer ? desc.initializer.call(context) : void 0;
875
- desc.initializer = undefined;
876
- }
877
- if (desc.initializer === void 0) {
878
- Object.defineProperty(target, property, desc);
879
- desc = null;
880
- }
881
- return desc;
882
- }
883
1087
  let tokenId = 0;
884
1088
  const CacheOperations = new Set(['added', 'removed', 'state', 'updated']);
885
1089
  function isCacheOperationValue(value) {
@@ -907,16 +1111,12 @@ function unsubscribe(token) {
907
1111
  }
908
1112
  }
909
1113
 
910
- /*
911
- Currently only support a single callback per identifier
912
- */
913
-
914
1114
  /**
915
1115
  * The NotificationManager provides the ability to subscribe to
916
1116
  * changes to Cache state.
917
1117
  *
918
1118
  * This Feature is what allows EmberData to create subscriptions that
919
- * work with any framework or change notification system.
1119
+ * work with any framework or change-notification system.
920
1120
  *
921
1121
  * @class NotificationManager
922
1122
  * @public
@@ -930,20 +1130,30 @@ class NotificationManager {
930
1130
  }
931
1131
 
932
1132
  /**
933
- * Subscribe to changes for a given resource identifier
1133
+ * Subscribe to changes for a given resource identifier, resource addition/removal, or document addition/removal.
934
1134
  *
935
1135
  * ```ts
936
- * interface NotificationCallback {
1136
+ * export type CacheOperation = 'added' | 'removed' | 'updated' | 'state';
1137
+ *
1138
+ * export interface NotificationCallback {
937
1139
  * (identifier: StableRecordIdentifier, notificationType: 'attributes' | 'relationships', key?: string): void;
938
1140
  * (identifier: StableRecordIdentifier, notificationType: 'errors' | 'meta' | 'identity' | 'state'): void;
939
1141
  * (identifier: StableRecordIdentifier, notificationType: NotificationType, key?: string): void;
940
1142
  * }
1143
+ * export interface ResourceOperationCallback {
1144
+ * // resource updates
1145
+ * (identifier: StableRecordIdentifier, notificationType: CacheOperation): void;
1146
+ * }
1147
+ * export interface DocumentOperationCallback {
1148
+ * // document updates
1149
+ * (identifier: StableDocumentIdentifier, notificationType: CacheOperation): void;
1150
+ * }
941
1151
  * ```
942
1152
  *
943
1153
  * @method subscribe
944
1154
  * @public
945
- * @param {StableRecordIdentifier} identifier
946
- * @param {NotificationCallback} callback
1155
+ * @param {StableDocumentIdentifier | StableRecordIdentifier | 'resource' | 'document'} identifier
1156
+ * @param {NotificationCallback | ResourceOperationCallback | DocumentOperationCallback} callback
947
1157
  * @returns {UnsubscribeToken} an opaque token to be used with unsubscribe
948
1158
  */
949
1159
 
@@ -1266,16 +1476,6 @@ let RecordReference = (_class$2 = class RecordReference {
1266
1476
  return 0;
1267
1477
  }
1268
1478
  }), _class$2);
1269
- function _classPrivateFieldBase(receiver, privateKey) {
1270
- if (!Object.prototype.hasOwnProperty.call(receiver, privateKey)) {
1271
- throw new TypeError("attempted to use private field on non-instance");
1272
- }
1273
- return receiver;
1274
- }
1275
- var id = 0;
1276
- function _classPrivateFieldKey(name) {
1277
- return "__private_" + id++ + "_" + name;
1278
- }
1279
1479
  function legacyCachePut(store, doc) {
1280
1480
  const jsonApiDoc = doc.content;
1281
1481
  let ret;
@@ -1397,7 +1597,6 @@ class NonSingletonCacheManager {
1397
1597
  }
1398
1598
  // Cache Management
1399
1599
  // ================
1400
-
1401
1600
  /**
1402
1601
  * Cache the response to a request
1403
1602
  *
@@ -2575,7 +2774,7 @@ class LegacyWrapper {
2575
2774
  assert(`Expected a stable identifier`, isStableIdentifier(type));
2576
2775
  identifier = type;
2577
2776
  }
2578
- const cache = macroCondition(getOwnConfig().deprecations.DEPRECATE_CREATE_RECORD_DATA_FOR_HOOK) ? this._store._instanceCache.getResourceCache(identifier) : this._store.cache;
2777
+ const cache = macroCondition(getOwnConfig().deprecations.DEPRECATE_V1_RECORD_DATA) ? this._store._instanceCache.getResourceCache(identifier) : this._store.cache;
2579
2778
  if (macroCondition(getOwnConfig().deprecations.DEPRECATE_V1CACHE_STORE_APIS)) {
2580
2779
  if (!id && !lid && typeof type === 'string') {
2581
2780
  cache.clientDidCreate(identifier);
@@ -2710,7 +2909,7 @@ class V2CacheStoreWrapper {
2710
2909
  return this._store.getSchemaDefinitionService();
2711
2910
  }
2712
2911
  recordDataFor(identifier) {
2713
- if (macroCondition(getOwnConfig().deprecations.DEPRECATE_CREATE_RECORD_DATA_FOR_HOOK)) {
2912
+ if (macroCondition(getOwnConfig().deprecations.DEPRECATE_V1_RECORD_DATA)) {
2714
2913
  deprecate(`StoreWrapper.recordDataFor is deprecated. With Singleton Cache, this method is no longer needed as the caller is its own cache reference.`, false, {
2715
2914
  for: '@ember-data/store',
2716
2915
  id: 'ember-data:deprecate-record-data-for',
@@ -2722,7 +2921,7 @@ class V2CacheStoreWrapper {
2722
2921
  });
2723
2922
  }
2724
2923
  assert(`Expected a stable identifier`, isStableIdentifier(identifier));
2725
- return macroCondition(getOwnConfig().deprecations.DEPRECATE_CREATE_RECORD_DATA_FOR_HOOK) ? this._store._instanceCache.getResourceCache(identifier) : void 0;
2924
+ return macroCondition(getOwnConfig().deprecations.DEPRECATE_V1_RECORD_DATA) ? this._store._instanceCache.getResourceCache(identifier) : void 0;
2726
2925
  }
2727
2926
  setRecordId(identifier, id) {
2728
2927
  assert(`Expected a stable identifier`, isStableIdentifier(identifier));
@@ -2815,7 +3014,7 @@ class InstanceCache {
2815
3014
  };
2816
3015
  this.store = store;
2817
3016
  this._storeWrapper = new CacheStoreWrapper(this.store);
2818
- if (macroCondition(getOwnConfig().deprecations.DEPRECATE_CREATE_RECORD_DATA_FOR_HOOK)) {
3017
+ if (macroCondition(getOwnConfig().deprecations.DEPRECATE_V1_RECORD_DATA)) {
2819
3018
  this.__cacheFor = resource => {
2820
3019
  // TODO enforce strict
2821
3020
  const identifier = this.store.identifierCache.getOrCreateRecordIdentifier(resource);
@@ -2856,7 +3055,7 @@ class InstanceCache {
2856
3055
  record: staleIdentifier,
2857
3056
  value: keptIdentifier
2858
3057
  });
2859
- } else if (macroCondition(!getOwnConfig().deprecations.DEPRECATE_CREATE_RECORD_DATA_FOR_HOOK)) {
3058
+ } else if (macroCondition(!getOwnConfig().deprecations.DEPRECATE_V1_RECORD_DATA)) {
2860
3059
  this.store.cache.patch({
2861
3060
  op: 'mergeIdentifiers',
2862
3061
  record: staleIdentifier,
@@ -3498,7 +3697,6 @@ var _dec, _class$1, _descriptor$1;
3498
3697
  @extends Ember.ArrayProxy
3499
3698
  @uses Ember.PromiseProxyMixin
3500
3699
  */
3501
-
3502
3700
  let PromiseArray = (_dec = reads('content.meta'), (_class$1 = class PromiseArray extends PromiseArrayProxy {
3503
3701
  constructor(...args) {
3504
3702
  super(...args);
@@ -3638,6 +3836,10 @@ function convertToInt(prop) {
3638
3836
  return num % 1 === 0 ? num : null;
3639
3837
  }
3640
3838
  let Tag = (_class = class Tag {
3839
+ /*
3840
+ * whether this was part of a transaction when last mutated
3841
+ */
3842
+
3641
3843
  constructor() {
3642
3844
  _initializerDefineProperty(this, "ref", _descriptor, this);
3643
3845
  this.shouldReset = false;
@@ -3712,6 +3914,22 @@ let IdentifierArray = (_class3 = class IdentifierArray {
3712
3914
  [NOTIFY]() {
3713
3915
  notifyArray(this);
3714
3916
  }
3917
+
3918
+ /**
3919
+ The modelClass represented by this record array.
3920
+ @property type
3921
+ @public
3922
+ @deprecated
3923
+ @type {subclass of Model}
3924
+ */
3925
+
3926
+ /**
3927
+ The store that created this record array.
3928
+ @property store
3929
+ @private
3930
+ @type Store
3931
+ */
3932
+
3715
3933
  destroy() {
3716
3934
  this.isDestroying = true;
3717
3935
  // changing the reference breaks the Proxy
@@ -4710,12 +4928,23 @@ function sync(array, changes) {
4710
4928
  }
4711
4929
  }
4712
4930
 
4931
+ /**
4932
+ * @module @ember-data/store
4933
+ */
4713
4934
  const Touching = Symbol('touching');
4714
4935
  const RequestPromise = Symbol('promise');
4715
4936
  function hasRecordIdentifier(op) {
4716
4937
  return 'recordIdentifier' in op;
4717
4938
  }
4718
- class RequestCache {
4939
+
4940
+ /**
4941
+ * The RequestStateService is used to track the state of requests
4942
+ * for fetching or updating known resource identifies that are inflight.
4943
+ *
4944
+ * @class RequestStateService
4945
+ * @public
4946
+ */
4947
+ class RequestStateService {
4719
4948
  constructor(store) {
4720
4949
  this._pending = Object.create(null);
4721
4950
  this._done = new Map();
@@ -4727,7 +4956,7 @@ class RequestCache {
4727
4956
  _clearEntries(identifier) {
4728
4957
  this._done.delete(identifier);
4729
4958
  }
4730
- enqueue(promise, queryRequest) {
4959
+ _enqueue(promise, queryRequest) {
4731
4960
  let query = queryRequest.data[0];
4732
4961
  if (hasRecordIdentifier(query)) {
4733
4962
  let lid = query.recordIdentifier.lid;
@@ -4826,18 +5055,66 @@ class RequestCache {
4826
5055
  this._done.set(identifier, requests);
4827
5056
  });
4828
5057
  }
5058
+
5059
+ /**
5060
+ * Subscribe to requests for a given resource identity.
5061
+ *
5062
+ * The callback will receive the current state of the request.
5063
+ *
5064
+ * ```ts
5065
+ * interface RequestState {
5066
+ * state: 'pending' | 'fulfilled' | 'rejected';
5067
+ * type: 'query' | 'mutation';
5068
+ * request: Request;
5069
+ * response?: { data: unknown };
5070
+ * }
5071
+ * ```
5072
+ *
5073
+ * Note: It should be considered dangerous to use this API for more than simple
5074
+ * state derivation or debugging. The `request` and `response` properties are poorly
5075
+ * spec'd and may change unexpectedly when shifting what Handlers are in use or how
5076
+ * requests are issued from the Store.
5077
+ *
5078
+ * We expect to revisit this API in the near future as we continue to refine the
5079
+ * RequestManager ergonomics, as a simpler but more powerful direct integration
5080
+ * with the RequestManager for these purposes is likely to be a better long-term
5081
+ * design.
5082
+ *
5083
+ * @method subscribeForRecord
5084
+ * @public
5085
+ * @param {StableRecordIdentifier} identifier
5086
+ * @param {(state: RequestState) => void} callback
5087
+ */
4829
5088
  subscribeForRecord(identifier, callback) {
4830
5089
  if (!this._subscriptions[identifier.lid]) {
4831
5090
  this._subscriptions[identifier.lid] = [];
4832
5091
  }
4833
5092
  this._subscriptions[identifier.lid].push(callback);
4834
5093
  }
5094
+
5095
+ /**
5096
+ * Retrieve all active requests for a given resource identity.
5097
+ *
5098
+ * @method getPendingRequestsForRecord
5099
+ * @public
5100
+ * @param {StableRecordIdentifier} identifier
5101
+ * @returns {RequestState[]} an array of request states for any pending requests for the given identifier
5102
+ */
4835
5103
  getPendingRequestsForRecord(identifier) {
4836
5104
  if (this._pending[identifier.lid]) {
4837
5105
  return this._pending[identifier.lid];
4838
5106
  }
4839
5107
  return [];
4840
5108
  }
5109
+
5110
+ /**
5111
+ * Retrieve the last completed request for a given resource identity.
5112
+ *
5113
+ * @method getLastRequestForRecord
5114
+ * @public
5115
+ * @param {StableRecordIdentifier} identifier
5116
+ * @returns {RequestState | null} the state of the most recent request for the given identifier
5117
+ */
4841
5118
  getLastRequestForRecord(identifier) {
4842
5119
  let requests = this._done.get(identifier);
4843
5120
  if (requests) {
@@ -4854,6 +5131,27 @@ class RequestCache {
4854
5131
  // hello world
4855
5132
 
4856
5133
  let _Cache;
5134
+
5135
+ /**
5136
+ * A Store coordinates interaction between your application, a [Cache](https://api.emberjs.com/ember-data/release/classes/%3CInterface%3E%20Cache),
5137
+ * and sources of data (such as your API or a local persistence layer)
5138
+ * accessed via a [RequestManager](https://github.com/emberjs/data/tree/main/packages/request).
5139
+ *
5140
+ * ```app/services/store.js
5141
+ * import Store from '@ember-data/store';
5142
+ *
5143
+ * export default class extends Store {}
5144
+ * ```
5145
+ *
5146
+ * Most Ember applications will only have a single `Store` configured as a Service
5147
+ * in this manner. However, setting up multiple stores is possible, including using
5148
+ * each as a unique service.
5149
+ *
5150
+
5151
+ @class Store
5152
+ @public
5153
+ */
5154
+
4857
5155
  class Store {
4858
5156
  /**
4859
5157
  * Provides access to the NotificationManager associated
@@ -4879,6 +5177,80 @@ class Store {
4879
5177
  get schema() {
4880
5178
  return this.getSchemaDefinitionService();
4881
5179
  }
5180
+
5181
+ /**
5182
+ * Provides access to the IdentifierCache instance
5183
+ * for this store.
5184
+ *
5185
+ * The IdentifierCache can be used to generate or
5186
+ * retrieve a stable unique identifier for any resource.
5187
+ *
5188
+ * @property {IdentifierCache} identifierCache
5189
+ * @public
5190
+ */
5191
+
5192
+ /**
5193
+ * Provides access to the requestManager instance associated
5194
+ * with this Store instance.
5195
+ *
5196
+ * When using `ember-data` this property is automatically
5197
+ * set to an instance of `RequestManager`. When not using `ember-data`
5198
+ * you must configure this property yourself, either by declaring
5199
+ * it as a service or by initializing it.
5200
+ *
5201
+ * ```ts
5202
+ * import Store, { CacheHandler } from '@ember-data/store';
5203
+ * import RequestManager from '@ember-data/request';
5204
+ * import Fetch from '@ember/data/request/fetch';
5205
+ *
5206
+ * class extends Store {
5207
+ * constructor() {
5208
+ * super(...arguments);
5209
+ * this.requestManager = new RequestManager();
5210
+ * this.requestManager.use([Fetch]);
5211
+ * this.requestManager.useCache(CacheHandler);
5212
+ * }
5213
+ * }
5214
+ * ```
5215
+ *
5216
+ * @public
5217
+ * @property {RequestManager} requestManager
5218
+ */
5219
+
5220
+ /**
5221
+ * A Property which an App may set to provide a Lifetimes Service
5222
+ * to control when a cached request becomes stale.
5223
+ *
5224
+ * Note, when defined, these methods will only be invoked if a
5225
+ * cache key exists for the request, either because the request
5226
+ * contains `cacheOptions.key` or because the [IdentifierCache](/ember-data/release/classes/IdentifierCache)
5227
+ * was able to generate a key for the request using the configured
5228
+ * [generation method](/ember-data/release/functions/@ember-data%2Fstore/setIdentifierGenerationMethod).
5229
+ *
5230
+ * `isSoftExpired` will only be invoked if `isHardExpired` returns `false`.
5231
+ *
5232
+ * ```ts
5233
+ * store.lifetimes = {
5234
+ * // make the request and ignore the current cache state
5235
+ * isHardExpired(identifier: StableDocumentIdentifier): boolean {
5236
+ * return false;
5237
+ * }
5238
+ *
5239
+ * // make the request in the background if true, return cache state
5240
+ * isSoftExpired(identifier: StableDocumentIdentifier): boolean {
5241
+ * return false;
5242
+ * }
5243
+ * }
5244
+ * ```
5245
+ *
5246
+ * @public
5247
+ * @property {LivetimesService|undefined} lifetimes
5248
+ */
5249
+
5250
+ // Private
5251
+
5252
+ // DEBUG-only properties
5253
+
4882
5254
  /**
4883
5255
  @method init
4884
5256
  @private
@@ -4896,11 +5268,12 @@ class Store {
4896
5268
  });
4897
5269
 
4898
5270
  // private
4899
- this._requestCache = new RequestCache(this);
5271
+ this._requestCache = new RequestStateService(this);
4900
5272
  this._instanceCache = new InstanceCache(this);
4901
5273
  this._adapterCache = Object.create(null);
4902
5274
  this._serializerCache = Object.create(null);
4903
5275
  this._modelFactoryCache = Object.create(null);
5276
+ this._documentCache = new Map();
4904
5277
  }
4905
5278
  _run(cb) {
4906
5279
  assert(`EmberData should never encounter a nested run`, !this._cbs);
@@ -4952,6 +5325,7 @@ class Store {
4952
5325
  lids.forEach(lid => {
4953
5326
  all.push(...pending[lid].map(v => v[RequestPromise]));
4954
5327
  });
5328
+ this.requestManager._pending.forEach(v => all.push(v));
4955
5329
  const promise = Promise.allSettled(all);
4956
5330
  promise.length = all.length;
4957
5331
  return promise;