@warp-drive-mirror/ember 0.0.0-beta.13 → 0.0.0-beta.15

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,25 @@ function getPromiseState(promise) {
487
670
  }
488
671
  return state;
489
672
  }
673
+
674
+ /**
675
+ * @module @warp-drive-mirror/ember
676
+ */
677
+
490
678
  const and = (x, y) => Boolean(x && y);
679
+ /**
680
+ * The `<Throw />` component is used to throw an error in a template.
681
+ *
682
+ * That's all it does. So don't use it unless the application should
683
+ * throw an error if it reaches this point in the template.
684
+ *
685
+ * ```hbs
686
+ * <Throw @error={{anError}} />
687
+ * ```
688
+ *
689
+ * @class <Throw />
690
+ * @public
691
+ */
491
692
  class Throw extends Component {
492
693
  constructor(owner, args) {
493
694
  super(owner, args);
@@ -502,6 +703,41 @@ class Throw extends Component {
502
703
  }), this);
503
704
  }
504
705
  }
706
+ /**
707
+ * The <Await /> component allow you to utilize reactive control flow
708
+ * for asynchronous states in your application.
709
+ *
710
+ * Await is ideal for handling "boundaries", outside which some state is
711
+ * still allowed to be unresolved and within which it MUST be resolved.
712
+ *
713
+ * ```gjs
714
+ * import { Await } from '@warp-drive-mirror/ember';
715
+ *
716
+ * <template>
717
+ * <Await @promise={{@request}}>
718
+ * <:pending>
719
+ * <Spinner />
720
+ * </:pending>
721
+ *
722
+ * <:error as |error|>
723
+ * <ErrorForm @error={{error}} />
724
+ * </:error>
725
+ *
726
+ * <:success as |result|>
727
+ * <h1>{{result.title}}</h1>
728
+ * </:success>
729
+ * </Await>
730
+ * </template>
731
+ * ```
732
+ *
733
+ * The <Await /> component requires that error states are properly handled.
734
+ *
735
+ * If no error block is provided and the promise rejects, the error will
736
+ * be thrown.
737
+ *
738
+ * @class <Await />
739
+ * @public
740
+ */
505
741
  class Await extends Component {
506
742
  get state() {
507
743
  return getPromiseState(this.args.promise);
@@ -522,6 +758,11 @@ class Await extends Component {
522
758
  }), this);
523
759
  }
524
760
  }
761
+
762
+ /**
763
+ * @module @warp-drive-mirror/ember
764
+ */
765
+
525
766
  function notNull(x) {
526
767
  macroCondition(getGlobalConfig().WarpDriveMirror.env.DEBUG) ? (test => {
527
768
  if (!test) {
@@ -545,11 +786,221 @@ function isNeverString(val) {
545
786
  return val;
546
787
  }
547
788
  /**
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.
789
+ * The `<Request />` component is a powerful tool for managing data fetching and
790
+ * state in your Ember application. It provides a declarative approach to reactive
791
+ * control-flow for managing requests and state in your application.
792
+ *
793
+ * The `<Request />` component is ideal for handling "boundaries", outside which some
794
+ * state is still allowed to be unresolved and within which it MUST be resolved.
795
+ *
796
+ * ## Request States
797
+ *
798
+ * `<Request />` has five states, only one of which will be active and rendered at a time.
799
+ *
800
+ * - `idle`: The component is waiting to be given a request to monitor
801
+ * - `loading`: The request is in progress
802
+ * - `error`: The request failed
803
+ * - `content`: The request succeeded
804
+ * - `cancelled`: The request was cancelled
805
+ *
806
+ * Additionally, the `content` state has a `refresh` method that can be used to
807
+ * refresh the request in the background, which is available as a sub-state of
808
+ * the `content` state.
809
+ *
810
+ * As with the `<Await />` component, if no error block is provided and the request
811
+ * rejects, the error will be thrown. Cancellation errors are swallowed instead of
812
+ * rethrown if no error block or cancellation block is present.
813
+ *
814
+ * ```gts
815
+ * import { Request } from '@warp-drive-mirror/ember';
816
+ *
817
+ * <template>
818
+ * <Request @request={{@request}}>
819
+ * <:loading as |state|>
820
+ * <Spinner @percentDone={{state.completedRatio}} />
821
+ * <button {{on "click" state.abort}}>Cancel</button>
822
+ * </:loading>
823
+ *
824
+ * <:error as |error state|>
825
+ * <ErrorForm @error={{error}} />
826
+ * <button {{on "click" state.retry}}>Retry</button>
827
+ * </:error>
828
+ *
829
+ * <:content as |data state|>
830
+ * <h1>{{data.title}}</h1>
831
+ * {{#if state.isBackgroundReloading}}
832
+ * <SmallSpinner />
833
+ * <button {{on "click" state.abort}}>Cancel</button>
834
+ * {{else}}
835
+ * <button {{on "click" state.refresh}}>Refresh</button>
836
+ * {{/if}}
837
+ * </:content>
838
+ *
839
+ * <:cancelled as |error state|>
840
+ * <h2>The Request was cancelled</h2>
841
+ * <button {{on "click" state.retry}}>Retry</button>
842
+ * </:cancelled>
843
+ *
844
+ * <:idle>
845
+ * <button {{on "click" @kickOffRequest}}>Load Preview?</button>
846
+ * </:idle>
847
+ *
848
+ * </Request>
849
+ * </template>
850
+ * ```
851
+ *
852
+ * ## Streaming Data
853
+ *
854
+ * The loading state exposes the download `ReadableStream` instance for consumption
855
+ *
856
+ * ```gjs
857
+ * import { Request } from '@warp-drive-mirror/ember';
858
+ *
859
+ * <template>
860
+ * <Request @request={{@request}}>
861
+ * <:loading as |state|>
862
+ * <Video @stream={{state.stream}} />
863
+ * </:loading>
864
+ *
865
+ * <:error as |error|>
866
+ * <ErrorForm @error={{error}} />
867
+ * </:error>
868
+ * </Request>
869
+ * </template>
870
+ * ```
871
+ *
872
+ * ## Retry
873
+ *
874
+ * Cancelled and error'd requests may be retried by calling the `retry` method.
875
+ *
876
+ * Retry will restart the state progression, using the loading, error, cancelled,
877
+ * and content blocks as appropriate.
878
+ *
879
+ * ## Reloading
880
+ *
881
+ * The `reload` method will force the request to be fully re-executed, bypassing
882
+ * cache and restarting the state progression through the loading, error, and
883
+ * content blocks as appropriate.
884
+ *
885
+ * Background reload (refresh) is a special substate of the content state that
886
+ * allows you to refresh the request in the background. This is useful for when
887
+ * you want to update the data in the background without blocking the UI.
888
+ *
889
+ * Reload and refresh are available as methods on the `content` state.
890
+ *
891
+ * ```gjs
892
+ * import { Request } from '@warp-drive-mirror/ember';
893
+ *
894
+ * <template>
895
+ * <Request @request={{@request}}>
896
+ * <:content as |data state|>
897
+ * <h1>{{data.title}}</h1>
898
+ * {{#if state.isBackgroundReloading}}
899
+ * <SmallSpinner />
900
+ * <button {{on "click" state.abort}}>Cancel</button>
901
+ * {{/if}}
902
+ *
903
+ * <button {{on "click" state.refresh}}>Refresh</button>
904
+ * <button {{on "click" state.reload}}>Reload</button>
905
+ * </:content>
906
+ * </Request>
907
+ * </template>
908
+ * ```
909
+ *
910
+ * ## Advanced Reloading
911
+ *
912
+ * We can nest our usage of `<Request />` to handle more advanced
913
+ * reloading scenarios.
914
+ *
915
+ * ```gjs
916
+ * import { Request } from '@warp-drive-mirror/ember';
917
+ *
918
+ * <template>
919
+ * <Request @request={{@request}}>
920
+ * <:cancelled>
921
+ * <h2>The Request Cancelled</h2>
922
+ * </:cancelled>
923
+ *
924
+ * <:error as |error|>
925
+ * <ErrorForm @error={{error}} />
926
+ * </:error>
927
+ *
928
+ * <:content as |result state|>
929
+ * <Request @request={{state.latestRequest}}>
930
+ * <!-- Handle Background Request -->
931
+ * </Request>
932
+ *
933
+ * <h1>{{result.title}}</h1>
934
+ *
935
+ * <button {{on "click" state.refresh}}>Refresh</button>
936
+ * </:content>
937
+ * </Request>
938
+ * </template>
939
+ * ```
940
+ *
941
+ * ## Autorefresh
942
+ *
943
+ * `<Request />` supports automatic refresh and reload under certain conditions.
944
+ *
945
+ * - `online`: This occurs when a browser window or tab comes back to the foreground
946
+ * after being backgrounded or when the network reports as being online after
947
+ * having been offline.
948
+ * - `interval`: This occurs when a specified amount of time has passed.
949
+ * - `invalid`: This occurs when the store emits a notification that the request
950
+ * has become invalid.
951
+ *
952
+ * You can specify when autorefresh should occur by setting the `autorefresh` arg
953
+ * to `true` or a comma-separated list of the above values.
954
+ *
955
+ * A value of `true` is equivalent to `'online,invalid'`.
956
+ *
957
+ * By default, an autorefresh will only occur if the browser was backgrounded or
958
+ * offline for more than 30s before coming back available. This amount of time can
959
+ * be tweaked by setting the number of milliseconds via `@autorefreshThreshold`.
960
+ *
961
+ * This arg also controls the interval at which the request will be refreshed
962
+ * if the `interval` autorefresh type is enabled.
963
+ *
964
+ * Finally, the behavior of the request initiated by autorefresh can be adjusted
965
+ * by setting the `autorefreshBehavior` arg to `'refresh'`, `'reload'`, or `'policy'`.
966
+ *
967
+ * - `'refresh'`: Refresh the request in the background
968
+ * - `'reload'`: Force a reload of the request
969
+ * - `'policy'` (**default**): Let the store's configured CachePolicy decide whether to
970
+ * reload, refresh, or do nothing.
971
+ *
972
+ * More advanced refresh and reload behaviors can be created by passing the reload and
973
+ * refresh actions into another component. For instance, refresh could be set up on a
974
+ * timer or on a websocket subscription.
975
+ *
976
+ *
977
+ * ```gjs
978
+ * import { Request } from '@warp-drive-mirror/ember';
979
+ *
980
+ * <template>
981
+ * <Request @request={{@request}}>
982
+ * <:content as |result state|>
983
+ * <h1>{{result.title}}</h1>
984
+ *
985
+ * <Interval @period={{30_000}} @fn={{state.refresh}} />
986
+ * <Subscribe @channel={{@someValue}} @fn={{state.refresh}} />
987
+ * </:content>
988
+ * </Request>
989
+ * </template>
990
+ * ```
991
+ *
992
+ * If a matching request is refreshed or reloaded by any other component,
993
+ * the `Request` component will react accordingly.
994
+ *
995
+ * ## Deduping
996
+ *
997
+ * The store dedupes requests by identity. If a request is made for the same identity
998
+ * from multiple `<Request />` components, even if the request is not referentially the
999
+ * same, only one actual request will be made.
1000
+ *
551
1001
  *
552
- * @typedoc
1002
+ * @class <Request />
1003
+ * @public
553
1004
  */
554
1005
  class Request extends Component {
555
1006
  static {