@warp-drive/core 5.7.0-alpha.37 → 5.7.0-alpha.39

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (45) hide show
  1. package/declarations/graph/-private/-state.d.ts +0 -87
  2. package/declarations/graph/-private/-utils.d.ts +0 -6
  3. package/declarations/graph/-private/coerce-id.d.ts +0 -6
  4. package/declarations/graph/-private/debug/assert-polymorphic-type.d.ts +0 -12
  5. package/declarations/graph/-private/graph.d.ts +0 -36
  6. package/declarations/graph/-private/normalize-link.d.ts +0 -6
  7. package/declarations/graph/-private/operations/replace-related-records.d.ts +0 -55
  8. package/declarations/graph/-private/operations/update-relationship.d.ts +0 -4
  9. package/declarations/reactive/-private/fields/managed-array.d.ts +0 -1
  10. package/declarations/reactive/-private/fields/managed-object.d.ts +0 -2
  11. package/declarations/reactive/-private/record.d.ts +0 -1
  12. package/declarations/reactive/-private/schema.d.ts +0 -2
  13. package/declarations/reactive/-private/symbols.d.ts +0 -26
  14. package/declarations/request/-private/types.d.ts +0 -1
  15. package/declarations/store/-private/cache-handler/utils.d.ts +0 -1
  16. package/declarations/store/-private/caches/instance-cache.d.ts +0 -1
  17. package/declarations/store/-private/default-cache-policy.d.ts +0 -2
  18. package/declarations/store/-private/managers/cache-key-manager.d.ts +0 -4
  19. package/declarations/store/-private/managers/cache-manager.d.ts +0 -16
  20. package/declarations/store/-private/managers/notification-manager.d.ts +0 -2
  21. package/declarations/store/-private/network/request-cache.d.ts +0 -1
  22. package/declarations/store/-private/new-core-tmp/request-state.d.ts +0 -1
  23. package/declarations/store/-private/new-core-tmp/request-subscription.d.ts +0 -2
  24. package/declarations/store/-private/record-arrays/resource-array.d.ts +0 -8
  25. package/declarations/store/-private/store-service.d.ts +0 -5
  26. package/declarations/store/-private/utils/coerce-id.d.ts +0 -6
  27. package/declarations/store/-private.d.ts +0 -5
  28. package/declarations/store/deprecated/-private.d.ts +0 -1
  29. package/declarations/store/deprecated/store.d.ts +0 -4
  30. package/declarations/types/cache/aliases.d.ts +0 -11
  31. package/declarations/types/cache/mutations.d.ts +0 -24
  32. package/declarations/types/cache/relationship.d.ts +0 -3
  33. package/declarations/types/cache.d.ts +0 -12
  34. package/declarations/types/identifier.d.ts +0 -2
  35. package/declarations/types/request.d.ts +0 -3
  36. package/declarations/types/spec/document.d.ts +0 -4
  37. package/declarations/types/spec/json-api-raw.d.ts +0 -1
  38. package/declarations/types.d.ts +0 -1
  39. package/declarations/utils/string.d.ts +0 -1
  40. package/dist/index.js +1 -1
  41. package/dist/reactive.js +2 -2
  42. package/dist/{request-subscription-CjHGziEX.js → request-state-C955e0AL.js} +984 -982
  43. package/dist/store/-private.js +1 -1
  44. package/dist/types/-private.js +1 -1
  45. package/package.json +6 -6
@@ -3,10 +3,10 @@ import { macroCondition, getGlobalConfig, dependencySatisfies, importSync } from
3
3
  import { deprecate, warn } from '@ember/debug';
4
4
  import { setLogging, getRuntimeConfig } from './types/runtime.js';
5
5
  import { getOrSetGlobal, peekTransient, setTransient } from './types/-private.js';
6
- import { c as createSignal, a as consumeSignal, n as notifySignal, b as createMemo, A as ARRAY_SIGNAL, O as OBJECT_SIGNAL, d as willSyncFlushWatchers } from "./configure-C3x8YXzL.js";
7
6
  import { CACHE_OWNER, DEBUG_STALE_CACHE_OWNER, DEBUG_KEY_TYPE, DEBUG_CLIENT_ORIGINATED } from './types/identifier.js';
8
7
  import { dasherize } from './utils/string.js';
9
8
  import { S as SOURCE, C as Context, D as Destroy, a as Checkout, b as Commit } from "./symbols-sql1_mdx.js";
9
+ import { c as createSignal, a as consumeSignal, n as notifySignal, b as createMemo, A as ARRAY_SIGNAL, O as OBJECT_SIGNAL, d as willSyncFlushWatchers } from "./configure-C3x8YXzL.js";
10
10
  import { g as getPromiseResult, s as setPromiseResult } from "./context-C_7OLieY.js";
11
11
  import { RecordStore } from './types/symbols.js';
12
12
  const INITIALIZER_PROTO = {
@@ -9451,87 +9451,85 @@ function getPromiseState(promise) {
9451
9451
  }
9452
9452
  return state;
9453
9453
  }
9454
- const RequestCache = new WeakMap();
9455
- function isAbortError(error) {
9456
- return error instanceof DOMException && error.name === 'AbortError';
9457
- }
9458
- function upgradeLoadingState(state) {
9459
- return state;
9460
- }
9461
- async function watchStream(stream, loadingState) {
9462
- const state = upgradeLoadingState(loadingState);
9463
- const reader = stream.getReader();
9464
- let bytesLoaded = 0;
9465
- let shouldForward = state._stream !== null && state._stream.readable.locked;
9466
- let isForwarding = shouldForward;
9467
- let writer = state._stream?.writable.getWriter();
9468
- const buffer = [];
9469
- state._isPending = false;
9470
- state._isStarted = true;
9471
- state._startTime = performance.now();
9472
- while (true) {
9473
- const {
9474
- value,
9475
- done
9476
- } = await reader.read();
9477
- if (done) {
9478
- break;
9479
- }
9480
- bytesLoaded += value.byteLength;
9481
- state._bytesLoaded = bytesLoaded;
9482
- state._lastPacketTime = performance.now();
9483
- shouldForward = shouldForward || state._stream !== null && state._stream.readable.locked;
9484
- if (shouldForward) {
9485
- if (!isForwarding) {
9486
- isForwarding = true;
9487
- writer = state._stream.writable.getWriter();
9488
- for (const item of buffer) {
9489
- await writer.ready;
9490
- await writer.write(item);
9491
- }
9492
- buffer.length = 0;
9493
- }
9494
- await writer.ready;
9495
- await writer.write(value);
9496
- } else {
9497
- buffer.push(value);
9498
- }
9454
+ function decorateMethodV2(prototype, prop, decorators) {
9455
+ const origDesc = Object.getOwnPropertyDescriptor(prototype, prop);
9456
+ let desc = {
9457
+ ...origDesc
9458
+ };
9459
+ for (let decorator of decorators) {
9460
+ desc = decorator(prototype, prop, desc) || desc;
9499
9461
  }
9500
-
9501
- // if we are still forwarding, we need to close the writer
9502
- if (isForwarding) {
9503
- await writer.ready;
9504
- await writer.close();
9505
- } else if (state._stream) {
9506
- // if we are not forwarding, we need to cancel the stream
9507
- await state._stream.readable.cancel('The Stream Has Already Ended');
9508
- state._stream = null;
9462
+ if (desc.initializer !== void 0) {
9463
+ desc.value = desc.initializer ? desc.initializer.call(prototype) : void 0;
9464
+ desc.initializer = void 0;
9509
9465
  }
9510
- const endTime = performance.now();
9511
- state._endTime = endTime;
9512
- state._isComplete = true;
9513
- state._isStarted = false;
9466
+ Object.defineProperty(prototype, prop, desc);
9467
+ }
9468
+
9469
+ // default to 30 seconds unavailable before we refresh
9470
+ const DEFAULT_DEADLINE = 30_000;
9471
+ const DISPOSE = Symbol.dispose || Symbol.for('dispose');
9472
+ function isNeverString(val) {
9473
+ return val;
9514
9474
  }
9515
9475
 
9516
9476
  /**
9517
- * Lazily consumes the stream of a request, providing a number of
9518
- * reactive properties that can be used to build UIs that respond
9519
- * to the progress of a request.
9477
+ * Utilities to assist in recovering from the error.
9478
+ */
9479
+
9480
+ /** @deprecated use {@link RecoveryFeatures} */
9481
+
9482
+ /**
9483
+ * Utilities for keeping the request fresh
9484
+ */
9485
+
9486
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
9487
+
9488
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
9489
+
9490
+ /**
9491
+ * A reactive class
9520
9492
  *
9521
9493
  * @hideconstructor
9522
9494
  */
9523
- class RequestLoadingState {
9524
- /** @internal */
9495
+ class RequestSubscription {
9496
+ /**
9497
+ * Whether the browser reports that the network is online.
9498
+ */
9525
9499
 
9526
- /** @internal */
9500
+ /**
9501
+ * Whether the browser reports that the tab is hidden.
9502
+ */
9527
9503
 
9528
- /** @internal */
9504
+ /**
9505
+ * Whether the component is currently refreshing the request.
9506
+ */
9529
9507
 
9530
- /** @internal */
9508
+ /**
9509
+ * The most recent blocking request that was made, typically
9510
+ * the result of a reload.
9511
+ *
9512
+ * This will never be the original request passed as an arg to
9513
+ * the component.
9514
+ *
9515
+ * @internal
9516
+ */
9531
9517
 
9532
- /** @internal */
9518
+ /**
9519
+ * The most recent request that was made, typically due to either a
9520
+ * reload or a refresh.
9521
+ *
9522
+ * This will never be the original request passed as an arg to
9523
+ * the component.
9524
+ *
9525
+ * @internal
9526
+ */
9533
9527
 
9534
- /** @internal */
9528
+ /**
9529
+ * The time at which the network was reported as offline.
9530
+ *
9531
+ * @internal
9532
+ */
9535
9533
 
9536
9534
  /** @internal */
9537
9535
 
@@ -9543,1035 +9541,1039 @@ class RequestLoadingState {
9543
9541
 
9544
9542
  /** @internal */
9545
9543
 
9544
+ /**
9545
+ * The event listener for network status changes,
9546
+ * cached to use the reference for removal.
9547
+ *
9548
+ * @internal
9549
+ */
9550
+
9551
+ /**
9552
+ * The event listener for visibility status changes,
9553
+ * cached to use the reference for removal.
9554
+ *
9555
+ * @internal
9556
+ */
9557
+
9558
+ /**
9559
+ * The last request passed as an arg to the component,
9560
+ * cached for comparison.
9561
+ *
9562
+ * @internal
9563
+ */
9564
+
9565
+ /**
9566
+ * The last query passed as an arg to the component,
9567
+ * cached for comparison.
9568
+ *
9569
+ * @internal
9570
+ */
9571
+
9546
9572
  /** @internal */
9547
- _stream = null;
9548
- /** @internal */
9549
- _future;
9573
+
9550
9574
  /** @internal */
9551
- _triggered = false;
9575
+
9552
9576
  /** @internal */
9553
- _trigger() {
9554
- if (this._triggered) {
9555
- return;
9556
- }
9557
- this._triggered = true;
9558
- const future = this._future;
9559
- const promise = future.getStream();
9560
- if (promise.sizeHint) {
9561
- this._sizeHint = promise.sizeHint;
9562
- }
9563
- this.promise = promise.then(stream => {
9564
- if (!stream) {
9565
- this._isPending = false;
9566
- this._isComplete = true;
9567
- return;
9577
+
9578
+ /**
9579
+ * The Store this subscription subscribes to or the RequestManager
9580
+ * which issues this request.
9581
+ */
9582
+
9583
+ /**
9584
+ * The Store or RequestManager that the last subscription is attached to.
9585
+ *
9586
+ * This differs from 'store' because a <Request /> may be passed a
9587
+ * request originating from a different store than the <Request />
9588
+ * component would use if it were to issue the request itself.
9589
+ *
9590
+ * @internal
9591
+ */
9592
+ _requester;
9593
+ constructor(store, args) {
9594
+ this._args = args;
9595
+ this.store = store;
9596
+ this._subscribedTo = null;
9597
+ this._subscription = null;
9598
+ this._intervalStart = null;
9599
+ this._invalidated = false;
9600
+ this._nextInterval = null;
9601
+ this._requester = null;
9602
+ this.isDestroyed = false;
9603
+ this[DISPOSE] = _DISPOSE;
9604
+ this._installListeners();
9605
+ void this._beginPolling();
9606
+ }
9607
+
9608
+ /**
9609
+ * @internal
9610
+ */
9611
+ async _beginPolling() {
9612
+ // await the initial request
9613
+ try {
9614
+ if (!this.isIdle) {
9615
+ await this.request;
9568
9616
  }
9569
- return watchStream(stream, this);
9570
- }, error => {
9571
- this._isPending = false;
9572
- this._isStarted = false;
9573
- if (isAbortError(error)) {
9574
- this._isCancelled = true;
9575
- this._isComplete = true;
9617
+ } catch {
9618
+ // ignore errors here, we just want to wait for the request to finish
9619
+ } finally {
9620
+ if (!this.isDestroyed) {
9621
+ void this._scheduleInterval();
9576
9622
  }
9577
- this._isErrored = true;
9578
- this._error = error;
9579
- });
9623
+ }
9580
9624
  }
9581
- promise = null;
9582
- get isPending() {
9583
- this._trigger();
9584
- return this._isPending;
9625
+ get isIdle() {
9626
+ const {
9627
+ request,
9628
+ query
9629
+ } = this._args;
9630
+ return Boolean(!request && !query);
9585
9631
  }
9586
- get sizeHint() {
9587
- this._trigger();
9588
- return this._sizeHint;
9632
+ static {
9633
+ decorateMethodV2(this.prototype, "isIdle", [memoized]);
9589
9634
  }
9590
- get stream() {
9591
- this._trigger();
9592
- if (!this._stream) {
9593
- if (this._isComplete || this._isCancelled || this._isErrored) {
9594
- return null;
9595
- }
9596
- this._stream = new TransformStream();
9635
+ get autorefreshTypes() {
9636
+ const {
9637
+ autorefresh
9638
+ } = this._args;
9639
+ let types;
9640
+ if (autorefresh === true) {
9641
+ types = ['online', 'invalid'];
9642
+ } else if (typeof autorefresh === 'string') {
9643
+ types = autorefresh.split(',');
9644
+ } else {
9645
+ types = [];
9597
9646
  }
9598
- return this._stream.readable;
9599
- }
9600
- get isStarted() {
9601
- this._trigger();
9602
- return this._isStarted;
9603
- }
9604
- get bytesLoaded() {
9605
- this._trigger();
9606
- return this._bytesLoaded;
9607
- }
9608
- get startTime() {
9609
- this._trigger();
9610
- return this._startTime;
9611
- }
9612
- get endTime() {
9613
- this._trigger();
9614
- return this._endTime;
9615
- }
9616
- get lastPacketTime() {
9617
- this._trigger();
9618
- return this._lastPacketTime;
9619
- }
9620
- get isComplete() {
9621
- this._trigger();
9622
- return this._isComplete;
9623
- }
9624
- get isCancelled() {
9625
- this._trigger();
9626
- return this._isCancelled;
9627
- }
9628
- get isErrored() {
9629
- this._trigger();
9630
- return this._isErrored;
9631
- }
9632
- get error() {
9633
- this._trigger();
9634
- return this._error;
9635
- }
9636
- get elapsedTime() {
9637
- return (this.endTime || this.lastPacketTime) - this.startTime;
9638
- }
9639
- get completedRatio() {
9640
- return this.sizeHint ? this.bytesLoaded / this.sizeHint : 0;
9641
- }
9642
- get remainingRatio() {
9643
- return 1 - this.completedRatio;
9644
- }
9645
- get duration() {
9646
- return this.endTime - this.startTime;
9647
- }
9648
- get speed() {
9649
- // bytes per second
9650
- return this.bytesLoaded / (this.elapsedTime / 1000);
9651
- }
9652
- constructor(future) {
9653
- this._future = future;
9654
- }
9655
- abort = () => {
9656
- this._future.abort();
9657
- };
9658
- }
9659
- defineNonEnumerableSignal(RequestLoadingState.prototype, '_isPending', true);
9660
- defineNonEnumerableSignal(RequestLoadingState.prototype, '_isStarted', false);
9661
- defineNonEnumerableSignal(RequestLoadingState.prototype, '_isComplete', false);
9662
- defineNonEnumerableSignal(RequestLoadingState.prototype, '_isCancelled', false);
9663
- defineNonEnumerableSignal(RequestLoadingState.prototype, '_isErrored', false);
9664
- defineNonEnumerableSignal(RequestLoadingState.prototype, '_error', null);
9665
- defineNonEnumerableSignal(RequestLoadingState.prototype, '_sizeHint', 0);
9666
- defineNonEnumerableSignal(RequestLoadingState.prototype, '_bytesLoaded', 0);
9667
- defineNonEnumerableSignal(RequestLoadingState.prototype, '_startTime', 0);
9668
- defineNonEnumerableSignal(RequestLoadingState.prototype, '_endTime', 0);
9669
- defineNonEnumerableSignal(RequestLoadingState.prototype, '_lastPacketTime', 0);
9670
-
9671
- /**
9672
- * The state of a request in the "pending"
9673
- * state. This is the default initial state.
9674
- *
9675
- * Extends the {@link PendingPromise} interface.
9676
- *
9677
- */
9678
-
9679
- /**
9680
- * The state of a request in the "fulfilled" state.
9681
- * This is the state of a request that has resolved
9682
- * successfully.
9683
- *
9684
- * Extends the {@link ResolvedPromise} interface.
9685
- *
9686
- */
9687
-
9688
- /**
9689
- * The state of a request in the "rejected" state.
9690
- * This is the state of a request that has rejected
9691
- * with an error.
9692
- *
9693
- * Extends the {@link RejectedPromise} interface.
9694
- *
9695
- */
9696
-
9697
- /**
9698
- * The state of a request in the "cancelled" state.
9699
- * This is the state of a promise that has been
9700
- * cancelled.
9701
- *
9702
- */
9703
-
9704
- /**
9705
- * RequestState extends the concept of {@link PromiseState} to provide a reactive
9706
- * wrapper for a request {@link Future} which allows you write declarative code
9707
- * around a Future's control flow.
9708
- *
9709
- * It is useful in both Template and JavaScript contexts, allowing you
9710
- * to quickly derive behaviors and data from pending, error and success
9711
- * states.
9712
- *
9713
- * The key difference between a {@link Promise} and a Future is that Futures provide
9714
- * access to a {@link ReadableStream | stream} of their content, the {@link RequestKey} of the request (if any)
9715
- * as well as the ability to attempt to {@link Future.abort | abort} the request.
9716
- *
9717
- * ```ts
9718
- * interface Future<T> extends Promise<T>> {
9719
- * getStream(): Promise<ReadableStream>;
9720
- * abort(): void;
9721
- * lid: RequestKey | null;
9722
- * }
9723
- * ```
9724
- *
9725
- * These additional APIs allow us to craft even richer state experiences.
9726
- *
9727
- * To get the state of a request, use {@link getRequestState}.
9728
- *
9729
- * See also:
9730
- * - {@link PendingRequest}
9731
- * - {@link ResolvedRequest}
9732
- * - {@link RejectedRequest}
9733
- * - {@link CancelledRequest}
9734
- *
9735
- */
9736
-
9737
- const RequestStateProto = {};
9738
- function performRefresh(requester, request, isReload) {
9739
- const req = Object.assign({}, request);
9740
- const cacheOptions = Object.assign({}, req.cacheOptions);
9741
- if (isReload) {
9742
- // force direct to network
9743
- cacheOptions.reload = true;
9744
- } else if (isReload === false) {
9745
- // delete reload to ensure we use backgroundReload / policy
9746
- delete cacheOptions.reload;
9747
- cacheOptions.backgroundReload = true;
9748
- } else {
9749
- // delete props to ensure we use the policy
9750
- delete cacheOptions.backgroundReload;
9751
- delete cacheOptions.reload;
9647
+ return new Set(types);
9752
9648
  }
9753
- req.cacheOptions = cacheOptions;
9754
- return requester.request(req);
9755
- }
9756
9649
 
9757
- // TODO introduce a new mechanism for defining multiple properties
9758
- // that share a common signal
9759
- defineSignal(RequestStateProto, 'reason', null);
9760
- defineSignal(RequestStateProto, 'value', null);
9761
- defineSignal(RequestStateProto, 'result', null);
9762
- defineSignal(RequestStateProto, 'error', null);
9763
- defineSignal(RequestStateProto, 'status', 'pending');
9764
- defineSignal(RequestStateProto, 'isPending', true);
9765
- defineSignal(RequestStateProto, 'isLoading', true);
9766
- defineSignal(RequestStateProto, 'isSuccess', false);
9767
- defineSignal(RequestStateProto, 'isError', false);
9768
- defineSignal(RequestStateProto, 'request', null);
9769
- defineSignal(RequestStateProto, 'response', null);
9770
- Object.defineProperty(RequestStateProto, 'isCancelled', {
9771
- get() {
9772
- return this.isError && isAbortError(this.reason);
9650
+ // we only run this function on component creation
9651
+ // and when an update is triggered, so it does not
9652
+ // react to changes in the autorefreshThreshold
9653
+ // or autorefresh args.
9654
+ //
9655
+ // if we need to react to those changes, we can
9656
+ // use a modifier or internal component or some
9657
+ // such to trigger a re-run of this function.
9658
+ /** @internal */
9659
+ static {
9660
+ decorateMethodV2(this.prototype, "autorefreshTypes", [memoized]);
9773
9661
  }
9774
- });
9775
- Object.defineProperty(RequestStateProto, 'loadingState', {
9776
- get() {
9777
- if (!this._loadingState) {
9778
- this._loadingState = new RequestLoadingState(this._request);
9662
+ async _scheduleInterval() {
9663
+ const {
9664
+ autorefreshThreshold
9665
+ } = this._args;
9666
+ const hasValidThreshold = typeof autorefreshThreshold === 'number' && autorefreshThreshold > 0;
9667
+ if (
9668
+ // dont schedule in SSR
9669
+ typeof window === 'undefined' ||
9670
+ // dont schedule without a threshold
9671
+ !hasValidThreshold ||
9672
+ // dont schedule if we weren't told to
9673
+ !this.autorefreshTypes.has('interval') ||
9674
+ // dont schedule if we're already scheduled
9675
+ this._intervalStart !== null) {
9676
+ return;
9779
9677
  }
9780
- return this._loadingState;
9781
- }
9782
- });
9783
- function createRequestState(future) {
9784
- const state = getPromiseResult(future);
9785
- const promiseState = Object.create(RequestStateProto);
9786
- promiseState._request = future;
9787
- // @ts-expect-error - we still attach it for PendingState
9788
- promiseState.reload = () => {
9789
- macroCondition(getGlobalConfig().WarpDrive.env.DEBUG) ? (test => {
9790
- if (!test) {
9791
- throw new Error(`Cannot reload a request that is still pending. Await or abort the original request first.`);
9792
- }
9793
- })(!promiseState.isPending) : {};
9794
- return performRefresh(future.requester, promiseState.request, true);
9795
- };
9796
9678
 
9797
- // @ts-expect-error - we still attach it for PendingState
9798
- promiseState.refresh = (usePolicy = false) => {
9799
- macroCondition(getGlobalConfig().WarpDrive.env.DEBUG) ? (test => {
9800
- if (!test) {
9801
- throw new Error(`Cannot refresh a request that is still pending. Await or abort the original request first.`);
9679
+ // if we have a current request, wait for it to finish
9680
+ // before scheduling the next one
9681
+ if (this._latestRequest) {
9682
+ try {
9683
+ await this._latestRequest;
9684
+ } catch {
9685
+ // ignore errors here, we just want to wait for the request to finish
9686
+ }
9687
+ if (this.isDestroyed) {
9688
+ return;
9802
9689
  }
9803
- })(!promiseState.isPending) : {};
9804
- return performRefresh(future.requester, promiseState.request, usePolicy === true ? null : false);
9805
- };
9806
- if (state) {
9807
- if (state.isError) {
9808
- promiseState.error = state.result;
9809
- promiseState.reason = state.result;
9810
- promiseState.status = 'rejected';
9811
- promiseState.isError = true;
9812
- promiseState.isPending = false;
9813
- promiseState.isLoading = false;
9814
- promiseState.request = state.result.request;
9815
- promiseState.response = state.result.response;
9816
- } else {
9817
- promiseState.result = state.result.content;
9818
- promiseState.value = state.result.content;
9819
- promiseState.status = 'fulfilled';
9820
- promiseState.isSuccess = true;
9821
- promiseState.isPending = false;
9822
- promiseState.isLoading = false;
9823
- promiseState.request = state.result.request;
9824
- promiseState.response = state.result.response;
9825
9690
  }
9826
- } else {
9827
- void future.then(result => {
9828
- setPromiseResult(future, {
9829
- isError: false,
9830
- result
9831
- });
9832
- promiseState.result = result.content;
9833
- promiseState.value = result.content;
9834
- promiseState.status = 'fulfilled';
9835
- promiseState.isSuccess = true;
9836
- promiseState.isPending = false;
9837
- promiseState.isLoading = false;
9838
- promiseState.request = result.request;
9839
- promiseState.response = result.response;
9840
- }, error => {
9841
- setPromiseResult(future, {
9842
- isError: true,
9843
- result: error
9844
- });
9845
- promiseState.error = error;
9846
- promiseState.reason = error;
9847
- promiseState.status = 'rejected';
9848
- promiseState.isError = true;
9849
- promiseState.isPending = false;
9850
- promiseState.isLoading = false;
9851
- promiseState.request = error.request;
9852
- promiseState.response = error.response;
9853
- });
9854
- }
9855
- return promiseState;
9856
- }
9857
9691
 
9858
- /**
9859
- * `getRequestState` can be used in both JavaScript and Template contexts.
9860
- *
9861
- * ```ts
9862
- * import { getRequestState } from '@warp-drive/ember';
9863
- *
9864
- * const state = getRequestState(future);
9865
- * ```
9866
- *
9867
- * For instance, we could write a getter on a component that updates whenever
9868
- * the request state advances or the future changes, by combining the function
9869
- * with the use of `@cached`
9870
- *
9871
- * ```ts
9872
- * class Component {
9873
- * @cached
9874
- * get title() {
9875
- * const state = getRequestState(this.args.request);
9876
- * if (state.isPending) {
9877
- * return 'loading...';
9878
- * }
9879
- * if (state.isError) { return null; }
9880
- * return state.result.title;
9881
- * }
9882
- * }
9883
- * ```
9884
- *
9885
- * Or in a template as a helper:
9886
- *
9887
- * ```gjs
9888
- * import { getRequestState } from '@warp-drive/ember';
9889
- *
9890
- * <template>
9891
- * {{#let (getRequestState @request) as |state|}}
9892
- * {{#if state.isPending}}
9893
- * <Spinner />
9894
- * {{else if state.isError}}
9895
- * <ErrorForm @error={{state.error}} />
9896
- * {{else}}
9897
- * <h1>{{state.result.title}}</h1>
9898
- * {{/if}}
9899
- * {{/let}}
9900
- * </template>
9901
- * ```
9902
- *
9903
- * If looking to use in a template, consider also the `<Request />` component
9904
- * which offers a numbe of additional capabilities for requests *beyond* what
9905
- * `RequestState` provides.
9906
- *
9907
- */
9908
- function getRequestState(future) {
9909
- let state = RequestCache.get(future);
9910
- if (!state) {
9911
- state = createRequestState(future);
9912
- RequestCache.set(future, state);
9913
- }
9914
- return state;
9915
- }
9916
- function decorateMethodV2(prototype, prop, decorators) {
9917
- const origDesc = Object.getOwnPropertyDescriptor(prototype, prop);
9918
- let desc = {
9919
- ...origDesc
9920
- };
9921
- for (let decorator of decorators) {
9922
- desc = decorator(prototype, prop, desc) || desc;
9923
- }
9924
- if (desc.initializer !== void 0) {
9925
- desc.value = desc.initializer ? desc.initializer.call(prototype) : void 0;
9926
- desc.initializer = void 0;
9692
+ // setup the next interval
9693
+ this._intervalStart = Date.now();
9694
+ this._nextInterval = setTimeout(() => {
9695
+ this._maybeUpdate();
9696
+ }, autorefreshThreshold);
9927
9697
  }
9928
- Object.defineProperty(prototype, prop, desc);
9929
- }
9930
- const DEFAULT_DEADLINE = 30_000;
9931
- const DISPOSE = Symbol.dispose || Symbol.for('dispose');
9932
- function isNeverString(val) {
9933
- return val;
9934
- }
9935
-
9936
- /**
9937
- * Utilities to assist in recovering from the error.
9938
- */
9939
9698
 
9940
- /** @deprecated use {@link RecoveryFeatures} */
9941
-
9942
- /**
9943
- * Utilities for keeping the request fresh
9944
- */
9699
+ /** @internal */
9700
+ _clearInterval() {
9701
+ if (this._nextInterval) {
9702
+ clearTimeout(this._nextInterval);
9703
+ this._intervalStart = null;
9704
+ }
9705
+ }
9706
+ /**
9707
+ * @internal
9708
+ */
9709
+ _updateSubscriptions() {
9710
+ if (this.isIdle) {
9711
+ return;
9712
+ }
9713
+ const requestId = this._request.lid;
9945
9714
 
9946
- // eslint-disable-next-line @typescript-eslint/no-unused-vars
9715
+ // if we're already subscribed to this request, we don't need to do anything
9716
+ if (this._subscribedTo === requestId) {
9717
+ return;
9718
+ }
9947
9719
 
9948
- // eslint-disable-next-line @typescript-eslint/no-unused-vars
9720
+ // if we're subscribed to a different request, we need to unsubscribe
9721
+ this._removeSubscriptions();
9949
9722
 
9950
- /**
9951
- * A reactive class
9952
- *
9953
- * @hideconstructor
9954
- */
9955
- class RequestSubscription {
9956
- /**
9957
- * Whether the browser reports that the network is online.
9958
- */
9723
+ // if we have a request, we need to subscribe to it
9724
+ const store = this._getRequester();
9725
+ this._requester = store;
9726
+ if (requestId && isStore(store)) {
9727
+ this._subscribedTo = requestId;
9728
+ this._subscription = store.notifications.subscribe(requestId, (_id, op) => {
9729
+ // ignore subscription events that occur while our own component's request
9730
+ // is occurring
9731
+ if (this._isUpdating) {
9732
+ return;
9733
+ }
9734
+ switch (op) {
9735
+ case 'invalidated':
9736
+ {
9737
+ // if we're subscribed to invalidations, we need to update
9738
+ if (this.autorefreshTypes.has('invalid')) {
9739
+ this._invalidated = true;
9740
+ this._maybeUpdate();
9741
+ }
9742
+ break;
9743
+ }
9744
+ case 'state':
9745
+ {
9746
+ const latest = store.requestManager._deduped.get(requestId);
9747
+ const priority = latest?.priority;
9748
+ const state = this.reqState;
9749
+ if (!priority) {
9750
+ // if there is no priority, we have completed whatever request
9751
+ // was occurring and so we are no longer refreshing (if we were)
9752
+ this.isRefreshing = false;
9753
+ } else if (priority.blocking && !state.isLoading) {
9754
+ // if we are blocking, there is an active request for this identity
9755
+ // that MUST be fulfilled from network (not cache).
9756
+ // Thus this is not "refreshing" because we should clear out and
9757
+ // block on this request.
9758
+ //
9759
+ // we receive state notifications when either a request initiates
9760
+ // or completes.
9761
+ //
9762
+ // In the completes case: we may receive the state notification
9763
+ // slightly before the request is finalized because the NotificationManager
9764
+ // may sync flush it (and thus deliver it before the microtask completes)
9765
+ //
9766
+ // In the initiates case: we aren't supposed to receive one unless there
9767
+ // is no other request in flight for this identity.
9768
+ //
9769
+ // However, there is a race condition here where the completed
9770
+ // notification can trigger an update that generates a new request
9771
+ // thus giving us an initiated notification before the older request
9772
+ // finalizes.
9773
+ //
9774
+ // When this occurs, if the triggered update happens to have caused
9775
+ // a new request to be made for the same identity AND that request
9776
+ // is the one passed into this component as the @request arg, then
9777
+ // getRequestState will return the state of the new request.
9778
+ // We can detect this by checking if the request state is "loading"
9779
+ // as outside of this case we would have a completed request.
9780
+ //
9781
+ // That is the reason for the `&& !state.isLoading` check above.
9959
9782
 
9960
- /**
9961
- * Whether the browser reports that the tab is hidden.
9962
- */
9783
+ // TODO should we just treat this as refreshing?
9784
+ this.isRefreshing = false;
9785
+ this._maybeUpdate('policy', true);
9786
+ } else {
9787
+ this.isRefreshing = true;
9788
+ }
9789
+ }
9790
+ }
9791
+ });
9792
+ }
9793
+ }
9963
9794
 
9964
9795
  /**
9965
- * Whether the component is currently refreshing the request.
9796
+ * @internal
9966
9797
  */
9798
+ _removeSubscriptions() {
9799
+ const store = this._requester;
9800
+ if (this._subscription && store && isStore(store)) {
9801
+ store.notifications.unsubscribe(this._subscription);
9802
+ this._subscribedTo = null;
9803
+ this._subscription = null;
9804
+ this._requester = null;
9805
+ }
9806
+ }
9967
9807
 
9968
9808
  /**
9969
- * The most recent blocking request that was made, typically
9970
- * the result of a reload.
9971
- *
9972
- * This will never be the original request passed as an arg to
9973
- * the component.
9809
+ * Install the event listeners for network and visibility changes.
9810
+ * This is only done in browser environments with a global `window`.
9974
9811
  *
9975
9812
  * @internal
9976
9813
  */
9814
+ _installListeners() {
9815
+ if (typeof window === 'undefined') {
9816
+ return;
9817
+ }
9818
+ this.isOnline = window.navigator.onLine;
9819
+ this._unavailableStart = this.isOnline ? null : Date.now();
9820
+ this.isHidden = document.visibilityState === 'hidden';
9821
+ this._onlineChanged = event => {
9822
+ this.isOnline = event.type === 'online';
9823
+ if (event.type === 'offline' && this._unavailableStart === null) {
9824
+ this._unavailableStart = Date.now();
9825
+ }
9826
+ this._maybeUpdate();
9827
+ };
9828
+ this._backgroundChanged = () => {
9829
+ const isHidden = document.visibilityState === 'hidden';
9830
+ this.isHidden = isHidden;
9831
+ if (isHidden && this._unavailableStart === null) {
9832
+ this._unavailableStart = Date.now();
9833
+ }
9834
+ this._maybeUpdate();
9835
+ };
9836
+ window.addEventListener('online', this._onlineChanged, {
9837
+ passive: true,
9838
+ capture: true
9839
+ });
9840
+ window.addEventListener('offline', this._onlineChanged, {
9841
+ passive: true,
9842
+ capture: true
9843
+ });
9844
+ document.addEventListener('visibilitychange', this._backgroundChanged, {
9845
+ passive: true,
9846
+ capture: true
9847
+ });
9848
+ }
9977
9849
 
9978
9850
  /**
9979
- * The most recent request that was made, typically due to either a
9980
- * reload or a refresh.
9851
+ * If the network is online and the tab is visible, either reload or refresh the request
9852
+ * based on the component's configuration and the requested update mode.
9981
9853
  *
9982
- * This will never be the original request passed as an arg to
9983
- * the component.
9854
+ * Valid modes are:
9984
9855
  *
9985
- * @internal
9986
- */
9987
-
9988
- /**
9989
- * The time at which the network was reported as offline.
9856
+ * - `'reload'`: Force a reload of the request.
9857
+ * - `'refresh'`: Refresh the request in the background.
9858
+ * - `'policy'`: Make the request, letting the store's configured CachePolicy decide whether to reload, refresh, or do nothing.
9859
+ * - `undefined`: Make the request using the component's autorefreshBehavior setting if the autorefreshThreshold has passed.
9990
9860
  *
9991
9861
  * @internal
9992
9862
  */
9863
+ _maybeUpdate(mode, silent) {
9864
+ if (this.isIdle) {
9865
+ return;
9866
+ }
9867
+ const {
9868
+ reqState
9869
+ } = this;
9870
+ if (reqState.isPending) {
9871
+ return;
9872
+ }
9873
+ const canAttempt = Boolean(this.isOnline && !this.isHidden && (mode || this.autorefreshTypes.size));
9874
+ if (!canAttempt) {
9875
+ if (!silent && mode && mode !== '_invalidated') {
9876
+ throw new Error(`Reload not available: the network is not online or the tab is hidden`);
9877
+ }
9878
+ return;
9879
+ }
9880
+ const {
9881
+ autorefreshTypes
9882
+ } = this;
9883
+ let shouldAttempt = this._invalidated || Boolean(mode);
9884
+ if (!shouldAttempt && autorefreshTypes.has('online')) {
9885
+ const {
9886
+ _unavailableStart
9887
+ } = this;
9888
+ const {
9889
+ autorefreshThreshold
9890
+ } = this._args;
9891
+ const deadline = typeof autorefreshThreshold === 'number' ? autorefreshThreshold : DEFAULT_DEADLINE;
9892
+ shouldAttempt = Boolean(_unavailableStart && Date.now() - _unavailableStart > deadline);
9893
+ }
9894
+ if (!shouldAttempt && autorefreshTypes.has('interval')) {
9895
+ const {
9896
+ _intervalStart
9897
+ } = this;
9898
+ const {
9899
+ autorefreshThreshold
9900
+ } = this._args;
9901
+ if (_intervalStart && typeof autorefreshThreshold === 'number' && autorefreshThreshold > 0) {
9902
+ shouldAttempt = Boolean(Date.now() - _intervalStart >= autorefreshThreshold);
9903
+ }
9904
+ }
9905
+ this._unavailableStart = null;
9906
+ this._invalidated = false;
9907
+ if (shouldAttempt) {
9908
+ this._clearInterval();
9909
+ this._isUpdating = true;
9910
+ const realMode = mode === '_invalidated' ? null : mode;
9911
+ const val = realMode ?? this._args.autorefreshBehavior ?? 'policy';
9993
9912
 
9994
- /** @internal */
9995
-
9996
- /** @internal */
9997
-
9998
- /** @internal */
9999
-
10000
- /** @internal */
10001
-
10002
- /** @internal */
10003
-
10004
- /**
10005
- * The event listener for network status changes,
10006
- * cached to use the reference for removal.
10007
- *
10008
- * @internal
10009
- */
9913
+ // if the future was generated by an older store version, it may not have
9914
+ // a requester set. In this case we append it to ensure that reload and
9915
+ // refresh will work appropriately.
9916
+ const requester = this._getRequester();
9917
+ if (!reqState._request.requester) {
9918
+ reqState._request.requester = requester;
9919
+ }
9920
+ switch (val) {
9921
+ case 'reload':
9922
+ this._latestRequest = reqState.reload();
9923
+ break;
9924
+ case 'refresh':
9925
+ this._latestRequest = reqState.refresh();
9926
+ break;
9927
+ case 'policy':
9928
+ this._latestRequest = reqState.refresh(true);
9929
+ break;
9930
+ default:
9931
+ macroCondition(getGlobalConfig().WarpDrive.env.DEBUG) ? (test => {
9932
+ {
9933
+ throw new Error(`Invalid ${mode ? 'update mode' : '@autorefreshBehavior'} for <Request />: ${isNeverString(val)}`);
9934
+ }
9935
+ })() : {};
9936
+ }
9937
+ if (val !== 'refresh') {
9938
+ this._localRequest = this._latestRequest;
9939
+ }
9940
+ void this._scheduleInterval();
9941
+ void this._latestRequest.finally(() => {
9942
+ this._isUpdating = false;
9943
+ });
9944
+ }
9945
+ }
10010
9946
 
10011
9947
  /**
10012
- * The event listener for visibility status changes,
10013
- * cached to use the reference for removal.
10014
- *
10015
9948
  * @internal
10016
9949
  */
9950
+ _getRequester() {
9951
+ // Note: we check for the requester's presence
9952
+ // as well as the request's presence because we may
9953
+ // be subscribed to a request issued by a store from an older
9954
+ // version of the library that didn't yet set requester.
9955
+ if (this._args.request?.requester) {
9956
+ return this._args.request.requester;
9957
+ }
9958
+ return this.store;
9959
+ }
10017
9960
 
10018
9961
  /**
10019
- * The last request passed as an arg to the component,
10020
- * cached for comparison.
10021
- *
10022
- * @internal
9962
+ * Retry the request, reloading it from the server.
10023
9963
  */
9964
+ retry = async () => {
9965
+ this._maybeUpdate('reload');
9966
+ await this._localRequest;
9967
+ };
10024
9968
 
10025
9969
  /**
10026
- * The last query passed as an arg to the component,
10027
- * cached for comparison.
10028
- *
10029
- * @internal
9970
+ * Refresh the request, updating it in the background.
10030
9971
  */
10031
-
10032
- /** @internal */
10033
-
10034
- /** @internal */
10035
-
10036
- /** @internal */
9972
+ refresh = async () => {
9973
+ this._maybeUpdate('refresh');
9974
+ await this._latestRequest;
9975
+ };
10037
9976
 
10038
9977
  /**
10039
- * The Store this subscription subscribes to or the RequestManager
10040
- * which issues this request.
9978
+ * features to yield to the error slot of a component
10041
9979
  */
9980
+ get errorFeatures() {
9981
+ return {
9982
+ isHidden: this.isHidden,
9983
+ isOnline: this.isOnline,
9984
+ retry: this.retry
9985
+ };
9986
+ }
10042
9987
 
10043
9988
  /**
10044
- * The Store or RequestManager that the last subscription is attached to.
10045
- *
10046
- * This differs from 'store' because a <Request /> may be passed a
10047
- * request originating from a different store than the <Request />
10048
- * component would use if it were to issue the request itself.
10049
- *
10050
- * @internal
9989
+ * features to yield to the content slot of a component
10051
9990
  */
10052
- _requester;
10053
- constructor(store, args) {
10054
- this._args = args;
10055
- this.store = store;
10056
- this._subscribedTo = null;
10057
- this._subscription = null;
10058
- this._intervalStart = null;
10059
- this._invalidated = false;
10060
- this._nextInterval = null;
10061
- this._requester = null;
10062
- this.isDestroyed = false;
10063
- this[DISPOSE] = _DISPOSE;
10064
- this._installListeners();
10065
- void this._beginPolling();
9991
+ static {
9992
+ decorateMethodV2(this.prototype, "errorFeatures", [memoized]);
9993
+ }
9994
+ get contentFeatures() {
9995
+ const feat = {
9996
+ isHidden: this.isHidden,
9997
+ isOnline: this.isOnline,
9998
+ reload: this.retry,
9999
+ refresh: this.refresh,
10000
+ isRefreshing: this.isRefreshing,
10001
+ latestRequest: this._latestRequest
10002
+ };
10003
+ if (feat.isRefreshing) {
10004
+ feat.abort = () => {
10005
+ this._latestRequest?.abort();
10006
+ };
10007
+ }
10008
+ return feat;
10066
10009
  }
10067
10010
 
10068
10011
  /**
10069
10012
  * @internal
10070
10013
  */
10071
- async _beginPolling() {
10072
- // await the initial request
10073
- try {
10074
- if (!this.isIdle) {
10075
- await this.request;
10076
- }
10077
- } catch {
10078
- // ignore errors here, we just want to wait for the request to finish
10079
- } finally {
10080
- if (!this.isDestroyed) {
10081
- void this._scheduleInterval();
10082
- }
10083
- }
10014
+ static {
10015
+ decorateMethodV2(this.prototype, "contentFeatures", [memoized]);
10084
10016
  }
10085
- get isIdle() {
10017
+ get _request() {
10086
10018
  const {
10087
10019
  request,
10088
10020
  query
10089
10021
  } = this._args;
10090
- return Boolean(!request && !query);
10022
+ macroCondition(getGlobalConfig().WarpDrive.env.DEBUG) ? (test => {
10023
+ if (!test) {
10024
+ throw new Error(`Cannot use both @request and @query args with the <Request> component`);
10025
+ }
10026
+ })(!request || !query) : {};
10027
+ const {
10028
+ _localRequest,
10029
+ _originalRequest,
10030
+ _originalQuery
10031
+ } = this;
10032
+ const isOriginalRequest = request === _originalRequest && query === _originalQuery;
10033
+ if (_localRequest && isOriginalRequest) {
10034
+ return _localRequest;
10035
+ }
10036
+
10037
+ // update state checks for the next time
10038
+ this._originalQuery = query;
10039
+ this._originalRequest = request;
10040
+ if (request) {
10041
+ return request;
10042
+ }
10043
+ macroCondition(getGlobalConfig().WarpDrive.env.DEBUG) ? (test => {
10044
+ if (!test) {
10045
+ throw new Error(`You must provide either @request or an @query arg with the <Request> component`);
10046
+ }
10047
+ })(query) : {};
10048
+ return this.store.request(query);
10091
10049
  }
10092
10050
  static {
10093
- decorateMethodV2(this.prototype, "isIdle", [memoized]);
10051
+ decorateMethodV2(this.prototype, "_request", [memoized]);
10094
10052
  }
10095
- get autorefreshTypes() {
10096
- const {
10097
- autorefresh
10098
- } = this._args;
10099
- let types;
10100
- if (autorefresh === true) {
10101
- types = ['online', 'invalid'];
10102
- } else if (typeof autorefresh === 'string') {
10103
- types = autorefresh.split(',');
10053
+ get request() {
10054
+ if (macroCondition(getGlobalConfig().WarpDrive.env.DEBUG)) {
10055
+ try {
10056
+ const request = this._request;
10057
+ this._updateSubscriptions();
10058
+ return request;
10059
+ } catch (e) {
10060
+ // eslint-disable-next-line no-console
10061
+ console.log(e);
10062
+ throw new Error(`Unable to initialize the request`, {
10063
+ cause: e
10064
+ });
10065
+ }
10104
10066
  } else {
10105
- types = [];
10067
+ const request = this._request;
10068
+ this._updateSubscriptions();
10069
+ return request;
10106
10070
  }
10107
- return new Set(types);
10108
10071
  }
10109
-
10110
- // we only run this function on component creation
10111
- // and when an update is triggered, so it does not
10112
- // react to changes in the autorefreshThreshold
10113
- // or autorefresh args.
10114
- //
10115
- // if we need to react to those changes, we can
10116
- // use a modifier or internal component or some
10117
- // such to trigger a re-run of this function.
10118
- /** @internal */
10119
10072
  static {
10120
- decorateMethodV2(this.prototype, "autorefreshTypes", [memoized]);
10073
+ decorateMethodV2(this.prototype, "request", [memoized]);
10121
10074
  }
10122
- async _scheduleInterval() {
10075
+ get reqState() {
10076
+ return getRequestState(this.request);
10077
+ }
10078
+ get result() {
10079
+ return this.reqState.result;
10080
+ }
10081
+ }
10082
+ defineSignal(RequestSubscription.prototype, 'isOnline', true);
10083
+ defineSignal(RequestSubscription.prototype, 'isHidden', false);
10084
+ defineSignal(RequestSubscription.prototype, 'isRefreshing', false);
10085
+ defineSignal(RequestSubscription.prototype, '_localRequest', undefined);
10086
+ defineSignal(RequestSubscription.prototype, '_latestRequest', undefined);
10087
+ function isStore(store) {
10088
+ return 'requestManager' in store;
10089
+ }
10090
+ function createRequestSubscription(store, args) {
10091
+ return new RequestSubscription(store, args);
10092
+ }
10093
+ function upgradeSubscription(sub) {
10094
+ return sub;
10095
+ }
10096
+ function _DISPOSE() {
10097
+ const self = upgradeSubscription(this);
10098
+ self.isDestroyed = true;
10099
+ self._removeSubscriptions();
10100
+ if (typeof window === 'undefined') {
10101
+ return;
10102
+ }
10103
+ self._clearInterval();
10104
+ window.removeEventListener('online', self._onlineChanged, {
10105
+ passive: true,
10106
+ capture: true
10107
+ });
10108
+ window.removeEventListener('offline', self._onlineChanged, {
10109
+ passive: true,
10110
+ capture: true
10111
+ });
10112
+ document.removeEventListener('visibilitychange', self._backgroundChanged, {
10113
+ passive: true,
10114
+ capture: true
10115
+ });
10116
+ }
10117
+ const RequestCache = new WeakMap();
10118
+ function isAbortError(error) {
10119
+ return error instanceof DOMException && error.name === 'AbortError';
10120
+ }
10121
+ function upgradeLoadingState(state) {
10122
+ return state;
10123
+ }
10124
+ async function watchStream(stream, loadingState) {
10125
+ const state = upgradeLoadingState(loadingState);
10126
+ const reader = stream.getReader();
10127
+ let bytesLoaded = 0;
10128
+ let shouldForward = state._stream !== null && state._stream.readable.locked;
10129
+ let isForwarding = shouldForward;
10130
+ let writer = state._stream?.writable.getWriter();
10131
+ const buffer = [];
10132
+ state._isPending = false;
10133
+ state._isStarted = true;
10134
+ state._startTime = performance.now();
10135
+ while (true) {
10123
10136
  const {
10124
- autorefreshThreshold
10125
- } = this._args;
10126
- const hasValidThreshold = typeof autorefreshThreshold === 'number' && autorefreshThreshold > 0;
10127
- if (
10128
- // dont schedule in SSR
10129
- typeof window === 'undefined' ||
10130
- // dont schedule without a threshold
10131
- !hasValidThreshold ||
10132
- // dont schedule if we weren't told to
10133
- !this.autorefreshTypes.has('interval') ||
10134
- // dont schedule if we're already scheduled
10135
- this._intervalStart !== null) {
10136
- return;
10137
+ value,
10138
+ done
10139
+ } = await reader.read();
10140
+ if (done) {
10141
+ break;
10137
10142
  }
10138
-
10139
- // if we have a current request, wait for it to finish
10140
- // before scheduling the next one
10141
- if (this._latestRequest) {
10142
- try {
10143
- await this._latestRequest;
10144
- } catch {
10145
- // ignore errors here, we just want to wait for the request to finish
10146
- }
10147
- if (this.isDestroyed) {
10148
- return;
10143
+ bytesLoaded += value.byteLength;
10144
+ state._bytesLoaded = bytesLoaded;
10145
+ state._lastPacketTime = performance.now();
10146
+ shouldForward = shouldForward || state._stream !== null && state._stream.readable.locked;
10147
+ if (shouldForward) {
10148
+ if (!isForwarding) {
10149
+ isForwarding = true;
10150
+ writer = state._stream.writable.getWriter();
10151
+ for (const item of buffer) {
10152
+ await writer.ready;
10153
+ await writer.write(item);
10154
+ }
10155
+ buffer.length = 0;
10149
10156
  }
10157
+ await writer.ready;
10158
+ await writer.write(value);
10159
+ } else {
10160
+ buffer.push(value);
10150
10161
  }
10162
+ }
10151
10163
 
10152
- // setup the next interval
10153
- this._intervalStart = Date.now();
10154
- this._nextInterval = setTimeout(() => {
10155
- this._maybeUpdate();
10156
- }, autorefreshThreshold);
10164
+ // if we are still forwarding, we need to close the writer
10165
+ if (isForwarding) {
10166
+ await writer.ready;
10167
+ await writer.close();
10168
+ } else if (state._stream) {
10169
+ // if we are not forwarding, we need to cancel the stream
10170
+ await state._stream.readable.cancel('The Stream Has Already Ended');
10171
+ state._stream = null;
10157
10172
  }
10173
+ const endTime = performance.now();
10174
+ state._endTime = endTime;
10175
+ state._isComplete = true;
10176
+ state._isStarted = false;
10177
+ }
10158
10178
 
10179
+ /**
10180
+ * Lazily consumes the stream of a request, providing a number of
10181
+ * reactive properties that can be used to build UIs that respond
10182
+ * to the progress of a request.
10183
+ *
10184
+ * @hideconstructor
10185
+ */
10186
+ class RequestLoadingState {
10159
10187
  /** @internal */
10160
- _clearInterval() {
10161
- if (this._nextInterval) {
10162
- clearTimeout(this._nextInterval);
10163
- this._intervalStart = null;
10164
- }
10165
- }
10166
- /**
10167
- * @internal
10168
- */
10169
- _updateSubscriptions() {
10170
- if (this.isIdle) {
10171
- return;
10172
- }
10173
- const requestId = this._request.lid;
10174
10188
 
10175
- // if we're already subscribed to this request, we don't need to do anything
10176
- if (this._subscribedTo === requestId) {
10177
- return;
10178
- }
10189
+ /** @internal */
10179
10190
 
10180
- // if we're subscribed to a different request, we need to unsubscribe
10181
- this._removeSubscriptions();
10191
+ /** @internal */
10182
10192
 
10183
- // if we have a request, we need to subscribe to it
10184
- const store = this._getRequester();
10185
- this._requester = store;
10186
- if (requestId && isStore(store)) {
10187
- this._subscribedTo = requestId;
10188
- this._subscription = store.notifications.subscribe(requestId, (_id, op) => {
10189
- // ignore subscription events that occur while our own component's request
10190
- // is occurring
10191
- if (this._isUpdating) {
10192
- return;
10193
- }
10194
- switch (op) {
10195
- case 'invalidated':
10196
- {
10197
- // if we're subscribed to invalidations, we need to update
10198
- if (this.autorefreshTypes.has('invalid')) {
10199
- this._invalidated = true;
10200
- this._maybeUpdate();
10201
- }
10202
- break;
10203
- }
10204
- case 'state':
10205
- {
10206
- const latest = store.requestManager._deduped.get(requestId);
10207
- const priority = latest?.priority;
10208
- const state = this.reqState;
10209
- if (!priority) {
10210
- // if there is no priority, we have completed whatever request
10211
- // was occurring and so we are no longer refreshing (if we were)
10212
- this.isRefreshing = false;
10213
- } else if (priority.blocking && !state.isLoading) {
10214
- // if we are blocking, there is an active request for this identity
10215
- // that MUST be fulfilled from network (not cache).
10216
- // Thus this is not "refreshing" because we should clear out and
10217
- // block on this request.
10218
- //
10219
- // we receive state notifications when either a request initiates
10220
- // or completes.
10221
- //
10222
- // In the completes case: we may receive the state notification
10223
- // slightly before the request is finalized because the NotificationManager
10224
- // may sync flush it (and thus deliver it before the microtask completes)
10225
- //
10226
- // In the initiates case: we aren't supposed to receive one unless there
10227
- // is no other request in flight for this identity.
10228
- //
10229
- // However, there is a race condition here where the completed
10230
- // notification can trigger an update that generates a new request
10231
- // thus giving us an initiated notification before the older request
10232
- // finalizes.
10233
- //
10234
- // When this occurs, if the triggered update happens to have caused
10235
- // a new request to be made for the same identity AND that request
10236
- // is the one passed into this component as the @request arg, then
10237
- // getRequestState will return the state of the new request.
10238
- // We can detect this by checking if the request state is "loading"
10239
- // as outside of this case we would have a completed request.
10240
- //
10241
- // That is the reason for the `&& !state.isLoading` check above.
10193
+ /** @internal */
10242
10194
 
10243
- // TODO should we just treat this as refreshing?
10244
- this.isRefreshing = false;
10245
- this._maybeUpdate('policy', true);
10246
- } else {
10247
- this.isRefreshing = true;
10248
- }
10249
- }
10250
- }
10251
- });
10252
- }
10253
- }
10195
+ /** @internal */
10254
10196
 
10255
- /**
10256
- * @internal
10257
- */
10258
- _removeSubscriptions() {
10259
- const store = this._requester;
10260
- if (this._subscription && store && isStore(store)) {
10261
- store.notifications.unsubscribe(this._subscription);
10262
- this._subscribedTo = null;
10263
- this._subscription = null;
10264
- this._requester = null;
10265
- }
10266
- }
10197
+ /** @internal */
10267
10198
 
10268
- /**
10269
- * Install the event listeners for network and visibility changes.
10270
- * This is only done in browser environments with a global `window`.
10271
- *
10272
- * @internal
10273
- */
10274
- _installListeners() {
10275
- if (typeof window === 'undefined') {
10276
- return;
10277
- }
10278
- this.isOnline = window.navigator.onLine;
10279
- this._unavailableStart = this.isOnline ? null : Date.now();
10280
- this.isHidden = document.visibilityState === 'hidden';
10281
- this._onlineChanged = event => {
10282
- this.isOnline = event.type === 'online';
10283
- if (event.type === 'offline' && this._unavailableStart === null) {
10284
- this._unavailableStart = Date.now();
10285
- }
10286
- this._maybeUpdate();
10287
- };
10288
- this._backgroundChanged = () => {
10289
- const isHidden = document.visibilityState === 'hidden';
10290
- this.isHidden = isHidden;
10291
- if (isHidden && this._unavailableStart === null) {
10292
- this._unavailableStart = Date.now();
10293
- }
10294
- this._maybeUpdate();
10295
- };
10296
- window.addEventListener('online', this._onlineChanged, {
10297
- passive: true,
10298
- capture: true
10299
- });
10300
- window.addEventListener('offline', this._onlineChanged, {
10301
- passive: true,
10302
- capture: true
10303
- });
10304
- document.addEventListener('visibilitychange', this._backgroundChanged, {
10305
- passive: true,
10306
- capture: true
10307
- });
10308
- }
10199
+ /** @internal */
10309
10200
 
10310
- /**
10311
- * If the network is online and the tab is visible, either reload or refresh the request
10312
- * based on the component's configuration and the requested update mode.
10313
- *
10314
- * Valid modes are:
10315
- *
10316
- * - `'reload'`: Force a reload of the request.
10317
- * - `'refresh'`: Refresh the request in the background.
10318
- * - `'policy'`: Make the request, letting the store's configured CachePolicy decide whether to reload, refresh, or do nothing.
10319
- * - `undefined`: Make the request using the component's autorefreshBehavior setting if the autorefreshThreshold has passed.
10320
- *
10321
- * @internal
10322
- */
10323
- _maybeUpdate(mode, silent) {
10324
- if (this.isIdle) {
10325
- return;
10326
- }
10327
- const {
10328
- reqState
10329
- } = this;
10330
- if (reqState.isPending) {
10331
- return;
10332
- }
10333
- const canAttempt = Boolean(this.isOnline && !this.isHidden && (mode || this.autorefreshTypes.size));
10334
- if (!canAttempt) {
10335
- if (!silent && mode && mode !== '_invalidated') {
10336
- throw new Error(`Reload not available: the network is not online or the tab is hidden`);
10337
- }
10201
+ /** @internal */
10202
+
10203
+ /** @internal */
10204
+
10205
+ /** @internal */
10206
+
10207
+ /** @internal */
10208
+
10209
+ /** @internal */
10210
+ _stream = null;
10211
+ /** @internal */
10212
+ _future;
10213
+ /** @internal */
10214
+ _triggered = false;
10215
+ /** @internal */
10216
+ _trigger() {
10217
+ if (this._triggered) {
10338
10218
  return;
10339
10219
  }
10340
- const {
10341
- autorefreshTypes
10342
- } = this;
10343
- let shouldAttempt = this._invalidated || Boolean(mode);
10344
- if (!shouldAttempt && autorefreshTypes.has('online')) {
10345
- const {
10346
- _unavailableStart
10347
- } = this;
10348
- const {
10349
- autorefreshThreshold
10350
- } = this._args;
10351
- const deadline = typeof autorefreshThreshold === 'number' ? autorefreshThreshold : DEFAULT_DEADLINE;
10352
- shouldAttempt = Boolean(_unavailableStart && Date.now() - _unavailableStart > deadline);
10353
- }
10354
- if (!shouldAttempt && autorefreshTypes.has('interval')) {
10355
- const {
10356
- _intervalStart
10357
- } = this;
10358
- const {
10359
- autorefreshThreshold
10360
- } = this._args;
10361
- if (_intervalStart && typeof autorefreshThreshold === 'number' && autorefreshThreshold > 0) {
10362
- shouldAttempt = Boolean(Date.now() - _intervalStart >= autorefreshThreshold);
10363
- }
10220
+ this._triggered = true;
10221
+ const future = this._future;
10222
+ const promise = future.getStream();
10223
+ if (promise.sizeHint) {
10224
+ this._sizeHint = promise.sizeHint;
10364
10225
  }
10365
- this._unavailableStart = null;
10366
- this._invalidated = false;
10367
- if (shouldAttempt) {
10368
- this._clearInterval();
10369
- this._isUpdating = true;
10370
- const realMode = mode === '_invalidated' ? null : mode;
10371
- const val = realMode ?? this._args.autorefreshBehavior ?? 'policy';
10372
-
10373
- // if the future was generated by an older store version, it may not have
10374
- // a requester set. In this case we append it to ensure that reload and
10375
- // refresh will work appropriately.
10376
- const requester = this._getRequester();
10377
- if (!reqState._request.requester) {
10378
- reqState._request.requester = requester;
10226
+ this.promise = promise.then(stream => {
10227
+ if (!stream) {
10228
+ this._isPending = false;
10229
+ this._isComplete = true;
10230
+ return;
10379
10231
  }
10380
- switch (val) {
10381
- case 'reload':
10382
- this._latestRequest = reqState.reload();
10383
- break;
10384
- case 'refresh':
10385
- this._latestRequest = reqState.refresh();
10386
- break;
10387
- case 'policy':
10388
- this._latestRequest = reqState.refresh(true);
10389
- break;
10390
- default:
10391
- macroCondition(getGlobalConfig().WarpDrive.env.DEBUG) ? (test => {
10392
- {
10393
- throw new Error(`Invalid ${mode ? 'update mode' : '@autorefreshBehavior'} for <Request />: ${isNeverString(val)}`);
10394
- }
10395
- })() : {};
10232
+ return watchStream(stream, this);
10233
+ }, error => {
10234
+ this._isPending = false;
10235
+ this._isStarted = false;
10236
+ if (isAbortError(error)) {
10237
+ this._isCancelled = true;
10238
+ this._isComplete = true;
10396
10239
  }
10397
- if (val !== 'refresh') {
10398
- this._localRequest = this._latestRequest;
10240
+ this._isErrored = true;
10241
+ this._error = error;
10242
+ });
10243
+ }
10244
+ promise = null;
10245
+ get isPending() {
10246
+ this._trigger();
10247
+ return this._isPending;
10248
+ }
10249
+ get sizeHint() {
10250
+ this._trigger();
10251
+ return this._sizeHint;
10252
+ }
10253
+ get stream() {
10254
+ this._trigger();
10255
+ if (!this._stream) {
10256
+ if (this._isComplete || this._isCancelled || this._isErrored) {
10257
+ return null;
10399
10258
  }
10400
- void this._scheduleInterval();
10401
- void this._latestRequest.finally(() => {
10402
- this._isUpdating = false;
10403
- });
10259
+ this._stream = new TransformStream();
10404
10260
  }
10261
+ return this._stream.readable;
10405
10262
  }
10406
-
10407
- /**
10408
- * @internal
10409
- */
10410
- _getRequester() {
10411
- // Note: we check for the requester's presence
10412
- // as well as the request's presence because we may
10413
- // be subscribed to a request issued by a store from an older
10414
- // version of the library that didn't yet set requester.
10415
- if (this._args.request?.requester) {
10416
- return this._args.request.requester;
10417
- }
10418
- return this.store;
10263
+ get isStarted() {
10264
+ this._trigger();
10265
+ return this._isStarted;
10419
10266
  }
10420
-
10421
- /**
10422
- * Retry the request, reloading it from the server.
10423
- */
10424
- retry = async () => {
10425
- this._maybeUpdate('reload');
10426
- await this._localRequest;
10267
+ get bytesLoaded() {
10268
+ this._trigger();
10269
+ return this._bytesLoaded;
10270
+ }
10271
+ get startTime() {
10272
+ this._trigger();
10273
+ return this._startTime;
10274
+ }
10275
+ get endTime() {
10276
+ this._trigger();
10277
+ return this._endTime;
10278
+ }
10279
+ get lastPacketTime() {
10280
+ this._trigger();
10281
+ return this._lastPacketTime;
10282
+ }
10283
+ get isComplete() {
10284
+ this._trigger();
10285
+ return this._isComplete;
10286
+ }
10287
+ get isCancelled() {
10288
+ this._trigger();
10289
+ return this._isCancelled;
10290
+ }
10291
+ get isErrored() {
10292
+ this._trigger();
10293
+ return this._isErrored;
10294
+ }
10295
+ get error() {
10296
+ this._trigger();
10297
+ return this._error;
10298
+ }
10299
+ get elapsedTime() {
10300
+ return (this.endTime || this.lastPacketTime) - this.startTime;
10301
+ }
10302
+ get completedRatio() {
10303
+ return this.sizeHint ? this.bytesLoaded / this.sizeHint : 0;
10304
+ }
10305
+ get remainingRatio() {
10306
+ return 1 - this.completedRatio;
10307
+ }
10308
+ get duration() {
10309
+ return this.endTime - this.startTime;
10310
+ }
10311
+ get speed() {
10312
+ // bytes per second
10313
+ return this.bytesLoaded / (this.elapsedTime / 1000);
10314
+ }
10315
+ constructor(future) {
10316
+ this._future = future;
10317
+ }
10318
+ abort = () => {
10319
+ this._future.abort();
10427
10320
  };
10321
+ }
10322
+ defineNonEnumerableSignal(RequestLoadingState.prototype, '_isPending', true);
10323
+ defineNonEnumerableSignal(RequestLoadingState.prototype, '_isStarted', false);
10324
+ defineNonEnumerableSignal(RequestLoadingState.prototype, '_isComplete', false);
10325
+ defineNonEnumerableSignal(RequestLoadingState.prototype, '_isCancelled', false);
10326
+ defineNonEnumerableSignal(RequestLoadingState.prototype, '_isErrored', false);
10327
+ defineNonEnumerableSignal(RequestLoadingState.prototype, '_error', null);
10328
+ defineNonEnumerableSignal(RequestLoadingState.prototype, '_sizeHint', 0);
10329
+ defineNonEnumerableSignal(RequestLoadingState.prototype, '_bytesLoaded', 0);
10330
+ defineNonEnumerableSignal(RequestLoadingState.prototype, '_startTime', 0);
10331
+ defineNonEnumerableSignal(RequestLoadingState.prototype, '_endTime', 0);
10332
+ defineNonEnumerableSignal(RequestLoadingState.prototype, '_lastPacketTime', 0);
10428
10333
 
10429
- /**
10430
- * Refresh the request, updating it in the background.
10431
- */
10432
- refresh = async () => {
10433
- this._maybeUpdate('refresh');
10434
- await this._latestRequest;
10435
- };
10334
+ /**
10335
+ * The state of a request in the "pending"
10336
+ * state. This is the default initial state.
10337
+ *
10338
+ * Extends the {@link PendingPromise} interface.
10339
+ *
10340
+ */
10436
10341
 
10437
- /**
10438
- * features to yield to the error slot of a component
10439
- */
10440
- get errorFeatures() {
10441
- return {
10442
- isHidden: this.isHidden,
10443
- isOnline: this.isOnline,
10444
- retry: this.retry
10445
- };
10342
+ /**
10343
+ * The state of a request in the "fulfilled" state.
10344
+ * This is the state of a request that has resolved
10345
+ * successfully.
10346
+ *
10347
+ * Extends the {@link ResolvedPromise} interface.
10348
+ *
10349
+ */
10350
+
10351
+ /**
10352
+ * The state of a request in the "rejected" state.
10353
+ * This is the state of a request that has rejected
10354
+ * with an error.
10355
+ *
10356
+ * Extends the {@link RejectedPromise} interface.
10357
+ *
10358
+ */
10359
+
10360
+ /**
10361
+ * The state of a request in the "cancelled" state.
10362
+ * This is the state of a promise that has been
10363
+ * cancelled.
10364
+ *
10365
+ */
10366
+
10367
+ /**
10368
+ * RequestState extends the concept of {@link PromiseState} to provide a reactive
10369
+ * wrapper for a request {@link Future} which allows you write declarative code
10370
+ * around a Future's control flow.
10371
+ *
10372
+ * It is useful in both Template and JavaScript contexts, allowing you
10373
+ * to quickly derive behaviors and data from pending, error and success
10374
+ * states.
10375
+ *
10376
+ * The key difference between a {@link Promise} and a Future is that Futures provide
10377
+ * access to a {@link ReadableStream | stream} of their content, the {@link RequestKey} of the request (if any)
10378
+ * as well as the ability to attempt to {@link Future.abort | abort} the request.
10379
+ *
10380
+ * ```ts
10381
+ * interface Future<T> extends Promise<T>> {
10382
+ * getStream(): Promise<ReadableStream>;
10383
+ * abort(): void;
10384
+ * lid: RequestKey | null;
10385
+ * }
10386
+ * ```
10387
+ *
10388
+ * These additional APIs allow us to craft even richer state experiences.
10389
+ *
10390
+ * To get the state of a request, use {@link getRequestState}.
10391
+ *
10392
+ * See also:
10393
+ * - {@link PendingRequest}
10394
+ * - {@link ResolvedRequest}
10395
+ * - {@link RejectedRequest}
10396
+ * - {@link CancelledRequest}
10397
+ *
10398
+ */
10399
+
10400
+ const RequestStateProto = {};
10401
+ function performRefresh(requester, request, isReload) {
10402
+ const req = Object.assign({}, request);
10403
+ const cacheOptions = Object.assign({}, req.cacheOptions);
10404
+ if (isReload) {
10405
+ // force direct to network
10406
+ cacheOptions.reload = true;
10407
+ } else if (isReload === false) {
10408
+ // delete reload to ensure we use backgroundReload / policy
10409
+ delete cacheOptions.reload;
10410
+ cacheOptions.backgroundReload = true;
10411
+ } else {
10412
+ // delete props to ensure we use the policy
10413
+ delete cacheOptions.backgroundReload;
10414
+ delete cacheOptions.reload;
10446
10415
  }
10416
+ req.cacheOptions = cacheOptions;
10417
+ return requester.request(req);
10418
+ }
10447
10419
 
10448
- /**
10449
- * features to yield to the content slot of a component
10450
- */
10451
- static {
10452
- decorateMethodV2(this.prototype, "errorFeatures", [memoized]);
10420
+ // TODO introduce a new mechanism for defining multiple properties
10421
+ // that share a common signal
10422
+ defineSignal(RequestStateProto, 'reason', null);
10423
+ defineSignal(RequestStateProto, 'value', null);
10424
+ defineSignal(RequestStateProto, 'result', null);
10425
+ defineSignal(RequestStateProto, 'error', null);
10426
+ defineSignal(RequestStateProto, 'status', 'pending');
10427
+ defineSignal(RequestStateProto, 'isPending', true);
10428
+ defineSignal(RequestStateProto, 'isLoading', true);
10429
+ defineSignal(RequestStateProto, 'isSuccess', false);
10430
+ defineSignal(RequestStateProto, 'isError', false);
10431
+ defineSignal(RequestStateProto, 'request', null);
10432
+ defineSignal(RequestStateProto, 'response', null);
10433
+ Object.defineProperty(RequestStateProto, 'isCancelled', {
10434
+ get() {
10435
+ return this.isError && isAbortError(this.reason);
10453
10436
  }
10454
- get contentFeatures() {
10455
- const feat = {
10456
- isHidden: this.isHidden,
10457
- isOnline: this.isOnline,
10458
- reload: this.retry,
10459
- refresh: this.refresh,
10460
- isRefreshing: this.isRefreshing,
10461
- latestRequest: this._latestRequest
10462
- };
10463
- if (feat.isRefreshing) {
10464
- feat.abort = () => {
10465
- this._latestRequest?.abort();
10466
- };
10437
+ });
10438
+ Object.defineProperty(RequestStateProto, 'loadingState', {
10439
+ get() {
10440
+ if (!this._loadingState) {
10441
+ this._loadingState = new RequestLoadingState(this._request);
10467
10442
  }
10468
- return feat;
10469
- }
10470
-
10471
- /**
10472
- * @internal
10473
- */
10474
- static {
10475
- decorateMethodV2(this.prototype, "contentFeatures", [memoized]);
10443
+ return this._loadingState;
10476
10444
  }
10477
- get _request() {
10478
- const {
10479
- request,
10480
- query
10481
- } = this._args;
10445
+ });
10446
+ function createRequestState(future) {
10447
+ const state = getPromiseResult(future);
10448
+ const promiseState = Object.create(RequestStateProto);
10449
+ promiseState._request = future;
10450
+ // @ts-expect-error - we still attach it for PendingState
10451
+ promiseState.reload = () => {
10482
10452
  macroCondition(getGlobalConfig().WarpDrive.env.DEBUG) ? (test => {
10483
10453
  if (!test) {
10484
- throw new Error(`Cannot use both @request and @query args with the <Request> component`);
10454
+ throw new Error(`Cannot reload a request that is still pending. Await or abort the original request first.`);
10485
10455
  }
10486
- })(!request || !query) : {};
10487
- const {
10488
- _localRequest,
10489
- _originalRequest,
10490
- _originalQuery
10491
- } = this;
10492
- const isOriginalRequest = request === _originalRequest && query === _originalQuery;
10493
- if (_localRequest && isOriginalRequest) {
10494
- return _localRequest;
10495
- }
10456
+ })(!promiseState.isPending) : {};
10457
+ return performRefresh(future.requester, promiseState.request, true);
10458
+ };
10496
10459
 
10497
- // update state checks for the next time
10498
- this._originalQuery = query;
10499
- this._originalRequest = request;
10500
- if (request) {
10501
- return request;
10502
- }
10460
+ // @ts-expect-error - we still attach it for PendingState
10461
+ promiseState.refresh = (usePolicy = false) => {
10503
10462
  macroCondition(getGlobalConfig().WarpDrive.env.DEBUG) ? (test => {
10504
10463
  if (!test) {
10505
- throw new Error(`You must provide either @request or an @query arg with the <Request> component`);
10506
- }
10507
- })(query) : {};
10508
- return this.store.request(query);
10509
- }
10510
- static {
10511
- decorateMethodV2(this.prototype, "_request", [memoized]);
10512
- }
10513
- get request() {
10514
- if (macroCondition(getGlobalConfig().WarpDrive.env.DEBUG)) {
10515
- try {
10516
- const request = this._request;
10517
- this._updateSubscriptions();
10518
- return request;
10519
- } catch (e) {
10520
- // eslint-disable-next-line no-console
10521
- console.log(e);
10522
- throw new Error(`Unable to initialize the request`, {
10523
- cause: e
10524
- });
10464
+ throw new Error(`Cannot refresh a request that is still pending. Await or abort the original request first.`);
10525
10465
  }
10466
+ })(!promiseState.isPending) : {};
10467
+ return performRefresh(future.requester, promiseState.request, usePolicy === true ? null : false);
10468
+ };
10469
+ if (state) {
10470
+ if (state.isError) {
10471
+ promiseState.error = state.result;
10472
+ promiseState.reason = state.result;
10473
+ promiseState.status = 'rejected';
10474
+ promiseState.isError = true;
10475
+ promiseState.isPending = false;
10476
+ promiseState.isLoading = false;
10477
+ promiseState.request = state.result.request;
10478
+ promiseState.response = state.result.response;
10526
10479
  } else {
10527
- const request = this._request;
10528
- this._updateSubscriptions();
10529
- return request;
10480
+ promiseState.result = state.result.content;
10481
+ promiseState.value = state.result.content;
10482
+ promiseState.status = 'fulfilled';
10483
+ promiseState.isSuccess = true;
10484
+ promiseState.isPending = false;
10485
+ promiseState.isLoading = false;
10486
+ promiseState.request = state.result.request;
10487
+ promiseState.response = state.result.response;
10530
10488
  }
10489
+ } else {
10490
+ void future.then(result => {
10491
+ setPromiseResult(future, {
10492
+ isError: false,
10493
+ result
10494
+ });
10495
+ promiseState.result = result.content;
10496
+ promiseState.value = result.content;
10497
+ promiseState.status = 'fulfilled';
10498
+ promiseState.isSuccess = true;
10499
+ promiseState.isPending = false;
10500
+ promiseState.isLoading = false;
10501
+ promiseState.request = result.request;
10502
+ promiseState.response = result.response;
10503
+ }, error => {
10504
+ setPromiseResult(future, {
10505
+ isError: true,
10506
+ result: error
10507
+ });
10508
+ promiseState.error = error;
10509
+ promiseState.reason = error;
10510
+ promiseState.status = 'rejected';
10511
+ promiseState.isError = true;
10512
+ promiseState.isPending = false;
10513
+ promiseState.isLoading = false;
10514
+ promiseState.request = error.request;
10515
+ promiseState.response = error.response;
10516
+ });
10531
10517
  }
10532
- static {
10533
- decorateMethodV2(this.prototype, "request", [memoized]);
10534
- }
10535
- get reqState() {
10536
- return getRequestState(this.request);
10537
- }
10538
- get result() {
10539
- return this.reqState.result;
10540
- }
10541
- }
10542
- defineSignal(RequestSubscription.prototype, 'isOnline', true);
10543
- defineSignal(RequestSubscription.prototype, 'isHidden', false);
10544
- defineSignal(RequestSubscription.prototype, 'isRefreshing', false);
10545
- defineSignal(RequestSubscription.prototype, '_localRequest', undefined);
10546
- defineSignal(RequestSubscription.prototype, '_latestRequest', undefined);
10547
- function isStore(store) {
10548
- return 'requestManager' in store;
10549
- }
10550
- function createRequestSubscription(store, args) {
10551
- return new RequestSubscription(store, args);
10552
- }
10553
- function upgradeSubscription(sub) {
10554
- return sub;
10518
+ return promiseState;
10555
10519
  }
10556
- function _DISPOSE() {
10557
- const self = upgradeSubscription(this);
10558
- self.isDestroyed = true;
10559
- self._removeSubscriptions();
10560
- if (typeof window === 'undefined') {
10561
- return;
10520
+
10521
+ /**
10522
+ * `getRequestState` can be used in both JavaScript and Template contexts.
10523
+ *
10524
+ * ```ts
10525
+ * import { getRequestState } from '@warp-drive/ember';
10526
+ *
10527
+ * const state = getRequestState(future);
10528
+ * ```
10529
+ *
10530
+ * For instance, we could write a getter on a component that updates whenever
10531
+ * the request state advances or the future changes, by combining the function
10532
+ * with the use of `@cached`
10533
+ *
10534
+ * ```ts
10535
+ * class Component {
10536
+ * @cached
10537
+ * get title() {
10538
+ * const state = getRequestState(this.args.request);
10539
+ * if (state.isPending) {
10540
+ * return 'loading...';
10541
+ * }
10542
+ * if (state.isError) { return null; }
10543
+ * return state.result.title;
10544
+ * }
10545
+ * }
10546
+ * ```
10547
+ *
10548
+ * Or in a template as a helper:
10549
+ *
10550
+ * ```gjs
10551
+ * import { getRequestState } from '@warp-drive/ember';
10552
+ *
10553
+ * <template>
10554
+ * {{#let (getRequestState @request) as |state|}}
10555
+ * {{#if state.isPending}}
10556
+ * <Spinner />
10557
+ * {{else if state.isError}}
10558
+ * <ErrorForm @error={{state.error}} />
10559
+ * {{else}}
10560
+ * <h1>{{state.result.title}}</h1>
10561
+ * {{/if}}
10562
+ * {{/let}}
10563
+ * </template>
10564
+ * ```
10565
+ *
10566
+ * If looking to use in a template, consider also the `<Request />` component
10567
+ * which offers a numbe of additional capabilities for requests *beyond* what
10568
+ * `RequestState` provides.
10569
+ *
10570
+ */
10571
+ function getRequestState(future) {
10572
+ let state = RequestCache.get(future);
10573
+ if (!state) {
10574
+ state = createRequestState(future);
10575
+ RequestCache.set(future, state);
10562
10576
  }
10563
- self._clearInterval();
10564
- window.removeEventListener('online', self._onlineChanged, {
10565
- passive: true,
10566
- capture: true
10567
- });
10568
- window.removeEventListener('offline', self._onlineChanged, {
10569
- passive: true,
10570
- capture: true
10571
- });
10572
- document.removeEventListener('visibilitychange', self._backgroundChanged, {
10573
- passive: true,
10574
- capture: true
10575
- });
10577
+ return state;
10576
10578
  }
10577
10579
  export { defineGate as A, defineNonEnumerableSignal as B, CacheHandler as C, DISPOSE as D, Signals as E, peekInternalSignal as F, createInternalMemo as G, withSignalStore as H, notifyInternalSignal as I, consumeInternalSignal as J, getOrCreateInternalSignal as K, ReactiveResource as L, isNonIdentityCacheableField as M, getFieldCacheKeyStrict as N, checkout as O, commit as P, setIdentifierGenerationMethod as Q, RecordArrayManager as R, Store as S, setIdentifierUpdateMethod as T, setIdentifierForgetMethod as U, setIdentifierResetMethod as V, setKeyInfoForResource as W, _clearCaches as _, isRequestKey as a, coerceId as b, constructResource as c, assertPrivateStore as d, ensureStringId as e, fastPush as f, isPrivateStore as g, assertPrivateCapabilities as h, isResourceKey as i, setRecordIdentifier as j, StoreMap as k, createLegacyManyArray as l, log as m, normalizeModelName as n, logGroup as o, getPromiseState as p, createRequestSubscription as q, recordIdentifierFor as r, storeFor as s, getRequestState as t, signal as u, memoized as v, gate as w, entangleSignal as x, entangleInitiallyStaleSignal as y, defineSignal as z };