@ethlete/query 2.1.1 → 2.3.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
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';
2
+ import { Observable, filter, takeWhile, switchMap, of, BehaviorSubject, Subject, takeUntil, interval, startWith, tap, timer, fromEvent, take, concat, 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';
@@ -71,7 +71,7 @@ const queryStateDoesNotContainKeyError = (data) => new QueryError('005', 'The qu
71
71
  const queryStateCannotTransformError = (data) => new QueryError('006', 'The query state cannot be transformed because it is not loading', data);
72
72
  const invalidBodyError = (data) => new QueryError('007', 'The body is not valid or upsupported', data);
73
73
 
74
- const isRequestError = (error) => error instanceof Object && 'code' in error && 'message' in error;
74
+ const isRequestError = (error) => error instanceof Object && 'status' in error && 'statusText' in error && 'url' in error;
75
75
  const buildRoute = (options) => {
76
76
  var _a;
77
77
  if (options.base.endsWith('/')) {
@@ -272,6 +272,37 @@ const getResponseUrl = (xhr) => {
272
272
  }
273
273
  return null;
274
274
  };
275
+ const shouldRetryRequest = (config) => {
276
+ const defaultRetryDelay = 1000 + 1000 * config.currentRetryCount;
277
+ if (config.currentRetryCount > 3) {
278
+ return { retry: false };
279
+ }
280
+ if (!isRequestError(config.error)) {
281
+ return { retry: false };
282
+ }
283
+ const { status } = config.error;
284
+ // Retry on 5xx errors
285
+ if (status >= 500) {
286
+ return { retry: true, delay: defaultRetryDelay };
287
+ }
288
+ // Retry on 408 or 425 errors
289
+ if (status === 408 /* HttpStatusCode.RequestTimeout */ || status === 425 /* HttpStatusCode.TooEarly */) {
290
+ return { retry: true, delay: defaultRetryDelay };
291
+ }
292
+ // Retry on 429 errors
293
+ if (status === 429 /* HttpStatusCode.TooManyRequests */) {
294
+ const retryAfter = config.headers['retry-after'] ||
295
+ config.headers['Retry-After'] ||
296
+ config.headers['x-retry-after'] ||
297
+ config.headers['X-Retry-After'];
298
+ if (retryAfter) {
299
+ const delay = parseInt(retryAfter) * 1000;
300
+ return { retry: true, delay: Number.isNaN(delay) ? defaultRetryDelay : delay };
301
+ }
302
+ return { retry: true, delay: defaultRetryDelay };
303
+ }
304
+ return { retry: false };
305
+ };
275
306
 
276
307
  const XSSI_PREFIX = /^\)\]\}',?\n/;
277
308
  const request = (config) => {
@@ -279,25 +310,32 @@ const request = (config) => {
279
310
  const responseType = config.responseType || 'json';
280
311
  const body = config.body || null;
281
312
  const url = config.urlWithParams.split('?')[0];
313
+ const retryFn = config.retryFn || shouldRetryRequest;
314
+ let currentRetryCount = 0;
315
+ let retryTimeout = null;
282
316
  return new Observable((observer) => {
283
317
  const xhr = new XMLHttpRequest();
284
- xhr.open(config.method, config.urlWithParams);
285
- if (config.withCredentials) {
286
- xhr.withCredentials = true;
287
- }
288
- forEachHeader(headers, (name, value) => xhr.setRequestHeader(name, value));
289
- if (!hasHeader(headers, 'Accept')) {
290
- xhr.setRequestHeader('Accept', 'application/json, text/plain, */*');
291
- }
292
- if (!hasHeader(headers, 'Content-Type')) {
293
- const detectedType = detectContentTypeHeader(body);
294
- if (detectedType) {
295
- xhr.setRequestHeader('Content-Type', detectedType);
296
- }
297
- }
298
- xhr.responseType = responseType !== 'json' ? responseType : 'text';
299
318
  const reqBody = serializeBody(body);
300
319
  let headerResponse = null;
320
+ const setupXhr = () => {
321
+ headerResponse = null;
322
+ xhr.open(config.method, config.urlWithParams);
323
+ if (config.withCredentials) {
324
+ xhr.withCredentials = true;
325
+ }
326
+ forEachHeader(headers, (name, value) => xhr.setRequestHeader(name, value));
327
+ if (!hasHeader(headers, 'Accept')) {
328
+ xhr.setRequestHeader('Accept', 'application/json, text/plain, */*');
329
+ }
330
+ if (!hasHeader(headers, 'Content-Type')) {
331
+ const detectedType = detectContentTypeHeader(body);
332
+ if (detectedType) {
333
+ xhr.setRequestHeader('Content-Type', detectedType);
334
+ }
335
+ }
336
+ xhr.responseType = responseType !== 'json' ? responseType : 'text';
337
+ };
338
+ setupXhr();
301
339
  const partialFromXhr = () => {
302
340
  if (headerResponse !== null) {
303
341
  return headerResponse;
@@ -308,6 +346,24 @@ const request = (config) => {
308
346
  headerResponse = { headers, status: xhr.status, statusText, url: _url };
309
347
  return headerResponse;
310
348
  };
349
+ const handleRetry = (error) => {
350
+ const newRetryCount = currentRetryCount + 1;
351
+ const { headers } = partialFromXhr();
352
+ const { retry, delay } = retryFn({ currentRetryCount: newRetryCount, headers, error });
353
+ if (!retry) {
354
+ observer.next({ type: 'failure', headers, error });
355
+ observer.complete();
356
+ return;
357
+ }
358
+ const _delay = delay !== null && delay !== void 0 ? delay : 1000;
359
+ observer.next({ type: 'delay-retry', headers, retryNumber: newRetryCount, retryDelay: _delay });
360
+ retryTimeout = window.setTimeout(() => {
361
+ setupXhr();
362
+ xhr.send(reqBody);
363
+ currentRetryCount = newRetryCount;
364
+ observer.next({ type: 'start', headers, isRetry: true, retryNumber: currentRetryCount, retryDelay: _delay });
365
+ }, _delay);
366
+ };
311
367
  const onLoad = () => {
312
368
  var _a, _b;
313
369
  const { headers, statusText, url } = partialFromXhr();
@@ -349,32 +405,22 @@ const request = (config) => {
349
405
  observer.complete();
350
406
  }
351
407
  else {
352
- observer.next({
353
- type: 'failure',
354
- headers,
355
- error: {
356
- url,
357
- detail: body,
358
- status,
359
- statusText,
360
- },
408
+ handleRetry({
409
+ url,
410
+ detail: body,
411
+ status,
412
+ statusText,
361
413
  });
362
- observer.complete();
363
414
  }
364
415
  };
365
416
  const onError = (error) => {
366
- const { url, headers } = partialFromXhr();
367
- observer.next({
368
- type: 'failure',
369
- headers,
370
- error: {
371
- url,
372
- detail: error,
373
- status: xhr.status || 0,
374
- statusText: xhr.statusText || 'Unknown Error',
375
- },
417
+ const { url } = partialFromXhr();
418
+ handleRetry({
419
+ url,
420
+ detail: error,
421
+ status: xhr.status || 0,
422
+ statusText: xhr.statusText || 'Unknown Error',
376
423
  });
377
- observer.complete();
378
424
  };
379
425
  const onDownProgress = (event) => {
380
426
  const { headers } = partialFromXhr();
@@ -428,6 +474,9 @@ const request = (config) => {
428
474
  xhr.removeEventListener('abort', onError);
429
475
  xhr.removeEventListener('load', onLoad);
430
476
  xhr.removeEventListener('timeout', onError);
477
+ if (retryTimeout) {
478
+ window.clearTimeout(retryTimeout);
479
+ }
431
480
  if (config.reportProgress) {
432
481
  xhr.removeEventListener('progress', onDownProgress);
433
482
  if (reqBody !== null && xhr.upload) {
@@ -447,6 +496,11 @@ function filterSuccess() {
447
496
  return source.pipe(filter((value) => isQueryStateSuccess(value)));
448
497
  };
449
498
  }
499
+ function ignoreAutoRefresh() {
500
+ return function (source) {
501
+ return source.pipe(filter((value) => !isAutoRefresh(value)));
502
+ };
503
+ }
450
504
  function filterFailure() {
451
505
  return function (source) {
452
506
  return source.pipe(filter((value) => isQueryStateFailure(value)));
@@ -487,6 +541,7 @@ const isQueryStateSuccess = (state) => (state === null || state === void 0 ? voi
487
541
  const isQueryStateFailure = (state) => (state === null || state === void 0 ? void 0 : state.type) === "FAILURE" /* QueryStateType.Failure */;
488
542
  const isQueryStateCancelled = (state) => (state === null || state === void 0 ? void 0 : state.type) === "CANCELLED" /* QueryStateType.Cancelled */;
489
543
  const isQueryStatePrepared = (state) => (state === null || state === void 0 ? void 0 : state.type) === "PREPARED" /* QueryStateType.Prepared */;
544
+ const isAutoRefresh = (state) => (state === null || state === void 0 ? void 0 : state.meta.triggeredVia) === 'auto';
490
545
  const mergeHeaders = (...headers) => {
491
546
  return headers.reduce((acc, headers) => {
492
547
  if (!headers) {
@@ -527,6 +582,14 @@ const getDefaultHeaders = (headers, method) => {
527
582
  }
528
583
  return headers;
529
584
  };
585
+ const castQueryCreatorTypes = (config) => {
586
+ var _a;
587
+ if ((_a = config.args) === null || _a === void 0 ? void 0 : _a.pathParams) {
588
+ console.error(config);
589
+ throw new Error('Path params cannot be overridden in castQueryCreatorTypes. Create a new query creator instead.');
590
+ }
591
+ return config.creator;
592
+ };
530
593
 
531
594
  class Query {
532
595
  get _nextId() {
@@ -592,7 +655,7 @@ class Query {
592
655
  return this._client.fetch(this._queryConfig);
593
656
  }
594
657
  execute(options) {
595
- var _a, _b, _c, _d, _e, _f, _g, _h;
658
+ var _a, _b, _c, _d, _e, _f, _g, _h, _j;
596
659
  const triggeredVia = (_a = options === null || options === void 0 ? void 0 : options._triggeredVia) !== null && _a !== void 0 ? _a : 'program';
597
660
  if (!this.isExpired && !(options === null || options === void 0 ? void 0 : options.skipCache) && isQueryStateSuccess(this.state)) {
598
661
  return this;
@@ -631,23 +694,26 @@ class Query {
631
694
  authHeader = this._client.authProvider.header;
632
695
  }
633
696
  }
697
+ const mergedHeaders = mergeHeaders(getDefaultHeaders((_f = this._client.config.request) === null || _f === void 0 ? void 0 : _f.headers, this._queryConfig.method), authHeader, (_g = this._args) === null || _g === void 0 ? void 0 : _g.headers) || undefined;
634
698
  request({
635
699
  urlWithParams: this._routeWithParams,
636
700
  method: transformMethod(this._queryConfig.method),
637
701
  body,
638
- headers: mergeHeaders(getDefaultHeaders((_f = this._client.config.request) === null || _f === void 0 ? void 0 : _f.headers, this._queryConfig.method), authHeader, (_g = this._args) === null || _g === void 0 ? void 0 : _g.headers) || undefined,
639
- cacheAdapter: (_h = this._client.config.request) === null || _h === void 0 ? void 0 : _h.cacheAdapter,
702
+ headers: mergedHeaders,
640
703
  reportProgress: this._queryConfig.reportProgress,
641
704
  responseType: this._queryConfig.responseType,
642
705
  withCredentials: this._queryConfig.withCredentials,
706
+ cacheAdapter: (_h = this._client.config.request) === null || _h === void 0 ? void 0 : _h.cacheAdapter,
707
+ retryFn: (_j = this._client.config.request) === null || _j === void 0 ? void 0 : _j.retryFn,
643
708
  })
644
709
  .pipe(takeUntil(this._onAbort$))
645
710
  .subscribe({
646
711
  next: (state) => {
647
- if (state.type === 'start') {
712
+ if (state.type === 'start' || state.type === 'delay-retry') {
713
+ const newMeta = Object.assign(Object.assign({}, meta), { isWaitingForRetry: state.type === 'delay-retry', retryDelay: state.retryDelay, retryNumber: state.retryNumber });
648
714
  this._state$.next({
649
715
  type: "LOADING" /* QueryStateType.Loading */,
650
- meta,
716
+ meta: newMeta,
651
717
  });
652
718
  }
653
719
  else if (state.type === 'download-progress') {
@@ -1118,6 +1184,9 @@ class InfinityQuery {
1118
1184
  this._data$.next([]);
1119
1185
  this.nextPage();
1120
1186
  }
1187
+ /**
1188
+ * @internal
1189
+ */
1121
1190
  _clearSubscriptions() {
1122
1191
  this._subscriptions.forEach((sub) => sub.unsubscribe());
1123
1192
  this._subscriptions = [];
@@ -1230,7 +1299,7 @@ class InfinityQueryDirective {
1230
1299
  this._cleanup();
1231
1300
  this._infinityQuery = v;
1232
1301
  this._infinityQueryInstance = this._setupInfinityQuery(v);
1233
- this._loadNextPage();
1302
+ this.loadNextPage();
1234
1303
  }
1235
1304
  get context() {
1236
1305
  return this._viewContext;
@@ -1304,12 +1373,25 @@ class InfinityQueryDirective {
1304
1373
  .subscribe();
1305
1374
  return instance;
1306
1375
  }
1376
+ /**
1377
+ * @deprecated Use `loadNextPage` instead
1378
+ * @breaking 3.0.0
1379
+ */
1307
1380
  _loadNextPage() {
1381
+ this.loadNextPage();
1382
+ }
1383
+ loadNextPage() {
1308
1384
  if (!this._infinityQueryInstance) {
1309
1385
  return;
1310
1386
  }
1311
1387
  this._infinityQueryInstance.nextPage();
1312
1388
  }
1389
+ reset(newConfig) {
1390
+ if (!this._infinityQueryInstance) {
1391
+ return;
1392
+ }
1393
+ this._infinityQueryInstance.reset(newConfig);
1394
+ }
1313
1395
  _renderMainView() {
1314
1396
  if (!this._isMainViewCreated) {
1315
1397
  this._isMainViewCreated = true;
@@ -1359,7 +1441,7 @@ class InfinityQueryTriggerDirective {
1359
1441
  ngOnInit() {
1360
1442
  const isInteractive = this._elementRef.nativeElement.hasAttribute('infinityQueryTrigger');
1361
1443
  if (isInteractive) {
1362
- this.click$.pipe(takeUntil(this._destroy)).subscribe(() => this._infinityQuery._loadNextPage());
1444
+ this.click$.pipe(takeUntil(this._destroy)).subscribe(() => this._infinityQuery.loadNextPage());
1363
1445
  }
1364
1446
  else {
1365
1447
  this._setupIntersectionObserver();
@@ -1375,7 +1457,7 @@ class InfinityQueryTriggerDirective {
1375
1457
  this._observer = new IntersectionObserver((entries) => {
1376
1458
  const entry = entries[0];
1377
1459
  if (entry.isIntersecting && !this._infinityQuery.context.loading) {
1378
- this._infinityQuery._loadNextPage();
1460
+ this._infinityQuery.loadNextPage();
1379
1461
  }
1380
1462
  }, {
1381
1463
  root: this.scrollContainerSelector ? document.querySelector(this.scrollContainerSelector) : null,
@@ -1403,6 +1485,8 @@ class QueryStore {
1403
1485
  this._config = _config;
1404
1486
  this._store = new Map();
1405
1487
  this._garbageCollector = null;
1488
+ this._isInLowResourceMode = false;
1489
+ this._lastBlurTimestamp = Date.now();
1406
1490
  this._initSmartQueryHandling();
1407
1491
  }
1408
1492
  add(id, query) {
@@ -1440,30 +1524,42 @@ class QueryStore {
1440
1524
  const windowBlur$ = fromEvent(window, 'blur');
1441
1525
  const windowFocus$ = fromEvent(window, 'focus');
1442
1526
  windowBlur$.subscribe(() => {
1443
- var _a;
1444
- this._stopGarbageCollector();
1445
- if ((_a = this._config) === null || _a === void 0 ? void 0 : _a.enableSmartPolling) {
1446
- this.forEach((query) => {
1447
- if (!query.isPolling || !query._enableSmartPolling) {
1448
- return;
1449
- }
1450
- query.pausePolling();
1451
- });
1452
- }
1527
+ timer(5000)
1528
+ .pipe(takeUntil(windowFocus$), take(1))
1529
+ .subscribe(() => {
1530
+ var _a;
1531
+ this._lastBlurTimestamp = Date.now();
1532
+ this._isInLowResourceMode = true;
1533
+ this._stopGarbageCollector();
1534
+ if ((_a = this._config) === null || _a === void 0 ? void 0 : _a.enableSmartPolling) {
1535
+ this.forEach((query) => {
1536
+ if (!query.isPolling || !query._enableSmartPolling) {
1537
+ return;
1538
+ }
1539
+ query.pausePolling();
1540
+ });
1541
+ }
1542
+ });
1453
1543
  });
1454
1544
  windowFocus$.subscribe(() => {
1455
1545
  var _a, _b;
1546
+ if (!this._isInLowResourceMode) {
1547
+ return;
1548
+ }
1549
+ this._isInLowResourceMode = false;
1456
1550
  if (((_a = this._config) === null || _a === void 0 ? void 0 : _a.enableSmartPolling) || ((_b = this._config) === null || _b === void 0 ? void 0 : _b.autoRefreshQueriesOnWindowFocus)) {
1457
1551
  this.forEach((query) => {
1458
1552
  var _a, _b;
1459
1553
  if (((_a = this._config) === null || _a === void 0 ? void 0 : _a.enableSmartPolling) && query._isPollingPaused) {
1460
1554
  query.resumePolling();
1461
1555
  }
1462
- if (((_b = this._config) === null || _b === void 0 ? void 0 : _b.autoRefreshQueriesOnWindowFocus) &&
1463
- query.isExpired &&
1464
- query.isInUse &&
1465
- query.autoRefreshOnConfig.windowFocus) {
1466
- query.execute({ skipCache: true, _triggeredVia: 'auto' });
1556
+ if (Date.now() - this._lastBlurTimestamp > 15000) {
1557
+ if (((_b = this._config) === null || _b === void 0 ? void 0 : _b.autoRefreshQueriesOnWindowFocus) &&
1558
+ query.isExpired &&
1559
+ query.isInUse &&
1560
+ query.autoRefreshOnConfig.windowFocus) {
1561
+ query.execute({ skipCache: true, _triggeredVia: 'auto' });
1562
+ }
1467
1563
  }
1468
1564
  });
1469
1565
  }
@@ -1495,6 +1591,7 @@ class QueryStore {
1495
1591
  if (this._garbageCollector !== null) {
1496
1592
  window.clearInterval(this._garbageCollector);
1497
1593
  this._garbageCollector = null;
1594
+ this._logGarbageCollector('Stop');
1498
1595
  }
1499
1596
  }
1500
1597
  _runGarbageCollector() {
@@ -1507,7 +1604,6 @@ class QueryStore {
1507
1604
  this._logGarbageCollector('Collection done');
1508
1605
  if (!this._store.size) {
1509
1606
  this._stopGarbageCollector();
1510
- this._logGarbageCollector('Stop');
1511
1607
  }
1512
1608
  }
1513
1609
  _logGarbageCollector(action) {
@@ -1867,5 +1963,5 @@ const def = () => null;
1867
1963
  * Generated bundle index. Do not edit.
1868
1964
  */
1869
1965
 
1870
- export { BasicAuthProvider, BearerAuthProvider, CustomHeaderAuthProvider, INFINITY_QUERY_TOKEN, InfinityQuery, InfinityQueryDirective, InfinityQueryTriggerDirective, Query, QueryClient, QueryDirective, QueryError, QueryField, QueryForm, QueryStore, buildErrorMessage, buildGqlCacheKey, buildQueryArrayString, buildQueryString, buildRoute, buildTimestampFromSeconds, createInfinityQueryConfig, createQueryCollection, decryptBearer, deepFreeze, def, detectContentTypeHeader, extractExpiresInSeconds, extractQuery, filterFailure, filterInvalidParams, filterNull, filterQueryStates, filterSuccess, forEachHeader, getDefaultHeaders, getResponseUrl, gql, hasHeader, invalidBaseRouteError, invalidBodyError, invalidRouteError, isBasicAuthProvider, isBearerAuthProvider, isCustomHeaderAuthProvider, isFetchResponse, isGqlQueryConfig, isParamValid, isQuery, isQueryStateCancelled, isQueryStateFailure, isQueryStateLoading, isQueryStatePrepared, isQueryStateSuccess, isRequestError, mergeHeaders, parseAllXhrResponseHeaders, pathParamsMissingInRouteFunctionError, queryStateAlreadyHasKeyError, queryStateCannotTransformError, queryStateDoesNotContainKeyError, request, serializeBody, shouldCacheQuery, skipPaginationPageParamCalculator, switchQueryCollectionState, switchQueryState, takeUntilResponse, transformGql, transformMethod, transformToBoolean, transformToBooleanArray, transformToDate, transformToDateArray, transformToNumber, transformToNumberArray, transformToSort, transformToSortQueryParam, transformToString, transformToStringArray };
1966
+ export { BasicAuthProvider, BearerAuthProvider, CustomHeaderAuthProvider, INFINITY_QUERY_TOKEN, InfinityQuery, InfinityQueryDirective, InfinityQueryTriggerDirective, Query, QueryClient, QueryDirective, QueryError, QueryField, QueryForm, QueryStore, buildErrorMessage, buildGqlCacheKey, buildQueryArrayString, buildQueryString, buildRoute, buildTimestampFromSeconds, castQueryCreatorTypes, createInfinityQueryConfig, createQueryCollection, decryptBearer, deepFreeze, def, detectContentTypeHeader, extractExpiresInSeconds, extractQuery, filterFailure, filterInvalidParams, filterNull, filterQueryStates, filterSuccess, forEachHeader, getDefaultHeaders, getResponseUrl, gql, hasHeader, ignoreAutoRefresh, invalidBaseRouteError, invalidBodyError, invalidRouteError, isAutoRefresh, isBasicAuthProvider, isBearerAuthProvider, isCustomHeaderAuthProvider, isFetchResponse, isGqlQueryConfig, isParamValid, isQuery, isQueryStateCancelled, isQueryStateFailure, isQueryStateLoading, isQueryStatePrepared, isQueryStateSuccess, isRequestError, mergeHeaders, parseAllXhrResponseHeaders, pathParamsMissingInRouteFunctionError, queryStateAlreadyHasKeyError, queryStateCannotTransformError, queryStateDoesNotContainKeyError, request, serializeBody, shouldCacheQuery, shouldRetryRequest, skipPaginationPageParamCalculator, switchQueryCollectionState, switchQueryState, takeUntilResponse, transformGql, transformMethod, transformToBoolean, transformToBooleanArray, transformToDate, transformToDateArray, transformToNumber, transformToNumberArray, transformToSort, transformToSortQueryParam, transformToString, transformToStringArray };
1871
1967
  //# sourceMappingURL=ethlete-query.mjs.map