@warp-drive/ember 5.6.0-beta.0 → 5.6.0-beta.2

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
@@ -1,17 +1,11 @@
1
1
  import { service } from '@ember/service';
2
2
  import Component from '@glimmer/component';
3
- import { tracked, cached } from '@glimmer/tracking';
4
3
  import { macroCondition, moduleExists, importSync, getGlobalConfig } from '@embroider/macros';
5
- import { getPromiseState, getRequestState } from '@ember-data/store/-private';
6
- export { getPromiseState, getRequestState } from '@ember-data/store/-private';
7
- import { EnableHydration } from '@warp-drive/core-types/request';
4
+ import { getPromiseState, DISPOSE, createRequestSubscription } from '@warp-drive/core/store/-private';
5
+ export { getPromiseState, getRequestState } from '@warp-drive/core/store/-private';
8
6
  import { precompileTemplate } from '@ember/template-compilation';
9
7
  import { setComponentTemplate } from '@ember/component';
10
8
 
11
- /**
12
- * @module @warp-drive/ember
13
- */
14
-
15
9
  const and = (x, y) => Boolean(x && y);
16
10
  /**
17
11
  * The `<Throw />` component is used to throw an error in a template.
@@ -95,6 +89,7 @@ class Await extends Component {
95
89
  }), this);
96
90
  }
97
91
  }
92
+
98
93
  const deferred = /* @__PURE__ */new WeakMap();
99
94
  function deferDecorator(proto, prop, desc) {
100
95
  let map = deferred.get(proto);
@@ -134,20 +129,6 @@ function decorateFieldV2(prototype, prop, decorators, initializer) {
134
129
  deferDecorator(prototype, prop, desc);
135
130
  }
136
131
  }
137
- function decorateMethodV2(prototype, prop, decorators) {
138
- const origDesc = Object.getOwnPropertyDescriptor(prototype, prop);
139
- let desc = {
140
- ...origDesc
141
- };
142
- for (let decorator of decorators) {
143
- desc = decorator(prototype, prop, desc) || desc;
144
- }
145
- if (desc.initializer !== void 0) {
146
- desc.value = desc.initializer ? desc.initializer.call(prototype) : void 0;
147
- desc.initializer = void 0;
148
- }
149
- Object.defineProperty(prototype, prop, desc);
150
- }
151
132
  function initializeDeferredDecorator(target, prop) {
152
133
  let desc = findDeferredDecorator(target.constructor, prop);
153
134
  if (desc) {
@@ -160,10 +141,6 @@ function initializeDeferredDecorator(target, prop) {
160
141
  }
161
142
  }
162
143
 
163
- /**
164
- * @module @warp-drive/ember
165
- */
166
-
167
144
  function notNull(x) {
168
145
  macroCondition(getGlobalConfig().WarpDrive.env.DEBUG) ? (test => {
169
146
  if (!test) {
@@ -173,8 +150,6 @@ function notNull(x) {
173
150
  return x;
174
151
  }
175
152
  const not = x => !x;
176
- // default to 30 seconds unavailable before we refresh
177
- const DEFAULT_DEADLINE = 30_000;
178
153
  const IdleBlockMissingError = new Error('No idle block provided for <Request> component, and no query or request was provided.');
179
154
  let consume = service;
180
155
  if (macroCondition(moduleExists('ember-provide-consume-context'))) {
@@ -183,9 +158,6 @@ if (macroCondition(moduleExists('ember-provide-consume-context'))) {
183
158
  } = importSync('ember-provide-consume-context');
184
159
  consume = contextConsume;
185
160
  }
186
- function isNeverString(val) {
187
- return val;
188
- }
189
161
  /**
190
162
  * The `<Request />` component is a powerful tool for managing data fetching and
191
163
  * state in your Ember application. It provides a declarative approach to reactive
@@ -414,525 +386,6 @@ class Request extends Component {
414
386
  *
415
387
  * @internal
416
388
  */
417
- static {
418
- decorateFieldV2(this.prototype, "isOnline", [tracked], function () {
419
- return true;
420
- });
421
- }
422
- #isOnline = (initializeDeferredDecorator(this, "isOnline"), void 0);
423
- /**
424
- * Whether the browser reports that the network is online.
425
- *
426
- * @internal
427
- */
428
- static {
429
- decorateFieldV2(this.prototype, "isHidden", [tracked], function () {
430
- return true;
431
- });
432
- }
433
- #isHidden = (initializeDeferredDecorator(this, "isHidden"), void 0);
434
- /**
435
- * Whether the browser reports that the tab is hidden.
436
- *
437
- * @internal
438
- */
439
- static {
440
- decorateFieldV2(this.prototype, "isRefreshing", [tracked], function () {
441
- return false;
442
- });
443
- }
444
- #isRefreshing = (initializeDeferredDecorator(this, "isRefreshing"), void 0);
445
- /**
446
- * Whether the component is currently refreshing the request.
447
- *
448
- * @internal
449
- */
450
- static {
451
- decorateFieldV2(this.prototype, "_localRequest", [tracked]);
452
- }
453
- #_localRequest = (initializeDeferredDecorator(this, "_localRequest"), void 0);
454
- /**
455
- * The most recent blocking request that was made, typically
456
- * the result of a reload.
457
- *
458
- * This will never be the original request passed as an arg to
459
- * the component.
460
- *
461
- * @internal
462
- */
463
- static {
464
- decorateFieldV2(this.prototype, "_latestRequest", [tracked]);
465
- }
466
- #_latestRequest = (initializeDeferredDecorator(this, "_latestRequest"), void 0);
467
- /**
468
- * The most recent request that was made, typically due to either a
469
- * reload or a refresh.
470
- *
471
- * This will never be the original request passed as an arg to
472
- * the component.
473
- *
474
- * @internal
475
- */
476
- /**
477
- * The time at which the network was reported as offline.
478
- *
479
- * @internal
480
- */
481
- unavailableStart;
482
- intervalStart;
483
- nextInterval;
484
- invalidated;
485
- isUpdating;
486
- /**
487
- * The event listener for network status changes,
488
- * cached to use the reference for removal.
489
- *
490
- * @internal
491
- */
492
- onlineChanged;
493
- /**
494
- * The event listener for visibility status changes,
495
- * cached to use the reference for removal.
496
- *
497
- * @internal
498
- */
499
- backgroundChanged;
500
- /**
501
- * The last request passed as an arg to the component,
502
- * cached for comparison.
503
- *
504
- * @internal
505
- */
506
- _originalRequest;
507
- /**
508
- * The last query passed as an arg to the component,
509
- * cached for comparison.
510
- *
511
- * @internal
512
- */
513
- _originalQuery;
514
- _subscription;
515
- _subscribedTo;
516
- constructor(owner, args) {
517
- super(owner, args);
518
- this._subscribedTo = null;
519
- this._subscription = null;
520
- this.intervalStart = null;
521
- this.invalidated = false;
522
- this.nextInterval = null;
523
- this.installListeners();
524
- void this.beginPolling();
525
- }
526
- async beginPolling() {
527
- // await the initial request
528
- try {
529
- await this.request;
530
- } catch {
531
- // ignore errors here, we just want to wait for the request to finish
532
- } finally {
533
- if (!this.isDestroyed) {
534
- void this.scheduleInterval();
535
- }
536
- }
537
- }
538
- get isIdle() {
539
- const {
540
- request,
541
- query
542
- } = this.args;
543
- return Boolean(!request && !query);
544
- }
545
- static {
546
- decorateMethodV2(this.prototype, "isIdle", [cached]);
547
- }
548
- get autorefreshTypes() {
549
- const {
550
- autorefresh
551
- } = this.args;
552
- let types;
553
- if (autorefresh === true) {
554
- types = ['online', 'invalid'];
555
- } else if (typeof autorefresh === 'string') {
556
- types = autorefresh.split(',');
557
- } else {
558
- types = [];
559
- }
560
- return new Set(types);
561
- }
562
- // we only run this function on component creation
563
- // and when an update is triggered, so it does not
564
- // react to changes in the autorefreshThreshold
565
- // or autorefresh args.
566
- //
567
- // if we need to react to those changes, we can
568
- // use a modifier or internal component or some
569
- // such to trigger a re-run of this function.
570
- static {
571
- decorateMethodV2(this.prototype, "autorefreshTypes", [cached]);
572
- }
573
- async scheduleInterval() {
574
- const {
575
- autorefreshThreshold
576
- } = this.args;
577
- const hasValidThreshold = typeof autorefreshThreshold === 'number' && autorefreshThreshold > 0;
578
- if (typeof window === 'undefined' ||
579
- // dont schedule without a threshold
580
- !hasValidThreshold ||
581
- // dont schedule if we weren't told to
582
- !this.autorefreshTypes.has('interval') ||
583
- // dont schedule if we're already scheduled
584
- this.intervalStart !== null) {
585
- return;
586
- }
587
- // if we have a current request, wait for it to finish
588
- // before scheduling the next one
589
- if (this._latestRequest) {
590
- try {
591
- await this._latestRequest;
592
- } catch {
593
- // ignore errors here, we just want to wait for the request to finish
594
- }
595
- if (this.isDestroyed) {
596
- return;
597
- }
598
- }
599
- // setup the next interval
600
- this.intervalStart = Date.now();
601
- this.nextInterval = setTimeout(() => {
602
- this.maybeUpdate();
603
- }, autorefreshThreshold);
604
- }
605
- clearInterval() {
606
- if (this.nextInterval) {
607
- clearTimeout(this.nextInterval);
608
- this.intervalStart = null;
609
- }
610
- }
611
- updateSubscriptions() {
612
- if (this.isIdle) {
613
- return;
614
- }
615
- const requestId = this._request.lid;
616
- // if we're already subscribed to this request, we don't need to do anything
617
- if (this._subscribedTo === requestId) {
618
- return;
619
- }
620
- // if we're subscribed to a different request, we need to unsubscribe
621
- this.removeSubscriptions();
622
- // if we have a request, we need to subscribe to it
623
- if (requestId) {
624
- this._subscribedTo = requestId;
625
- this._subscription = this.store.notifications.subscribe(requestId, (_id, op) => {
626
- // ignore subscription events that occur while our own component's request
627
- // is ocurring
628
- if (this.isUpdating) {
629
- return;
630
- }
631
- switch (op) {
632
- case 'invalidated':
633
- {
634
- // if we're subscribed to invalidations, we need to update
635
- if (this.autorefreshTypes.has('invalid')) {
636
- this.invalidated = true;
637
- this.maybeUpdate();
638
- }
639
- break;
640
- }
641
- case 'state':
642
- {
643
- const latest = this.store.requestManager._deduped.get(requestId);
644
- const priority = latest?.priority;
645
- const state = this.reqState;
646
- if (!priority) {
647
- // if there is no priority, we have completed whatever request
648
- // was occurring and so we are no longer refreshing (if we were)
649
- this.isRefreshing = false;
650
- } else if (priority.blocking && !state.isLoading) {
651
- // if we are blocking, there is an active request for this identity
652
- // that MUST be fulfilled from network (not cache).
653
- // Thus this is not "refreshing" because we should clear out and
654
- // block on this request.
655
- //
656
- // we receive state notifications when either a request initiates
657
- // or completes.
658
- //
659
- // In the completes case: we may receive the state notification
660
- // slightly before the request is finalized because the NotificationManager
661
- // may sync flush it (and thus deliver it before the microtask completes)
662
- //
663
- // In the initiates case: we aren't supposed to receive one unless there
664
- // is no other request in flight for this identity.
665
- //
666
- // However, there is a race condition here where the completed
667
- // notification can trigger an update that generates a new request
668
- // thus giving us an initiated notification before the older request
669
- // finalizes.
670
- //
671
- // When this occurs, if the triggered update happens to have caused
672
- // a new request to be made for the same identity AND that request
673
- // is the one passed into this component as the @request arg, then
674
- // getRequestState will return the state of the new request.
675
- // We can detect this by checking if the request state is "loading"
676
- // as outside of this case we would have a completed request.
677
- //
678
- // That is the reason for the `&& !state.isLoading` check above.
679
- // TODO should we just treat this as refreshing?
680
- this.isRefreshing = false;
681
- this.maybeUpdate('policy', true);
682
- } else {
683
- this.isRefreshing = true;
684
- }
685
- }
686
- }
687
- });
688
- }
689
- }
690
- removeSubscriptions() {
691
- if (this._subscription) {
692
- this.store.notifications.unsubscribe(this._subscription);
693
- this._subscribedTo = null;
694
- this._subscription = null;
695
- }
696
- }
697
- /**
698
- * Install the event listeners for network and visibility changes.
699
- * This is only done in browser environments with a global `window`.
700
- *
701
- * @internal
702
- */
703
- installListeners() {
704
- if (typeof window === 'undefined') {
705
- return;
706
- }
707
- this.isOnline = window.navigator.onLine;
708
- this.unavailableStart = this.isOnline ? null : Date.now();
709
- this.isHidden = document.visibilityState === 'hidden';
710
- this.onlineChanged = event => {
711
- this.isOnline = event.type === 'online';
712
- if (event.type === 'offline' && this.unavailableStart === null) {
713
- this.unavailableStart = Date.now();
714
- }
715
- this.maybeUpdate();
716
- };
717
- this.backgroundChanged = () => {
718
- const isHidden = document.visibilityState === 'hidden';
719
- this.isHidden = isHidden;
720
- if (isHidden && this.unavailableStart === null) {
721
- this.unavailableStart = Date.now();
722
- }
723
- this.maybeUpdate();
724
- };
725
- window.addEventListener('online', this.onlineChanged, {
726
- passive: true,
727
- capture: true
728
- });
729
- window.addEventListener('offline', this.onlineChanged, {
730
- passive: true,
731
- capture: true
732
- });
733
- document.addEventListener('visibilitychange', this.backgroundChanged, {
734
- passive: true,
735
- capture: true
736
- });
737
- }
738
- /**
739
- * If the network is online and the tab is visible, either reload or refresh the request
740
- * based on the component's configuration and the requested update mode.
741
- *
742
- * Valid modes are:
743
- *
744
- * - `'reload'`: Force a reload of the request.
745
- * - `'refresh'`: Refresh the request in the background.
746
- * - `'policy'`: Make the request, letting the store's configured CachePolicy decide whether to reload, refresh, or do nothing.
747
- * - `undefined`: Make the request using the component's autorefreshBehavior setting if the autorefreshThreshold has passed.
748
- *
749
- * @internal
750
- */
751
- maybeUpdate(mode, silent) {
752
- if (this.isIdle) {
753
- return;
754
- }
755
- const canAttempt = Boolean(this.isOnline && !this.isHidden && (mode || this.autorefreshTypes.size));
756
- if (!canAttempt) {
757
- if (!silent && mode && mode !== 'invalidated') {
758
- throw new Error(`Reload not available: the network is not online or the tab is hidden`);
759
- }
760
- return;
761
- }
762
- const {
763
- autorefreshTypes
764
- } = this;
765
- let shouldAttempt = this.invalidated || Boolean(mode);
766
- if (!shouldAttempt && autorefreshTypes.has('online')) {
767
- const {
768
- unavailableStart
769
- } = this;
770
- const {
771
- autorefreshThreshold
772
- } = this.args;
773
- const deadline = typeof autorefreshThreshold === 'number' ? autorefreshThreshold : DEFAULT_DEADLINE;
774
- shouldAttempt = Boolean(unavailableStart && Date.now() - unavailableStart > deadline);
775
- }
776
- if (!shouldAttempt && autorefreshTypes.has('interval')) {
777
- const {
778
- intervalStart
779
- } = this;
780
- const {
781
- autorefreshThreshold
782
- } = this.args;
783
- if (intervalStart && typeof autorefreshThreshold === 'number' && autorefreshThreshold > 0) {
784
- shouldAttempt = Boolean(Date.now() - intervalStart >= autorefreshThreshold);
785
- }
786
- }
787
- this.unavailableStart = null;
788
- this.invalidated = false;
789
- if (shouldAttempt) {
790
- this.clearInterval();
791
- const request = Object.assign({}, this.reqState.request);
792
- const realMode = mode === 'invalidated' ? null : mode;
793
- const val = realMode ?? this.args.autorefreshBehavior ?? 'policy';
794
- switch (val) {
795
- case 'reload':
796
- request.cacheOptions = Object.assign({}, request.cacheOptions, {
797
- reload: true
798
- });
799
- break;
800
- case 'refresh':
801
- request.cacheOptions = Object.assign({}, request.cacheOptions, {
802
- backgroundReload: true
803
- });
804
- break;
805
- case 'policy':
806
- break;
807
- default:
808
- throw new Error(`Invalid ${mode ? 'update mode' : '@autorefreshBehavior'} for <Request />: ${isNeverString(val)}`);
809
- }
810
- const wasStoreRequest = request[EnableHydration] === true;
811
- macroCondition(getGlobalConfig().WarpDrive.env.DEBUG) ? (test => {
812
- if (!test) {
813
- throw new Error(`Cannot supply a different store via context than was used to create the request`);
814
- }
815
- })(!request.store || request.store === this.store) : {};
816
- this.isUpdating = true;
817
- this._latestRequest = wasStoreRequest ? this.store.request(request) : this.store.requestManager.request(request);
818
- if (val !== 'refresh') {
819
- this._localRequest = this._latestRequest;
820
- }
821
- void this.scheduleInterval();
822
- void this._latestRequest.finally(() => {
823
- this.isUpdating = false;
824
- });
825
- }
826
- }
827
- /**
828
- * Retry the request, reloading it from the server.
829
- *
830
- * @internal
831
- */
832
- retry = async () => {
833
- this.maybeUpdate('reload');
834
- await this._localRequest;
835
- };
836
- /**
837
- * Refresh the request, updating it in the background.
838
- *
839
- * @internal
840
- */
841
- refresh = async () => {
842
- this.maybeUpdate('refresh');
843
- await this._latestRequest;
844
- };
845
- get errorFeatures() {
846
- return {
847
- isHidden: this.isHidden,
848
- isOnline: this.isOnline,
849
- retry: this.retry
850
- };
851
- }
852
- static {
853
- decorateMethodV2(this.prototype, "errorFeatures", [cached]);
854
- }
855
- get contentFeatures() {
856
- const feat = {
857
- isHidden: this.isHidden,
858
- isOnline: this.isOnline,
859
- reload: this.retry,
860
- refresh: this.refresh,
861
- isRefreshing: this.isRefreshing,
862
- latestRequest: this._latestRequest
863
- };
864
- if (feat.isRefreshing) {
865
- feat.abort = () => {
866
- this._latestRequest?.abort();
867
- };
868
- }
869
- return feat;
870
- }
871
- static {
872
- decorateMethodV2(this.prototype, "contentFeatures", [cached]);
873
- }
874
- willDestroy() {
875
- this.removeSubscriptions();
876
- if (typeof window === 'undefined') {
877
- return;
878
- }
879
- this.clearInterval();
880
- window.removeEventListener('online', this.onlineChanged, {
881
- passive: true,
882
- capture: true
883
- });
884
- window.removeEventListener('offline', this.onlineChanged, {
885
- passive: true,
886
- capture: true
887
- });
888
- document.removeEventListener('visibilitychange', this.backgroundChanged, {
889
- passive: true,
890
- capture: true
891
- });
892
- }
893
- get _request() {
894
- const {
895
- request,
896
- query
897
- } = this.args;
898
- macroCondition(getGlobalConfig().WarpDrive.env.DEBUG) ? (test => {
899
- if (!test) {
900
- throw new Error(`Cannot use both @request and @query args with the <Request> component`);
901
- }
902
- })(!request || !query) : {};
903
- const {
904
- _localRequest,
905
- _originalRequest,
906
- _originalQuery
907
- } = this;
908
- const isOriginalRequest = request === _originalRequest && query === _originalQuery;
909
- if (_localRequest && isOriginalRequest) {
910
- return _localRequest;
911
- }
912
- // update state checks for the next time
913
- this._originalQuery = query;
914
- this._originalRequest = request;
915
- if (request) {
916
- return request;
917
- }
918
- macroCondition(getGlobalConfig().WarpDrive.env.DEBUG) ? (test => {
919
- if (!test) {
920
- throw new Error(`You must provide either @request or an @query arg with the <Request> component`);
921
- }
922
- })(query) : {};
923
- return this.store.request(query);
924
- }
925
- static {
926
- decorateMethodV2(this.prototype, "_request", [cached]);
927
- }
928
- get request() {
929
- const request = this._request;
930
- this.updateSubscriptions();
931
- return request;
932
- }
933
- static {
934
- decorateMethodV2(this.prototype, "request", [cached]);
935
- }
936
389
  get store() {
937
390
  const store = this.args.store || this._store;
938
391
  macroCondition(getGlobalConfig().WarpDrive.env.DEBUG) ? (test => {
@@ -942,14 +395,29 @@ class Request extends Component {
942
395
  })(store) : {};
943
396
  return store;
944
397
  }
945
- get reqState() {
946
- return getRequestState(this.request);
398
+ _state = null;
399
+ get state() {
400
+ let {
401
+ _state
402
+ } = this;
403
+ const {
404
+ store
405
+ } = this;
406
+ if (_state && _state.store !== store) {
407
+ _state[DISPOSE]();
408
+ _state = null;
409
+ }
410
+ if (!_state) {
411
+ this._state = _state = createRequestSubscription(store, this.args);
412
+ }
413
+ return _state;
947
414
  }
948
- get result() {
949
- return this.reqState.result;
415
+ willDestroy() {
416
+ this._state[DISPOSE]();
417
+ this._state = null;
950
418
  }
951
419
  static {
952
- setComponentTemplate(precompileTemplate("\n {{#if (and this.isIdle (has-block \"idle\"))}}\n {{yield to=\"idle\"}}\n {{else if this.isIdle}}\n <Throw @error={{IdleBlockMissingError}} />\n {{else if this.reqState.isLoading}}\n {{yield this.reqState.loadingState to=\"loading\"}}\n {{else if (and this.reqState.isCancelled (has-block \"cancelled\"))}}\n {{yield (notNull this.reqState.error) this.errorFeatures to=\"cancelled\"}}\n {{else if (and this.reqState.isError (has-block \"error\"))}}\n {{yield (notNull this.reqState.error) this.errorFeatures to=\"error\"}}\n {{else if this.reqState.isSuccess}}\n {{yield this.result this.contentFeatures to=\"content\"}}\n {{else if (not this.reqState.isCancelled)}}\n <Throw @error={{(notNull this.reqState.error)}} />\n {{/if}}\n {{yield this.reqState to=\"always\"}}\n ", {
420
+ setComponentTemplate(precompileTemplate("\n {{#if (and this.state.isIdle (has-block \"idle\"))}}\n {{yield to=\"idle\"}}\n\n {{else if this.state.isIdle}}\n <Throw @error={{IdleBlockMissingError}} />\n\n {{else if this.state.reqState.isLoading}}\n {{yield this.state.reqState.loadingState to=\"loading\"}}\n\n {{else if (and this.state.reqState.isCancelled (has-block \"cancelled\"))}}\n {{yield (notNull this.state.reqState.reason) this.state.errorFeatures to=\"cancelled\"}}\n\n {{else if (and this.state.reqState.isError (has-block \"error\"))}}\n {{yield (notNull this.state.reqState.reason) this.state.errorFeatures to=\"error\"}}\n\n {{else if this.state.reqState.isSuccess}}\n {{yield this.state.result this.state.contentFeatures to=\"content\"}}\n\n {{else if (not this.state.reqState.isCancelled)}}\n <Throw @error={{(notNull this.state.reqState.reason)}} />\n {{/if}}\n\n {{yield this.state.reqState to=\"always\"}}\n ", {
953
421
  strictMode: true,
954
422
  scope: () => ({
955
423
  and,
@@ -961,4 +429,5 @@ class Request extends Component {
961
429
  }), this);
962
430
  }
963
431
  }
964
- export { Await, Request, Throw };
432
+
433
+ export { Await, Request, Throw };
package/dist/install.js CHANGED
@@ -1,9 +1,9 @@
1
- import '@ember-data/store/-private';
2
1
  import { tagForProperty } from '@ember/-internals/metal';
3
- import { createCache, track, updateTag, consumeTag, getValue, dirtyTag } from '@glimmer/validator';
4
2
  import { _backburner } from '@ember/runloop';
5
- import { setupSignals } from '@ember-data/store/configure';
6
- import { macroCondition, getGlobalConfig } from '@embroider/macros';
3
+ import { createCache, track, updateTag, consumeTag, getValue, dirtyTag } from '@glimmer/validator';
4
+ import { macroCondition, getGlobalConfig, importSync } from '@embroider/macros';
5
+ import { setupSignals } from '@warp-drive/core/configure';
6
+
7
7
  const emberDirtyTag = dirtyTag;
8
8
  function buildSignalConfig(options) {
9
9
  const ARRAY_SIGNAL = options.wellknown.Array;
@@ -60,8 +60,18 @@ function buildSignalConfig(options) {
60
60
  willSyncFlushWatchers: () => {
61
61
  //@ts-expect-error
62
62
  return !!_backburner.currentInstance && _backburner._autorun !== true;
63
+ },
64
+ waitFor: async promise => {
65
+ if (macroCondition(getGlobalConfig().WarpDrive.env.TESTING)) {
66
+ const {
67
+ waitForPromise
68
+ } = importSync('@ember/test-waiters');
69
+ return waitForPromise(promise);
70
+ }
71
+ return promise;
63
72
  }
64
73
  };
65
74
  }
66
75
  setupSignals(buildSignalConfig);
67
- export { buildSignalConfig };
76
+
77
+ export { buildSignalConfig };