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