@ember-data-mirror/store 5.3.7 → 5.4.0-alpha.100

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (41) hide show
  1. package/addon-main.cjs +1 -1
  2. package/dist/-private.js +1 -1
  3. package/dist/{cache-handler-C5ilAUZ5.js → handler-Dj0xi5y4.js} +328 -249
  4. package/dist/handler-Dj0xi5y4.js.map +1 -0
  5. package/dist/index.js +1 -1
  6. package/package.json +13 -13
  7. package/unstable-preview-types/-private/cache-handler/handler.d.ts +64 -0
  8. package/unstable-preview-types/-private/cache-handler/handler.d.ts.map +1 -0
  9. package/unstable-preview-types/-private/{cache-handler.d.ts → cache-handler/types.d.ts} +26 -63
  10. package/unstable-preview-types/-private/cache-handler/types.d.ts.map +1 -0
  11. package/unstable-preview-types/-private/cache-handler/utils.d.ts +39 -0
  12. package/unstable-preview-types/-private/cache-handler/utils.d.ts.map +1 -0
  13. package/unstable-preview-types/-private/caches/cache-utils.d.ts +2 -2
  14. package/unstable-preview-types/-private/caches/identifier-cache.d.ts +3 -3
  15. package/unstable-preview-types/-private/caches/instance-cache.d.ts +4 -4
  16. package/unstable-preview-types/-private/document.d.ts +3 -3
  17. package/unstable-preview-types/-private/legacy-model-support/record-reference.d.ts +2 -2
  18. package/unstable-preview-types/-private/legacy-model-support/shim-model-class.d.ts +2 -2
  19. package/unstable-preview-types/-private/managers/cache-capabilities-manager.d.ts +1 -1
  20. package/unstable-preview-types/-private/managers/cache-manager.d.ts +11 -11
  21. package/unstable-preview-types/-private/managers/cache-manager.d.ts.map +1 -1
  22. package/unstable-preview-types/-private/managers/notification-manager.d.ts +5 -3
  23. package/unstable-preview-types/-private/managers/notification-manager.d.ts.map +1 -1
  24. package/unstable-preview-types/-private/managers/record-array-manager.d.ts +3 -3
  25. package/unstable-preview-types/-private/network/request-cache.d.ts +1 -1
  26. package/unstable-preview-types/-private/record-arrays/identifier-array.d.ts +4 -4
  27. package/unstable-preview-types/-private/store-service.d.ts +27 -7
  28. package/unstable-preview-types/-private/store-service.d.ts.map +1 -1
  29. package/unstable-preview-types/-private/utils/construct-resource.d.ts +1 -1
  30. package/unstable-preview-types/-private.d.ts +3 -2
  31. package/unstable-preview-types/-private.d.ts.map +1 -1
  32. package/unstable-preview-types/-types/q/cache-capabilities-manager.d.ts +1 -1
  33. package/unstable-preview-types/-types/q/ds-model.d.ts +2 -2
  34. package/unstable-preview-types/-types/q/identifier.d.ts +1 -1
  35. package/unstable-preview-types/-types/q/record-data-json-api.d.ts +2 -2
  36. package/unstable-preview-types/-types/q/schema-service.d.ts +5 -5
  37. package/unstable-preview-types/-types/q/store.d.ts +2 -2
  38. package/unstable-preview-types/index.d.ts +19 -17
  39. package/unstable-preview-types/index.d.ts.map +1 -1
  40. package/dist/cache-handler-C5ilAUZ5.js.map +0 -1
  41. package/unstable-preview-types/-private/cache-handler.d.ts.map +0 -1
package/addon-main.cjs CHANGED
@@ -1,5 +1,5 @@
1
1
  'use strict';
2
2
 
3
- const { addonShim } = require('@warp-drive/build-config/addon-shim.cjs');
3
+ const { addonShim } = require('@warp-drive-mirror/build-config/addon-shim.cjs');
4
4
 
5
5
  module.exports = addonShim(__dirname);
package/dist/-private.js CHANGED
@@ -1 +1 @@
1
- export { A as ARRAY_SIGNAL, C as CacheHandler, j as CollectionRecordArray, I as LiveArray, M as MUTATE, R as RecordArrayManager, k as SOURCE, S as Store, q as StoreMap, _ as _clearCaches, u as _deprecatingNormalize, g as coerceId, f as constructResource, h as ensureStringId, l as fastPush, i as isStableIdentifier, n as notifyArray, p as peekCache, r as recordIdentifierFor, m as removeRecordDataFor, t as setCacheFor, o as setRecordIdentifier, s as storeFor } from "./cache-handler-C5ilAUZ5.js";
1
+ export { A as ARRAY_SIGNAL, C as CacheHandler, j as CollectionRecordArray, I as LiveArray, M as MUTATE, R as RecordArrayManager, k as SOURCE, S as Store, q as StoreMap, _ as _clearCaches, u as _deprecatingNormalize, g as coerceId, f as constructResource, h as ensureStringId, l as fastPush, i as isStableIdentifier, n as notifyArray, p as peekCache, r as recordIdentifierFor, m as removeRecordDataFor, t as setCacheFor, o as setRecordIdentifier, s as storeFor } from "./handler-Dj0xi5y4.js";
@@ -1,8 +1,8 @@
1
1
  import { deprecate, warn } from '@ember/debug';
2
2
  import { macroCondition, getGlobalConfig, dependencySatisfies, importSync } from '@embroider/macros';
3
- import { SkipCache, EnableHydration } from '@warp-drive/core-types/request';
4
- import { getOrSetGlobal, setTransient, peekTransient } from '@warp-drive/core-types/-private';
5
- import { CACHE_OWNER, DEBUG_STALE_CACHE_OWNER, DEBUG_CLIENT_ORIGINATED, DEBUG_IDENTIFIER_BUCKET } from '@warp-drive/core-types/identifier';
3
+ import { SkipCache, EnableHydration } from '@warp-drive-mirror/core-types/request';
4
+ import { getOrSetGlobal, setTransient, peekTransient } from '@warp-drive-mirror/core-types/-private';
5
+ import { CACHE_OWNER, DEBUG_STALE_CACHE_OWNER, DEBUG_CLIENT_ORIGINATED, DEBUG_IDENTIFIER_BUCKET } from '@warp-drive-mirror/core-types/identifier';
6
6
  import { dasherize } from '@ember-data-mirror/request-utils/string';
7
7
  import { defineSignal, createSignal, subscribe, createArrayTags, addToTransaction, addTransactionCB } from '@ember-data-mirror/tracking/-private';
8
8
  import { _backburner } from '@ember/runloop';
@@ -75,7 +75,7 @@ function normalizeModelName(type) {
75
75
 
76
76
  function installPolyfill() {
77
77
  const isFastBoot = typeof FastBoot !== 'undefined';
78
- const CRYPTO = isFastBoot ? FastBoot.require('crypto') : window.crypto;
78
+ const CRYPTO = isFastBoot ? FastBoot.require('crypto') : globalThis.crypto;
79
79
  if (!CRYPTO.randomUUID) {
80
80
  // we might be able to optimize this by requesting more bytes than we need at a time
81
81
  const rng = function () {
@@ -138,7 +138,7 @@ function isDocumentIdentifier(identifier) {
138
138
  return DOCUMENTS.has(identifier);
139
139
  }
140
140
  const isFastBoot = typeof FastBoot !== 'undefined';
141
- const _crypto = isFastBoot ? FastBoot.require('crypto') : window.crypto;
141
+ const _crypto = isFastBoot ? FastBoot.require('crypto') : globalThis.crypto;
142
142
  if (macroCondition(getGlobalConfig().WarpDrive.polyfillUUID)) {
143
143
  installPolyfill();
144
144
  }
@@ -2158,7 +2158,7 @@ class CacheManager {
2158
2158
  */
2159
2159
  // eslint-disable-next-line no-restricted-imports
2160
2160
  let tokenId = 0;
2161
- const CacheOperations = new Set(['added', 'removed', 'state', 'updated']);
2161
+ const CacheOperations = new Set(['added', 'removed', 'state', 'updated', 'invalidated']);
2162
2162
  function isCacheOperationValue(value) {
2163
2163
  return CacheOperations.has(value);
2164
2164
  }
@@ -2318,14 +2318,15 @@ class NotificationManager {
2318
2318
  this._flush();
2319
2319
  }
2320
2320
  _flush() {
2321
- if (this._buffered.size) {
2322
- this._buffered.forEach((states, identifier) => {
2321
+ const buffered = this._buffered;
2322
+ if (buffered.size) {
2323
+ this._buffered = new Map();
2324
+ buffered.forEach((states, identifier) => {
2323
2325
  states.forEach(args => {
2324
2326
  // @ts-expect-error
2325
2327
  this._flushNotification(identifier, args[0], args[1]);
2326
2328
  });
2327
2329
  });
2328
- this._buffered = new Map();
2329
2330
  }
2330
2331
  this._hasFlush = false;
2331
2332
  this._onFlushCB?.();
@@ -3766,6 +3767,17 @@ class Store extends BaseClass {
3766
3767
 
3767
3768
  // Private
3768
3769
 
3770
+ /**
3771
+ * Async flush buffers notifications until flushed
3772
+ * by finalization of a future configured by store.request
3773
+ *
3774
+ * This is useful for ensuring that notifications are delivered
3775
+ * prior to the promise resolving but without risk of promise
3776
+ * interleaving.
3777
+ *
3778
+ * @internal
3779
+ */
3780
+
3769
3781
  // DEBUG-only properties
3770
3782
 
3771
3783
  get isDestroying() {
@@ -3839,6 +3851,16 @@ class Store extends BaseClass {
3839
3851
  this._cbs = null;
3840
3852
  }
3841
3853
  }
3854
+
3855
+ /**
3856
+ * Executes the callback, ensurng that any work that calls
3857
+ * store._schedule is executed after in the right order.
3858
+ *
3859
+ * When queues already exist, scheduled callbacks will
3860
+ * join the existing queue.
3861
+ *
3862
+ * @internal
3863
+ */
3842
3864
  _join(cb) {
3843
3865
  if (this._cbs) {
3844
3866
  cb();
@@ -5709,29 +5731,207 @@ defineSignal(Document.prototype, 'data');
5709
5731
  defineSignal(Document.prototype, 'links');
5710
5732
  defineSignal(Document.prototype, 'errors');
5711
5733
  defineSignal(Document.prototype, 'meta');
5734
+ const MUTATION_OPS = new Set(['createRecord', 'updateRecord', 'deleteRecord']);
5735
+ function calcShouldFetch(store, request, hasCachedValue, identifier) {
5736
+ const {
5737
+ cacheOptions
5738
+ } = request;
5739
+ return request.op && MUTATION_OPS.has(request.op) || cacheOptions?.reload || !hasCachedValue || (store.lifetimes && identifier ? store.lifetimes.isHardExpired(identifier, store) : false);
5740
+ }
5741
+ function calcShouldBackgroundFetch(store, request, willFetch, identifier) {
5742
+ const {
5743
+ cacheOptions
5744
+ } = request;
5745
+ return cacheOptions?.backgroundReload || (store.lifetimes && identifier ? store.lifetimes.isSoftExpired(identifier, store) : false);
5746
+ }
5747
+ function isMutation(request) {
5748
+ return Boolean(request.op && MUTATION_OPS.has(request.op));
5749
+ }
5750
+ function copyDocumentProperties(target, source) {
5751
+ if ('links' in source) {
5752
+ target.links = source.links;
5753
+ }
5754
+ if ('meta' in source) {
5755
+ target.meta = source.meta;
5756
+ }
5757
+ if ('errors' in source) {
5758
+ target.errors = source.errors;
5759
+ }
5760
+ }
5761
+ function isCacheAffecting(document) {
5762
+ if (!isMutation(document.request)) {
5763
+ return true;
5764
+ }
5765
+ // a mutation combined with a 204 has no cache impact when no known records were involved
5766
+ // a createRecord with a 201 with an empty response and no known records should similarly
5767
+ // have no cache impact
5768
+
5769
+ if (document.request.op === 'createRecord' && document.response?.status === 201) {
5770
+ return document.content ? Object.keys(document.content).length > 0 : false;
5771
+ }
5772
+ return document.response?.status !== 204;
5773
+ }
5774
+ function isAggregateError(error) {
5775
+ return error instanceof AggregateError || error.name === 'AggregateError' && Array.isArray(error.errors);
5776
+ }
5777
+ // TODO @runspired, consider if we should deep freeze errors (potentially only in debug) vs cloning them
5778
+ function cloneError(error) {
5779
+ const isAggregate = isAggregateError(error);
5780
+ const cloned = isAggregate ? new AggregateError(structuredClone(error.errors), error.message) : new Error(error.message);
5781
+ cloned.stack = error.stack;
5782
+ cloned.error = error.error;
5783
+
5784
+ // copy over enumerable properties
5785
+ Object.assign(cloned, error);
5786
+ return cloned;
5787
+ }
5788
+ function isErrorDocument(document) {
5789
+ return 'errors' in document;
5790
+ }
5791
+ function getPriority(identifier, deduped, priority) {
5792
+ if (identifier) {
5793
+ const existing = deduped.get(identifier);
5794
+ if (existing) {
5795
+ return existing.priority;
5796
+ }
5797
+ }
5798
+ return priority;
5799
+ }
5712
5800
 
5713
5801
  /**
5714
5802
  * @module @ember-data-mirror/store
5715
5803
  */
5716
5804
 
5717
5805
  /**
5718
- * A service which an application may provide to the store via
5719
- * the store's `lifetimes` property to configure the behavior
5720
- * of the CacheHandler.
5806
+ * A CacheHandler that adds support for using an EmberData Cache with a RequestManager.
5721
5807
  *
5722
- * The default behavior for request lifetimes is to never expire
5723
- * unless manually refreshed via `cacheOptions.reload` or `cacheOptions.backgroundReload`.
5808
+ * This handler will only run when a request has supplied a `store` instance. Requests
5809
+ * issued by the store via `store.request()` will automatically have the `store` instance
5810
+ * attached to the request.
5724
5811
  *
5725
- * Implementing this service allows you to programatically define
5726
- * when a request should be considered expired.
5812
+ * ```ts
5813
+ * requestManager.request({
5814
+ * store: store,
5815
+ * url: '/api/posts',
5816
+ * method: 'GET'
5817
+ * });
5818
+ * ```
5727
5819
  *
5728
- * @class <Interface> CachePolicy
5729
- * @public
5820
+ * When this handler elects to handle a request, it will return the raw `StructuredDocument`
5821
+ * unless the request has `[EnableHydration]` set to `true`. In this case, the handler will
5822
+ * return a `Document` instance that will automatically update the UI when the cache is updated
5823
+ * in the future and will hydrate any identifiers in the StructuredDocument into Record instances.
5824
+ *
5825
+ * When issuing a request via the store, [EnableHydration] is automatically set to `true`. This
5826
+ * means that if desired you can issue requests that utilize the cache without needing to also
5827
+ * utilize Record instances if desired.
5828
+ *
5829
+ * Said differently, you could elect to issue all requests via a RequestManager, without ever using
5830
+ * the store directly, by setting [EnableHydration] to `true` and providing a store instance. Not
5831
+ * necessarily the most useful thing, but the decoupled nature of the RequestManager and incremental-feature
5832
+ * approach of EmberData allows for this flexibility.
5833
+ *
5834
+ * ```ts
5835
+ * import { EnableHydration } from '@warp-drive-mirror/core-types/request';
5836
+ *
5837
+ * requestManager.request({
5838
+ * store: store,
5839
+ * url: '/api/posts',
5840
+ * method: 'GET',
5841
+ * [EnableHydration]: true
5842
+ * });
5843
+ *
5844
+ * @typedoc
5730
5845
  */
5731
- const MUTATION_OPS = new Set(['createRecord', 'updateRecord', 'deleteRecord']);
5732
- function isErrorDocument(document) {
5733
- return 'errors' in document;
5734
- }
5846
+ const CacheHandler = {
5847
+ request(context, next) {
5848
+ // if we have no cache or no cache-key skip cache handling
5849
+ if (!context.request.store || context.request.cacheOptions?.[SkipCache]) {
5850
+ return next(context.request);
5851
+ }
5852
+ const {
5853
+ store
5854
+ } = context.request;
5855
+ const identifier = store.identifierCache.getOrCreateDocumentIdentifier(context.request);
5856
+ if (identifier) {
5857
+ context.setIdentifier(identifier);
5858
+ }
5859
+
5860
+ // used to dedupe existing requests that match
5861
+ const DEDUPE = store.requestManager._deduped;
5862
+ const activeRequest = identifier && DEDUPE.get(identifier);
5863
+ const peeked = identifier ? store.cache.peekRequest(identifier) : null;
5864
+
5865
+ // determine if we should skip cache
5866
+ if (calcShouldFetch(store, context.request, !!peeked, identifier)) {
5867
+ if (activeRequest) {
5868
+ activeRequest.priority = {
5869
+ blocking: true
5870
+ };
5871
+ return activeRequest.promise;
5872
+ }
5873
+ let promise = fetchContentAndHydrate(next, context, identifier, {
5874
+ blocking: true
5875
+ });
5876
+ if (identifier) {
5877
+ promise = promise.finally(() => {
5878
+ DEDUPE.delete(identifier);
5879
+ store.notifications.notify(identifier, 'state');
5880
+ });
5881
+ DEDUPE.set(identifier, {
5882
+ priority: {
5883
+ blocking: true
5884
+ },
5885
+ promise
5886
+ });
5887
+ store.notifications.notify(identifier, 'state');
5888
+ }
5889
+ return promise;
5890
+ }
5891
+
5892
+ // if we have not skipped cache, determine if we should update behind the scenes
5893
+ if (calcShouldBackgroundFetch(store, context.request, false, identifier)) {
5894
+ let promise = activeRequest?.promise || fetchContentAndHydrate(next, context, identifier, {
5895
+ blocking: false
5896
+ });
5897
+ if (identifier && !activeRequest) {
5898
+ promise = promise.finally(() => {
5899
+ DEDUPE.delete(identifier);
5900
+ store.notifications.notify(identifier, 'state');
5901
+ });
5902
+ DEDUPE.set(identifier, {
5903
+ priority: {
5904
+ blocking: false
5905
+ },
5906
+ promise
5907
+ });
5908
+ store.notifications.notify(identifier, 'state');
5909
+ }
5910
+ store.requestManager._pending.set(context.id, promise);
5911
+ }
5912
+ macroCondition(getGlobalConfig().WarpDrive.env.DEBUG) ? (test => {
5913
+ if (!test) {
5914
+ throw new Error(`Expected a peeked request to be present`);
5915
+ }
5916
+ })(peeked) : {};
5917
+ const shouldHydrate = context.request[EnableHydration] || false;
5918
+ context.setResponse(peeked.response);
5919
+ if ('error' in peeked) {
5920
+ const content = shouldHydrate ? maybeUpdateErrorUiObjects(store, {
5921
+ shouldHydrate,
5922
+ identifier
5923
+ }, peeked.content, true) : peeked.content;
5924
+ const newError = cloneError(peeked);
5925
+ newError.content = content;
5926
+ throw newError;
5927
+ }
5928
+ const result = shouldHydrate ? maybeUpdateUiObjects(store, context.request, {
5929
+ shouldHydrate,
5930
+ identifier
5931
+ }, peeked.content, true) : peeked.content;
5932
+ return result;
5933
+ }
5934
+ };
5735
5935
  function maybeUpdateUiObjects(store, request, options, document, isFromCache) {
5736
5936
  const {
5737
5937
  identifier
@@ -5744,26 +5944,6 @@ function maybeUpdateUiObjects(store, request, options, document, isFromCache) {
5744
5944
  })(!options.shouldHydrate) : {};
5745
5945
  return document;
5746
5946
  }
5747
- if (isErrorDocument(document)) {
5748
- if (!identifier && !options.shouldHydrate) {
5749
- return document;
5750
- }
5751
- let doc;
5752
- if (identifier) {
5753
- doc = store._documentCache.get(identifier);
5754
- }
5755
- if (!doc) {
5756
- doc = new Document(store, identifier);
5757
- copyDocumentProperties(doc, document);
5758
- if (identifier) {
5759
- store._documentCache.set(identifier, doc);
5760
- }
5761
- } else if (!isFromCache) {
5762
- doc.data = undefined;
5763
- copyDocumentProperties(doc, document);
5764
- }
5765
- return options.shouldHydrate ? doc : document;
5766
- }
5767
5947
  if (Array.isArray(document.data)) {
5768
5948
  const {
5769
5949
  recordArrayManager
@@ -5831,26 +6011,119 @@ function maybeUpdateUiObjects(store, request, options, document, isFromCache) {
5831
6011
  return options.shouldHydrate ? doc : document;
5832
6012
  }
5833
6013
  }
5834
- function calcShouldFetch(store, request, hasCachedValue, identifier) {
6014
+ function maybeUpdateErrorUiObjects(store, options, document, isFromCache) {
5835
6015
  const {
5836
- cacheOptions
5837
- } = request;
5838
- return request.op && MUTATION_OPS.has(request.op) || cacheOptions?.reload || !hasCachedValue || (store.lifetimes && identifier ? store.lifetimes.isHardExpired(identifier, store) : false);
6016
+ identifier
6017
+ } = options;
6018
+
6019
+ // TODO investigate why ResourceErrorDocument is insufficient for expressing all error types
6020
+ if (!isErrorDocument(document) || !identifier && !options.shouldHydrate) {
6021
+ return document;
6022
+ }
6023
+ let doc;
6024
+ if (identifier) {
6025
+ doc = store._documentCache.get(identifier);
6026
+ }
6027
+ if (!doc) {
6028
+ doc = new Document(store, identifier);
6029
+ copyDocumentProperties(doc, document);
6030
+ if (identifier) {
6031
+ store._documentCache.set(identifier, doc);
6032
+ }
6033
+ } else if (!isFromCache) {
6034
+ doc.data = undefined;
6035
+ copyDocumentProperties(doc, document);
6036
+ }
6037
+ return options.shouldHydrate ? doc : document;
5839
6038
  }
5840
- function calcShouldBackgroundFetch(store, request, willFetch, identifier) {
6039
+ function updateCacheForSuccess(store, request, options, document) {
6040
+ let response = null;
6041
+ if (isMutation(request)) {
6042
+ const record = request.data?.record || request.records?.[0];
6043
+ if (record) {
6044
+ response = store.cache.didCommit(record, document);
6045
+
6046
+ // a mutation combined with a 204 has no cache impact when no known records were involved
6047
+ // a createRecord with a 201 with an empty response and no known records should similarly
6048
+ // have no cache impact
6049
+ } else if (isCacheAffecting(document)) {
6050
+ response = store.cache.put(document);
6051
+ }
6052
+ } else {
6053
+ response = store.cache.put(document);
6054
+ }
6055
+ return maybeUpdateUiObjects(store, request, options, response, false);
6056
+ }
6057
+ function handleFetchSuccess(store, context, options, document) {
5841
6058
  const {
5842
- cacheOptions
5843
- } = request;
5844
- return cacheOptions?.backgroundReload || (store.lifetimes && identifier ? store.lifetimes.isSoftExpired(identifier, store) : false);
6059
+ request
6060
+ } = context;
6061
+ store.requestManager._pending.delete(context.id);
6062
+ store._enableAsyncFlush = true;
6063
+ let response;
6064
+ store._join(() => {
6065
+ response = updateCacheForSuccess(store, request, options, document);
6066
+ });
6067
+ store._enableAsyncFlush = null;
6068
+ if (store.lifetimes?.didRequest) {
6069
+ store.lifetimes.didRequest(context.request, document.response, options.identifier, store);
6070
+ }
6071
+ const finalPriority = getPriority(options.identifier, store.requestManager._deduped, options.priority);
6072
+ if (finalPriority.blocking) {
6073
+ return response;
6074
+ } else {
6075
+ store.notifications._flush();
6076
+ }
5845
6077
  }
5846
- function isMutation(request) {
5847
- return Boolean(request.op && MUTATION_OPS.has(request.op));
6078
+ function updateCacheForError(store, context, options, error) {
6079
+ let response;
6080
+ if (isMutation(context.request)) {
6081
+ // TODO similar to didCommit we should spec this to be similar to cache.put for handling full response
6082
+ // currently we let the response remain undefiend.
6083
+ const errors = error && error.content && typeof error.content === 'object' && 'errors' in error.content && Array.isArray(error.content.errors) ? error.content.errors : undefined;
6084
+ const record = context.request.data?.record || context.request.records?.[0];
6085
+ store.cache.commitWasRejected(record, errors);
6086
+ } else {
6087
+ response = store.cache.put(error);
6088
+ return maybeUpdateErrorUiObjects(store, options, response, false);
6089
+ }
6090
+ }
6091
+ function handleFetchError(store, context, options, error) {
6092
+ store.requestManager._pending.delete(context.id);
6093
+ if (context.request.signal?.aborted) {
6094
+ throw error;
6095
+ }
6096
+ store._enableAsyncFlush = true;
6097
+ let response;
6098
+ store._join(() => {
6099
+ response = updateCacheForError(store, context, options, error);
6100
+ });
6101
+ store._enableAsyncFlush = null;
6102
+ if (options.identifier && store.lifetimes?.didRequest) {
6103
+ store.lifetimes.didRequest(context.request, error.response, options.identifier, store);
6104
+ }
6105
+ if (isMutation(context.request)) {
6106
+ throw error;
6107
+ }
6108
+ const finalPriority = getPriority(options.identifier, store.requestManager._deduped, options.priority);
6109
+ if (finalPriority.blocking) {
6110
+ const newError = cloneError(error);
6111
+ newError.content = response;
6112
+ throw newError;
6113
+ } else {
6114
+ store.notifications._flush();
6115
+ }
5848
6116
  }
5849
- function fetchContentAndHydrate(next, context, identifier, shouldFetch, shouldBackgroundFetch) {
6117
+ function fetchContentAndHydrate(next, context, identifier, priority) {
5850
6118
  const {
5851
6119
  store
5852
6120
  } = context.request;
5853
6121
  const shouldHydrate = context.request[EnableHydration] || false;
6122
+ const options = {
6123
+ shouldHydrate,
6124
+ identifier,
6125
+ priority
6126
+ };
5854
6127
  let isMut = false;
5855
6128
  if (isMutation(context.request)) {
5856
6129
  isMut = true;
@@ -5869,78 +6142,9 @@ function fetchContentAndHydrate(next, context, identifier, shouldFetch, shouldBa
5869
6142
  store.lifetimes.willRequest(context.request, identifier, store);
5870
6143
  }
5871
6144
  const promise = next(context.request).then(document => {
5872
- store.requestManager._pending.delete(context.id);
5873
- store._enableAsyncFlush = true;
5874
- let response;
5875
- store._join(() => {
5876
- if (isMutation(context.request)) {
5877
- const record = context.request.data?.record || context.request.records?.[0];
5878
- if (record) {
5879
- response = store.cache.didCommit(record, document);
5880
-
5881
- // a mutation combined with a 204 has no cache impact when no known records were involved
5882
- // a createRecord with a 201 with an empty response and no known records should similarly
5883
- // have no cache impact
5884
- } else if (isCacheAffecting(document)) {
5885
- response = store.cache.put(document);
5886
- }
5887
- } else {
5888
- response = store.cache.put(document);
5889
- }
5890
- response = maybeUpdateUiObjects(store, context.request, {
5891
- shouldHydrate,
5892
- shouldFetch,
5893
- shouldBackgroundFetch,
5894
- identifier
5895
- }, response, false);
5896
- });
5897
- store._enableAsyncFlush = null;
5898
- if (store.lifetimes?.didRequest) {
5899
- store.lifetimes.didRequest(context.request, document.response, identifier, store);
5900
- }
5901
- if (shouldFetch) {
5902
- return response;
5903
- } else if (shouldBackgroundFetch) {
5904
- store.notifications._flush();
5905
- }
6145
+ return handleFetchSuccess(store, context, options, document);
5906
6146
  }, error => {
5907
- store.requestManager._pending.delete(context.id);
5908
- if (context.request.signal?.aborted) {
5909
- throw error;
5910
- }
5911
- store.requestManager._pending.delete(context.id);
5912
- store._enableAsyncFlush = true;
5913
- let response;
5914
- store._join(() => {
5915
- if (isMutation(context.request)) {
5916
- // TODO similar to didCommit we should spec this to be similar to cache.put for handling full response
5917
- // currently we let the response remain undefiend.
5918
- const errors = error && error.content && typeof error.content === 'object' && 'errors' in error.content && Array.isArray(error.content.errors) ? error.content.errors : undefined;
5919
- const record = context.request.data?.record || context.request.records?.[0];
5920
- store.cache.commitWasRejected(record, errors);
5921
- // re-throw the original error to preserve `errors` property.
5922
- throw error;
5923
- } else {
5924
- response = store.cache.put(error);
5925
- response = maybeUpdateUiObjects(store, context.request, {
5926
- shouldHydrate,
5927
- shouldFetch,
5928
- shouldBackgroundFetch,
5929
- identifier
5930
- }, response, false);
5931
- }
5932
- });
5933
- store._enableAsyncFlush = null;
5934
- if (identifier && store.lifetimes?.didRequest) {
5935
- store.lifetimes.didRequest(context.request, error.response, identifier, store);
5936
- }
5937
- if (!shouldBackgroundFetch) {
5938
- const newError = cloneError(error);
5939
- newError.content = response;
5940
- throw newError;
5941
- } else {
5942
- store.notifications._flush();
5943
- }
6147
+ return handleFetchError(store, context, options, error);
5944
6148
  });
5945
6149
  if (!isMut) {
5946
6150
  return promise;
@@ -5962,129 +6166,4 @@ function fetchContentAndHydrate(next, context, identifier, shouldFetch, shouldBa
5962
6166
  }]
5963
6167
  });
5964
6168
  }
5965
- function isAggregateError(error) {
5966
- return error instanceof AggregateError || error.name === 'AggregateError' && Array.isArray(error.errors);
5967
- }
5968
- // TODO @runspired, consider if we should deep freeze errors (potentially only in debug) vs cloning them
5969
- function cloneError(error) {
5970
- const isAggregate = isAggregateError(error);
5971
- const cloned = isAggregate ? new AggregateError(structuredClone(error.errors), error.message) : new Error(error.message);
5972
- cloned.stack = error.stack;
5973
- cloned.error = error.error;
5974
-
5975
- // copy over enumerable properties
5976
- Object.assign(cloned, error);
5977
- return cloned;
5978
- }
5979
-
5980
- /**
5981
- * A CacheHandler that adds support for using an EmberData Cache with a RequestManager.
5982
- *
5983
- * This handler will only run when a request has supplied a `store` instance. Requests
5984
- * issued by the store via `store.request()` will automatically have the `store` instance
5985
- * attached to the request.
5986
- *
5987
- * ```ts
5988
- * requestManager.request({
5989
- * store: store,
5990
- * url: '/api/posts',
5991
- * method: 'GET'
5992
- * });
5993
- * ```
5994
- *
5995
- * When this handler elects to handle a request, it will return the raw `StructuredDocument`
5996
- * unless the request has `[EnableHydration]` set to `true`. In this case, the handler will
5997
- * return a `Document` instance that will automatically update the UI when the cache is updated
5998
- * in the future and will hydrate any identifiers in the StructuredDocument into Record instances.
5999
- *
6000
- * When issuing a request via the store, [EnableHydration] is automatically set to `true`. This
6001
- * means that if desired you can issue requests that utilize the cache without needing to also
6002
- * utilize Record instances if desired.
6003
- *
6004
- * Said differently, you could elect to issue all requests via a RequestManager, without ever using
6005
- * the store directly, by setting [EnableHydration] to `true` and providing a store instance. Not
6006
- * necessarily the most useful thing, but the decoupled nature of the RequestManager and incremental-feature
6007
- * approach of EmberData allows for this flexibility.
6008
- *
6009
- * ```ts
6010
- * import { EnableHydration } from '@warp-drive/core-types/request';
6011
- *
6012
- * requestManager.request({
6013
- * store: store,
6014
- * url: '/api/posts',
6015
- * method: 'GET',
6016
- * [EnableHydration]: true
6017
- * });
6018
- *
6019
- * @typedoc
6020
- */
6021
- const CacheHandler = {
6022
- request(context, next) {
6023
- // if we have no cache or no cache-key skip cache handling
6024
- if (!context.request.store || context.request.cacheOptions?.[SkipCache]) {
6025
- return next(context.request);
6026
- }
6027
- const {
6028
- store
6029
- } = context.request;
6030
- const identifier = store.identifierCache.getOrCreateDocumentIdentifier(context.request);
6031
- const peeked = identifier ? store.cache.peekRequest(identifier) : null;
6032
-
6033
- // determine if we should skip cache
6034
- if (calcShouldFetch(store, context.request, !!peeked, identifier)) {
6035
- return fetchContentAndHydrate(next, context, identifier, true, false);
6036
- }
6037
-
6038
- // if we have not skipped cache, determine if we should update behind the scenes
6039
- if (calcShouldBackgroundFetch(store, context.request, false, identifier)) {
6040
- const promise = fetchContentAndHydrate(next, context, identifier, false, true);
6041
- store.requestManager._pending.set(context.id, promise);
6042
- }
6043
- macroCondition(getGlobalConfig().WarpDrive.env.DEBUG) ? (test => {
6044
- if (!test) {
6045
- throw new Error(`Expected a peeked request to be present`);
6046
- }
6047
- })(peeked) : {};
6048
- const shouldHydrate = context.request[EnableHydration] || false;
6049
- context.setResponse(peeked.response);
6050
- if ('error' in peeked) {
6051
- const content = shouldHydrate ? maybeUpdateUiObjects(store, context.request, {
6052
- shouldHydrate,
6053
- identifier
6054
- }, peeked.content, true) : peeked.content;
6055
- const newError = cloneError(peeked);
6056
- newError.content = content;
6057
- throw newError;
6058
- }
6059
- const result = shouldHydrate ? maybeUpdateUiObjects(store, context.request, {
6060
- shouldHydrate,
6061
- identifier
6062
- }, peeked.content, true) : peeked.content;
6063
- return result;
6064
- }
6065
- };
6066
- function copyDocumentProperties(target, source) {
6067
- if ('links' in source) {
6068
- target.links = source.links;
6069
- }
6070
- if ('meta' in source) {
6071
- target.meta = source.meta;
6072
- }
6073
- if ('errors' in source) {
6074
- target.errors = source.errors;
6075
- }
6076
- }
6077
- function isCacheAffecting(document) {
6078
- if (!isMutation(document.request)) {
6079
- return true;
6080
- }
6081
- // a mutation combined with a 204 has no cache impact when no known records were involved
6082
- // a createRecord with a 201 with an empty response and no known records should similarly
6083
- // have no cache impact
6084
-
6085
- if (document.request.op === 'createRecord' && document.response?.status === 201) {
6086
- return document.content ? Object.keys(document.content).length > 0 : false;
6087
- }
6088
- return document.response?.status !== 204;
6089
- }
6090
6169
  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, setKeyInfoForResource as e, constructResource as f, coerceId as g, ensureStringId as h, isStableIdentifier as i, Collection as j, SOURCE as k, fastPush as l, removeRecordDataFor as m, notifyArray as n, setRecordIdentifier as o, peekCache as p, StoreMap as q, recordIdentifierFor as r, storeFor as s, setCacheFor as t, normalizeModelName as u };