@warp-drive-mirror/ember 5.4.0-alpha.149 → 5.4.0-alpha.151

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.
package/dist/index.js CHANGED
@@ -70,6 +70,10 @@ function initializeDeferredDecorator(target, prop) {
70
70
  });
71
71
  }
72
72
  }
73
+
74
+ /**
75
+ * @module @warp-drive-mirror/ember
76
+ */
73
77
  const RequestCache = new WeakMap();
74
78
  function isAbortError(error) {
75
79
  return error instanceof DOMException && error.name === 'AbortError';
@@ -84,8 +88,6 @@ async function watchStream(stream, state) {
84
88
  state._isPending = false;
85
89
  state._isStarted = true;
86
90
  state._startTime = performance.now();
87
-
88
- // eslint-disable-next-line no-constant-condition
89
91
  while (true) {
90
92
  const {
91
93
  value,
@@ -129,6 +131,15 @@ async function watchStream(stream, state) {
129
131
  state._isComplete = true;
130
132
  state._isStarted = false;
131
133
  }
134
+
135
+ /**
136
+ * Lazily consumes the stream of a request, providing a number of
137
+ * reactive properties that can be used to build UIs that respond
138
+ * to the progress of a request.
139
+ *
140
+ * @class RequestLoadingState
141
+ * @public
142
+ */
132
143
  class RequestLoadingState {
133
144
  _stream = null;
134
145
  _future;
@@ -305,6 +316,35 @@ class RequestLoadingState {
305
316
  this._future.abort();
306
317
  };
307
318
  }
319
+
320
+ /**
321
+ * RequestState extends the concept of PromiseState to provide a reactive
322
+ * wrapper for a request `Future` which allows you write declarative code
323
+ * around a Future's control flow.
324
+ *
325
+ * It is useful in both Template and JavaScript contexts, allowing you
326
+ * to quickly derive behaviors and data from pending, error and success
327
+ * states.
328
+ *
329
+ * The key difference between a Promise and a Future is that Futures provide
330
+ * access to a stream of their content, the identity of the request (if any)
331
+ * as well as the ability to attempt to abort the request.
332
+ *
333
+ * ```ts
334
+ * interface Future<T> extends Promise<T>> {
335
+ * getStream(): Promise<ReadableStream>;
336
+ * abort(): void;
337
+ * lid: StableDocumentIdentifier | null;
338
+ * }
339
+ * ```
340
+ *
341
+ * These additional APIs allow us to craft even richer state experiences.
342
+ *
343
+ * To get the state of a request, use `getRequestState`.
344
+ *
345
+ * @class RequestState
346
+ * @public
347
+ */
308
348
  class RequestState {
309
349
  #request;
310
350
  #loadingState = null;
@@ -398,6 +438,65 @@ class RequestState {
398
438
  }
399
439
  }
400
440
  }
441
+
442
+ /**
443
+ *
444
+ *
445
+ * `getRequestState` can be used in both JavaScript and Template contexts.
446
+ *
447
+ * ```ts
448
+ * import { getRequestState } from '@warp-drive-mirror/ember';
449
+ *
450
+ * const state = getRequestState(future);
451
+ * ```
452
+ *
453
+ * For instance, we could write a getter on a component that updates whenever
454
+ * the request state advances or the future changes, by combining the function
455
+ * with the use of `@cached`
456
+ *
457
+ * ```ts
458
+ * class Component {
459
+ * @cached
460
+ * get title() {
461
+ * const state = getRequestState(this.args.request);
462
+ * if (state.isPending) {
463
+ * return 'loading...';
464
+ * }
465
+ * if (state.isError) { return null; }
466
+ * return state.result.title;
467
+ * }
468
+ * }
469
+ * ```
470
+ *
471
+ * Or in a template as a helper:
472
+ *
473
+ * ```gjs
474
+ * import { getRequestState } from '@warp-drive-mirror/ember';
475
+ *
476
+ * <template>
477
+ * {{#let (getRequestState @request) as |state|}}
478
+ * {{#if state.isPending}}
479
+ * <Spinner />
480
+ * {{else if state.isError}}
481
+ * <ErrorForm @error={{state.error}} />
482
+ * {{else}}
483
+ * <h1>{{state.result.title}}</h1>
484
+ * {{/if}}
485
+ * {{/let}}
486
+ * </template>
487
+ * ```
488
+ *
489
+ * If looking to use in a template, consider also the `<Request />` component
490
+ * which offers a numbe of additional capabilities for requests *beyond* what
491
+ * `RequestState` provides.
492
+ *
493
+ * @method getRequestState
494
+ * @for @warp-drive-mirror/ember
495
+ * @static
496
+ * @public
497
+ * @param future
498
+ * @return {RequestState}
499
+ */
401
500
  function getRequestState(future) {
402
501
  let state = RequestCache.get(future);
403
502
  if (!state) {
@@ -406,7 +505,32 @@ function getRequestState(future) {
406
505
  }
407
506
  return state;
408
507
  }
508
+
509
+ /**
510
+ * @module @warp-drive-mirror/ember
511
+ */
409
512
  const PromiseCache = new WeakMap();
513
+
514
+ /**
515
+ * PromiseState provides a reactive wrapper for a promise which allows you write declarative
516
+ * code around a promise's control flow. It is useful in both Template and JavaScript contexts,
517
+ * allowing you to quickly derive behaviors and data from pending, error and success states.
518
+ *
519
+ * ```ts
520
+ * interface PromiseState<T = unknown, E = unknown> {
521
+ * isPending: boolean;
522
+ * isSuccess: boolean;
523
+ * isError: boolean;
524
+ * result: T | null;
525
+ * error: E | null;
526
+ * }
527
+ * ```
528
+ *
529
+ * To get the state of a promise, use `getPromiseState`.
530
+ *
531
+ * @class PromiseState
532
+ * @public
533
+ */
410
534
  class PromiseState {
411
535
  static {
412
536
  decorateFieldV2(this.prototype, "result", [tracked], function () {
@@ -478,6 +602,65 @@ function isLegacyAwaitable(promise) {
478
602
  function getPromise(promise) {
479
603
  return isLegacyAwaitable(promise) ? promise.promise : promise;
480
604
  }
605
+
606
+ /**
607
+ * Returns a reactive state-machine for the provided promise or awaitable.
608
+ *
609
+ * Repeat calls to `getPromiseState` with the same promise will return the same state object
610
+ * making is safe and easy to use in templates and JavaScript code to produce reactive
611
+ * behaviors around promises.
612
+ *
613
+ * `getPromiseState` can be used in both JavaScript and Template contexts.
614
+ *
615
+ * ```ts
616
+ * import { getPromiseState } from '@warp-drive-mirror/ember';
617
+ *
618
+ * const state = getPromiseState(promise);
619
+ * ```
620
+ *
621
+ * For instance, we could write a getter on a component that updates whenever
622
+ * the promise state advances or the promise changes, by combining the function
623
+ * with the use of `@cached`
624
+ *
625
+ * ```ts
626
+ * class Component {
627
+ * @cached
628
+ * get title() {
629
+ * const state = getPromiseState(this.args.request);
630
+ * if (state.isPending) {
631
+ * return 'loading...';
632
+ * }
633
+ * if (state.isError) { return null; }
634
+ * return state.result.title;
635
+ * }
636
+ * }
637
+ * ```
638
+ *
639
+ * Or in a template as a helper:
640
+ *
641
+ * ```gjs
642
+ * import { getPromiseState } from '@warp-drive-mirror/ember';
643
+ *
644
+ * <template>
645
+ * {{#let (getPromiseState @request) as |state|}}
646
+ * {{#if state.isPending}} <Spinner />
647
+ * {{else if state.isError}} <ErrorForm @error={{state.error}} />
648
+ * {{else}}
649
+ * <h1>{{state.result.title}}</h1>
650
+ * {{/if}}
651
+ * {{/let}}
652
+ * </template>
653
+ * ```
654
+ *
655
+ * If looking to use in a template, consider also the `<Await />` component.
656
+ *
657
+ * @method getPromiseState
658
+ * @for @warp-drive-mirror/ember
659
+ * @static
660
+ * @public
661
+ * @param {Promise<T> | Awaitable<T, E>} promise
662
+ * @return {PromiseState<T, E>}
663
+ */
481
664
  function getPromiseState(promise) {
482
665
  const _promise = getPromise(promise);
483
666
  let state = PromiseCache.get(_promise);
@@ -487,7 +670,24 @@ function getPromiseState(promise) {
487
670
  }
488
671
  return state;
489
672
  }
673
+
674
+ /**
675
+ * @module @warp-drive-mirror/ember
676
+ */
490
677
  const and = (x, y) => Boolean(x && y);
678
+ /**
679
+ * The `<Throw />` component is used to throw an error in a template.
680
+ *
681
+ * That's all it does. So don't use it unless the application should
682
+ * throw an error if it reaches this point in the template.
683
+ *
684
+ * ```hbs
685
+ * <Throw @error={{anError}} />
686
+ * ```
687
+ *
688
+ * @class <Throw />
689
+ * @public
690
+ */
491
691
  class Throw extends Component {
492
692
  constructor(owner, args) {
493
693
  super(owner, args);
@@ -502,6 +702,41 @@ class Throw extends Component {
502
702
  }), this);
503
703
  }
504
704
  }
705
+ /**
706
+ * The <Await /> component allow you to utilize reactive control flow
707
+ * for asynchronous states in your application.
708
+ *
709
+ * Await is ideal for handling "boundaries", outside which some state is
710
+ * still allowed to be unresolved and within which it MUST be resolved.
711
+ *
712
+ * ```gjs
713
+ * import { Await } from '@warp-drive-mirror/ember';
714
+ *
715
+ * <template>
716
+ * <Await @promise={{@request}}>
717
+ * <:pending>
718
+ * <Spinner />
719
+ * </:pending>
720
+ *
721
+ * <:error as |error|>
722
+ * <ErrorForm @error={{error}} />
723
+ * </:error>
724
+ *
725
+ * <:success as |result|>
726
+ * <h1>{{result.title}}</h1>
727
+ * </:success>
728
+ * </Await>
729
+ * </template>
730
+ * ```
731
+ *
732
+ * The <Await /> component requires that error states are properly handled.
733
+ *
734
+ * If no error block is provided and the promise rejects, the error will
735
+ * be thrown.
736
+ *
737
+ * @class <Await />
738
+ * @public
739
+ */
505
740
  class Await extends Component {
506
741
  get state() {
507
742
  return getPromiseState(this.args.promise);
@@ -522,6 +757,10 @@ class Await extends Component {
522
757
  }), this);
523
758
  }
524
759
  }
760
+
761
+ /**
762
+ * @module @warp-drive-mirror/ember
763
+ */
525
764
  function notNull(x) {
526
765
  macroCondition(getGlobalConfig().WarpDriveMirror.env.DEBUG) ? (test => {
527
766
  if (!test) {
@@ -545,11 +784,221 @@ function isNeverString(val) {
545
784
  return val;
546
785
  }
547
786
  /**
548
- * The `<Request>` component is a powerful tool for managing data fetching and
549
- * state in your Ember application. It provides declarative reactive control-flow
550
- * for managing requests and state in your application.
787
+ * The `<Request />` component is a powerful tool for managing data fetching and
788
+ * state in your Ember application. It provides a declarative approach to reactive
789
+ * control-flow for managing requests and state in your application.
790
+ *
791
+ * The `<Request />` component is ideal for handling "boundaries", outside which some
792
+ * state is still allowed to be unresolved and within which it MUST be resolved.
793
+ *
794
+ * ## Request States
795
+ *
796
+ * `<Request />` has five states, only one of which will be active and rendered at a time.
797
+ *
798
+ * - `idle`: The component is waiting to be given a request to monitor
799
+ * - `loading`: The request is in progress
800
+ * - `error`: The request failed
801
+ * - `content`: The request succeeded
802
+ * - `cancelled`: The request was cancelled
803
+ *
804
+ * Additionally, the `content` state has a `refresh` method that can be used to
805
+ * refresh the request in the background, which is available as a sub-state of
806
+ * the `content` state.
807
+ *
808
+ * As with the `<Await />` component, if no error block is provided and the request
809
+ * rejects, the error will be thrown. Cancellation errors are swallowed instead of
810
+ * rethrown if no error block or cancellation block is present.
811
+ *
812
+ * ```gts
813
+ * import { Request } from '@warp-drive-mirror/ember';
814
+ *
815
+ * <template>
816
+ * <Request @request={{@request}}>
817
+ * <:loading as |state|>
818
+ * <Spinner @percentDone={{state.completedRatio}} />
819
+ * <button {{on "click" state.abort}}>Cancel</button>
820
+ * </:loading>
821
+ *
822
+ * <:error as |error state|>
823
+ * <ErrorForm @error={{error}} />
824
+ * <button {{on "click" state.retry}}>Retry</button>
825
+ * </:error>
826
+ *
827
+ * <:content as |data state|>
828
+ * <h1>{{data.title}}</h1>
829
+ * {{#if state.isBackgroundReloading}}
830
+ * <SmallSpinner />
831
+ * <button {{on "click" state.abort}}>Cancel</button>
832
+ * {{else}}
833
+ * <button {{on "click" state.refresh}}>Refresh</button>
834
+ * {{/if}}
835
+ * </:content>
836
+ *
837
+ * <:cancelled as |error state|>
838
+ * <h2>The Request was cancelled</h2>
839
+ * <button {{on "click" state.retry}}>Retry</button>
840
+ * </:cancelled>
841
+ *
842
+ * <:idle>
843
+ * <button {{on "click" @kickOffRequest}}>Load Preview?</button>
844
+ * </:idle>
845
+ *
846
+ * </Request>
847
+ * </template>
848
+ * ```
849
+ *
850
+ * ## Streaming Data
851
+ *
852
+ * The loading state exposes the download `ReadableStream` instance for consumption
853
+ *
854
+ * ```gjs
855
+ * import { Request } from '@warp-drive-mirror/ember';
856
+ *
857
+ * <template>
858
+ * <Request @request={{@request}}>
859
+ * <:loading as |state|>
860
+ * <Video @stream={{state.stream}} />
861
+ * </:loading>
862
+ *
863
+ * <:error as |error|>
864
+ * <ErrorForm @error={{error}} />
865
+ * </:error>
866
+ * </Request>
867
+ * </template>
868
+ * ```
869
+ *
870
+ * ## Retry
871
+ *
872
+ * Cancelled and error'd requests may be retried by calling the `retry` method.
873
+ *
874
+ * Retry will restart the state progression, using the loading, error, cancelled,
875
+ * and content blocks as appropriate.
876
+ *
877
+ * ## Reloading
878
+ *
879
+ * The `reload` method will force the request to be fully re-executed, bypassing
880
+ * cache and restarting the state progression through the loading, error, and
881
+ * content blocks as appropriate.
882
+ *
883
+ * Background reload (refresh) is a special substate of the content state that
884
+ * allows you to refresh the request in the background. This is useful for when
885
+ * you want to update the data in the background without blocking the UI.
886
+ *
887
+ * Reload and refresh are available as methods on the `content` state.
888
+ *
889
+ * ```gjs
890
+ * import { Request } from '@warp-drive-mirror/ember';
891
+ *
892
+ * <template>
893
+ * <Request @request={{@request}}>
894
+ * <:content as |data state|>
895
+ * <h1>{{data.title}}</h1>
896
+ * {{#if state.isBackgroundReloading}}
897
+ * <SmallSpinner />
898
+ * <button {{on "click" state.abort}}>Cancel</button>
899
+ * {{/if}}
900
+ *
901
+ * <button {{on "click" state.refresh}}>Refresh</button>
902
+ * <button {{on "click" state.reload}}>Reload</button>
903
+ * </:content>
904
+ * </Request>
905
+ * </template>
906
+ * ```
907
+ *
908
+ * ## Advanced Reloading
909
+ *
910
+ * We can nest our usage of `<Request />` to handle more advanced
911
+ * reloading scenarios.
912
+ *
913
+ * ```gjs
914
+ * import { Request } from '@warp-drive-mirror/ember';
915
+ *
916
+ * <template>
917
+ * <Request @request={{@request}}>
918
+ * <:cancelled>
919
+ * <h2>The Request Cancelled</h2>
920
+ * </:cancelled>
921
+ *
922
+ * <:error as |error|>
923
+ * <ErrorForm @error={{error}} />
924
+ * </:error>
925
+ *
926
+ * <:content as |result state|>
927
+ * <Request @request={{state.latestRequest}}>
928
+ * <!-- Handle Background Request -->
929
+ * </Request>
930
+ *
931
+ * <h1>{{result.title}}</h1>
932
+ *
933
+ * <button {{on "click" state.refresh}}>Refresh</button>
934
+ * </:content>
935
+ * </Request>
936
+ * </template>
937
+ * ```
938
+ *
939
+ * ## Autorefresh
940
+ *
941
+ * `<Request />` supports automatic refresh and reload under certain conditions.
942
+ *
943
+ * - `online`: This occurs when a browser window or tab comes back to the foreground
944
+ * after being backgrounded or when the network reports as being online after
945
+ * having been offline.
946
+ * - `interval`: This occurs when a specified amount of time has passed.
947
+ * - `invalid`: This occurs when the store emits a notification that the request
948
+ * has become invalid.
949
+ *
950
+ * You can specify when autorefresh should occur by setting the `autorefresh` arg
951
+ * to `true` or a comma-separated list of the above values.
952
+ *
953
+ * A value of `true` is equivalent to `'online,invalid'`.
954
+ *
955
+ * By default, an autorefresh will only occur if the browser was backgrounded or
956
+ * offline for more than 30s before coming back available. This amount of time can
957
+ * be tweaked by setting the number of milliseconds via `@autorefreshThreshold`.
958
+ *
959
+ * This arg also controls the interval at which the request will be refreshed
960
+ * if the `interval` autorefresh type is enabled.
961
+ *
962
+ * Finally, the behavior of the request initiated by autorefresh can be adjusted
963
+ * by setting the `autorefreshBehavior` arg to `'refresh'`, `'reload'`, or `'policy'`.
964
+ *
965
+ * - `'refresh'`: Refresh the request in the background
966
+ * - `'reload'`: Force a reload of the request
967
+ * - `'policy'` (**default**): Let the store's configured CachePolicy decide whether to
968
+ * reload, refresh, or do nothing.
969
+ *
970
+ * More advanced refresh and reload behaviors can be created by passing the reload and
971
+ * refresh actions into another component. For instance, refresh could be set up on a
972
+ * timer or on a websocket subscription.
973
+ *
974
+ *
975
+ * ```gjs
976
+ * import { Request } from '@warp-drive-mirror/ember';
977
+ *
978
+ * <template>
979
+ * <Request @request={{@request}}>
980
+ * <:content as |result state|>
981
+ * <h1>{{result.title}}</h1>
982
+ *
983
+ * <Interval @period={{30_000}} @fn={{state.refresh}} />
984
+ * <Subscribe @channel={{@someValue}} @fn={{state.refresh}} />
985
+ * </:content>
986
+ * </Request>
987
+ * </template>
988
+ * ```
989
+ *
990
+ * If a matching request is refreshed or reloaded by any other component,
991
+ * the `Request` component will react accordingly.
992
+ *
993
+ * ## Deduping
994
+ *
995
+ * The store dedupes requests by identity. If a request is made for the same identity
996
+ * from multiple `<Request />` components, even if the request is not referentially the
997
+ * same, only one actual request will be made.
998
+ *
551
999
  *
552
- * @typedoc
1000
+ * @class <Request />
1001
+ * @public
553
1002
  */
554
1003
  class Request extends Component {
555
1004
  static {