@ethlete/query 1.0.1 → 2.1.0

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
- import { 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';
1
+ import { BehaviorSubjectWithSubscriberCount, getCookie, setCookie, deleteCookie, DestroyService, equal, clone } from '@ethlete/core';
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';
@@ -545,6 +545,26 @@ class Query {
545
545
  }
546
546
  return ts < Date.now();
547
547
  }
548
+ get isInUse() {
549
+ return this._state$.subscriberCount > 0;
550
+ }
551
+ get isPolling() {
552
+ return !!this._pollingSubscription;
553
+ }
554
+ get autoRefreshOnConfig() {
555
+ const base = this._queryConfig.autoRefreshOn ?? {};
556
+ const transformed = {
557
+ queryClientDefaultHeadersChange: base.queryClientDefaultHeadersChange ?? true,
558
+ windowFocus: base.windowFocus ?? true,
559
+ };
560
+ return transformed;
561
+ }
562
+ /**
563
+ * @internal
564
+ */
565
+ get _enableSmartPolling() {
566
+ return this._queryConfig.enableSmartPolling ?? true;
567
+ }
548
568
  constructor(_client, _queryConfig, _routeWithParams, _args) {
549
569
  this._client = _client;
550
570
  this._queryConfig = _queryConfig;
@@ -553,15 +573,21 @@ class Query {
553
573
  this._currentId = 0;
554
574
  this._pollingSubscription = null;
555
575
  this._onAbort$ = new Subject();
556
- this._state$ = new BehaviorSubject({
576
+ this._currentPollConfig = null;
577
+ /**
578
+ * @internal
579
+ */
580
+ this._isPollingPaused = false;
581
+ this._state$ = new BehaviorSubjectWithSubscriberCount({
557
582
  type: "PREPARED" /* QueryStateType.Prepared */,
558
- meta: { id: this._currentId },
583
+ meta: { id: this._currentId, triggeredVia: 'program' },
559
584
  });
560
585
  }
561
586
  clone() {
562
587
  return this._client.fetch(this._queryConfig);
563
588
  }
564
589
  execute(options) {
590
+ const triggeredVia = options?._triggeredVia ?? 'program';
565
591
  if (!this.isExpired && !options?.skipCache && isQueryStateSuccess(this.state)) {
566
592
  return this;
567
593
  }
@@ -572,7 +598,7 @@ class Query {
572
598
  if (isQueryStateLoading(this._state$.value)) {
573
599
  this.abort();
574
600
  }
575
- const meta = { id };
601
+ const meta = { id, triggeredVia };
576
602
  this._state$.next({
577
603
  type: "LOADING" /* QueryStateType.Loading */,
578
604
  meta,
@@ -682,16 +708,34 @@ class Query {
682
708
  if (this._pollingSubscription) {
683
709
  return this;
684
710
  }
685
- this._pollingSubscription = interval(config.interval)
711
+ this._currentPollConfig = config;
712
+ const _interval = config.triggerImmediately
713
+ ? interval(config.interval).pipe(startWith(-1))
714
+ : interval(config.interval);
715
+ this._pollingSubscription = _interval
686
716
  .pipe(takeUntil(config.takeUntil))
687
- .subscribe(() => this.execute({ skipCache: true }));
717
+ .subscribe(() => this.execute({ skipCache: true, _triggeredVia: 'poll' }));
688
718
  return this;
689
719
  }
690
720
  stopPolling() {
691
721
  this._pollingSubscription?.unsubscribe();
692
722
  this._pollingSubscription = null;
723
+ this._currentPollConfig = null;
724
+ return this;
725
+ }
726
+ pausePolling() {
727
+ this._pollingSubscription?.unsubscribe();
728
+ this._pollingSubscription = null;
729
+ this._isPollingPaused = true;
693
730
  return this;
694
731
  }
732
+ resumePolling() {
733
+ if (!this._isPollingPaused || !this._currentPollConfig) {
734
+ return this;
735
+ }
736
+ this._isPollingPaused = false;
737
+ return this.poll({ ...this._currentPollConfig, triggerImmediately: true });
738
+ }
695
739
  _updateUseResultInDependencies() {
696
740
  if (!this._args?.useResultIn?.length) {
697
741
  return;
@@ -957,9 +1001,9 @@ class QueryDirective {
957
1001
  }
958
1002
  }
959
1003
  }
960
- 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 });
961
- 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 });
962
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "15.1.4", ngImport: i0, type: QueryDirective, decorators: [{
1004
+ 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 });
1005
+ 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 });
1006
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "15.2.1", ngImport: i0, type: QueryDirective, decorators: [{
963
1007
  type: Directive,
964
1008
  args: [{
965
1009
  // eslint-disable-next-line @angular-eslint/directive-selector
@@ -1032,6 +1076,7 @@ class InfinityQuery {
1032
1076
  console.error('Cannot load more pages, already at the end. Make sure to not render the infinity query trigger using *ngIf canLoadMore');
1033
1077
  return;
1034
1078
  }
1079
+ this._clearSubscriptions();
1035
1080
  const args = this._prepareArgs(this._config, calculatedPage);
1036
1081
  const query = this._config.queryCreator.prepare(args).execute();
1037
1082
  this._handleNewQuery(query);
@@ -1040,7 +1085,7 @@ class InfinityQuery {
1040
1085
  this._currentQuery$.next(query);
1041
1086
  }
1042
1087
  reset(newConfig) {
1043
- this._destroy();
1088
+ this._clearSubscriptions();
1044
1089
  this._config = { ...this._config, ...(newConfig ?? {}) };
1045
1090
  this._currentPage$.next(null);
1046
1091
  this._currentCalculatedPage$.next(null);
@@ -1048,8 +1093,9 @@ class InfinityQuery {
1048
1093
  this._itemsPerPage$.next(this._config.limitParam?.value ?? 10);
1049
1094
  this._currentQuery$.next(null);
1050
1095
  this._data$.next([]);
1096
+ this.nextPage();
1051
1097
  }
1052
- _destroy() {
1098
+ _clearSubscriptions() {
1053
1099
  this._subscriptions.forEach((sub) => sub.unsubscribe());
1054
1100
  this._subscriptions = [];
1055
1101
  }
@@ -1079,7 +1125,14 @@ class InfinityQuery {
1079
1125
  this._subscriptions = this._subscriptions.filter((sub) => sub !== stateChangesSub);
1080
1126
  },
1081
1127
  });
1082
- this._subscriptions.push(stateChangesSub);
1128
+ const queryAutoRefreshSub = query.state$.subscribe({
1129
+ next: (state) => {
1130
+ if (state.meta.triggeredVia === 'auto') {
1131
+ this.reset();
1132
+ }
1133
+ },
1134
+ });
1135
+ this._subscriptions.push(stateChangesSub, queryAutoRefreshSub);
1083
1136
  }
1084
1137
  _prepareArgs(config, page) {
1085
1138
  const pageParamLocation = this._getPageParamLocation(config?.pageParam?.location);
@@ -1193,7 +1246,7 @@ class InfinityQueryDirective {
1193
1246
  this._renderMainView();
1194
1247
  }
1195
1248
  ngOnDestroy() {
1196
- this.instance?._destroy();
1249
+ this.instance?._clearSubscriptions();
1197
1250
  }
1198
1251
  _setupInfinityQuery(config) {
1199
1252
  const instance = new InfinityQuery(config);
@@ -1259,7 +1312,7 @@ class InfinityQueryDirective {
1259
1312
  }
1260
1313
  _cleanup() {
1261
1314
  this._queryConfigChanged$.next(true);
1262
- this.instance?._destroy();
1315
+ this.instance?._clearSubscriptions();
1263
1316
  this._viewContext.loading = false;
1264
1317
  this._viewContext.error = null;
1265
1318
  this._viewContext.infinityQuery = null;
@@ -1272,9 +1325,9 @@ class InfinityQueryDirective {
1272
1325
  this._data$.next([]);
1273
1326
  }
1274
1327
  }
1275
- InfinityQueryDirective.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "15.1.4", ngImport: i0, type: InfinityQueryDirective, deps: [], target: i0.ɵɵFactoryTarget.Directive });
1276
- 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 });
1277
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "15.1.4", ngImport: i0, type: InfinityQueryDirective, decorators: [{
1328
+ InfinityQueryDirective.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "15.2.1", ngImport: i0, type: InfinityQueryDirective, deps: [], target: i0.ɵɵFactoryTarget.Directive });
1329
+ 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 });
1330
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "15.2.1", ngImport: i0, type: InfinityQueryDirective, decorators: [{
1278
1331
  type: Directive,
1279
1332
  args: [{
1280
1333
  // eslint-disable-next-line @angular-eslint/directive-selector
@@ -1324,9 +1377,9 @@ class InfinityQueryTriggerDirective {
1324
1377
  this._observer.observe(this._elementRef.nativeElement);
1325
1378
  }
1326
1379
  }
1327
- InfinityQueryTriggerDirective.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "15.1.4", ngImport: i0, type: InfinityQueryTriggerDirective, deps: [], target: i0.ɵɵFactoryTarget.Directive });
1328
- 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 });
1329
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "15.1.4", ngImport: i0, type: InfinityQueryTriggerDirective, decorators: [{
1380
+ InfinityQueryTriggerDirective.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "15.2.1", ngImport: i0, type: InfinityQueryTriggerDirective, deps: [], target: i0.ɵɵFactoryTarget.Directive });
1381
+ 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 });
1382
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "15.2.1", ngImport: i0, type: InfinityQueryTriggerDirective, decorators: [{
1330
1383
  type: Directive,
1331
1384
  args: [{
1332
1385
  // eslint-disable-next-line @angular-eslint/directive-selector
@@ -1342,6 +1395,7 @@ class QueryStore {
1342
1395
  this._config = _config;
1343
1396
  this._store = new Map();
1344
1397
  this._garbageCollector = null;
1398
+ this._initSmartQueryHandling();
1345
1399
  }
1346
1400
  add(id, query) {
1347
1401
  this._store.set(id, query);
@@ -1355,6 +1409,55 @@ class QueryStore {
1355
1409
  this._store.delete(id);
1356
1410
  this._logState(id, null, 'REMOVE');
1357
1411
  }
1412
+ forEach(callback) {
1413
+ for (const [key, query] of this._store) {
1414
+ callback(query, key);
1415
+ }
1416
+ }
1417
+ refreshQueriesInUse(config) {
1418
+ const { ignoreCacheValidity, purgeUnused } = config ?? {};
1419
+ for (const [key, query] of this._store) {
1420
+ if (query.isInUse &&
1421
+ (query.isExpired || ignoreCacheValidity) &&
1422
+ query.autoRefreshOnConfig.queryClientDefaultHeadersChange) {
1423
+ query.execute({ skipCache: true, _triggeredVia: 'auto' });
1424
+ }
1425
+ else if (purgeUnused && !query.isInUse) {
1426
+ this.remove(key);
1427
+ }
1428
+ }
1429
+ }
1430
+ _initSmartQueryHandling() {
1431
+ const windowBlur$ = fromEvent(window, 'blur');
1432
+ const windowFocus$ = fromEvent(window, 'focus');
1433
+ windowBlur$.subscribe(() => {
1434
+ this._stopGarbageCollector();
1435
+ if (this._config?.enableSmartPolling) {
1436
+ this.forEach((query) => {
1437
+ if (!query.isPolling || !query._enableSmartPolling) {
1438
+ return;
1439
+ }
1440
+ query.pausePolling();
1441
+ });
1442
+ }
1443
+ });
1444
+ windowFocus$.subscribe(() => {
1445
+ if (this._config?.enableSmartPolling || this._config?.autoRefreshQueriesOnWindowFocus) {
1446
+ this.forEach((query) => {
1447
+ if (this._config?.enableSmartPolling && query._isPollingPaused) {
1448
+ query.resumePolling();
1449
+ }
1450
+ if (this._config?.autoRefreshQueriesOnWindowFocus &&
1451
+ query.isExpired &&
1452
+ query.isInUse &&
1453
+ query.autoRefreshOnConfig.windowFocus) {
1454
+ query.execute({ skipCache: true, _triggeredVia: 'auto' });
1455
+ }
1456
+ });
1457
+ }
1458
+ this._initGarbageCollector();
1459
+ });
1460
+ }
1358
1461
  _logState(key, item, operation) {
1359
1462
  if (!this._config?.enableChangeLogging)
1360
1463
  return;
@@ -1384,7 +1487,7 @@ class QueryStore {
1384
1487
  _runGarbageCollector() {
1385
1488
  this._logGarbageCollector('Collecting...');
1386
1489
  this._store.forEach((item, key) => {
1387
- if (item.isExpired) {
1490
+ if (item.isExpired && !item.isInUse) {
1388
1491
  this.remove(key);
1389
1492
  }
1390
1493
  });
@@ -1470,6 +1573,9 @@ class QueryClient {
1470
1573
  if (shouldCacheQuery(queryConfig.method)) {
1471
1574
  const existingQuery = this._store.get(cacheKey);
1472
1575
  if (existingQuery) {
1576
+ if (existingQuery.isExpired) {
1577
+ existingQuery.execute();
1578
+ }
1473
1579
  return existingQuery;
1474
1580
  }
1475
1581
  }
@@ -1491,14 +1597,17 @@ class QueryClient {
1491
1597
  }
1492
1598
  this._authProvider$.next(authProvider);
1493
1599
  };
1494
- this.setDefaultHeaders = (headers) => {
1600
+ this.setDefaultHeaders = (config) => {
1495
1601
  if (!this._clientConfig.request) {
1496
1602
  this._clientConfig.request = {
1497
- headers: headers ?? undefined,
1603
+ headers: config.headers ?? undefined,
1498
1604
  };
1499
1605
  }
1500
1606
  else {
1501
- this._clientConfig.request.headers = headers ?? undefined;
1607
+ this._clientConfig.request.headers = config.headers ?? undefined;
1608
+ }
1609
+ if (config.refreshQueriesInUse) {
1610
+ this._store.refreshQueriesInUse({ purgeUnused: true, ignoreCacheValidity: true });
1502
1611
  }
1503
1612
  };
1504
1613
  this.clearAuthProvider = () => {
@@ -1508,6 +1617,8 @@ class QueryClient {
1508
1617
  this._store = new QueryStore({
1509
1618
  enableChangeLogging: _clientConfig.logging?.queryStateChanges,
1510
1619
  enableGarbageCollectorLogging: _clientConfig.logging?.queryStateGarbageCollector,
1620
+ autoRefreshQueriesOnWindowFocus: _clientConfig.request?.autoRefreshQueriesOnWindowFocus ?? true,
1621
+ enableSmartPolling: _clientConfig.request?.enableSmartPolling ?? true,
1511
1622
  });
1512
1623
  }
1513
1624
  }