@ethlete/query 0.1.0-next.15 → 0.1.0-next.17

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,7 +1,7 @@
1
- import { Subject, timer, filter, takeWhile, switchMap, of, BehaviorSubject, interval, takeUntil, tap, fromEvent, startWith, concat, take, debounceTime, combineLatest, map, distinctUntilChanged } from 'rxjs';
1
+ import { getCookie, deleteCookie, setCookie, DestroyService } from '@ethlete/core';
2
+ import { filter, takeWhile, switchMap, of, BehaviorSubject, Subject, interval, takeUntil, tap, timer, fromEvent, startWith, concat, take, debounceTime, combineLatest, map, distinctUntilChanged } from 'rxjs';
2
3
  import * as i0 from '@angular/core';
3
4
  import { Directive, Input, InjectionToken, inject, ChangeDetectorRef, ViewContainerRef, TemplateRef, ErrorHandler, ElementRef } from '@angular/core';
4
- import { DestroyService } from '@ethlete/core';
5
5
  import { FormGroup } from '@angular/forms';
6
6
  import { Router, ActivatedRoute } from '@angular/router';
7
7
 
@@ -11,7 +11,6 @@ class CustomHeaderAuthProvider {
11
11
  }
12
12
  constructor(_config) {
13
13
  this._config = _config;
14
- this.queryClient = null;
15
14
  }
16
15
  cleanUp() {
17
16
  // noop
@@ -27,13 +26,35 @@ class BasicAuthProvider {
27
26
  }
28
27
  constructor(_config) {
29
28
  this._config = _config;
30
- this.queryClient = null;
31
29
  }
32
30
  cleanUp() {
33
31
  // noop
34
32
  }
35
33
  }
36
34
 
35
+ const getOpName = /(query|mutation) ?([\w\d-_]+)? ?\(.*?\)? \{/;
36
+ const transformGql = (str) => {
37
+ str = Array.isArray(str) ? str.join('') : str;
38
+ const name = getOpName.exec(str);
39
+ return function (variables) {
40
+ const data = { query: str };
41
+ if (variables)
42
+ data['variables'] = JSON.stringify(variables);
43
+ if (name && name.length) {
44
+ const operationName = name[2];
45
+ if (operationName)
46
+ data['operationName'] = name[2];
47
+ }
48
+ return JSON.stringify(data);
49
+ };
50
+ };
51
+ const gql = (strings, ...values) => {
52
+ const str = strings.reduce((acc, cur, i) => {
53
+ return acc + cur + (values[i] || '');
54
+ }, '');
55
+ return str;
56
+ };
57
+
37
58
  const buildErrorMessage = (code, message) => `[@ethlete\\query:${code}] ${message}`;
38
59
  class QueryError extends Error {
39
60
  constructor(code, message, data) {
@@ -293,141 +314,6 @@ const request = async (options) => {
293
314
  }
294
315
  };
295
316
 
296
- const isBasicAuthProvider = (authProvider) => authProvider instanceof BasicAuthProvider;
297
- const isBearerAuthProvider = (authProvider) => authProvider instanceof BearerAuthProvider;
298
- const isCustomHeaderAuthProvider = (authProvider) => authProvider instanceof CustomHeaderAuthProvider;
299
- const decryptBearer = (token) => {
300
- try {
301
- const base64Url = token.split('.')[1];
302
- const base64 = base64Url.replace(/-/g, '+').replace(/_/g, '/');
303
- const jsonPayload = decodeURIComponent(atob(base64)
304
- .split('')
305
- .map((c) => '%' + ('00' + c.charCodeAt(0).toString(16)).slice(-2))
306
- .join(''));
307
- return JSON.parse(jsonPayload);
308
- }
309
- catch (error) {
310
- console.error(`Invalid bearer token: ${token}`, error);
311
- return null;
312
- }
313
- };
314
-
315
- class BearerAuthProvider {
316
- get header() {
317
- return { Authorization: `Bearer ${this._token}` };
318
- }
319
- constructor(_config) {
320
- this._config = _config;
321
- this._refreshToken = null;
322
- this._refreshTimerSubscription = null;
323
- this._failureCount = 0;
324
- this.queryClient = null;
325
- this.onRefreshInitiation$ = new Subject();
326
- this.onRefreshSuccess$ = new Subject();
327
- this.onRefreshFailure$ = new Subject();
328
- this._token = _config.token;
329
- this._refreshToken = _config.refreshConfig?.token ?? null;
330
- if (this._config.refreshConfig) {
331
- this._setupRefresh(this._config.refreshConfig);
332
- }
333
- }
334
- cleanUp() {
335
- this._refreshTimerSubscription?.unsubscribe();
336
- this._refreshTimerSubscription = null;
337
- }
338
- _setupRefresh(config) {
339
- this.cleanUp();
340
- const bearer = decryptBearer(this._token);
341
- if (!bearer) {
342
- return;
343
- }
344
- const expiresInPropertyName = config.expiresInPropertyName || 'exp';
345
- const expiresIn = bearer[expiresInPropertyName];
346
- const fiveMinutes = 5 * 60 * 1000;
347
- const refreshBuffer = config.refreshBuffer ?? fiveMinutes;
348
- if (expiresIn === undefined) {
349
- throw new Error(`Bearer token does not contain an '${expiresInPropertyName}' property`);
350
- }
351
- const remainingTime = new Date(expiresIn * 1000).getTime() - refreshBuffer - new Date().getTime();
352
- const strategy = config.strategy ?? "beforeExpiration" /* AuthBearerRefreshStrategy.BeforeExpiration */;
353
- switch (strategy) {
354
- case "beforeExpiration" /* AuthBearerRefreshStrategy.BeforeExpiration */:
355
- this._refreshTimerSubscription = timer(remainingTime).subscribe(() => this._refresh(config));
356
- break;
357
- }
358
- }
359
- async _refresh(config) {
360
- if (!this._refreshToken) {
361
- throw new Error('No refresh token found');
362
- }
363
- if (!this.queryClient) {
364
- throw new Error('Query client instance is null');
365
- }
366
- const { method, route, requestAdapter, responseAdapter, paramLocation } = config;
367
- const data = requestAdapter?.(this._refreshToken) ?? { refreshToken: this._refreshToken };
368
- const fullRoute = buildRoute({
369
- base: this.queryClient.config.baseRoute,
370
- route,
371
- queryParams: paramLocation === 'query' ? data : undefined,
372
- });
373
- const requestInit = { body: paramLocation === 'body' ? buildBody(data) : undefined, method };
374
- this.onRefreshInitiation$.next();
375
- try {
376
- const result = await request({
377
- route: fullRoute,
378
- init: requestInit,
379
- });
380
- const tokens = responseAdapter ? responseAdapter(result.data) : result.data;
381
- if (!tokens.token) {
382
- throw new Error('Token not found in response');
383
- }
384
- if (!tokens.refreshToken) {
385
- throw new Error('Refresh token not found in response');
386
- }
387
- this._token = tokens.token;
388
- this._refreshToken = tokens.refreshToken;
389
- this.onRefreshSuccess$.next(tokens);
390
- this._setupRefresh(config);
391
- this._failureCount = 0;
392
- }
393
- catch (error) {
394
- if (error instanceof Error) {
395
- this.onRefreshFailure$.next(await buildRequestError(error, fullRoute, requestInit));
396
- }
397
- else {
398
- this.onRefreshFailure$.next(error);
399
- }
400
- this._failureCount++;
401
- if (this._failureCount <= (config.maxRefreshAttempts ?? 3)) {
402
- this._setupRefresh(config);
403
- }
404
- }
405
- }
406
- }
407
-
408
- const getOpName = /(query|mutation) ?([\w\d-_]+)? ?\(.*?\)? \{/;
409
- const transformGql = (str) => {
410
- str = Array.isArray(str) ? str.join('') : str;
411
- const name = getOpName.exec(str);
412
- return function (variables) {
413
- const data = { query: str };
414
- if (variables)
415
- data['variables'] = JSON.stringify(variables);
416
- if (name && name.length) {
417
- const operationName = name[2];
418
- if (operationName)
419
- data['operationName'] = name[2];
420
- }
421
- return JSON.stringify(data);
422
- };
423
- };
424
- const gql = (strings, ...values) => {
425
- const str = strings.reduce((acc, cur, i) => {
426
- return acc + cur + (values[i] || '');
427
- }, '');
428
- return str;
429
- };
430
-
431
317
  function filterSuccess() {
432
318
  return function (source) {
433
319
  return source.pipe(filter((value) => isQueryStateSuccess(value)));
@@ -443,9 +329,14 @@ function filterQueryStates(allowedStates) {
443
329
  return source.pipe(filter((value) => value && allowedStates.includes(value.type)));
444
330
  };
445
331
  }
446
- function takeUntilResponse() {
332
+ function takeUntilResponse(config) {
447
333
  return function (source) {
448
- return source.pipe(takeWhile((value) => isQueryStateLoading(value), true));
334
+ return source.pipe(takeWhile((value) => {
335
+ if (config?.excludeNull && value === null) {
336
+ return false;
337
+ }
338
+ return isQueryStateLoading(value) || value === null;
339
+ }, true));
449
340
  };
450
341
  }
451
342
  function filterNull() {
@@ -687,6 +578,123 @@ class Query {
687
578
  }
688
579
  }
689
580
 
581
+ const isBasicAuthProvider = (authProvider) => authProvider instanceof BasicAuthProvider;
582
+ const isBearerAuthProvider = (authProvider) => authProvider instanceof BearerAuthProvider;
583
+ const isCustomHeaderAuthProvider = (authProvider) => authProvider instanceof CustomHeaderAuthProvider;
584
+ const decryptBearer = (token) => {
585
+ try {
586
+ const base64Url = token.split('.')[1];
587
+ const base64 = base64Url.replace(/-/g, '+').replace(/_/g, '/');
588
+ const jsonPayload = decodeURIComponent(atob(base64)
589
+ .split('')
590
+ .map((c) => '%' + ('00' + c.charCodeAt(0).toString(16)).slice(-2))
591
+ .join(''));
592
+ return JSON.parse(jsonPayload);
593
+ }
594
+ catch (error) {
595
+ console.error(`Invalid bearer token: ${token}`, error);
596
+ return null;
597
+ }
598
+ };
599
+
600
+ class BearerAuthProvider {
601
+ get header() {
602
+ return { Authorization: `Bearer ${this._token}` };
603
+ }
604
+ get currentRefreshQuery$() {
605
+ return this._currentRefreshQuery$.asObservable();
606
+ }
607
+ get currentRefreshQuery() {
608
+ return this._currentRefreshQuery$.getValue();
609
+ }
610
+ constructor(_config) {
611
+ this._config = _config;
612
+ this._destroy$ = new Subject();
613
+ this._currentRefreshQuery$ = new BehaviorSubject(null);
614
+ this._token = null;
615
+ this._refreshToken = null;
616
+ const cookieToken = _config.refreshConfig?.cookieName ? getCookie(_config.refreshConfig.cookieName) ?? null : null;
617
+ this._token = _config.token || null;
618
+ this._refreshToken = _config.refreshConfig?.token || cookieToken || null;
619
+ if (!this._token && !this._refreshToken) {
620
+ if (!_config.refreshConfig?.cookieName) {
621
+ console.error('A BearerAuthProvider was created without token or refresh token. You should provide at least a cookieName where the refresh token might be stored.');
622
+ }
623
+ return;
624
+ }
625
+ this._prepareForRefresh();
626
+ }
627
+ cleanUp() {
628
+ this._destroy$.next(true);
629
+ this._destroy$.unsubscribe();
630
+ this._currentRefreshQuery$.next(null);
631
+ if (this._config.refreshConfig?.cookieName) {
632
+ deleteCookie(this._config.refreshConfig.cookieName, '/');
633
+ }
634
+ }
635
+ _prepareForRefresh() {
636
+ if (!this._token || !this._config.refreshConfig) {
637
+ return;
638
+ }
639
+ const bearer = decryptBearer(this._token);
640
+ if (!bearer) {
641
+ return;
642
+ }
643
+ const expiresInPropertyName = this._config.refreshConfig.expiresInPropertyName || 'exp';
644
+ const expiresIn = bearer[expiresInPropertyName];
645
+ const fiveMinutes = 5 * 60 * 1000;
646
+ const refreshBuffer = this._config.refreshConfig.refreshBuffer ?? fiveMinutes;
647
+ if (expiresIn === undefined) {
648
+ throw new Error(`Bearer token does not contain an '${expiresInPropertyName}' property`);
649
+ }
650
+ const remainingTime = new Date(expiresIn * 1000).getTime() - refreshBuffer - new Date().getTime();
651
+ const strategy = this._config.refreshConfig.strategy ?? "beforeExpiration" /* AuthBearerRefreshStrategy.BeforeExpiration */;
652
+ switch (strategy) {
653
+ case "beforeExpiration" /* AuthBearerRefreshStrategy.BeforeExpiration */:
654
+ timer(remainingTime)
655
+ .pipe(takeUntil(this._destroy$))
656
+ .subscribe(() => this._refreshQuery());
657
+ break;
658
+ }
659
+ }
660
+ _refreshQuery() {
661
+ if (!this._refreshToken || !this._config.refreshConfig?.queryCreator) {
662
+ return;
663
+ }
664
+ const args = this._config.refreshConfig.requestArgsAdapter?.(this._refreshToken, this._token) ?? {
665
+ body: { refreshToken: this._refreshToken },
666
+ };
667
+ const query = this._config.refreshConfig.queryCreator.prepare(args).execute({ skipCache: true });
668
+ this._currentRefreshQuery$.next(query);
669
+ this._currentRefreshQuery$
670
+ .pipe(switchQueryState(), tap((state) => {
671
+ if (isQueryStateSuccess(state)) {
672
+ if (this._config.refreshConfig?.responseAdapter) {
673
+ const tokens = this._config.refreshConfig.responseAdapter(state.response);
674
+ this._token = tokens.token;
675
+ this._refreshToken = tokens.refreshToken || null;
676
+ }
677
+ else {
678
+ this._token = state.response['token'];
679
+ this._refreshToken = state.response['refreshToken'];
680
+ }
681
+ if (this._config.refreshConfig?.cookieName && this._refreshToken) {
682
+ setCookie(this._config.refreshConfig.cookieName, this._refreshToken);
683
+ }
684
+ this._prepareForRefresh();
685
+ }
686
+ else if (isQueryStateFailure(state)) {
687
+ this._token = null;
688
+ this._refreshToken = null;
689
+ if (this._config.refreshConfig?.cookieName) {
690
+ deleteCookie(this._config.refreshConfig.cookieName, '/');
691
+ }
692
+ }
693
+ }), takeUntilResponse(), takeUntil(this._destroy$))
694
+ .subscribe();
695
+ }
696
+ }
697
+
690
698
  class QueryDirective {
691
699
  get query() {
692
700
  return this._query;
@@ -769,9 +777,9 @@ class QueryDirective {
769
777
  }
770
778
  }
771
779
  }
772
- QueryDirective.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "15.1.1", ngImport: i0, type: QueryDirective, deps: [{ token: i0.TemplateRef }, { token: i0.ViewContainerRef }, { token: i0.ErrorHandler }, { token: i0.ChangeDetectorRef }], target: i0.ɵɵFactoryTarget.Directive });
773
- QueryDirective.ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "15.1.1", type: QueryDirective, isStandalone: true, selector: "[query]", inputs: { query: "query", cache: ["queryCache", "cache"] }, ngImport: i0 });
774
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "15.1.1", ngImport: i0, type: QueryDirective, decorators: [{
780
+ QueryDirective.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "15.1.2", ngImport: i0, type: QueryDirective, deps: [{ token: i0.TemplateRef }, { token: i0.ViewContainerRef }, { token: i0.ErrorHandler }, { token: i0.ChangeDetectorRef }], target: i0.ɵɵFactoryTarget.Directive });
781
+ QueryDirective.ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "15.1.2", type: QueryDirective, isStandalone: true, selector: "[query]", inputs: { query: "query", cache: ["queryCache", "cache"] }, ngImport: i0 });
782
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "15.1.2", ngImport: i0, type: QueryDirective, decorators: [{
775
783
  type: Directive,
776
784
  args: [{
777
785
  // eslint-disable-next-line @angular-eslint/directive-selector
@@ -1080,9 +1088,9 @@ class InfinityQueryDirective {
1080
1088
  this._data$.next([]);
1081
1089
  }
1082
1090
  }
1083
- InfinityQueryDirective.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "15.1.1", ngImport: i0, type: InfinityQueryDirective, deps: [], target: i0.ɵɵFactoryTarget.Directive });
1084
- InfinityQueryDirective.ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "15.1.1", type: InfinityQueryDirective, isStandalone: true, selector: "[infinityQuery]", inputs: { infinityQuery: "infinityQuery" }, providers: [{ provide: INFINITY_QUERY_TOKEN, useExisting: InfinityQueryDirective }, DestroyService], exportAs: ["infinityQuery"], ngImport: i0 });
1085
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "15.1.1", ngImport: i0, type: InfinityQueryDirective, decorators: [{
1091
+ InfinityQueryDirective.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "15.1.2", ngImport: i0, type: InfinityQueryDirective, deps: [], target: i0.ɵɵFactoryTarget.Directive });
1092
+ InfinityQueryDirective.ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "15.1.2", type: InfinityQueryDirective, isStandalone: true, selector: "[infinityQuery]", inputs: { infinityQuery: "infinityQuery" }, providers: [{ provide: INFINITY_QUERY_TOKEN, useExisting: InfinityQueryDirective }, DestroyService], exportAs: ["infinityQuery"], ngImport: i0 });
1093
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "15.1.2", ngImport: i0, type: InfinityQueryDirective, decorators: [{
1086
1094
  type: Directive,
1087
1095
  args: [{
1088
1096
  // eslint-disable-next-line @angular-eslint/directive-selector
@@ -1132,9 +1140,9 @@ class InfinityQueryTriggerDirective {
1132
1140
  this._observer.observe(this._elementRef.nativeElement);
1133
1141
  }
1134
1142
  }
1135
- InfinityQueryTriggerDirective.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "15.1.1", ngImport: i0, type: InfinityQueryTriggerDirective, deps: [], target: i0.ɵɵFactoryTarget.Directive });
1136
- InfinityQueryTriggerDirective.ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "15.1.1", type: InfinityQueryTriggerDirective, isStandalone: true, selector: "[infinityQueryTrigger], infinity-query-trigger", inputs: { scrollContainerSelector: "scrollContainerSelector" }, ngImport: i0 });
1137
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "15.1.1", ngImport: i0, type: InfinityQueryTriggerDirective, decorators: [{
1143
+ InfinityQueryTriggerDirective.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "15.1.2", ngImport: i0, type: InfinityQueryTriggerDirective, deps: [], target: i0.ɵɵFactoryTarget.Directive });
1144
+ InfinityQueryTriggerDirective.ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "15.1.2", type: InfinityQueryTriggerDirective, isStandalone: true, selector: "[infinityQueryTrigger], infinity-query-trigger", inputs: { scrollContainerSelector: "scrollContainerSelector" }, ngImport: i0 });
1145
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "15.1.2", ngImport: i0, type: InfinityQueryTriggerDirective, decorators: [{
1138
1146
  type: Directive,
1139
1147
  args: [{
1140
1148
  // eslint-disable-next-line @angular-eslint/directive-selector
@@ -1230,11 +1238,14 @@ class QueryClient {
1230
1238
  return this._clientConfig;
1231
1239
  }
1232
1240
  get authProvider() {
1233
- return this._authProvider;
1241
+ return this._authProvider$.getValue();
1242
+ }
1243
+ get authProvider$() {
1244
+ return this._authProvider$.asObservable();
1234
1245
  }
1235
1246
  constructor(_clientConfig) {
1236
1247
  this._clientConfig = _clientConfig;
1237
- this._authProvider = null;
1248
+ this._authProvider$ = new BehaviorSubject(null);
1238
1249
  this.get = (queryConfig) => this.fetch({
1239
1250
  ...queryConfig,
1240
1251
  method: 'GET',
@@ -1291,15 +1302,14 @@ class QueryClient {
1291
1302
  };
1292
1303
  };
1293
1304
  this.setAuthProvider = (authProvider) => {
1294
- if (this._authProvider) {
1305
+ if (this.authProvider) {
1295
1306
  throw new Error('The auth provider is already set. Please call clearAuthProvider() first.');
1296
1307
  }
1297
- this._authProvider = authProvider;
1298
- authProvider.queryClient = this;
1308
+ this._authProvider$.next(authProvider);
1299
1309
  };
1300
1310
  this.clearAuthProvider = () => {
1301
- this._authProvider?.cleanUp();
1302
- this._authProvider = null;
1311
+ this.authProvider?.cleanUp();
1312
+ this._authProvider$.next(null);
1303
1313
  };
1304
1314
  this._store = new QueryStore({
1305
1315
  enableChangeLogging: _clientConfig.logging?.queryStateChanges,