@ethlete/query 2.0.0 → 2.1.1

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.
@@ -1,5 +1,5 @@
1
1
  import { BehaviorSubjectWithSubscriberCount, getCookie, setCookie, deleteCookie, DestroyService, equal, clone } from '@ethlete/core';
2
- import { Observable, filter, takeWhile, switchMap, of, BehaviorSubject, Subject, takeUntil, interval, tap, timer, fromEvent, startWith, concat, take, debounceTime, combineLatest, map, distinctUntilChanged } from 'rxjs';
2
+ import { Observable, filter, takeWhile, switchMap, of, BehaviorSubject, Subject, takeUntil, interval, startWith, tap, timer, fromEvent, concat, take, debounceTime, combineLatest, map, distinctUntilChanged } from 'rxjs';
3
3
  import * as i0 from '@angular/core';
4
4
  import { Directive, Input, InjectionToken, inject, ChangeDetectorRef, ViewContainerRef, TemplateRef, ErrorHandler, ElementRef } from '@angular/core';
5
5
  import { FormGroup } from '@angular/forms';
@@ -163,6 +163,9 @@ const extractExpiresInSeconds = (headers) => {
163
163
  const ageSeconds = parseInt(age);
164
164
  expiresIn = maxAge - ageSeconds;
165
165
  }
166
+ else if (maxAge) {
167
+ expiresIn = maxAge / 2; // We assume the response is half way to its expiration
168
+ }
166
169
  else if (expires) {
167
170
  // Used by some apis to tell the response will never expire
168
171
  // In this case we let the response expire after 1 hour
@@ -548,8 +551,22 @@ class Query {
548
551
  get isInUse() {
549
552
  return this._state$.subscriberCount > 0;
550
553
  }
551
- get _config() {
552
- return this._queryConfig;
554
+ get isPolling() {
555
+ return !!this._pollingSubscription;
556
+ }
557
+ get autoRefreshOnConfig() {
558
+ const base = this._queryConfig.autoRefreshOn ?? {};
559
+ const transformed = {
560
+ queryClientDefaultHeadersChange: base.queryClientDefaultHeadersChange ?? true,
561
+ windowFocus: base.windowFocus ?? true,
562
+ };
563
+ return transformed;
564
+ }
565
+ /**
566
+ * @internal
567
+ */
568
+ get _enableSmartPolling() {
569
+ return this._queryConfig.enableSmartPolling ?? true;
553
570
  }
554
571
  constructor(_client, _queryConfig, _routeWithParams, _args) {
555
572
  this._client = _client;
@@ -559,15 +576,21 @@ class Query {
559
576
  this._currentId = 0;
560
577
  this._pollingSubscription = null;
561
578
  this._onAbort$ = new Subject();
579
+ this._currentPollConfig = null;
580
+ /**
581
+ * @internal
582
+ */
583
+ this._isPollingPaused = false;
562
584
  this._state$ = new BehaviorSubjectWithSubscriberCount({
563
585
  type: "PREPARED" /* QueryStateType.Prepared */,
564
- meta: { id: this._currentId },
586
+ meta: { id: this._currentId, triggeredVia: 'program' },
565
587
  });
566
588
  }
567
589
  clone() {
568
590
  return this._client.fetch(this._queryConfig);
569
591
  }
570
592
  execute(options) {
593
+ const triggeredVia = options?._triggeredVia ?? 'program';
571
594
  if (!this.isExpired && !options?.skipCache && isQueryStateSuccess(this.state)) {
572
595
  return this;
573
596
  }
@@ -578,7 +601,7 @@ class Query {
578
601
  if (isQueryStateLoading(this._state$.value)) {
579
602
  this.abort();
580
603
  }
581
- const meta = { id };
604
+ const meta = { id, triggeredVia };
582
605
  this._state$.next({
583
606
  type: "LOADING" /* QueryStateType.Loading */,
584
607
  meta,
@@ -688,16 +711,34 @@ class Query {
688
711
  if (this._pollingSubscription) {
689
712
  return this;
690
713
  }
691
- this._pollingSubscription = interval(config.interval)
714
+ this._currentPollConfig = config;
715
+ const _interval = config.triggerImmediately
716
+ ? interval(config.interval).pipe(startWith(-1))
717
+ : interval(config.interval);
718
+ this._pollingSubscription = _interval
692
719
  .pipe(takeUntil(config.takeUntil))
693
- .subscribe(() => this.execute({ skipCache: true }));
720
+ .subscribe(() => this.execute({ skipCache: true, _triggeredVia: 'poll' }));
694
721
  return this;
695
722
  }
696
723
  stopPolling() {
697
724
  this._pollingSubscription?.unsubscribe();
698
725
  this._pollingSubscription = null;
726
+ this._currentPollConfig = null;
699
727
  return this;
700
728
  }
729
+ pausePolling() {
730
+ this._pollingSubscription?.unsubscribe();
731
+ this._pollingSubscription = null;
732
+ this._isPollingPaused = true;
733
+ return this;
734
+ }
735
+ resumePolling() {
736
+ if (!this._isPollingPaused || !this._currentPollConfig) {
737
+ return this;
738
+ }
739
+ this._isPollingPaused = false;
740
+ return this.poll({ ...this._currentPollConfig, triggerImmediately: true });
741
+ }
701
742
  _updateUseResultInDependencies() {
702
743
  if (!this._args?.useResultIn?.length) {
703
744
  return;
@@ -963,9 +1004,9 @@ class QueryDirective {
963
1004
  }
964
1005
  }
965
1006
  }
966
- QueryDirective.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "15.1.4", ngImport: i0, type: QueryDirective, deps: [{ token: i0.TemplateRef }, { token: i0.ViewContainerRef }, { token: i0.ErrorHandler }, { token: i0.ChangeDetectorRef }], target: i0.ɵɵFactoryTarget.Directive });
967
- QueryDirective.ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "15.1.4", type: QueryDirective, isStandalone: true, selector: "[query]", inputs: { query: "query", cache: ["queryCache", "cache"] }, ngImport: i0 });
968
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "15.1.4", ngImport: i0, type: QueryDirective, decorators: [{
1007
+ QueryDirective.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "15.2.1", ngImport: i0, type: QueryDirective, deps: [{ token: i0.TemplateRef }, { token: i0.ViewContainerRef }, { token: i0.ErrorHandler }, { token: i0.ChangeDetectorRef }], target: i0.ɵɵFactoryTarget.Directive });
1008
+ QueryDirective.ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "15.2.1", type: QueryDirective, isStandalone: true, selector: "[query]", inputs: { query: "query", cache: ["queryCache", "cache"] }, ngImport: i0 });
1009
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "15.2.1", ngImport: i0, type: QueryDirective, decorators: [{
969
1010
  type: Directive,
970
1011
  args: [{
971
1012
  // eslint-disable-next-line @angular-eslint/directive-selector
@@ -1038,6 +1079,7 @@ class InfinityQuery {
1038
1079
  console.error('Cannot load more pages, already at the end. Make sure to not render the infinity query trigger using *ngIf canLoadMore');
1039
1080
  return;
1040
1081
  }
1082
+ this._clearSubscriptions();
1041
1083
  const args = this._prepareArgs(this._config, calculatedPage);
1042
1084
  const query = this._config.queryCreator.prepare(args).execute();
1043
1085
  this._handleNewQuery(query);
@@ -1046,7 +1088,7 @@ class InfinityQuery {
1046
1088
  this._currentQuery$.next(query);
1047
1089
  }
1048
1090
  reset(newConfig) {
1049
- this._destroy();
1091
+ this._clearSubscriptions();
1050
1092
  this._config = { ...this._config, ...(newConfig ?? {}) };
1051
1093
  this._currentPage$.next(null);
1052
1094
  this._currentCalculatedPage$.next(null);
@@ -1054,8 +1096,9 @@ class InfinityQuery {
1054
1096
  this._itemsPerPage$.next(this._config.limitParam?.value ?? 10);
1055
1097
  this._currentQuery$.next(null);
1056
1098
  this._data$.next([]);
1099
+ this.nextPage();
1057
1100
  }
1058
- _destroy() {
1101
+ _clearSubscriptions() {
1059
1102
  this._subscriptions.forEach((sub) => sub.unsubscribe());
1060
1103
  this._subscriptions = [];
1061
1104
  }
@@ -1085,7 +1128,14 @@ class InfinityQuery {
1085
1128
  this._subscriptions = this._subscriptions.filter((sub) => sub !== stateChangesSub);
1086
1129
  },
1087
1130
  });
1088
- this._subscriptions.push(stateChangesSub);
1131
+ const queryAutoRefreshSub = query.state$.subscribe({
1132
+ next: (state) => {
1133
+ if (state.meta.triggeredVia === 'auto') {
1134
+ this.reset();
1135
+ }
1136
+ },
1137
+ });
1138
+ this._subscriptions.push(stateChangesSub, queryAutoRefreshSub);
1089
1139
  }
1090
1140
  _prepareArgs(config, page) {
1091
1141
  const pageParamLocation = this._getPageParamLocation(config?.pageParam?.location);
@@ -1199,7 +1249,7 @@ class InfinityQueryDirective {
1199
1249
  this._renderMainView();
1200
1250
  }
1201
1251
  ngOnDestroy() {
1202
- this.instance?._destroy();
1252
+ this.instance?._clearSubscriptions();
1203
1253
  }
1204
1254
  _setupInfinityQuery(config) {
1205
1255
  const instance = new InfinityQuery(config);
@@ -1265,7 +1315,7 @@ class InfinityQueryDirective {
1265
1315
  }
1266
1316
  _cleanup() {
1267
1317
  this._queryConfigChanged$.next(true);
1268
- this.instance?._destroy();
1318
+ this.instance?._clearSubscriptions();
1269
1319
  this._viewContext.loading = false;
1270
1320
  this._viewContext.error = null;
1271
1321
  this._viewContext.infinityQuery = null;
@@ -1278,9 +1328,9 @@ class InfinityQueryDirective {
1278
1328
  this._data$.next([]);
1279
1329
  }
1280
1330
  }
1281
- InfinityQueryDirective.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "15.1.4", ngImport: i0, type: InfinityQueryDirective, deps: [], target: i0.ɵɵFactoryTarget.Directive });
1282
- InfinityQueryDirective.ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "15.1.4", type: InfinityQueryDirective, isStandalone: true, selector: "[infinityQuery]", inputs: { infinityQuery: "infinityQuery" }, providers: [{ provide: INFINITY_QUERY_TOKEN, useExisting: InfinityQueryDirective }, DestroyService], exportAs: ["infinityQuery"], ngImport: i0 });
1283
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "15.1.4", ngImport: i0, type: InfinityQueryDirective, decorators: [{
1331
+ InfinityQueryDirective.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "15.2.1", ngImport: i0, type: InfinityQueryDirective, deps: [], target: i0.ɵɵFactoryTarget.Directive });
1332
+ InfinityQueryDirective.ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "15.2.1", type: InfinityQueryDirective, isStandalone: true, selector: "[infinityQuery]", inputs: { infinityQuery: "infinityQuery" }, providers: [{ provide: INFINITY_QUERY_TOKEN, useExisting: InfinityQueryDirective }, DestroyService], exportAs: ["infinityQuery"], ngImport: i0 });
1333
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "15.2.1", ngImport: i0, type: InfinityQueryDirective, decorators: [{
1284
1334
  type: Directive,
1285
1335
  args: [{
1286
1336
  // eslint-disable-next-line @angular-eslint/directive-selector
@@ -1330,9 +1380,9 @@ class InfinityQueryTriggerDirective {
1330
1380
  this._observer.observe(this._elementRef.nativeElement);
1331
1381
  }
1332
1382
  }
1333
- InfinityQueryTriggerDirective.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "15.1.4", ngImport: i0, type: InfinityQueryTriggerDirective, deps: [], target: i0.ɵɵFactoryTarget.Directive });
1334
- InfinityQueryTriggerDirective.ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "15.1.4", type: InfinityQueryTriggerDirective, isStandalone: true, selector: "[infinityQueryTrigger], infinity-query-trigger", inputs: { scrollContainerSelector: "scrollContainerSelector" }, ngImport: i0 });
1335
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "15.1.4", ngImport: i0, type: InfinityQueryTriggerDirective, decorators: [{
1383
+ InfinityQueryTriggerDirective.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "15.2.1", ngImport: i0, type: InfinityQueryTriggerDirective, deps: [], target: i0.ɵɵFactoryTarget.Directive });
1384
+ InfinityQueryTriggerDirective.ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "15.2.1", type: InfinityQueryTriggerDirective, isStandalone: true, selector: "[infinityQueryTrigger], infinity-query-trigger", inputs: { scrollContainerSelector: "scrollContainerSelector" }, ngImport: i0 });
1385
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "15.2.1", ngImport: i0, type: InfinityQueryTriggerDirective, decorators: [{
1336
1386
  type: Directive,
1337
1387
  args: [{
1338
1388
  // eslint-disable-next-line @angular-eslint/directive-selector
@@ -1348,6 +1398,7 @@ class QueryStore {
1348
1398
  this._config = _config;
1349
1399
  this._store = new Map();
1350
1400
  this._garbageCollector = null;
1401
+ this._initSmartQueryHandling();
1351
1402
  }
1352
1403
  add(id, query) {
1353
1404
  this._store.set(id, query);
@@ -1371,14 +1422,45 @@ class QueryStore {
1371
1422
  for (const [key, query] of this._store) {
1372
1423
  if (query.isInUse &&
1373
1424
  (query.isExpired || ignoreCacheValidity) &&
1374
- (query._config.autoRefreshOn?.queryClientDefaultHeadersChange ?? true)) {
1375
- query.execute({ skipCache: true });
1425
+ query.autoRefreshOnConfig.queryClientDefaultHeadersChange) {
1426
+ query.execute({ skipCache: true, _triggeredVia: 'auto' });
1376
1427
  }
1377
1428
  else if (purgeUnused && !query.isInUse) {
1378
1429
  this.remove(key);
1379
1430
  }
1380
1431
  }
1381
1432
  }
1433
+ _initSmartQueryHandling() {
1434
+ const windowBlur$ = fromEvent(window, 'blur');
1435
+ const windowFocus$ = fromEvent(window, 'focus');
1436
+ windowBlur$.subscribe(() => {
1437
+ this._stopGarbageCollector();
1438
+ if (this._config?.enableSmartPolling) {
1439
+ this.forEach((query) => {
1440
+ if (!query.isPolling || !query._enableSmartPolling) {
1441
+ return;
1442
+ }
1443
+ query.pausePolling();
1444
+ });
1445
+ }
1446
+ });
1447
+ windowFocus$.subscribe(() => {
1448
+ if (this._config?.enableSmartPolling || this._config?.autoRefreshQueriesOnWindowFocus) {
1449
+ this.forEach((query) => {
1450
+ if (this._config?.enableSmartPolling && query._isPollingPaused) {
1451
+ query.resumePolling();
1452
+ }
1453
+ if (this._config?.autoRefreshQueriesOnWindowFocus &&
1454
+ query.isExpired &&
1455
+ query.isInUse &&
1456
+ query.autoRefreshOnConfig.windowFocus) {
1457
+ query.execute({ skipCache: true, _triggeredVia: 'auto' });
1458
+ }
1459
+ });
1460
+ }
1461
+ this._initGarbageCollector();
1462
+ });
1463
+ }
1382
1464
  _logState(key, item, operation) {
1383
1465
  if (!this._config?.enableChangeLogging)
1384
1466
  return;
@@ -1538,6 +1620,8 @@ class QueryClient {
1538
1620
  this._store = new QueryStore({
1539
1621
  enableChangeLogging: _clientConfig.logging?.queryStateChanges,
1540
1622
  enableGarbageCollectorLogging: _clientConfig.logging?.queryStateGarbageCollector,
1623
+ autoRefreshQueriesOnWindowFocus: _clientConfig.request?.autoRefreshQueriesOnWindowFocus ?? true,
1624
+ enableSmartPolling: _clientConfig.request?.enableSmartPolling ?? true,
1541
1625
  });
1542
1626
  }
1543
1627
  }