@warp-drive/core 5.7.0-alpha.34 → 5.7.0-alpha.36

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