@ethlete/query 5.16.0 → 5.16.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/CHANGELOG.md +12 -0
- package/esm2022/ethlete-query.mjs +5 -0
- package/esm2022/index.mjs +20 -0
- package/esm2022/lib/auth/auth-provider.types.mjs +2 -0
- package/esm2022/lib/auth/auth-provider.utils.mjs +25 -0
- package/esm2022/lib/auth/basic-auth-provider.mjs +15 -0
- package/esm2022/lib/auth/bearer-auth-provider.mjs +168 -0
- package/esm2022/lib/auth/custom-header-auth-provider.mjs +12 -0
- package/esm2022/lib/auth/index.mjs +6 -0
- package/esm2022/lib/class-validator/class-validator.types.mjs +2 -0
- package/esm2022/lib/class-validator/class-validator.utils.mjs +4 -0
- package/esm2022/lib/class-validator/index.mjs +3 -0
- package/esm2022/lib/devtools/components/query-devtools/index.mjs +2 -0
- package/esm2022/lib/devtools/components/query-devtools/query-devtools.component.mjs +297 -0
- package/esm2022/lib/devtools/pipes/index.mjs +2 -0
- package/esm2022/lib/devtools/pipes/query-short-name.pipe.mjs +24 -0
- package/esm2022/lib/devtools/utils/index.mjs +2 -0
- package/esm2022/lib/devtools/utils/query-client.utils.mjs +13 -0
- package/esm2022/lib/directives/index.mjs +4 -0
- package/esm2022/lib/directives/infinity-query-trigger.directive.mjs +56 -0
- package/esm2022/lib/directives/infinity-query.directive.mjs +147 -0
- package/esm2022/lib/directives/query.directive.mjs +123 -0
- package/esm2022/lib/entity/entity-store.mjs +127 -0
- package/esm2022/lib/entity/entity.types.mjs +2 -0
- package/esm2022/lib/entity/entity.utils.mjs +29 -0
- package/esm2022/lib/entity/index.mjs +4 -0
- package/esm2022/lib/gql/gql.mjs +23 -0
- package/esm2022/lib/gql/index.mjs +2 -0
- package/esm2022/lib/infinite-query/index.mjs +4 -0
- package/esm2022/lib/infinite-query/infinity-query.mjs +171 -0
- package/esm2022/lib/infinite-query/infinity-query.types.mjs +2 -0
- package/esm2022/lib/infinite-query/infinity-query.utils.mjs +3 -0
- package/esm2022/lib/logger/index.mjs +2 -0
- package/esm2022/lib/logger/logger.mjs +16 -0
- package/esm2022/lib/query/index.mjs +4 -0
- package/esm2022/lib/query/query.mjs +430 -0
- package/esm2022/lib/query/query.types.mjs +2 -0
- package/esm2022/lib/query/query.utils.mjs +253 -0
- package/esm2022/lib/query-client/index.mjs +4 -0
- package/esm2022/lib/query-client/query-client.mjs +80 -0
- package/esm2022/lib/query-client/query-client.types.mjs +2 -0
- package/esm2022/lib/query-client/query-client.utils.mjs +20 -0
- package/esm2022/lib/query-creator/index.mjs +3 -0
- package/esm2022/lib/query-creator/query-creator.mjs +55 -0
- package/esm2022/lib/query-creator/query-creator.types.mjs +2 -0
- package/esm2022/lib/query-form/index.mjs +4 -0
- package/esm2022/lib/query-form/query-form.mjs +379 -0
- package/esm2022/lib/query-form/query-form.types.mjs +2 -0
- package/esm2022/lib/query-form/query-form.utils.mjs +97 -0
- package/esm2022/lib/query-store/index.mjs +2 -0
- package/esm2022/lib/query-store/query-store.mjs +144 -0
- package/esm2022/lib/request/index.mjs +4 -0
- package/esm2022/lib/request/request.mjs +196 -0
- package/esm2022/lib/request/request.types.mjs +2 -0
- package/esm2022/lib/request/request.util.mjs +252 -0
- package/esm2022/lib/symfony/index.mjs +3 -0
- package/esm2022/lib/symfony/symfony.types.mjs +2 -0
- package/esm2022/lib/symfony/symfony.utils.mjs +31 -0
- package/esm2022/lib/utils/data.utils.mjs +138 -0
- package/esm2022/lib/utils/deep-freeze.util.mjs +17 -0
- package/esm2022/lib/utils/def.util.mjs +8 -0
- package/esm2022/lib/utils/index.mjs +4 -0
- package/fesm2022/ethlete-query.mjs +3289 -0
- package/fesm2022/ethlete-query.mjs.map +1 -0
- package/{src/index.ts → index.d.ts} +0 -2
- package/lib/auth/auth-provider.types.d.ts +133 -0
- package/lib/auth/auth-provider.utils.d.ts +9 -0
- package/lib/auth/basic-auth-provider.d.ts +10 -0
- package/lib/auth/bearer-auth-provider.d.ts +23 -0
- package/lib/auth/custom-header-auth-provider.d.ts +9 -0
- package/{src/lib/class-validator/class-validator.types.ts → lib/class-validator/class-validator.types.d.ts} +3 -4
- package/lib/class-validator/class-validator.utils.d.ts +2 -0
- package/lib/devtools/components/query-devtools/query-devtools.component.d.ts +151 -0
- package/lib/devtools/pipes/query-short-name.pipe.d.ts +8 -0
- package/lib/devtools/utils/query-client.utils.d.ts +12 -0
- package/lib/directives/infinity-query-trigger.directive.d.ts +15 -0
- package/lib/directives/infinity-query.directive.d.ts +49 -0
- package/lib/directives/query.directive.d.ts +63 -0
- package/lib/entity/entity-store.d.ts +25 -0
- package/lib/entity/entity.types.d.ts +25 -0
- package/lib/entity/entity.utils.d.ts +12 -0
- package/lib/gql/gql.d.ts +6 -0
- package/lib/infinite-query/infinity-query.d.ts +29 -0
- package/lib/infinite-query/infinity-query.types.d.ts +104 -0
- package/lib/infinite-query/infinity-query.utils.d.ts +4 -0
- package/lib/logger/logger.d.ts +12 -0
- package/lib/query/query.d.ts +60 -0
- package/lib/query/query.types.d.ts +388 -0
- package/lib/query/query.utils.d.ts +82 -0
- package/lib/query-client/query-client.d.ts +29 -0
- package/lib/query-client/query-client.types.d.ts +82 -0
- package/lib/query-client/query-client.utils.d.ts +4 -0
- package/lib/query-creator/query-creator.d.ts +20 -0
- package/lib/query-creator/query-creator.types.d.ts +13 -0
- package/lib/query-form/query-form.d.ts +91 -0
- package/lib/query-form/query-form.types.d.ts +145 -0
- package/lib/query-form/query-form.utils.d.ts +13 -0
- package/lib/query-store/query-store.d.ts +31 -0
- package/lib/request/request.d.ts +3 -0
- package/lib/request/request.types.d.ts +126 -0
- package/lib/request/request.util.d.ts +92 -0
- package/lib/symfony/symfony.types.d.ts +19 -0
- package/lib/symfony/symfony.utils.d.ts +6 -0
- package/lib/utils/data.utils.d.ts +59 -0
- package/lib/utils/deep-freeze.util.d.ts +1 -0
- package/{src/lib/utils/def.util.ts → lib/utils/def.util.d.ts} +1 -1
- package/package.json +23 -6
- package/.eslintrc.json +0 -40
- package/jest.config.ts +0 -23
- package/ng-package.json +0 -8
- package/project.json +0 -37
- package/src/lib/auth/auth-provider.types.ts +0 -160
- package/src/lib/auth/auth-provider.utils.ts +0 -39
- package/src/lib/auth/basic-auth-provider.ts +0 -17
- package/src/lib/auth/bearer-auth-provider.ts +0 -243
- package/src/lib/auth/custom-header-auth-provider.ts +0 -13
- package/src/lib/class-validator/class-validator.utils.ts +0 -5
- package/src/lib/devtools/components/query-devtools/query-devtools.component.html +0 -402
- package/src/lib/devtools/components/query-devtools/query-devtools.component.scss +0 -360
- package/src/lib/devtools/components/query-devtools/query-devtools.component.ts +0 -427
- package/src/lib/devtools/pipes/query-short-name.pipe.ts +0 -24
- package/src/lib/devtools/utils/query-client.utils.ts +0 -25
- package/src/lib/directives/infinity-query-trigger.directive.ts +0 -58
- package/src/lib/directives/infinity-query.directive.ts +0 -208
- package/src/lib/directives/query.directive.ts +0 -201
- package/src/lib/entity/entity-store.ts +0 -180
- package/src/lib/entity/entity.types.ts +0 -34
- package/src/lib/entity/entity.utils.ts +0 -51
- package/src/lib/gql/gql.ts +0 -22
- package/src/lib/infinite-query/infinity-query.ts +0 -245
- package/src/lib/infinite-query/infinity-query.types.ts +0 -149
- package/src/lib/infinite-query/infinity-query.utils.ts +0 -14
- package/src/lib/logger/logger.ts +0 -30
- package/src/lib/query/query.ts +0 -631
- package/src/lib/query/query.types.ts +0 -532
- package/src/lib/query/query.utils.spec.ts +0 -92
- package/src/lib/query/query.utils.ts +0 -450
- package/src/lib/query-client/query-client.spec.ts +0 -77
- package/src/lib/query-client/query-client.ts +0 -195
- package/src/lib/query-client/query-client.types.ts +0 -101
- package/src/lib/query-client/query-client.utils.spec.ts +0 -61
- package/src/lib/query-client/query-client.utils.ts +0 -28
- package/src/lib/query-creator/query-creator.ts +0 -103
- package/src/lib/query-creator/query-creator.types.ts +0 -74
- package/src/lib/query-form/query-form.ts +0 -526
- package/src/lib/query-form/query-form.types.ts +0 -169
- package/src/lib/query-form/query-form.utils.ts +0 -116
- package/src/lib/query-store/query-store.ts +0 -192
- package/src/lib/request/request.ts +0 -253
- package/src/lib/request/request.types.ts +0 -164
- package/src/lib/request/request.util.spec.ts +0 -177
- package/src/lib/request/request.util.ts +0 -399
- package/src/lib/stories/angular-example-page.mdx +0 -85
- package/src/lib/stories/authentication-page.mdx +0 -35
- package/src/lib/stories/changelog-page.mdx +0 -9
- package/src/lib/stories/client-page.mdx +0 -100
- package/src/lib/stories/queries-page.mdx +0 -117
- package/src/lib/symfony/symfony.types.ts +0 -21
- package/src/lib/symfony/symfony.utils.ts +0 -43
- package/src/lib/utils/data.utils.ts +0 -280
- package/src/lib/utils/deep-freeze.util.ts +0 -21
- package/src/test-setup.ts +0 -1
- package/tsconfig.json +0 -29
- package/tsconfig.lib.json +0 -12
- package/tsconfig.lib.prod.json +0 -10
- package/tsconfig.spec.json +0 -11
- /package/{src/lib/auth/index.ts → lib/auth/index.d.ts} +0 -0
- /package/{src/lib/class-validator/index.ts → lib/class-validator/index.d.ts} +0 -0
- /package/{src/lib/devtools/components/query-devtools/index.ts → lib/devtools/components/query-devtools/index.d.ts} +0 -0
- /package/{src/lib/devtools/pipes/index.ts → lib/devtools/pipes/index.d.ts} +0 -0
- /package/{src/lib/devtools/utils/index.ts → lib/devtools/utils/index.d.ts} +0 -0
- /package/{src/lib/directives/index.ts → lib/directives/index.d.ts} +0 -0
- /package/{src/lib/entity/index.ts → lib/entity/index.d.ts} +0 -0
- /package/{src/lib/gql/index.ts → lib/gql/index.d.ts} +0 -0
- /package/{src/lib/infinite-query/index.ts → lib/infinite-query/index.d.ts} +0 -0
- /package/{src/lib/logger/index.ts → lib/logger/index.d.ts} +0 -0
- /package/{src/lib/query/index.ts → lib/query/index.d.ts} +0 -0
- /package/{src/lib/query-client/index.ts → lib/query-client/index.d.ts} +0 -0
- /package/{src/lib/query-creator/index.ts → lib/query-creator/index.d.ts} +0 -0
- /package/{src/lib/query-form/index.ts → lib/query-form/index.d.ts} +0 -0
- /package/{src/lib/query-store/index.ts → lib/query-store/index.d.ts} +0 -0
- /package/{src/lib/request/index.ts → lib/request/index.d.ts} +0 -0
- /package/{src/lib/symfony/index.ts → lib/symfony/index.d.ts} +0 -0
- /package/{src/lib/utils/index.ts → lib/utils/index.d.ts} +0 -0
|
@@ -0,0 +1,3289 @@
|
|
|
1
|
+
import * as i1 from '@ethlete/core';
|
|
2
|
+
import { createDestroy, computedTillTruthy, getCookie, setCookie, deleteCookie, DelayableDirective, nextFrame, RouterStateService, clone, equal, ET_PROPERTY_REMOVED } from '@ethlete/core';
|
|
3
|
+
import { Observable, takeUntil, startWith, pairwise, tap, switchMap, of, BehaviorSubject, Subject, fromEvent, timer, take, filter, takeWhile, map, shareReplay, ReplaySubject, combineLatest, interval, skip, withLatestFrom, debounceTime, merge } from 'rxjs';
|
|
4
|
+
import * as i0 from '@angular/core';
|
|
5
|
+
import { assertInInjectionContext, inject, Injector, computed, signal, isDevMode, untracked, TemplateRef, ViewContainerRef, ErrorHandler, ChangeDetectorRef, Directive, Input, InjectionToken, ElementRef, Pipe, effect, Component, ChangeDetectionStrategy, ViewEncapsulation, NgZone } from '@angular/core';
|
|
6
|
+
import { toSignal, toObservable, takeUntilDestroyed } from '@angular/core/rxjs-interop';
|
|
7
|
+
import { AsyncPipe, JsonPipe, NgClass } from '@angular/common';
|
|
8
|
+
import * as i1$1 from '@angular/forms';
|
|
9
|
+
import { FormControl, ReactiveFormsModule, FormGroup } from '@angular/forms';
|
|
10
|
+
import { Router, ActivatedRoute } from '@angular/router';
|
|
11
|
+
|
|
12
|
+
class CustomHeaderAuthProvider {
|
|
13
|
+
get header() {
|
|
14
|
+
return { [this._config.name]: this._config.value };
|
|
15
|
+
}
|
|
16
|
+
constructor(_config) {
|
|
17
|
+
this._config = _config;
|
|
18
|
+
}
|
|
19
|
+
cleanUp() {
|
|
20
|
+
// noop
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
class BasicAuthProvider {
|
|
25
|
+
get header() {
|
|
26
|
+
return { Authorization: `Basic ${this._basicAuthString}` };
|
|
27
|
+
}
|
|
28
|
+
get _basicAuthString() {
|
|
29
|
+
return btoa(`${this._config.username}:${this._config.password}`);
|
|
30
|
+
}
|
|
31
|
+
constructor(_config) {
|
|
32
|
+
this._config = _config;
|
|
33
|
+
}
|
|
34
|
+
cleanUp() {
|
|
35
|
+
// noop
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
const buildErrorMessage = (code, message) => `[@ethlete\\query:${code}] ${message}`;
|
|
40
|
+
class QueryError extends Error {
|
|
41
|
+
constructor(code, message, data) {
|
|
42
|
+
super(buildErrorMessage(code, message));
|
|
43
|
+
this.name = 'QueryError';
|
|
44
|
+
this.data = data;
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
const invalidBaseRouteError = (data) => new QueryError('001', 'The baseRoute must not end with a slash', data);
|
|
48
|
+
const invalidRouteError = (data) => new QueryError('002', 'The route must start with a slash', data);
|
|
49
|
+
const pathParamsMissingInRouteFunctionError = (data) => new QueryError('003', 'The route is a function but pathParams are missing', data);
|
|
50
|
+
const queryStateAlreadyHasKeyError = (data) => new QueryError('004', 'The query state already contains the provided key', data);
|
|
51
|
+
const queryStateDoesNotContainKeyError = (data) => new QueryError('005', 'The query state does not contain the provided key', data);
|
|
52
|
+
const queryStateCannotTransformError = (data) => new QueryError('006', 'The query state cannot be transformed because it is not loading', data);
|
|
53
|
+
const invalidBodyError = (data) => new QueryError('007', 'The body is not valid or upsupported', data);
|
|
54
|
+
|
|
55
|
+
const isSymfonyError = (error) => {
|
|
56
|
+
if (!error || typeof error !== 'object') {
|
|
57
|
+
return false;
|
|
58
|
+
}
|
|
59
|
+
const symfonyError = error;
|
|
60
|
+
return (symfonyError.class !== undefined &&
|
|
61
|
+
symfonyError.detail !== undefined &&
|
|
62
|
+
symfonyError.status !== undefined &&
|
|
63
|
+
symfonyError.title !== undefined &&
|
|
64
|
+
symfonyError.trace !== undefined &&
|
|
65
|
+
symfonyError.type !== undefined);
|
|
66
|
+
};
|
|
67
|
+
const isSymfonyPagerfantaOutOfRangeError = (error) => {
|
|
68
|
+
if (!isSymfonyError(error)) {
|
|
69
|
+
return false;
|
|
70
|
+
}
|
|
71
|
+
return error.class.startsWith('Pagerfanta') && error.class.endsWith('OutOfRangeCurrentPageException');
|
|
72
|
+
};
|
|
73
|
+
const isSymfonyFormViolationListError = (error) => {
|
|
74
|
+
return typeof error === 'object' && error !== null && 'violations' in error;
|
|
75
|
+
};
|
|
76
|
+
const isSymfonyListError = (error) => {
|
|
77
|
+
return (!!error &&
|
|
78
|
+
typeof error === 'object' &&
|
|
79
|
+
Array.isArray(error) &&
|
|
80
|
+
!!error.length &&
|
|
81
|
+
'message' in error[0] &&
|
|
82
|
+
'propertyPath' in error[0] &&
|
|
83
|
+
'invalidValue' in error[0]);
|
|
84
|
+
};
|
|
85
|
+
|
|
86
|
+
const isNaN$1 = (value) => typeof value === 'number' && Number.isNaN(value);
|
|
87
|
+
const isEmptyString = (value) => typeof value === 'string' && value.trim() === '';
|
|
88
|
+
const isRequestError = (error) => error instanceof Object && 'status' in error && 'statusText' in error && 'url' in error;
|
|
89
|
+
const buildRoute = (options) => {
|
|
90
|
+
if (options.base.endsWith('/')) {
|
|
91
|
+
throw invalidBaseRouteError(options.base);
|
|
92
|
+
}
|
|
93
|
+
let route = null;
|
|
94
|
+
if (typeof options.route === 'function') {
|
|
95
|
+
if (!options.pathParams) {
|
|
96
|
+
throw pathParamsMissingInRouteFunctionError(options.route({}));
|
|
97
|
+
}
|
|
98
|
+
route = options.route(options.pathParams);
|
|
99
|
+
}
|
|
100
|
+
else {
|
|
101
|
+
route = options.route ?? null;
|
|
102
|
+
}
|
|
103
|
+
if (route && !route.startsWith('/')) {
|
|
104
|
+
throw invalidRouteError(route);
|
|
105
|
+
}
|
|
106
|
+
if (options.queryParams) {
|
|
107
|
+
const queryString = buildQueryString(options.queryParams, options.queryParamConfig);
|
|
108
|
+
if (queryString) {
|
|
109
|
+
route = route ? `${route}?${queryString}` : `/?${queryString}`;
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
return `${options.base}${route ?? ''}`;
|
|
113
|
+
};
|
|
114
|
+
const buildQueryString = (params, config) => {
|
|
115
|
+
const objectNotation = config?.objectNotation ?? 'bracket';
|
|
116
|
+
const writeArrayIndexes = config?.writeArrayIndexes ?? false;
|
|
117
|
+
const ignoredValues = config?.ignoredValues ?? [undefined, null, Infinity, -Infinity];
|
|
118
|
+
const ignoredValuesFns = config?.ignoredValuesFns ?? [isNaN$1, isEmptyString];
|
|
119
|
+
const queryParams = [];
|
|
120
|
+
function processValue(key, value) {
|
|
121
|
+
if (Array.isArray(value)) {
|
|
122
|
+
let currentFilteredIndex = 0;
|
|
123
|
+
for (const arrayValue of value) {
|
|
124
|
+
const nestedKey = writeArrayIndexes ? `${key}[${currentFilteredIndex}]` : `${key}[]`;
|
|
125
|
+
const didAddValue = processValue(nestedKey, arrayValue);
|
|
126
|
+
if (didAddValue) {
|
|
127
|
+
currentFilteredIndex++;
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
return null;
|
|
131
|
+
}
|
|
132
|
+
else if (typeof value === 'object' && value !== null) {
|
|
133
|
+
for (const [objKey, val] of Object.entries(value)) {
|
|
134
|
+
const nestedKey = objectNotation === 'dot' ? `${key}.${objKey}` : `${key}[${objKey}]`;
|
|
135
|
+
processValue(nestedKey, val);
|
|
136
|
+
}
|
|
137
|
+
return null;
|
|
138
|
+
}
|
|
139
|
+
else {
|
|
140
|
+
if (ignoredValues.includes(value)) {
|
|
141
|
+
return false;
|
|
142
|
+
}
|
|
143
|
+
if (ignoredValuesFns.some((fn) => fn(value))) {
|
|
144
|
+
return false;
|
|
145
|
+
}
|
|
146
|
+
const encodedKey = encodeURIComponent(key);
|
|
147
|
+
const encodedValue = encodeURIComponent(value);
|
|
148
|
+
queryParams.push(`${encodedKey}=${encodedValue}`);
|
|
149
|
+
return true;
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
for (const [key, val] of Object.entries(params)) {
|
|
153
|
+
processValue(key, val);
|
|
154
|
+
}
|
|
155
|
+
return queryParams.length ? queryParams.join('&') : null;
|
|
156
|
+
};
|
|
157
|
+
const extractExpiresInSeconds = (headers) => {
|
|
158
|
+
const cacheControl = headers['cache-control'];
|
|
159
|
+
const age = headers['age'];
|
|
160
|
+
const expires = headers['expires'];
|
|
161
|
+
// In seconds
|
|
162
|
+
let expiresIn = null;
|
|
163
|
+
let maxAge = null;
|
|
164
|
+
if (cacheControl?.includes('no-cache')) {
|
|
165
|
+
return null;
|
|
166
|
+
}
|
|
167
|
+
if (cacheControl?.includes('max-age')) {
|
|
168
|
+
const m = cacheControl.split('max-age=')[1];
|
|
169
|
+
if (m) {
|
|
170
|
+
maxAge = parseInt(m);
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
else if (cacheControl?.includes('s-maxage')) {
|
|
174
|
+
const m = cacheControl.split('s-maxage=')[1];
|
|
175
|
+
if (m) {
|
|
176
|
+
maxAge = parseInt(m);
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
if (maxAge && age) {
|
|
180
|
+
const ageSeconds = parseInt(age);
|
|
181
|
+
expiresIn = maxAge - ageSeconds;
|
|
182
|
+
}
|
|
183
|
+
else if (maxAge) {
|
|
184
|
+
expiresIn = maxAge / 2; // We assume the response is half way to its expiration
|
|
185
|
+
}
|
|
186
|
+
else if (expires) {
|
|
187
|
+
// Used by some apis to tell the response will never expire
|
|
188
|
+
// In this case we let the response expire after 1 hour
|
|
189
|
+
if (expires === '-1') {
|
|
190
|
+
expiresIn = 3600;
|
|
191
|
+
}
|
|
192
|
+
else {
|
|
193
|
+
const expiresDate = new Date(expires);
|
|
194
|
+
// check if the date is valid
|
|
195
|
+
if (expiresDate.toString() !== 'Invalid Date') {
|
|
196
|
+
expiresIn = Math.floor((expiresDate.getTime() - Date.now()) / 1000);
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
return expiresIn;
|
|
201
|
+
};
|
|
202
|
+
const buildTimestampFromSeconds = (seconds) => {
|
|
203
|
+
if (seconds === null) {
|
|
204
|
+
return null;
|
|
205
|
+
}
|
|
206
|
+
return new Date(Date.now() + seconds * 1000).getTime();
|
|
207
|
+
};
|
|
208
|
+
const serializeBody = (body) => {
|
|
209
|
+
if (body === null || body === undefined) {
|
|
210
|
+
return null;
|
|
211
|
+
}
|
|
212
|
+
if (body instanceof ArrayBuffer ||
|
|
213
|
+
body instanceof Blob ||
|
|
214
|
+
body instanceof FormData ||
|
|
215
|
+
body instanceof URLSearchParams ||
|
|
216
|
+
typeof body === 'string') {
|
|
217
|
+
return body;
|
|
218
|
+
}
|
|
219
|
+
if (typeof body === 'object' || typeof body === 'boolean' || Array.isArray(body)) {
|
|
220
|
+
return JSON.stringify(body);
|
|
221
|
+
}
|
|
222
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
223
|
+
return body.toString();
|
|
224
|
+
};
|
|
225
|
+
const transformMethod = (config) => {
|
|
226
|
+
if (config.method === 'GQL_QUERY' || config.method === 'GQL_MUTATE') {
|
|
227
|
+
if (!config.transferVia) {
|
|
228
|
+
return 'POST';
|
|
229
|
+
}
|
|
230
|
+
return config.transferVia;
|
|
231
|
+
}
|
|
232
|
+
return config.method;
|
|
233
|
+
};
|
|
234
|
+
const detectContentTypeHeader = (body) => {
|
|
235
|
+
// An empty body has no content type.
|
|
236
|
+
if (body === null) {
|
|
237
|
+
return null;
|
|
238
|
+
}
|
|
239
|
+
// FormData bodies rely on the browser's content type assignment.
|
|
240
|
+
if (body instanceof FormData) {
|
|
241
|
+
return null;
|
|
242
|
+
}
|
|
243
|
+
// Blobs usually have their own content type. If it doesn't, then
|
|
244
|
+
// no type can be inferred.
|
|
245
|
+
if (body instanceof Blob) {
|
|
246
|
+
return body.type || null;
|
|
247
|
+
}
|
|
248
|
+
// Array buffers have unknown contents and thus no type can be inferred.
|
|
249
|
+
if (body instanceof ArrayBuffer) {
|
|
250
|
+
return null;
|
|
251
|
+
}
|
|
252
|
+
// Technically, strings could be a form of JSON data, but it's safe enough
|
|
253
|
+
// to assume they're plain strings.
|
|
254
|
+
if (typeof body === 'string') {
|
|
255
|
+
return 'text/plain';
|
|
256
|
+
}
|
|
257
|
+
// Arrays, objects, boolean and numbers will be encoded as JSON.
|
|
258
|
+
if (typeof body === 'object' || typeof body === 'number' || typeof body === 'boolean') {
|
|
259
|
+
return 'application/json';
|
|
260
|
+
}
|
|
261
|
+
// No type could be inferred.
|
|
262
|
+
return null;
|
|
263
|
+
};
|
|
264
|
+
const hasHeader = (headers, header) => {
|
|
265
|
+
return Object.keys(headers).some((key) => key.toLowerCase() === header.toLowerCase());
|
|
266
|
+
};
|
|
267
|
+
const forEachHeader = (headers, callback) => {
|
|
268
|
+
Object.entries(headers).forEach(([key, value]) => {
|
|
269
|
+
callback(key, value);
|
|
270
|
+
});
|
|
271
|
+
};
|
|
272
|
+
const parseAllXhrResponseHeaders = (xhr) => {
|
|
273
|
+
const headers = xhr.getAllResponseHeaders();
|
|
274
|
+
const parsedHeaders = {};
|
|
275
|
+
for (const line of headers.split('\n')) {
|
|
276
|
+
const index = line.indexOf(':');
|
|
277
|
+
if (index > 0) {
|
|
278
|
+
const name = line.slice(0, index);
|
|
279
|
+
const key = name.toLowerCase();
|
|
280
|
+
const value = line.slice(index + 1).trim();
|
|
281
|
+
parsedHeaders[key] = value;
|
|
282
|
+
}
|
|
283
|
+
}
|
|
284
|
+
return parsedHeaders;
|
|
285
|
+
};
|
|
286
|
+
const getResponseUrl = (xhr) => {
|
|
287
|
+
if ('responseURL' in xhr && xhr.responseURL) {
|
|
288
|
+
return xhr.responseURL;
|
|
289
|
+
}
|
|
290
|
+
if (/^X-Request-URL:/m.test(xhr.getAllResponseHeaders())) {
|
|
291
|
+
return xhr.getResponseHeader('X-Request-URL');
|
|
292
|
+
}
|
|
293
|
+
return null;
|
|
294
|
+
};
|
|
295
|
+
const shouldRetryRequest = (config) => {
|
|
296
|
+
const defaultRetryDelay = 1000 + 1000 * config.currentRetryCount;
|
|
297
|
+
if (config.currentRetryCount > 3) {
|
|
298
|
+
return { retry: false };
|
|
299
|
+
}
|
|
300
|
+
if (!isRequestError(config.error)) {
|
|
301
|
+
return { retry: false };
|
|
302
|
+
}
|
|
303
|
+
const { status, detail } = config.error;
|
|
304
|
+
// Retry on 5xx errors
|
|
305
|
+
if (status >= 500) {
|
|
306
|
+
// Don't retry if a requested page is out of range
|
|
307
|
+
if (isSymfonyPagerfantaOutOfRangeError(detail)) {
|
|
308
|
+
return { retry: false };
|
|
309
|
+
}
|
|
310
|
+
return { retry: true, delay: defaultRetryDelay };
|
|
311
|
+
}
|
|
312
|
+
// Retry on 408 or 425 errors
|
|
313
|
+
if (status === 408 /* HttpStatusCode.RequestTimeout */ || status === 425 /* HttpStatusCode.TooEarly */) {
|
|
314
|
+
return { retry: true, delay: defaultRetryDelay };
|
|
315
|
+
}
|
|
316
|
+
// Retry on 429 errors
|
|
317
|
+
if (status === 429 /* HttpStatusCode.TooManyRequests */) {
|
|
318
|
+
const retryAfter = config.headers['retry-after'] ||
|
|
319
|
+
config.headers['Retry-After'] ||
|
|
320
|
+
config.headers['x-retry-after'] ||
|
|
321
|
+
config.headers['X-Retry-After'];
|
|
322
|
+
if (retryAfter) {
|
|
323
|
+
const delay = parseInt(retryAfter) * 1000;
|
|
324
|
+
return { retry: true, delay: Number.isNaN(delay) ? defaultRetryDelay : delay };
|
|
325
|
+
}
|
|
326
|
+
return { retry: true, delay: defaultRetryDelay };
|
|
327
|
+
}
|
|
328
|
+
// Code 0 usually means the internet connection is down. We retry in this case.
|
|
329
|
+
// It could also be a CORS issue but that should not be the case in production.
|
|
330
|
+
if (status === 0) {
|
|
331
|
+
return { retry: true, delay: defaultRetryDelay };
|
|
332
|
+
}
|
|
333
|
+
return { retry: false };
|
|
334
|
+
};
|
|
335
|
+
|
|
336
|
+
const XSSI_PREFIX = /^\)\]\}',?\n/;
|
|
337
|
+
const request = (config) => {
|
|
338
|
+
const headers = config.headers || {};
|
|
339
|
+
const responseType = config.responseType || 'json';
|
|
340
|
+
const body = config.body || null;
|
|
341
|
+
const url = config.urlWithParams.split('?')[0] || '';
|
|
342
|
+
const retryFn = config.retryFn || shouldRetryRequest;
|
|
343
|
+
let currentRetryCount = 0;
|
|
344
|
+
let retryTimeout = null;
|
|
345
|
+
return new Observable((observer) => {
|
|
346
|
+
const xhr = new XMLHttpRequest();
|
|
347
|
+
const reqBody = serializeBody(body);
|
|
348
|
+
const setupXhr = () => {
|
|
349
|
+
xhr.open(config.method, config.urlWithParams);
|
|
350
|
+
if (config.withCredentials) {
|
|
351
|
+
xhr.withCredentials = true;
|
|
352
|
+
}
|
|
353
|
+
forEachHeader(headers, (name, value) => xhr.setRequestHeader(name, value));
|
|
354
|
+
if (!hasHeader(headers, 'Accept')) {
|
|
355
|
+
xhr.setRequestHeader('Accept', 'application/json, text/plain, */*');
|
|
356
|
+
}
|
|
357
|
+
if (!hasHeader(headers, 'Content-Type')) {
|
|
358
|
+
const detectedType = detectContentTypeHeader(body);
|
|
359
|
+
if (detectedType) {
|
|
360
|
+
xhr.setRequestHeader('Content-Type', detectedType);
|
|
361
|
+
}
|
|
362
|
+
}
|
|
363
|
+
xhr.responseType = responseType !== 'json' ? responseType : 'text';
|
|
364
|
+
};
|
|
365
|
+
setupXhr();
|
|
366
|
+
const partialFromXhr = () => {
|
|
367
|
+
const statusText = xhr.statusText || 'OK';
|
|
368
|
+
const headers = parseAllXhrResponseHeaders(xhr);
|
|
369
|
+
const _url = getResponseUrl(xhr) || url;
|
|
370
|
+
return { headers, status: xhr.status, statusText, url: _url };
|
|
371
|
+
};
|
|
372
|
+
const handleRetry = (error) => {
|
|
373
|
+
const newRetryCount = currentRetryCount + 1;
|
|
374
|
+
const { headers } = partialFromXhr();
|
|
375
|
+
const { retry, delay } = retryFn({ currentRetryCount: newRetryCount, headers, error });
|
|
376
|
+
if (!retry) {
|
|
377
|
+
observer.next({ type: 'failure', headers, error });
|
|
378
|
+
observer.complete();
|
|
379
|
+
return;
|
|
380
|
+
}
|
|
381
|
+
const _delay = delay ?? 1000;
|
|
382
|
+
observer.next({ type: 'delay-retry', headers, retryNumber: newRetryCount, retryDelay: _delay });
|
|
383
|
+
retryTimeout = window.setTimeout(() => {
|
|
384
|
+
setupXhr();
|
|
385
|
+
xhr.send(reqBody);
|
|
386
|
+
currentRetryCount = newRetryCount;
|
|
387
|
+
observer.next({ type: 'start', headers, isRetry: true, retryNumber: currentRetryCount, retryDelay: _delay });
|
|
388
|
+
}, _delay);
|
|
389
|
+
};
|
|
390
|
+
const onLoad = async () => {
|
|
391
|
+
const { headers, statusText, url } = partialFromXhr();
|
|
392
|
+
let { status } = partialFromXhr();
|
|
393
|
+
let body = null;
|
|
394
|
+
if (status !== 204 /* HttpStatusCode.NoContent */) {
|
|
395
|
+
body = typeof xhr.response === 'undefined' ? xhr.responseText : xhr.response;
|
|
396
|
+
}
|
|
397
|
+
if (status === 0) {
|
|
398
|
+
status = body ? 200 /* HttpStatusCode.Ok */ : 0;
|
|
399
|
+
}
|
|
400
|
+
let ok = status >= 200 && status < 300;
|
|
401
|
+
if (responseType === 'json' && typeof body === 'string') {
|
|
402
|
+
const originalBody = body;
|
|
403
|
+
body = body.replace(XSSI_PREFIX, '');
|
|
404
|
+
try {
|
|
405
|
+
body = body !== '' ? JSON.parse(body) : null;
|
|
406
|
+
}
|
|
407
|
+
catch (error) {
|
|
408
|
+
body = originalBody;
|
|
409
|
+
if (ok) {
|
|
410
|
+
ok = false;
|
|
411
|
+
body = { error, text: body };
|
|
412
|
+
}
|
|
413
|
+
}
|
|
414
|
+
}
|
|
415
|
+
else if (!ok && body instanceof Blob) {
|
|
416
|
+
try {
|
|
417
|
+
const text = await body.text();
|
|
418
|
+
if (body.type === 'application/json') {
|
|
419
|
+
body = JSON.parse(text);
|
|
420
|
+
}
|
|
421
|
+
else {
|
|
422
|
+
body = text;
|
|
423
|
+
}
|
|
424
|
+
}
|
|
425
|
+
catch (error) {
|
|
426
|
+
// Ignore the error.
|
|
427
|
+
}
|
|
428
|
+
}
|
|
429
|
+
if (ok) {
|
|
430
|
+
const successEvent = {
|
|
431
|
+
type: 'success',
|
|
432
|
+
headers,
|
|
433
|
+
response: body,
|
|
434
|
+
};
|
|
435
|
+
const expiresInSeconds = config.cacheAdapter?.(headers) ?? extractExpiresInSeconds(headers);
|
|
436
|
+
const expiresInTimestamp = buildTimestampFromSeconds(expiresInSeconds);
|
|
437
|
+
if (expiresInTimestamp) {
|
|
438
|
+
successEvent.expiresInTimestamp = expiresInTimestamp;
|
|
439
|
+
}
|
|
440
|
+
observer.next(successEvent);
|
|
441
|
+
observer.complete();
|
|
442
|
+
}
|
|
443
|
+
else {
|
|
444
|
+
handleRetry({
|
|
445
|
+
url,
|
|
446
|
+
detail: body,
|
|
447
|
+
status,
|
|
448
|
+
statusText,
|
|
449
|
+
});
|
|
450
|
+
}
|
|
451
|
+
};
|
|
452
|
+
const onError = (error) => {
|
|
453
|
+
const { url } = partialFromXhr();
|
|
454
|
+
handleRetry({
|
|
455
|
+
url,
|
|
456
|
+
detail: error,
|
|
457
|
+
status: xhr.status || 0,
|
|
458
|
+
statusText: xhr.statusText || 'Unknown Error',
|
|
459
|
+
});
|
|
460
|
+
};
|
|
461
|
+
const onDownProgress = (event) => {
|
|
462
|
+
const { headers } = partialFromXhr();
|
|
463
|
+
const progress = {
|
|
464
|
+
type: 'download-progress',
|
|
465
|
+
headers,
|
|
466
|
+
progress: {
|
|
467
|
+
current: event.loaded,
|
|
468
|
+
},
|
|
469
|
+
};
|
|
470
|
+
if (event.lengthComputable) {
|
|
471
|
+
const progressPercent = (event.loaded / event.total) * 100;
|
|
472
|
+
progress.progress.percentage = Math.round(progressPercent * 100) / 100;
|
|
473
|
+
progress.progress.total = event.total;
|
|
474
|
+
}
|
|
475
|
+
if (responseType === 'text' && !!xhr.responseText) {
|
|
476
|
+
progress.partialText = xhr.responseText;
|
|
477
|
+
}
|
|
478
|
+
observer.next(progress);
|
|
479
|
+
};
|
|
480
|
+
const onUpProgress = (event) => {
|
|
481
|
+
const { headers } = partialFromXhr();
|
|
482
|
+
const progress = {
|
|
483
|
+
type: 'upload-progress',
|
|
484
|
+
headers,
|
|
485
|
+
progress: {
|
|
486
|
+
current: event.loaded,
|
|
487
|
+
},
|
|
488
|
+
};
|
|
489
|
+
if (event.lengthComputable) {
|
|
490
|
+
const progressPercent = (event.loaded / event.total) * 100;
|
|
491
|
+
progress.progress.percentage = Math.round(progressPercent * 100) / 100;
|
|
492
|
+
progress.progress.total = event.total;
|
|
493
|
+
}
|
|
494
|
+
observer.next(progress);
|
|
495
|
+
};
|
|
496
|
+
xhr.addEventListener('load', onLoad);
|
|
497
|
+
xhr.addEventListener('error', onError);
|
|
498
|
+
xhr.addEventListener('timeout', onError);
|
|
499
|
+
xhr.addEventListener('abort', onError);
|
|
500
|
+
if (config.reportProgress) {
|
|
501
|
+
xhr.addEventListener('progress', onDownProgress);
|
|
502
|
+
if (reqBody !== null && xhr.upload) {
|
|
503
|
+
xhr.upload.addEventListener('progress', onUpProgress);
|
|
504
|
+
}
|
|
505
|
+
}
|
|
506
|
+
xhr.send(reqBody);
|
|
507
|
+
observer.next({ type: 'start', headers });
|
|
508
|
+
return () => {
|
|
509
|
+
xhr.removeEventListener('error', onError);
|
|
510
|
+
xhr.removeEventListener('abort', onError);
|
|
511
|
+
xhr.removeEventListener('load', onLoad);
|
|
512
|
+
xhr.removeEventListener('timeout', onError);
|
|
513
|
+
if (retryTimeout) {
|
|
514
|
+
window.clearTimeout(retryTimeout);
|
|
515
|
+
}
|
|
516
|
+
if (config.reportProgress) {
|
|
517
|
+
xhr.removeEventListener('progress', onDownProgress);
|
|
518
|
+
if (reqBody !== null && xhr.upload) {
|
|
519
|
+
xhr.upload.removeEventListener('progress', onUpProgress);
|
|
520
|
+
}
|
|
521
|
+
}
|
|
522
|
+
if (xhr.readyState !== xhr.DONE) {
|
|
523
|
+
xhr.abort();
|
|
524
|
+
}
|
|
525
|
+
observer.next({ type: 'cancel', headers });
|
|
526
|
+
};
|
|
527
|
+
});
|
|
528
|
+
};
|
|
529
|
+
|
|
530
|
+
const addQueryContainerHandling = (obs, valueFn, config) => {
|
|
531
|
+
assertInInjectionContext(addQueryContainerHandling);
|
|
532
|
+
const { abortPrevious, abortOnDestroy, stopPreviousPolling } = config ?? {};
|
|
533
|
+
const injector = inject(Injector);
|
|
534
|
+
const destroy$ = createDestroy();
|
|
535
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
536
|
+
const tNode = injector._tNode;
|
|
537
|
+
const componentId = tNode?.index ?? -1;
|
|
538
|
+
obs
|
|
539
|
+
.pipe(takeUntil(destroy$), startWith(null), pairwise(), tap(([prevQuery, currQuery]) => {
|
|
540
|
+
const cleanQuery = (q) => {
|
|
541
|
+
if (!q?._hasDependents() && ((abortPrevious === undefined && q?.canBeCached) || abortPrevious)) {
|
|
542
|
+
q?.abort();
|
|
543
|
+
}
|
|
544
|
+
if (!q?._hasDependents() &&
|
|
545
|
+
((stopPreviousPolling === undefined && q?.canBeCached) || stopPreviousPolling) &&
|
|
546
|
+
q?.isPolling) {
|
|
547
|
+
q?.stopPolling();
|
|
548
|
+
}
|
|
549
|
+
};
|
|
550
|
+
if ((isQuery(prevQuery) || prevQuery === null) && (isQuery(currQuery) || currQuery === null)) {
|
|
551
|
+
prevQuery?._removeDependent(componentId);
|
|
552
|
+
currQuery?._addDependent(componentId);
|
|
553
|
+
cleanQuery(prevQuery);
|
|
554
|
+
}
|
|
555
|
+
else if ((Array.isArray(prevQuery) || prevQuery === null) &&
|
|
556
|
+
(Array.isArray(currQuery) || currQuery === null)) {
|
|
557
|
+
if (prevQuery) {
|
|
558
|
+
for (let i = 0; i < prevQuery.length; i++) {
|
|
559
|
+
prevQuery[i]?._removeDependent(componentId);
|
|
560
|
+
}
|
|
561
|
+
}
|
|
562
|
+
if (currQuery) {
|
|
563
|
+
for (let i = 0; i < currQuery.length; i++) {
|
|
564
|
+
currQuery[i]?._addDependent(componentId);
|
|
565
|
+
}
|
|
566
|
+
}
|
|
567
|
+
if (prevQuery) {
|
|
568
|
+
for (let i = 0; i < prevQuery.length; i++) {
|
|
569
|
+
cleanQuery(prevQuery[i]);
|
|
570
|
+
}
|
|
571
|
+
}
|
|
572
|
+
}
|
|
573
|
+
else if ((isQuery(prevQuery) && Array.isArray(currQuery)) ||
|
|
574
|
+
(Array.isArray(prevQuery) && isQuery(currQuery))) {
|
|
575
|
+
throw new Error('Cannot mix queries and arrays of queries in the same query container.');
|
|
576
|
+
}
|
|
577
|
+
}))
|
|
578
|
+
.subscribe();
|
|
579
|
+
destroy$.subscribe(() => {
|
|
580
|
+
const query = valueFn();
|
|
581
|
+
const handleQuery = (q) => {
|
|
582
|
+
q?._removeDependent(componentId);
|
|
583
|
+
if (!q?._hasDependents() && ((q?.canBeCached && abortOnDestroy === undefined) || abortOnDestroy)) {
|
|
584
|
+
q?.abort();
|
|
585
|
+
}
|
|
586
|
+
};
|
|
587
|
+
if (isQuery(query)) {
|
|
588
|
+
handleQuery(query);
|
|
589
|
+
}
|
|
590
|
+
else if (Array.isArray(query)) {
|
|
591
|
+
for (const q of query) {
|
|
592
|
+
handleQuery(q);
|
|
593
|
+
}
|
|
594
|
+
}
|
|
595
|
+
});
|
|
596
|
+
};
|
|
597
|
+
function toQuerySignal(source, options) {
|
|
598
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
599
|
+
const s = toSignal(source, options);
|
|
600
|
+
addQueryContainerHandling(source, () => s(), options);
|
|
601
|
+
return s;
|
|
602
|
+
}
|
|
603
|
+
function queryComputed(computation, options) {
|
|
604
|
+
const c = computed(computation, options);
|
|
605
|
+
const obs = toObservable(c, options);
|
|
606
|
+
addQueryContainerHandling(obs, () => c(), options);
|
|
607
|
+
return c;
|
|
608
|
+
}
|
|
609
|
+
/**
|
|
610
|
+
* Creates a signal that will only be reactive until the first query is created.
|
|
611
|
+
* All subsequent changes inside the computation will be ignored.
|
|
612
|
+
*/
|
|
613
|
+
function queryComputedTillTruthy(computation, options) {
|
|
614
|
+
const c = computedTillTruthy(computed(computation, options));
|
|
615
|
+
const obs = toObservable(c, options);
|
|
616
|
+
addQueryContainerHandling(obs, () => c(), options);
|
|
617
|
+
return c;
|
|
618
|
+
}
|
|
619
|
+
function queryArrayComputed(computation, options) {
|
|
620
|
+
const c = computed(computation, options);
|
|
621
|
+
const obs = toObservable(c, options);
|
|
622
|
+
addQueryContainerHandling(obs, () => c(), options);
|
|
623
|
+
return c;
|
|
624
|
+
}
|
|
625
|
+
function toQuerySubject(source, options) {
|
|
626
|
+
const obs = toObservable(source, options);
|
|
627
|
+
addQueryContainerHandling(obs, () => source(), options);
|
|
628
|
+
return obs;
|
|
629
|
+
}
|
|
630
|
+
function queryStateSignal(source, options) {
|
|
631
|
+
const { cacheResponse } = options ?? {};
|
|
632
|
+
return toSignal(toObservable(source).pipe(switchMap((q) => extractQuery(q)?.state$ ?? of(null)), switchMap((state) => {
|
|
633
|
+
if (cacheResponse) {
|
|
634
|
+
return of(state).pipe(filterQueryStates(["SUCCESS" /* QueryStateType.Success */, "FAILURE" /* QueryStateType.Failure */, "CANCELLED" /* QueryStateType.Cancelled */]));
|
|
635
|
+
}
|
|
636
|
+
return of(state);
|
|
637
|
+
})), {
|
|
638
|
+
initialValue: null,
|
|
639
|
+
});
|
|
640
|
+
}
|
|
641
|
+
function queryStateResponseSignal(source, options) {
|
|
642
|
+
const s = queryStateSignal(source, options);
|
|
643
|
+
return computed(() => {
|
|
644
|
+
const state = s();
|
|
645
|
+
return isQueryStateSuccess(state) ? state.response : null;
|
|
646
|
+
});
|
|
647
|
+
}
|
|
648
|
+
function queryStateErrorSignal(source) {
|
|
649
|
+
const s = queryStateSignal(source);
|
|
650
|
+
return computed(() => {
|
|
651
|
+
const state = s();
|
|
652
|
+
return isQueryStateFailure(state) ? state.error : null;
|
|
653
|
+
});
|
|
654
|
+
}
|
|
655
|
+
function queryStateLoadingSignal(source) {
|
|
656
|
+
const s = queryStateSignal(source);
|
|
657
|
+
return computed(() => {
|
|
658
|
+
const state = s();
|
|
659
|
+
return isQueryStateLoading(state) ? state : null;
|
|
660
|
+
});
|
|
661
|
+
}
|
|
662
|
+
|
|
663
|
+
const deepFreeze = (object) => {
|
|
664
|
+
// check if object is actually an object or frozen
|
|
665
|
+
if (!object || typeof object !== 'object' || Object.isFrozen(object)) {
|
|
666
|
+
return object;
|
|
667
|
+
}
|
|
668
|
+
// Retrieve the property names defined on object
|
|
669
|
+
const propNames = Object.getOwnPropertyNames(object);
|
|
670
|
+
// Freeze properties before freezing self
|
|
671
|
+
for (const name of propNames) {
|
|
672
|
+
const value = object[name];
|
|
673
|
+
if (value && typeof value === 'object') {
|
|
674
|
+
deepFreeze(value);
|
|
675
|
+
}
|
|
676
|
+
}
|
|
677
|
+
return Object.freeze(object);
|
|
678
|
+
};
|
|
679
|
+
|
|
680
|
+
/**
|
|
681
|
+
* Dummy function for providing types while not breaking inference.
|
|
682
|
+
* **This function always returns null.**
|
|
683
|
+
* @see https://github.com/microsoft/TypeScript/issues/26242
|
|
684
|
+
* @returns null
|
|
685
|
+
*/
|
|
686
|
+
const def = () => null;
|
|
687
|
+
|
|
688
|
+
class QueryCreator {
|
|
689
|
+
constructor(_queryConfig, _client, _store) {
|
|
690
|
+
this._queryConfig = _queryConfig;
|
|
691
|
+
this._client = _client;
|
|
692
|
+
this._store = _store;
|
|
693
|
+
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
|
694
|
+
// @ts-ignore
|
|
695
|
+
this.prepare = (args) => {
|
|
696
|
+
const route = buildRoute({
|
|
697
|
+
base: this._client.config.baseRoute,
|
|
698
|
+
route: this._queryConfig.route,
|
|
699
|
+
pathParams: args?.pathParams,
|
|
700
|
+
queryParams: computeQueryQueryParams({ config: this._queryConfig, client: this._client, args }),
|
|
701
|
+
queryParamConfig: this._client.config.request?.queryParams,
|
|
702
|
+
});
|
|
703
|
+
const cacheKey = (args?.config?.queryStoreCacheKey ?? '') + buildQueryCacheKey(route, args);
|
|
704
|
+
if (shouldCacheQuery(this._queryConfig.method) && !args?.config?.skipQueryStore) {
|
|
705
|
+
const existingQuery = this._store.get(cacheKey);
|
|
706
|
+
if (existingQuery) {
|
|
707
|
+
return existingQuery;
|
|
708
|
+
}
|
|
709
|
+
}
|
|
710
|
+
const query = new Query(this._client, this._queryConfig, route, args ?? {}, cacheKey);
|
|
711
|
+
if (shouldCacheQuery(this._queryConfig.method) && !args?.config?.skipQueryStore) {
|
|
712
|
+
this._store.add(cacheKey, query);
|
|
713
|
+
}
|
|
714
|
+
this._store._dispatchQueryCreated(query);
|
|
715
|
+
return query;
|
|
716
|
+
};
|
|
717
|
+
this.createSubject = (initialValue, config) => {
|
|
718
|
+
assertInInjectionContext(this.createSubject);
|
|
719
|
+
const subject = new BehaviorSubject(initialValue ?? null);
|
|
720
|
+
addQueryContainerHandling(subject, () => subject.getValue(), config);
|
|
721
|
+
return subject;
|
|
722
|
+
};
|
|
723
|
+
this.createSignal = (initialValue, config) => {
|
|
724
|
+
assertInInjectionContext(this.createSignal);
|
|
725
|
+
const _signal = signal(initialValue ?? null);
|
|
726
|
+
addQueryContainerHandling(toObservable(_signal), () => _signal(), config);
|
|
727
|
+
return _signal;
|
|
728
|
+
};
|
|
729
|
+
/**
|
|
730
|
+
* @deprecated Use `myQuery.createSubject()` or `myQuery.createSignal()` instead. Will be removed in v6.
|
|
731
|
+
*/
|
|
732
|
+
this.behaviorSubject = (initialValue) => new BehaviorSubject(initialValue ?? null);
|
|
733
|
+
}
|
|
734
|
+
}
|
|
735
|
+
|
|
736
|
+
class QueryStore {
|
|
737
|
+
constructor(_config) {
|
|
738
|
+
this._config = _config;
|
|
739
|
+
/**
|
|
740
|
+
* @internal
|
|
741
|
+
*/
|
|
742
|
+
this._store = new Map();
|
|
743
|
+
this._garbageCollector = null;
|
|
744
|
+
this._isInLowResourceMode = false;
|
|
745
|
+
this._lastBlurTimestamp = Date.now();
|
|
746
|
+
this._storeChange$ = new Subject();
|
|
747
|
+
this._queryCreated$ = new Subject();
|
|
748
|
+
this.storeChange$ = this._storeChange$.asObservable();
|
|
749
|
+
this.queryCreated$ = this._queryCreated$.asObservable();
|
|
750
|
+
this._initSmartQueryHandling();
|
|
751
|
+
}
|
|
752
|
+
add(id, query) {
|
|
753
|
+
this._store.set(id, query);
|
|
754
|
+
this._initGarbageCollector();
|
|
755
|
+
this._logState(id, query, 'SET');
|
|
756
|
+
this._storeChange$.next(id);
|
|
757
|
+
}
|
|
758
|
+
get(id) {
|
|
759
|
+
return this._store.get(id) ?? null;
|
|
760
|
+
}
|
|
761
|
+
remove(id) {
|
|
762
|
+
this._store.delete(id);
|
|
763
|
+
this._logState(id, null, 'REMOVE');
|
|
764
|
+
this._storeChange$.next(id);
|
|
765
|
+
}
|
|
766
|
+
forEach(callback) {
|
|
767
|
+
for (const [key, query] of this._store) {
|
|
768
|
+
callback(query, key);
|
|
769
|
+
}
|
|
770
|
+
}
|
|
771
|
+
refreshQueriesInUse(config) {
|
|
772
|
+
const { ignoreCacheValidity, purgeUnused } = config ?? {};
|
|
773
|
+
for (const [key, query] of this._store) {
|
|
774
|
+
if (query.isInUse &&
|
|
775
|
+
(query.isExpired || ignoreCacheValidity) &&
|
|
776
|
+
query.autoRefreshOnConfig.queryClientDefaultHeadersChange) {
|
|
777
|
+
query.execute({ skipCache: true, _triggeredVia: 'auto' });
|
|
778
|
+
}
|
|
779
|
+
else if (purgeUnused && !query.isInUse) {
|
|
780
|
+
this.remove(key);
|
|
781
|
+
}
|
|
782
|
+
}
|
|
783
|
+
}
|
|
784
|
+
/**
|
|
785
|
+
* @internal
|
|
786
|
+
*/
|
|
787
|
+
_dispatchQueryCreated(query) {
|
|
788
|
+
this._queryCreated$.next(query);
|
|
789
|
+
}
|
|
790
|
+
_initSmartQueryHandling() {
|
|
791
|
+
const windowBlur$ = fromEvent(window, 'blur');
|
|
792
|
+
const windowFocus$ = fromEvent(window, 'focus');
|
|
793
|
+
windowBlur$.subscribe(() => {
|
|
794
|
+
timer(5000)
|
|
795
|
+
.pipe(takeUntil(windowFocus$), take(1))
|
|
796
|
+
.subscribe(() => {
|
|
797
|
+
this._lastBlurTimestamp = Date.now();
|
|
798
|
+
this._isInLowResourceMode = true;
|
|
799
|
+
this._stopGarbageCollector();
|
|
800
|
+
if (this._config?.enableSmartPolling) {
|
|
801
|
+
this.forEach((query) => {
|
|
802
|
+
if (!query.isPolling || !query._enableSmartPolling) {
|
|
803
|
+
return;
|
|
804
|
+
}
|
|
805
|
+
query.pausePolling();
|
|
806
|
+
});
|
|
807
|
+
}
|
|
808
|
+
});
|
|
809
|
+
});
|
|
810
|
+
windowFocus$.subscribe(() => {
|
|
811
|
+
if (!this._isInLowResourceMode) {
|
|
812
|
+
return;
|
|
813
|
+
}
|
|
814
|
+
this._isInLowResourceMode = false;
|
|
815
|
+
if (this._config?.enableSmartPolling || this._config?.autoRefreshQueriesOnWindowFocus) {
|
|
816
|
+
this.forEach((query) => {
|
|
817
|
+
if (this._config?.enableSmartPolling && query._isPollingPaused) {
|
|
818
|
+
query.resumePolling();
|
|
819
|
+
}
|
|
820
|
+
if (Date.now() - this._lastBlurTimestamp > 15000) {
|
|
821
|
+
if (this._config?.autoRefreshQueriesOnWindowFocus &&
|
|
822
|
+
query.isExpired &&
|
|
823
|
+
query.isInUse &&
|
|
824
|
+
query.autoRefreshOnConfig.windowFocus) {
|
|
825
|
+
query.execute({ skipCache: true, _triggeredVia: 'auto' });
|
|
826
|
+
}
|
|
827
|
+
}
|
|
828
|
+
});
|
|
829
|
+
}
|
|
830
|
+
this._initGarbageCollector();
|
|
831
|
+
});
|
|
832
|
+
}
|
|
833
|
+
_logState(key, item, operation) {
|
|
834
|
+
if (!this._config?.enableChangeLogging)
|
|
835
|
+
return;
|
|
836
|
+
const stateAsJson = {};
|
|
837
|
+
this._store.forEach((value, key) => {
|
|
838
|
+
stateAsJson[key] = value;
|
|
839
|
+
});
|
|
840
|
+
console.log(`%c[${operation}] ${key}`, 'font-weight: bold');
|
|
841
|
+
console.log({ operation, key, item });
|
|
842
|
+
console.log(stateAsJson);
|
|
843
|
+
}
|
|
844
|
+
_initGarbageCollector() {
|
|
845
|
+
if (this._garbageCollector !== null) {
|
|
846
|
+
return;
|
|
847
|
+
}
|
|
848
|
+
this._logGarbageCollector('Start');
|
|
849
|
+
this._garbageCollector = window.setInterval(() => {
|
|
850
|
+
this._runGarbageCollector();
|
|
851
|
+
}, 15000);
|
|
852
|
+
}
|
|
853
|
+
_stopGarbageCollector() {
|
|
854
|
+
if (this._garbageCollector !== null) {
|
|
855
|
+
window.clearInterval(this._garbageCollector);
|
|
856
|
+
this._garbageCollector = null;
|
|
857
|
+
this._logGarbageCollector('Stop');
|
|
858
|
+
}
|
|
859
|
+
}
|
|
860
|
+
_runGarbageCollector() {
|
|
861
|
+
this._logGarbageCollector('Collecting...');
|
|
862
|
+
this._store.forEach((item, key) => {
|
|
863
|
+
if (item.isExpired && !item.isInUse) {
|
|
864
|
+
this.remove(key);
|
|
865
|
+
}
|
|
866
|
+
});
|
|
867
|
+
this._logGarbageCollector('Collection done');
|
|
868
|
+
if (!this._store.size) {
|
|
869
|
+
this._stopGarbageCollector();
|
|
870
|
+
}
|
|
871
|
+
}
|
|
872
|
+
_logGarbageCollector(action) {
|
|
873
|
+
if (!this._config?.enableGarbageCollectorLogging)
|
|
874
|
+
return;
|
|
875
|
+
console.log(`%cGC: ${action}`, 'color: yellow; font-weight: bold');
|
|
876
|
+
}
|
|
877
|
+
}
|
|
878
|
+
|
|
879
|
+
class QueryClient {
|
|
880
|
+
get config() {
|
|
881
|
+
return this._clientConfig;
|
|
882
|
+
}
|
|
883
|
+
get authProvider() {
|
|
884
|
+
return this._authProvider$.getValue() ?? this._clientConfig.parent?._authProvider$.getValue() ?? null;
|
|
885
|
+
}
|
|
886
|
+
get authProvider$() {
|
|
887
|
+
return this._authProvider$.asObservable() ?? this._clientConfig.parent?._authProvider$.asObservable() ?? null;
|
|
888
|
+
}
|
|
889
|
+
constructor(_clientConfig) {
|
|
890
|
+
this._clientConfig = _clientConfig;
|
|
891
|
+
this._authProvider$ = new BehaviorSubject(null);
|
|
892
|
+
this.get = (queryConfig) => this.fetch({
|
|
893
|
+
...queryConfig,
|
|
894
|
+
method: 'GET',
|
|
895
|
+
});
|
|
896
|
+
this.post = (queryConfig) => this.fetch({
|
|
897
|
+
...queryConfig,
|
|
898
|
+
method: 'POST',
|
|
899
|
+
});
|
|
900
|
+
this.put = (queryConfig) => this.fetch({
|
|
901
|
+
...queryConfig,
|
|
902
|
+
method: 'PUT',
|
|
903
|
+
});
|
|
904
|
+
this.patch = (queryConfig) => this.fetch({
|
|
905
|
+
...queryConfig,
|
|
906
|
+
method: 'PATCH',
|
|
907
|
+
});
|
|
908
|
+
this.delete = (queryConfig) => this.fetch({
|
|
909
|
+
...queryConfig,
|
|
910
|
+
method: 'DELETE',
|
|
911
|
+
});
|
|
912
|
+
this.gqlQuery = (queryConfig) => this.fetch({
|
|
913
|
+
...queryConfig,
|
|
914
|
+
method: 'GQL_QUERY',
|
|
915
|
+
});
|
|
916
|
+
this.gqlMutate = (queryConfig) => this.fetch({
|
|
917
|
+
...queryConfig,
|
|
918
|
+
method: 'GQL_MUTATE',
|
|
919
|
+
});
|
|
920
|
+
this.fetch = (queryConfig) => new QueryCreator(queryConfig, this, this._store);
|
|
921
|
+
this.setAuthProvider = (authProvider) => {
|
|
922
|
+
if (this.authProvider) {
|
|
923
|
+
this.authProvider?.cleanUp();
|
|
924
|
+
}
|
|
925
|
+
this._authProvider$.next(authProvider);
|
|
926
|
+
};
|
|
927
|
+
this.setDefaultHeaders = (config) => {
|
|
928
|
+
if (!this._clientConfig.request) {
|
|
929
|
+
this._clientConfig.request = {
|
|
930
|
+
headers: config.headers ?? undefined,
|
|
931
|
+
};
|
|
932
|
+
}
|
|
933
|
+
else {
|
|
934
|
+
this._clientConfig.request.headers = config.headers ?? undefined;
|
|
935
|
+
}
|
|
936
|
+
if (config.refreshQueriesInUse) {
|
|
937
|
+
this._store.refreshQueriesInUse({ purgeUnused: true, ignoreCacheValidity: true });
|
|
938
|
+
}
|
|
939
|
+
};
|
|
940
|
+
this.clearAuthProvider = () => {
|
|
941
|
+
this.authProvider?.cleanUp();
|
|
942
|
+
this._authProvider$.next(null);
|
|
943
|
+
};
|
|
944
|
+
this._updateBaseRoute = (route) => {
|
|
945
|
+
this._clientConfig.baseRoute = route;
|
|
946
|
+
};
|
|
947
|
+
this._store = new QueryStore({
|
|
948
|
+
enableChangeLogging: _clientConfig.logging?.queryStateChanges,
|
|
949
|
+
enableGarbageCollectorLogging: _clientConfig.logging?.queryStateGarbageCollector,
|
|
950
|
+
autoRefreshQueriesOnWindowFocus: _clientConfig.request?.autoRefreshQueriesOnWindowFocus ?? true,
|
|
951
|
+
enableSmartPolling: _clientConfig.request?.enableSmartPolling ?? true,
|
|
952
|
+
});
|
|
953
|
+
}
|
|
954
|
+
}
|
|
955
|
+
|
|
956
|
+
const shouldCacheQuery = (method) => {
|
|
957
|
+
return method === 'GET' || method === 'OPTIONS' || method === 'HEAD' || method === 'GQL_QUERY';
|
|
958
|
+
};
|
|
959
|
+
const buildQueryCacheKey = (route, args) => {
|
|
960
|
+
const variables = JSON.stringify(args?.variables || {})
|
|
961
|
+
// replace all curly braces with empty string
|
|
962
|
+
.replace(/{|}/g, '')
|
|
963
|
+
// replace new lines and whitespaces with empty string
|
|
964
|
+
.replace(/\s/g, '');
|
|
965
|
+
const seed = `${route}...${variables}`;
|
|
966
|
+
let hash = 0;
|
|
967
|
+
for (const char of seed) {
|
|
968
|
+
hash = (Math.imul(31, hash) + char.charCodeAt(0)) << 0;
|
|
969
|
+
}
|
|
970
|
+
// Force positive number hash.
|
|
971
|
+
// 2147483647 = equivalent of Integer.MAX_VALUE.
|
|
972
|
+
hash += 2147483647 + 1;
|
|
973
|
+
return hash.toString();
|
|
974
|
+
};
|
|
975
|
+
|
|
976
|
+
const getOpName = /(query|mutation) ?([\w\d-_]+)? ?\(.*?\)? \{/;
|
|
977
|
+
const transformGql = (str) => {
|
|
978
|
+
str = Array.isArray(str) ? str.join('') : str;
|
|
979
|
+
const name = getOpName.exec(str);
|
|
980
|
+
return function (variables) {
|
|
981
|
+
const data = { query: str };
|
|
982
|
+
if (variables)
|
|
983
|
+
data['variables'] = JSON.stringify(variables);
|
|
984
|
+
if (name && name.length) {
|
|
985
|
+
const operationName = name[2];
|
|
986
|
+
if (operationName)
|
|
987
|
+
data['operationName'] = name[2];
|
|
988
|
+
}
|
|
989
|
+
return data;
|
|
990
|
+
};
|
|
991
|
+
};
|
|
992
|
+
const gql = (strings, ...values) => {
|
|
993
|
+
const str = strings.reduce((acc, cur, i) => {
|
|
994
|
+
return acc + cur + (values[i] || '');
|
|
995
|
+
}, '');
|
|
996
|
+
return str;
|
|
997
|
+
};
|
|
998
|
+
|
|
999
|
+
/* eslint-disable @typescript-eslint/no-explicit-any */
|
|
1000
|
+
function filterSuccess() {
|
|
1001
|
+
return function (source) {
|
|
1002
|
+
return source.pipe(filter((value) => isQueryStateSuccess(value)));
|
|
1003
|
+
};
|
|
1004
|
+
}
|
|
1005
|
+
function ignoreAutoRefresh() {
|
|
1006
|
+
return function (source) {
|
|
1007
|
+
return source.pipe(filter((value) => !isAutoRefresh(value)));
|
|
1008
|
+
};
|
|
1009
|
+
}
|
|
1010
|
+
function filterFailure() {
|
|
1011
|
+
return function (source) {
|
|
1012
|
+
return source.pipe(filter((value) => isQueryStateFailure(value)));
|
|
1013
|
+
};
|
|
1014
|
+
}
|
|
1015
|
+
function filterQueryStates(allowedStates) {
|
|
1016
|
+
return function (source) {
|
|
1017
|
+
return source.pipe(filter((value) => value && allowedStates.includes(value.type)));
|
|
1018
|
+
};
|
|
1019
|
+
}
|
|
1020
|
+
function takeUntilResponse(config) {
|
|
1021
|
+
return function (source) {
|
|
1022
|
+
return source.pipe(takeWhile((value) => {
|
|
1023
|
+
if (config?.excludeNull && value === null) {
|
|
1024
|
+
return false;
|
|
1025
|
+
}
|
|
1026
|
+
return isQueryStateLoading(value) || isQueryStatePrepared(value) || value === null;
|
|
1027
|
+
}, true));
|
|
1028
|
+
};
|
|
1029
|
+
}
|
|
1030
|
+
function filterNull() {
|
|
1031
|
+
return function (source) {
|
|
1032
|
+
return source.pipe(filter((value) => value !== null));
|
|
1033
|
+
};
|
|
1034
|
+
}
|
|
1035
|
+
function switchQueryState() {
|
|
1036
|
+
return function (source) {
|
|
1037
|
+
return source.pipe(switchMap((value) => value?.state$ ?? of(null)));
|
|
1038
|
+
};
|
|
1039
|
+
}
|
|
1040
|
+
function switchQueryCollectionState() {
|
|
1041
|
+
return function (source) {
|
|
1042
|
+
return source.pipe(switchMap((value) => value?.query.state$ ?? of(null)));
|
|
1043
|
+
};
|
|
1044
|
+
}
|
|
1045
|
+
const resetPageOnError = (config) => (source) => {
|
|
1046
|
+
const { queryForm, pageControlKey = 'page' } = config;
|
|
1047
|
+
if (isDevMode() && !queryForm.form.controls[pageControlKey]) {
|
|
1048
|
+
console.warn(`resetPageOnError: queryForm.form.controls["${pageControlKey}"] is undefined`);
|
|
1049
|
+
}
|
|
1050
|
+
return source.pipe(switchMap((q) => {
|
|
1051
|
+
if (!q) {
|
|
1052
|
+
return of(q);
|
|
1053
|
+
}
|
|
1054
|
+
return q.state$.pipe(tap((state) => {
|
|
1055
|
+
if (!isQueryStateFailure(state)) {
|
|
1056
|
+
return;
|
|
1057
|
+
}
|
|
1058
|
+
switch (state.error.status) {
|
|
1059
|
+
// The page is invalid (in prod mode, code is 416)
|
|
1060
|
+
case 416 /* HttpStatusCode.RangeNotSatisfiable */:
|
|
1061
|
+
queryForm.form.controls[pageControlKey]?.patchValue(1);
|
|
1062
|
+
break;
|
|
1063
|
+
// The page might be invalid (in dev mode, code is 500)
|
|
1064
|
+
case 500 /* HttpStatusCode.InternalServerError */:
|
|
1065
|
+
if (isSymfonyPagerfantaOutOfRangeError(state.error.detail)) {
|
|
1066
|
+
queryForm.form.controls[pageControlKey].patchValue(1);
|
|
1067
|
+
}
|
|
1068
|
+
break;
|
|
1069
|
+
}
|
|
1070
|
+
}), takeUntilResponse());
|
|
1071
|
+
}));
|
|
1072
|
+
};
|
|
1073
|
+
const isQueryStateLoading = (state) => state?.type === "LOADING" /* QueryStateType.Loading */;
|
|
1074
|
+
const isQueryStateSuccess = (state) => state?.type === "SUCCESS" /* QueryStateType.Success */;
|
|
1075
|
+
const isQueryStateFailure = (state) => state?.type === "FAILURE" /* QueryStateType.Failure */;
|
|
1076
|
+
const isQueryStateCancelled = (state) => state?.type === "CANCELLED" /* QueryStateType.Cancelled */;
|
|
1077
|
+
const isQueryStatePrepared = (state) => state?.type === "PREPARED" /* QueryStateType.Prepared */;
|
|
1078
|
+
const isAutoRefresh = (state) => state?.meta.triggeredVia === 'auto';
|
|
1079
|
+
const mergeHeaders = (...headers) => {
|
|
1080
|
+
return headers.reduce((acc, headers) => {
|
|
1081
|
+
if (!headers) {
|
|
1082
|
+
return acc;
|
|
1083
|
+
}
|
|
1084
|
+
return {
|
|
1085
|
+
...acc,
|
|
1086
|
+
...headers,
|
|
1087
|
+
};
|
|
1088
|
+
}, {});
|
|
1089
|
+
};
|
|
1090
|
+
const isGqlQueryConfig = (config) => {
|
|
1091
|
+
if (!config || typeof config !== 'object' || Array.isArray(config)) {
|
|
1092
|
+
return false;
|
|
1093
|
+
}
|
|
1094
|
+
if (!('query' in config)) {
|
|
1095
|
+
return false;
|
|
1096
|
+
}
|
|
1097
|
+
return true;
|
|
1098
|
+
};
|
|
1099
|
+
const isQuery = (query) => {
|
|
1100
|
+
if (!query || typeof query !== 'object' || Array.isArray(query)) {
|
|
1101
|
+
return false;
|
|
1102
|
+
}
|
|
1103
|
+
if (!('state$' in query)) {
|
|
1104
|
+
return false;
|
|
1105
|
+
}
|
|
1106
|
+
return true;
|
|
1107
|
+
};
|
|
1108
|
+
const isQueryCollection = (query) => {
|
|
1109
|
+
if (!query || typeof query !== 'object' || Array.isArray(query)) {
|
|
1110
|
+
return false;
|
|
1111
|
+
}
|
|
1112
|
+
if (!('query' in query)) {
|
|
1113
|
+
return false;
|
|
1114
|
+
}
|
|
1115
|
+
return true;
|
|
1116
|
+
};
|
|
1117
|
+
/**
|
|
1118
|
+
* @deprecated Use `createQueryCollectionSubject` instead. Will be removed in v6.
|
|
1119
|
+
*/
|
|
1120
|
+
const createQueryCollection = (
|
|
1121
|
+
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
1122
|
+
queryMap) => new BehaviorSubject(null);
|
|
1123
|
+
const createQueryCollectionSubject = (
|
|
1124
|
+
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
1125
|
+
queryMap, config) => {
|
|
1126
|
+
assertInInjectionContext(createQueryCollectionSubject);
|
|
1127
|
+
const subject = new BehaviorSubject(null);
|
|
1128
|
+
addQueryContainerHandling(subject.pipe(map((s) => s?.query ?? null)), () => subject.getValue()?.query ?? null, config);
|
|
1129
|
+
return subject;
|
|
1130
|
+
};
|
|
1131
|
+
const createQueryCollectionSignal = (
|
|
1132
|
+
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
1133
|
+
queryMap, config) => {
|
|
1134
|
+
assertInInjectionContext(createQueryCollectionSignal);
|
|
1135
|
+
const _signal = signal(null);
|
|
1136
|
+
addQueryContainerHandling(toObservable(_signal).pipe(map((s) => s?.query ?? null)), () => _signal()?.query ?? null, config);
|
|
1137
|
+
return _signal;
|
|
1138
|
+
};
|
|
1139
|
+
const extractQuery = (v) => (isQuery(v) ? v : v?.query) ?? null;
|
|
1140
|
+
const getDefaultHeaders = (headers, method) => {
|
|
1141
|
+
if (!headers) {
|
|
1142
|
+
return {};
|
|
1143
|
+
}
|
|
1144
|
+
if (method in headers) {
|
|
1145
|
+
return (headers[method] ?? {});
|
|
1146
|
+
}
|
|
1147
|
+
return headers;
|
|
1148
|
+
};
|
|
1149
|
+
const castQueryCreatorTypes = (config) => {
|
|
1150
|
+
if (config.args?.pathParams) {
|
|
1151
|
+
console.error(config);
|
|
1152
|
+
throw new Error('Path params cannot be overridden in castQueryCreatorTypes. Create a new query creator instead.');
|
|
1153
|
+
}
|
|
1154
|
+
return config.creator;
|
|
1155
|
+
};
|
|
1156
|
+
const computeQueryMethod = (config) => {
|
|
1157
|
+
let method;
|
|
1158
|
+
if (isGqlQueryConfig(config.config)) {
|
|
1159
|
+
const transferVia = config.config.transferVia ?? config.client.config.request?.gql?.transferVia;
|
|
1160
|
+
method = transformMethod({ method: config.config.method, transferVia });
|
|
1161
|
+
}
|
|
1162
|
+
else {
|
|
1163
|
+
method = transformMethod({ method: config.config.method });
|
|
1164
|
+
}
|
|
1165
|
+
return method;
|
|
1166
|
+
};
|
|
1167
|
+
const computeQueryBody = (config) => {
|
|
1168
|
+
let body;
|
|
1169
|
+
if (config.method === 'GET' ||
|
|
1170
|
+
config.method === 'HEAD' ||
|
|
1171
|
+
config.method === 'OPTIONS' ||
|
|
1172
|
+
(isGqlQueryConfig(config.config) &&
|
|
1173
|
+
(config.config.transferVia === 'GET' ||
|
|
1174
|
+
(config.client.config.request?.gql?.transferVia === 'GET' && !config.config.transferVia)))) {
|
|
1175
|
+
return undefined;
|
|
1176
|
+
}
|
|
1177
|
+
if (isGqlQueryConfig(config.config)) {
|
|
1178
|
+
const queryTemplate = config.config.query;
|
|
1179
|
+
const query = transformGql(queryTemplate);
|
|
1180
|
+
body = query(config.args?.variables);
|
|
1181
|
+
}
|
|
1182
|
+
else {
|
|
1183
|
+
body = config.args?.body;
|
|
1184
|
+
}
|
|
1185
|
+
return body;
|
|
1186
|
+
};
|
|
1187
|
+
const computeQueryAuthHeader = (config) => {
|
|
1188
|
+
let authHeader = null;
|
|
1189
|
+
if (config.config.secure) {
|
|
1190
|
+
const header = config.client.authProvider?.header;
|
|
1191
|
+
if (header) {
|
|
1192
|
+
authHeader = header;
|
|
1193
|
+
}
|
|
1194
|
+
}
|
|
1195
|
+
else if (config.client.authProvider?.header) {
|
|
1196
|
+
if (config.config.secure === undefined || config.config.secure) {
|
|
1197
|
+
authHeader = config.client.authProvider.header;
|
|
1198
|
+
}
|
|
1199
|
+
}
|
|
1200
|
+
return authHeader;
|
|
1201
|
+
};
|
|
1202
|
+
const computeQueryHeaders = (config) => {
|
|
1203
|
+
const authHeader = computeQueryAuthHeader(config);
|
|
1204
|
+
const mergedHeaders = mergeHeaders(getDefaultHeaders(config.client.config.request?.headers, config.config.method), authHeader, config.args?.headers) || undefined;
|
|
1205
|
+
return mergedHeaders;
|
|
1206
|
+
};
|
|
1207
|
+
const computeQueryQueryParams = (config) => {
|
|
1208
|
+
if (isGqlQueryConfig(config.config) &&
|
|
1209
|
+
(config.config.transferVia === 'GET' ||
|
|
1210
|
+
(config.client.config.request?.gql?.transferVia === 'GET' && !config.config.transferVia))) {
|
|
1211
|
+
const queryTemplate = config.config.query;
|
|
1212
|
+
const query = transformGql(queryTemplate);
|
|
1213
|
+
let params = query(config.args?.variables);
|
|
1214
|
+
if (config.args?.queryParams) {
|
|
1215
|
+
params = { ...params, ...config.args?.queryParams };
|
|
1216
|
+
}
|
|
1217
|
+
return params;
|
|
1218
|
+
}
|
|
1219
|
+
return config.args?.queryParams;
|
|
1220
|
+
};
|
|
1221
|
+
const paginatedEntityValueUpdater = (findFn) => ({ response, rawResponse, entity }) => {
|
|
1222
|
+
const indexOfEntityRaw = rawResponse.items.findIndex((item) => findFn(item, entity));
|
|
1223
|
+
const indexOfEntity = response.items.findIndex((item) => findFn(item, entity));
|
|
1224
|
+
if (indexOfEntity === -1 || indexOfEntityRaw === -1) {
|
|
1225
|
+
return null;
|
|
1226
|
+
}
|
|
1227
|
+
const newRawResponse = {
|
|
1228
|
+
...rawResponse,
|
|
1229
|
+
items: [
|
|
1230
|
+
...rawResponse.items.slice(0, indexOfEntityRaw),
|
|
1231
|
+
entity,
|
|
1232
|
+
...rawResponse.items.slice(indexOfEntityRaw + 1),
|
|
1233
|
+
],
|
|
1234
|
+
};
|
|
1235
|
+
const newResponse = {
|
|
1236
|
+
...response,
|
|
1237
|
+
items: [...response.items.slice(0, indexOfEntity), entity, ...response.items.slice(indexOfEntity + 1)],
|
|
1238
|
+
};
|
|
1239
|
+
return {
|
|
1240
|
+
response: newResponse,
|
|
1241
|
+
rawResponse: newRawResponse,
|
|
1242
|
+
};
|
|
1243
|
+
};
|
|
1244
|
+
|
|
1245
|
+
let _nextQueryId = 0;
|
|
1246
|
+
class Query {
|
|
1247
|
+
get _nextId() {
|
|
1248
|
+
return ++this._currentLocalId;
|
|
1249
|
+
}
|
|
1250
|
+
get state$() {
|
|
1251
|
+
return this._state$.pipe(tap((s) => {
|
|
1252
|
+
if (!this._client.config.logging?.preparedQuerySubscriptions)
|
|
1253
|
+
return;
|
|
1254
|
+
if (isQueryStatePrepared(s)) {
|
|
1255
|
+
console.warn(`Query ${this._routeWithParams} was subscribed to in the prepared state. Did you forget to call .execute()?`);
|
|
1256
|
+
}
|
|
1257
|
+
}), switchMap((s) => this._transformState(s)), shareReplay({ bufferSize: 1, refCount: true }));
|
|
1258
|
+
}
|
|
1259
|
+
/**
|
|
1260
|
+
* The current state of the query.
|
|
1261
|
+
*
|
|
1262
|
+
* **Warning!** This differs from the `state$` observable in that it does not use the query store. Thus, the response data might be outdated.
|
|
1263
|
+
* Use `state$` (or `firstValueFrom(state$)`) the most up-to-date data.
|
|
1264
|
+
*/
|
|
1265
|
+
get rawState() {
|
|
1266
|
+
return this._state$.value;
|
|
1267
|
+
}
|
|
1268
|
+
get isExpired() {
|
|
1269
|
+
if (isQueryStateLoading(this.rawState)) {
|
|
1270
|
+
return false;
|
|
1271
|
+
}
|
|
1272
|
+
if (isQueryStatePrepared(this.rawState) ||
|
|
1273
|
+
isQueryStateCancelled(this.rawState) ||
|
|
1274
|
+
isQueryStateFailure(this.rawState)) {
|
|
1275
|
+
return true;
|
|
1276
|
+
}
|
|
1277
|
+
const ts = this.rawState.meta.expiresAt;
|
|
1278
|
+
if (!ts) {
|
|
1279
|
+
return true;
|
|
1280
|
+
}
|
|
1281
|
+
return ts < Date.now();
|
|
1282
|
+
}
|
|
1283
|
+
get isInUse() {
|
|
1284
|
+
return this._state$.observed || Object.keys(this._dependents).length > 0;
|
|
1285
|
+
}
|
|
1286
|
+
/**
|
|
1287
|
+
* @internal
|
|
1288
|
+
*/
|
|
1289
|
+
get _subscriberCount() {
|
|
1290
|
+
return this._state$.observers.length;
|
|
1291
|
+
}
|
|
1292
|
+
get isPolling() {
|
|
1293
|
+
return !!this._pollingSubscription;
|
|
1294
|
+
}
|
|
1295
|
+
get autoRefreshOnConfig() {
|
|
1296
|
+
const base = this._queryConfig.autoRefreshOn ?? {};
|
|
1297
|
+
const transformed = {
|
|
1298
|
+
queryClientDefaultHeadersChange: base.queryClientDefaultHeadersChange ?? true,
|
|
1299
|
+
windowFocus: base.windowFocus ?? true,
|
|
1300
|
+
};
|
|
1301
|
+
return transformed;
|
|
1302
|
+
}
|
|
1303
|
+
/**
|
|
1304
|
+
* @internal
|
|
1305
|
+
*/
|
|
1306
|
+
get _enableSmartPolling() {
|
|
1307
|
+
return this._queryConfig.enableSmartPolling ?? true;
|
|
1308
|
+
}
|
|
1309
|
+
get store() {
|
|
1310
|
+
return this._queryConfig.entity?.store ?? null;
|
|
1311
|
+
}
|
|
1312
|
+
get canBeCached() {
|
|
1313
|
+
return shouldCacheQuery(this._queryConfig.method);
|
|
1314
|
+
}
|
|
1315
|
+
get _isInMockMode() {
|
|
1316
|
+
return !!this._arguments?.mock;
|
|
1317
|
+
}
|
|
1318
|
+
constructor(_client,
|
|
1319
|
+
/**
|
|
1320
|
+
* @internal
|
|
1321
|
+
*/
|
|
1322
|
+
_queryConfig,
|
|
1323
|
+
/**
|
|
1324
|
+
* @internal
|
|
1325
|
+
*/
|
|
1326
|
+
_routeWithParams,
|
|
1327
|
+
/**
|
|
1328
|
+
* @internal
|
|
1329
|
+
*/
|
|
1330
|
+
_arguments,
|
|
1331
|
+
/**
|
|
1332
|
+
* @internal
|
|
1333
|
+
*/
|
|
1334
|
+
_queryStoreKey) {
|
|
1335
|
+
this._client = _client;
|
|
1336
|
+
this._queryConfig = _queryConfig;
|
|
1337
|
+
this._routeWithParams = _routeWithParams;
|
|
1338
|
+
this._arguments = _arguments;
|
|
1339
|
+
this._queryStoreKey = _queryStoreKey;
|
|
1340
|
+
this._id = _nextQueryId++;
|
|
1341
|
+
this._currentLocalId = 0;
|
|
1342
|
+
this._pollingSubscription = null;
|
|
1343
|
+
this._onAbort$ = new Subject();
|
|
1344
|
+
this._currentPollConfig = null;
|
|
1345
|
+
/**
|
|
1346
|
+
* @internal
|
|
1347
|
+
* Used to track which components depend on this query.
|
|
1348
|
+
* The key is the component's _tNode index.
|
|
1349
|
+
* The value is the number of times the component has subscribed to this query.
|
|
1350
|
+
* The value might increase since we cant distinguish between a component and a host directive applied to it.
|
|
1351
|
+
*/
|
|
1352
|
+
this._dependents = {};
|
|
1353
|
+
/**
|
|
1354
|
+
* @internal
|
|
1355
|
+
*/
|
|
1356
|
+
this._dependentsChanged$ = new ReplaySubject();
|
|
1357
|
+
/**
|
|
1358
|
+
* @internal
|
|
1359
|
+
*/
|
|
1360
|
+
this._isPollingPaused = false;
|
|
1361
|
+
this._state$ = new BehaviorSubject({
|
|
1362
|
+
type: "PREPARED" /* QueryStateType.Prepared */,
|
|
1363
|
+
meta: { id: this._currentLocalId, triggeredVia: 'program' },
|
|
1364
|
+
});
|
|
1365
|
+
}
|
|
1366
|
+
execute(options = {}) {
|
|
1367
|
+
const { skipCache = false, _triggeredVia: triggeredVia = 'program', cancelPrevious = false } = options;
|
|
1368
|
+
const { authProvider } = this._client;
|
|
1369
|
+
const queryConfig = this._queryConfig;
|
|
1370
|
+
if (!this.isExpired && !skipCache && isQueryStateSuccess(this.rawState)) {
|
|
1371
|
+
return this;
|
|
1372
|
+
}
|
|
1373
|
+
if (queryConfig.secure && !authProvider && !this._isInMockMode) {
|
|
1374
|
+
throw new Error('Cannot execute secure query without auth provider');
|
|
1375
|
+
}
|
|
1376
|
+
const id = this._nextId;
|
|
1377
|
+
const meta = { id, triggeredVia };
|
|
1378
|
+
if (isQueryStateLoading(this.rawState)) {
|
|
1379
|
+
if (cancelPrevious) {
|
|
1380
|
+
this.abort();
|
|
1381
|
+
}
|
|
1382
|
+
else {
|
|
1383
|
+
return this;
|
|
1384
|
+
}
|
|
1385
|
+
}
|
|
1386
|
+
this._updateState({ type: "LOADING" /* QueryStateType.Loading */, meta });
|
|
1387
|
+
if (this._isInMockMode) {
|
|
1388
|
+
this._mockRequest({}, meta, options);
|
|
1389
|
+
}
|
|
1390
|
+
else {
|
|
1391
|
+
let goSignal = of(true);
|
|
1392
|
+
if (authProvider &&
|
|
1393
|
+
isBearerAuthProvider(authProvider) &&
|
|
1394
|
+
queryConfig.secure &&
|
|
1395
|
+
!isQueryStateSuccess(authProvider.currentRefreshQuery?.rawState)) {
|
|
1396
|
+
// On page load the query might be executed before the auth provider has been initialized.
|
|
1397
|
+
// In this case we need to wait for the auth provider to be ready before we can execute the query.
|
|
1398
|
+
goSignal = combineLatest([
|
|
1399
|
+
// The tokens might get set without a refresh query being created (e.g.) directly after login.
|
|
1400
|
+
authProvider.currentRefreshQuery$.pipe(switchQueryState(), takeUntilResponse()),
|
|
1401
|
+
authProvider.tokens$.pipe(take(1)),
|
|
1402
|
+
]).pipe(takeUntil(this._onAbort$), filter(([state, tokens]) => {
|
|
1403
|
+
if (state) {
|
|
1404
|
+
return !isQueryStateLoading(state);
|
|
1405
|
+
}
|
|
1406
|
+
return !!tokens;
|
|
1407
|
+
}), tap(([state]) => {
|
|
1408
|
+
if (isQueryStateFailure(state)) {
|
|
1409
|
+
this._updateEntityState({ type: 'failure', error: state.error, headers: {} }, meta, options);
|
|
1410
|
+
}
|
|
1411
|
+
}), map(() => true), take(1));
|
|
1412
|
+
}
|
|
1413
|
+
goSignal
|
|
1414
|
+
.pipe(switchMap(() => {
|
|
1415
|
+
const method = computeQueryMethod({ config: queryConfig, client: this._client });
|
|
1416
|
+
const body = computeQueryBody({ config: queryConfig, client: this._client, args: this._arguments, method });
|
|
1417
|
+
const headers = computeQueryHeaders({ client: this._client, config: queryConfig, args: this._arguments });
|
|
1418
|
+
return request({
|
|
1419
|
+
urlWithParams: this._routeWithParams,
|
|
1420
|
+
method,
|
|
1421
|
+
body,
|
|
1422
|
+
headers,
|
|
1423
|
+
reportProgress: queryConfig.reportProgress,
|
|
1424
|
+
responseType: queryConfig.responseType,
|
|
1425
|
+
withCredentials: queryConfig.withCredentials,
|
|
1426
|
+
cacheAdapter: this._client.config.request?.cacheAdapter,
|
|
1427
|
+
retryFn: this._client.config.request?.retryFn,
|
|
1428
|
+
}).pipe(tap((state) => this._updateEntityState(state, meta, options)), takeUntil(this._onAbort$));
|
|
1429
|
+
}))
|
|
1430
|
+
.subscribe();
|
|
1431
|
+
}
|
|
1432
|
+
return this;
|
|
1433
|
+
}
|
|
1434
|
+
abort() {
|
|
1435
|
+
if (!isQueryStateLoading(this.rawState)) {
|
|
1436
|
+
return this;
|
|
1437
|
+
}
|
|
1438
|
+
this._onAbort$.next();
|
|
1439
|
+
this._updateState({
|
|
1440
|
+
type: "CANCELLED" /* QueryStateType.Cancelled */,
|
|
1441
|
+
meta: { id: this.rawState.meta.id, triggeredVia: this.rawState.meta.triggeredVia },
|
|
1442
|
+
});
|
|
1443
|
+
return this;
|
|
1444
|
+
}
|
|
1445
|
+
poll(config) {
|
|
1446
|
+
if (this._pollingSubscription) {
|
|
1447
|
+
return this;
|
|
1448
|
+
}
|
|
1449
|
+
this._currentPollConfig = config;
|
|
1450
|
+
const interval$ = interval(config.interval);
|
|
1451
|
+
const poll$ = interval$.pipe(skip(config.triggerImmediately ? 0 : 1), takeUntil(config.takeUntil), takeWhile(() => !this._isPollingPaused), filter(() => !isQueryStateLoading(this._state$.value)));
|
|
1452
|
+
this._pollingSubscription = poll$.subscribe({
|
|
1453
|
+
next: () => this.execute({ skipCache: true, _triggeredVia: 'poll' }),
|
|
1454
|
+
complete: () => this.stopPolling(),
|
|
1455
|
+
});
|
|
1456
|
+
return this;
|
|
1457
|
+
}
|
|
1458
|
+
stopPolling() {
|
|
1459
|
+
this._pollingSubscription?.unsubscribe();
|
|
1460
|
+
this._pollingSubscription = null;
|
|
1461
|
+
this._currentPollConfig = null;
|
|
1462
|
+
return this;
|
|
1463
|
+
}
|
|
1464
|
+
pausePolling() {
|
|
1465
|
+
this._pollingSubscription?.unsubscribe();
|
|
1466
|
+
this._pollingSubscription = null;
|
|
1467
|
+
this._isPollingPaused = true;
|
|
1468
|
+
return this;
|
|
1469
|
+
}
|
|
1470
|
+
resumePolling() {
|
|
1471
|
+
if (!this._isPollingPaused || !this._currentPollConfig) {
|
|
1472
|
+
return this;
|
|
1473
|
+
}
|
|
1474
|
+
this._isPollingPaused = false;
|
|
1475
|
+
return this.poll({ ...this._currentPollConfig, triggerImmediately: true });
|
|
1476
|
+
}
|
|
1477
|
+
onSuccess(callback) {
|
|
1478
|
+
this.state$.pipe(takeUntilResponse(), filterSuccess()).subscribe((state) => callback(state.response));
|
|
1479
|
+
return this;
|
|
1480
|
+
}
|
|
1481
|
+
onFailure(callback) {
|
|
1482
|
+
this.state$.pipe(takeUntilResponse(), filterFailure()).subscribe((state) => callback(state.error));
|
|
1483
|
+
return this;
|
|
1484
|
+
}
|
|
1485
|
+
_getBearerAuthProvider() {
|
|
1486
|
+
const authProvider = this._client.authProvider;
|
|
1487
|
+
if (authProvider && isBearerAuthProvider(authProvider)) {
|
|
1488
|
+
return authProvider;
|
|
1489
|
+
}
|
|
1490
|
+
return null;
|
|
1491
|
+
}
|
|
1492
|
+
_updateEntityState(requestEvent, meta, options) {
|
|
1493
|
+
switch (requestEvent.type) {
|
|
1494
|
+
case 'start':
|
|
1495
|
+
case 'delay-retry': {
|
|
1496
|
+
const { type, retryDelay, retryNumber } = requestEvent;
|
|
1497
|
+
const newMeta = { ...meta, isWaitingForRetry: type === 'delay-retry', retryDelay, retryNumber };
|
|
1498
|
+
this._updateState({
|
|
1499
|
+
type: "LOADING" /* QueryStateType.Loading */,
|
|
1500
|
+
meta: newMeta,
|
|
1501
|
+
});
|
|
1502
|
+
break;
|
|
1503
|
+
}
|
|
1504
|
+
case 'upload-progress':
|
|
1505
|
+
case 'download-progress': {
|
|
1506
|
+
this._updateState({
|
|
1507
|
+
type: "LOADING" /* QueryStateType.Loading */,
|
|
1508
|
+
progress: requestEvent.progress,
|
|
1509
|
+
partialText: 'partialText' in requestEvent ? requestEvent.partialText : undefined,
|
|
1510
|
+
meta,
|
|
1511
|
+
});
|
|
1512
|
+
break;
|
|
1513
|
+
}
|
|
1514
|
+
case 'success': {
|
|
1515
|
+
const { response, expiresInTimestamp } = requestEvent;
|
|
1516
|
+
const isGql = isGqlQueryConfig(this._queryConfig);
|
|
1517
|
+
const responseData = isGql && typeof response === 'object' && !!response && 'data' in response
|
|
1518
|
+
? response['data']
|
|
1519
|
+
: response;
|
|
1520
|
+
if (this._queryConfig.entity && this._queryConfig.entity.set) {
|
|
1521
|
+
const id = this._queryConfig.entity?.id({ args: this._arguments, response: responseData });
|
|
1522
|
+
this._queryConfig.entity?.set({
|
|
1523
|
+
args: this._arguments,
|
|
1524
|
+
response: responseData,
|
|
1525
|
+
id,
|
|
1526
|
+
store: this._queryConfig.entity.store,
|
|
1527
|
+
});
|
|
1528
|
+
}
|
|
1529
|
+
this._updateState({
|
|
1530
|
+
type: "SUCCESS" /* QueryStateType.Success */,
|
|
1531
|
+
response: responseData,
|
|
1532
|
+
meta: { ...meta, expiresAt: expiresInTimestamp },
|
|
1533
|
+
});
|
|
1534
|
+
break;
|
|
1535
|
+
}
|
|
1536
|
+
case 'failure': {
|
|
1537
|
+
const { error } = requestEvent;
|
|
1538
|
+
const failure = () => this._updateState({ type: "FAILURE" /* QueryStateType.Failure */, error, meta });
|
|
1539
|
+
const bearerAuthProvider = this._getBearerAuthProvider();
|
|
1540
|
+
if (!bearerAuthProvider ||
|
|
1541
|
+
options?._isUnauthorizedRetry ||
|
|
1542
|
+
error.status !== 401 /* HttpStatusCode.Unauthorized */ ||
|
|
1543
|
+
!bearerAuthProvider.shouldRefreshOnUnauthorizedResponse) {
|
|
1544
|
+
return failure();
|
|
1545
|
+
}
|
|
1546
|
+
const query = bearerAuthProvider._refreshQuery();
|
|
1547
|
+
if (!query)
|
|
1548
|
+
return failure();
|
|
1549
|
+
query.state$
|
|
1550
|
+
.pipe(takeUntilResponse(), takeUntil(this._onAbort$), tap((state) => isQueryStateSuccess(state) ? this.execute({ ...options, _isUnauthorizedRetry: true }) : failure()))
|
|
1551
|
+
.subscribe();
|
|
1552
|
+
break;
|
|
1553
|
+
}
|
|
1554
|
+
case 'cancel': {
|
|
1555
|
+
this._updateState({
|
|
1556
|
+
type: "CANCELLED" /* QueryStateType.Cancelled */,
|
|
1557
|
+
meta,
|
|
1558
|
+
});
|
|
1559
|
+
break;
|
|
1560
|
+
}
|
|
1561
|
+
}
|
|
1562
|
+
}
|
|
1563
|
+
_transformState(s) {
|
|
1564
|
+
if (!isQueryStateSuccess(s) || !this._queryConfig.entity?.get) {
|
|
1565
|
+
return of(s);
|
|
1566
|
+
}
|
|
1567
|
+
const id = this._queryConfig.entity.id({ args: this._arguments, response: s.response });
|
|
1568
|
+
return this._queryConfig.entity
|
|
1569
|
+
.get({ args: this._arguments, id, response: s.response, store: this._queryConfig.entity.store })
|
|
1570
|
+
.pipe(map((v) => ({ ...s, response: v })));
|
|
1571
|
+
}
|
|
1572
|
+
_updateState(s) {
|
|
1573
|
+
// We need to use untracked here to avoid Angular's "allowSignalWrites is false" error when executing queries inside Angular's computed/effect signal functions.
|
|
1574
|
+
untracked(() => this._state$.next(s));
|
|
1575
|
+
}
|
|
1576
|
+
_mockRequest(headers, meta, options) {
|
|
1577
|
+
if (!this._arguments?.mock)
|
|
1578
|
+
return;
|
|
1579
|
+
const { delay = 200, error, response, retryIntoResponse = false, progress } = this._arguments.mock;
|
|
1580
|
+
const progressFileSize = progress?.fileSize ?? 1000000;
|
|
1581
|
+
const progressEventType = progress?.eventType ?? 'upload';
|
|
1582
|
+
const progressEventCount = progress?.eventCount ?? 5;
|
|
1583
|
+
const progressOmitTotal = progress?.omitTotal ?? false;
|
|
1584
|
+
const omitPartialText = progress?.omitPartialText ?? false;
|
|
1585
|
+
const finalResolveMock = () => {
|
|
1586
|
+
if (error) {
|
|
1587
|
+
this._updateEntityState({ error, headers: headers ?? {}, type: 'failure' }, meta, options);
|
|
1588
|
+
return;
|
|
1589
|
+
}
|
|
1590
|
+
this._updateEntityState({ response: response, headers: headers ?? {}, type: 'success' }, meta, options);
|
|
1591
|
+
};
|
|
1592
|
+
if (progress) {
|
|
1593
|
+
interval(delay)
|
|
1594
|
+
.pipe(tap((a) => {
|
|
1595
|
+
if (a === progressEventCount + 1) {
|
|
1596
|
+
finalResolveMock();
|
|
1597
|
+
}
|
|
1598
|
+
else {
|
|
1599
|
+
const current = Math.min(progressFileSize * a, progressFileSize * progressEventCount);
|
|
1600
|
+
const percentage = (current / (progressFileSize * progressEventCount)) * 100;
|
|
1601
|
+
this._updateEntityState({
|
|
1602
|
+
type: progressEventType === 'upload' ? 'upload-progress' : 'download-progress',
|
|
1603
|
+
headers: headers ?? {},
|
|
1604
|
+
progress: { current, ...(progressOmitTotal ? {} : { percentage, total: progressFileSize }) },
|
|
1605
|
+
partialText: omitPartialText ? undefined : `Partial text ${a}`,
|
|
1606
|
+
}, meta, options);
|
|
1607
|
+
}
|
|
1608
|
+
}), takeUntil(this._onAbort$), takeWhile((a) => a < progressEventCount + 1, true))
|
|
1609
|
+
.subscribe();
|
|
1610
|
+
}
|
|
1611
|
+
else if (retryIntoResponse) {
|
|
1612
|
+
interval(delay)
|
|
1613
|
+
.pipe(tap((a) => {
|
|
1614
|
+
if (a === 3) {
|
|
1615
|
+
finalResolveMock();
|
|
1616
|
+
}
|
|
1617
|
+
else {
|
|
1618
|
+
const aModified = a + 1;
|
|
1619
|
+
this._updateEntityState({
|
|
1620
|
+
type: 'delay-retry',
|
|
1621
|
+
headers: headers ?? {},
|
|
1622
|
+
retryDelay: 1000 + 1000 * aModified,
|
|
1623
|
+
retryNumber: aModified,
|
|
1624
|
+
}, meta, options);
|
|
1625
|
+
}
|
|
1626
|
+
}), takeUntil(this._onAbort$), takeWhile((a) => a < 3, true))
|
|
1627
|
+
.subscribe();
|
|
1628
|
+
}
|
|
1629
|
+
else {
|
|
1630
|
+
timer(delay)
|
|
1631
|
+
.pipe(tap(() => finalResolveMock()), takeUntil(this._onAbort$))
|
|
1632
|
+
.subscribe();
|
|
1633
|
+
}
|
|
1634
|
+
}
|
|
1635
|
+
/**
|
|
1636
|
+
* @internal
|
|
1637
|
+
*/
|
|
1638
|
+
_addDependent(tNodeIndex) {
|
|
1639
|
+
if (!this._dependents[tNodeIndex]) {
|
|
1640
|
+
this._dependents[tNodeIndex] = 0;
|
|
1641
|
+
}
|
|
1642
|
+
this._dependents[tNodeIndex]++;
|
|
1643
|
+
this._dependentsChanged$.next(this._dependents);
|
|
1644
|
+
}
|
|
1645
|
+
/**
|
|
1646
|
+
* @internal
|
|
1647
|
+
*/
|
|
1648
|
+
_removeDependent(tNodeIndex) {
|
|
1649
|
+
const count = this._dependents[tNodeIndex];
|
|
1650
|
+
if (count === undefined) {
|
|
1651
|
+
return;
|
|
1652
|
+
}
|
|
1653
|
+
if (this._dependents[tNodeIndex] !== undefined) {
|
|
1654
|
+
this._dependents[tNodeIndex]--;
|
|
1655
|
+
}
|
|
1656
|
+
if (count <= 1) {
|
|
1657
|
+
delete this._dependents[tNodeIndex];
|
|
1658
|
+
}
|
|
1659
|
+
this._dependentsChanged$.next(this._dependents);
|
|
1660
|
+
}
|
|
1661
|
+
/**
|
|
1662
|
+
* @internal
|
|
1663
|
+
*/
|
|
1664
|
+
_hasDependents() {
|
|
1665
|
+
return Object.keys(this._dependents).length > 0;
|
|
1666
|
+
}
|
|
1667
|
+
}
|
|
1668
|
+
|
|
1669
|
+
const isBasicAuthProvider = (authProvider) => authProvider instanceof BasicAuthProvider;
|
|
1670
|
+
const isBearerAuthProvider = (authProvider) => authProvider instanceof BearerAuthProvider;
|
|
1671
|
+
const isCustomHeaderAuthProvider = (authProvider) => authProvider instanceof CustomHeaderAuthProvider;
|
|
1672
|
+
const decryptBearer = (token) => {
|
|
1673
|
+
try {
|
|
1674
|
+
const base64Url = token.split('.')[1];
|
|
1675
|
+
if (!base64Url) {
|
|
1676
|
+
return null;
|
|
1677
|
+
}
|
|
1678
|
+
const base64 = base64Url.replace(/-/g, '+').replace(/_/g, '/');
|
|
1679
|
+
const jsonPayload = decodeURIComponent(atob(base64)
|
|
1680
|
+
.split('')
|
|
1681
|
+
.map((c) => '%' + ('00' + c.charCodeAt(0).toString(16)).slice(-2))
|
|
1682
|
+
.join(''));
|
|
1683
|
+
return JSON.parse(jsonPayload);
|
|
1684
|
+
}
|
|
1685
|
+
catch (error) {
|
|
1686
|
+
console.error(`Invalid bearer token: ${token}`, error);
|
|
1687
|
+
return null;
|
|
1688
|
+
}
|
|
1689
|
+
};
|
|
1690
|
+
|
|
1691
|
+
class BearerAuthProvider {
|
|
1692
|
+
get header() {
|
|
1693
|
+
return { Authorization: `Bearer ${this._tokens$.getValue().token}` };
|
|
1694
|
+
}
|
|
1695
|
+
get tokens$() {
|
|
1696
|
+
return this._tokens$.asObservable();
|
|
1697
|
+
}
|
|
1698
|
+
get tokens() {
|
|
1699
|
+
return this._tokens$.getValue();
|
|
1700
|
+
}
|
|
1701
|
+
get currentRefreshQuery$() {
|
|
1702
|
+
return this._currentRefreshQuery$.asObservable();
|
|
1703
|
+
}
|
|
1704
|
+
get currentRefreshQuery() {
|
|
1705
|
+
return this._currentRefreshQuery$.getValue();
|
|
1706
|
+
}
|
|
1707
|
+
get shouldRefreshOnUnauthorizedResponse() {
|
|
1708
|
+
return (!!this._config.refreshConfig &&
|
|
1709
|
+
(this._config.refreshConfig.refreshOnUnauthorizedResponse === undefined ||
|
|
1710
|
+
this._config.refreshConfig.refreshOnUnauthorizedResponse));
|
|
1711
|
+
}
|
|
1712
|
+
constructor(_config) {
|
|
1713
|
+
this._config = _config;
|
|
1714
|
+
this._destroy$ = new Subject();
|
|
1715
|
+
this._currentRefreshQuery$ = new BehaviorSubject(null);
|
|
1716
|
+
this._tokens$ = new BehaviorSubject({
|
|
1717
|
+
token: null,
|
|
1718
|
+
refreshToken: null,
|
|
1719
|
+
});
|
|
1720
|
+
const cookieEnabled = _config.refreshConfig?.cookieEnabled ?? true;
|
|
1721
|
+
const cookieToken = _config.refreshConfig?.cookieName && cookieEnabled ? getCookie(_config.refreshConfig.cookieName) ?? null : null;
|
|
1722
|
+
this._tokens$.next({
|
|
1723
|
+
token: _config.token || null,
|
|
1724
|
+
refreshToken: _config.refreshConfig?.token || cookieToken || null,
|
|
1725
|
+
});
|
|
1726
|
+
if (!this.tokens.token && !this.tokens.refreshToken) {
|
|
1727
|
+
if (!_config.refreshConfig?.cookieName) {
|
|
1728
|
+
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.');
|
|
1729
|
+
}
|
|
1730
|
+
return;
|
|
1731
|
+
}
|
|
1732
|
+
if (this.tokens.token) {
|
|
1733
|
+
this._prepareForRefresh();
|
|
1734
|
+
if (this.tokens.refreshToken && this._config.refreshConfig?.cookieName && cookieEnabled) {
|
|
1735
|
+
this._setCookie();
|
|
1736
|
+
}
|
|
1737
|
+
}
|
|
1738
|
+
else if (this.tokens.refreshToken) {
|
|
1739
|
+
this._refreshQuery();
|
|
1740
|
+
}
|
|
1741
|
+
}
|
|
1742
|
+
cleanUp() {
|
|
1743
|
+
this._destroy$.next(true);
|
|
1744
|
+
this._destroy$.unsubscribe();
|
|
1745
|
+
this._currentRefreshQuery$.next(null);
|
|
1746
|
+
if (this._config.refreshConfig?.cookieName) {
|
|
1747
|
+
this._deleteCookie();
|
|
1748
|
+
}
|
|
1749
|
+
}
|
|
1750
|
+
enableCookie() {
|
|
1751
|
+
if (!this._config.refreshConfig?.cookieName) {
|
|
1752
|
+
throw new Error('No cookie name was provided');
|
|
1753
|
+
}
|
|
1754
|
+
if (!this.tokens.refreshToken) {
|
|
1755
|
+
throw new Error('No refresh token was provided');
|
|
1756
|
+
}
|
|
1757
|
+
this._config.refreshConfig.cookieEnabled = true;
|
|
1758
|
+
this._setCookie();
|
|
1759
|
+
}
|
|
1760
|
+
disableCookie() {
|
|
1761
|
+
if (!this._config.refreshConfig?.cookieName) {
|
|
1762
|
+
throw new Error('No cookie name was provided');
|
|
1763
|
+
}
|
|
1764
|
+
this._config.refreshConfig.cookieEnabled = false;
|
|
1765
|
+
this._deleteCookie();
|
|
1766
|
+
}
|
|
1767
|
+
_setCookie() {
|
|
1768
|
+
if (!this._config.refreshConfig?.cookieName || !this.tokens.refreshToken)
|
|
1769
|
+
return;
|
|
1770
|
+
setCookie(this._config.refreshConfig.cookieName, this.tokens.refreshToken, this._config.refreshConfig.cookieExpiresInDays, this._config.refreshConfig.cookieDomain, this._config.refreshConfig.cookiePath, this._config.refreshConfig.cookieSameSite);
|
|
1771
|
+
}
|
|
1772
|
+
_deleteCookie() {
|
|
1773
|
+
if (!this._config.refreshConfig?.cookieName)
|
|
1774
|
+
return;
|
|
1775
|
+
deleteCookie(this._config.refreshConfig.cookieName, this._config.refreshConfig.cookiePath, this._config.refreshConfig.cookieDomain);
|
|
1776
|
+
}
|
|
1777
|
+
_prepareForRefresh() {
|
|
1778
|
+
if (!this.tokens.token || !this._config.refreshConfig) {
|
|
1779
|
+
return;
|
|
1780
|
+
}
|
|
1781
|
+
const bearer = decryptBearer(this.tokens.token);
|
|
1782
|
+
if (!bearer) {
|
|
1783
|
+
return;
|
|
1784
|
+
}
|
|
1785
|
+
const expiresInPropertyName = this._config.refreshConfig.expiresInPropertyName || 'exp';
|
|
1786
|
+
const expiresIn = bearer[expiresInPropertyName];
|
|
1787
|
+
const fiveMinutes = 5 * 60 * 1000;
|
|
1788
|
+
const refreshBuffer = this._config.refreshConfig.refreshBuffer ?? fiveMinutes;
|
|
1789
|
+
if (expiresIn === undefined) {
|
|
1790
|
+
throw new Error(`Bearer token does not contain an '${expiresInPropertyName}' property`);
|
|
1791
|
+
}
|
|
1792
|
+
const remainingTime = new Date(expiresIn * 1000).getTime() - refreshBuffer - new Date().getTime();
|
|
1793
|
+
const strategy = this._config.refreshConfig.strategy ?? "beforeExpiration" /* AuthBearerRefreshStrategy.BeforeExpiration */;
|
|
1794
|
+
switch (strategy) {
|
|
1795
|
+
case "beforeExpiration" /* AuthBearerRefreshStrategy.BeforeExpiration */:
|
|
1796
|
+
timer(remainingTime)
|
|
1797
|
+
.pipe(takeUntil(this._destroy$))
|
|
1798
|
+
.subscribe(() => this._refreshQuery());
|
|
1799
|
+
break;
|
|
1800
|
+
}
|
|
1801
|
+
}
|
|
1802
|
+
/**
|
|
1803
|
+
* @internal
|
|
1804
|
+
*/
|
|
1805
|
+
_refreshQuery() {
|
|
1806
|
+
const currentQuery = this._currentRefreshQuery$.getValue();
|
|
1807
|
+
if (isQueryStateLoading(currentQuery?.rawState)) {
|
|
1808
|
+
return currentQuery;
|
|
1809
|
+
}
|
|
1810
|
+
const currentRefreshToken = this.tokens.refreshToken;
|
|
1811
|
+
if (!currentRefreshToken || !this._config.refreshConfig) {
|
|
1812
|
+
return;
|
|
1813
|
+
}
|
|
1814
|
+
const args = this._config.refreshConfig.requestArgsAdapter?.({
|
|
1815
|
+
token: this.tokens.token,
|
|
1816
|
+
refreshToken: currentRefreshToken,
|
|
1817
|
+
}) ?? {
|
|
1818
|
+
body: { refreshToken: currentRefreshToken },
|
|
1819
|
+
};
|
|
1820
|
+
const query = this._config.refreshConfig.queryCreator.prepare(args).execute({ skipCache: true });
|
|
1821
|
+
this._currentRefreshQuery$.next(query);
|
|
1822
|
+
this._currentRefreshQuery$
|
|
1823
|
+
.pipe(switchQueryState(), tap((state) => {
|
|
1824
|
+
if (isQueryStateSuccess(state)) {
|
|
1825
|
+
if (this._config.refreshConfig?.responseAdapter) {
|
|
1826
|
+
const tokens = this._config.refreshConfig.responseAdapter(state.response);
|
|
1827
|
+
this._tokens$.next(tokens);
|
|
1828
|
+
}
|
|
1829
|
+
else {
|
|
1830
|
+
this._tokens$.next({
|
|
1831
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
1832
|
+
token: state.response['token'],
|
|
1833
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
1834
|
+
refreshToken: state.response['refreshToken'],
|
|
1835
|
+
});
|
|
1836
|
+
}
|
|
1837
|
+
const cookieEnabled = this._config.refreshConfig?.cookieEnabled ?? true;
|
|
1838
|
+
if (this._config.refreshConfig?.cookieName && this.tokens.refreshToken && cookieEnabled) {
|
|
1839
|
+
this._setCookie();
|
|
1840
|
+
}
|
|
1841
|
+
this._prepareForRefresh();
|
|
1842
|
+
}
|
|
1843
|
+
else if (isQueryStateFailure(state)) {
|
|
1844
|
+
this._tokens$.next({ token: null, refreshToken: null });
|
|
1845
|
+
if (this._config.refreshConfig?.cookieName) {
|
|
1846
|
+
this._deleteCookie();
|
|
1847
|
+
}
|
|
1848
|
+
}
|
|
1849
|
+
}), takeUntilResponse(), takeUntil(this._destroy$))
|
|
1850
|
+
.subscribe();
|
|
1851
|
+
return query;
|
|
1852
|
+
}
|
|
1853
|
+
}
|
|
1854
|
+
|
|
1855
|
+
const isClassValidatorError = (error) => {
|
|
1856
|
+
return typeof error === 'object' && error !== null && 'statusCode' in error && 'message' in error && 'error' in error;
|
|
1857
|
+
};
|
|
1858
|
+
|
|
1859
|
+
class QueryDirective {
|
|
1860
|
+
constructor() {
|
|
1861
|
+
this._mainTemplateRef = inject(TemplateRef);
|
|
1862
|
+
this._viewContainerRef = inject(ViewContainerRef);
|
|
1863
|
+
this._errorHandler = inject(ErrorHandler);
|
|
1864
|
+
this._cdr = inject(ChangeDetectorRef);
|
|
1865
|
+
this._isMainViewCreated = false;
|
|
1866
|
+
this._subscription = null;
|
|
1867
|
+
this._viewContext = {
|
|
1868
|
+
$implicit: null,
|
|
1869
|
+
etQuery: null,
|
|
1870
|
+
loading: false,
|
|
1871
|
+
refreshing: false,
|
|
1872
|
+
error: null,
|
|
1873
|
+
progress: null,
|
|
1874
|
+
scope: null,
|
|
1875
|
+
query: null,
|
|
1876
|
+
};
|
|
1877
|
+
this._cache = false;
|
|
1878
|
+
}
|
|
1879
|
+
get query() {
|
|
1880
|
+
return this._query;
|
|
1881
|
+
}
|
|
1882
|
+
set query(v) {
|
|
1883
|
+
this._query = v;
|
|
1884
|
+
this._subscribeToQuery();
|
|
1885
|
+
}
|
|
1886
|
+
get cache() {
|
|
1887
|
+
return this._cache;
|
|
1888
|
+
}
|
|
1889
|
+
set cache(v) {
|
|
1890
|
+
this._cache = v;
|
|
1891
|
+
}
|
|
1892
|
+
static ngTemplateContextGuard(dir, ctx) {
|
|
1893
|
+
return true;
|
|
1894
|
+
}
|
|
1895
|
+
ngOnInit() {
|
|
1896
|
+
this._renderMainView();
|
|
1897
|
+
}
|
|
1898
|
+
ngOnDestroy() {
|
|
1899
|
+
this._subscription?.unsubscribe();
|
|
1900
|
+
}
|
|
1901
|
+
_subscribeToQuery() {
|
|
1902
|
+
this._subscription?.unsubscribe();
|
|
1903
|
+
this._subscription = null;
|
|
1904
|
+
const query = extractQuery(this.query);
|
|
1905
|
+
if (!query) {
|
|
1906
|
+
this._viewContext.$implicit = null;
|
|
1907
|
+
this._viewContext.etQuery = null;
|
|
1908
|
+
this._viewContext.loading = false;
|
|
1909
|
+
this._viewContext.refreshing = false;
|
|
1910
|
+
this._viewContext.error = null;
|
|
1911
|
+
this._viewContext.progress = null;
|
|
1912
|
+
this._viewContext.scope = null;
|
|
1913
|
+
this._viewContext.query = null;
|
|
1914
|
+
return;
|
|
1915
|
+
}
|
|
1916
|
+
if (isQueryStatePrepared(query.rawState) || isQueryStateCancelled(query.rawState)) {
|
|
1917
|
+
query.execute();
|
|
1918
|
+
}
|
|
1919
|
+
const sub = query.state$.pipe(tap((state) => this._updateView(state))).subscribe();
|
|
1920
|
+
this._viewContext.scope = isQueryCollection(query) ? query.type : null;
|
|
1921
|
+
this._viewContext.query = query;
|
|
1922
|
+
this._subscription = sub;
|
|
1923
|
+
}
|
|
1924
|
+
_updateView(state) {
|
|
1925
|
+
if (isQueryStateLoading(state)) {
|
|
1926
|
+
this._viewContext.progress = state.progress ?? null;
|
|
1927
|
+
this._viewContext.refreshing = state.meta.triggeredVia === 'auto' || state.meta.triggeredVia === 'poll';
|
|
1928
|
+
if (!this._viewContext.refreshing) {
|
|
1929
|
+
this._viewContext.loading = true;
|
|
1930
|
+
}
|
|
1931
|
+
}
|
|
1932
|
+
else {
|
|
1933
|
+
this._viewContext.loading = false;
|
|
1934
|
+
this._viewContext.refreshing = false;
|
|
1935
|
+
this._viewContext.progress = null;
|
|
1936
|
+
}
|
|
1937
|
+
if (isQueryStateSuccess(state)) {
|
|
1938
|
+
this._viewContext.etQuery = state.response;
|
|
1939
|
+
this._viewContext.$implicit = state.response;
|
|
1940
|
+
}
|
|
1941
|
+
else if (!this.cache) {
|
|
1942
|
+
this._viewContext.etQuery = null;
|
|
1943
|
+
this._viewContext.$implicit = null;
|
|
1944
|
+
}
|
|
1945
|
+
if (isQueryStateFailure(state)) {
|
|
1946
|
+
this._viewContext.error = state.error;
|
|
1947
|
+
this._errorHandler.handleError(state.error);
|
|
1948
|
+
}
|
|
1949
|
+
else {
|
|
1950
|
+
this._viewContext.error = null;
|
|
1951
|
+
}
|
|
1952
|
+
this._cdr.markForCheck();
|
|
1953
|
+
}
|
|
1954
|
+
_renderMainView() {
|
|
1955
|
+
if (!this._isMainViewCreated) {
|
|
1956
|
+
this._isMainViewCreated = true;
|
|
1957
|
+
this._viewContainerRef.createEmbeddedView(this._mainTemplateRef, this._viewContext);
|
|
1958
|
+
}
|
|
1959
|
+
}
|
|
1960
|
+
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "18.1.1", ngImport: i0, type: QueryDirective, deps: [], target: i0.ɵɵFactoryTarget.Directive }); }
|
|
1961
|
+
static { this.ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "18.1.1", type: QueryDirective, isStandalone: true, selector: "[etQuery]", inputs: { query: ["etQuery", "query"], cache: ["etQueryCache", "cache"] }, ngImport: i0 }); }
|
|
1962
|
+
}
|
|
1963
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.1.1", ngImport: i0, type: QueryDirective, decorators: [{
|
|
1964
|
+
type: Directive,
|
|
1965
|
+
args: [{
|
|
1966
|
+
// eslint-disable-next-line @angular-eslint/directive-selector
|
|
1967
|
+
selector: '[etQuery]',
|
|
1968
|
+
standalone: true,
|
|
1969
|
+
}]
|
|
1970
|
+
}], propDecorators: { query: [{
|
|
1971
|
+
type: Input,
|
|
1972
|
+
args: ['etQuery']
|
|
1973
|
+
}], cache: [{
|
|
1974
|
+
type: Input,
|
|
1975
|
+
args: ['etQueryCache']
|
|
1976
|
+
}] } });
|
|
1977
|
+
|
|
1978
|
+
class InfinityQuery {
|
|
1979
|
+
get currentPage$() {
|
|
1980
|
+
return this._currentPage$.asObservable();
|
|
1981
|
+
}
|
|
1982
|
+
get currentPage() {
|
|
1983
|
+
return this._currentPage$.getValue();
|
|
1984
|
+
}
|
|
1985
|
+
get currentCalculatedPage$() {
|
|
1986
|
+
return this._currentCalculatedPage$.asObservable();
|
|
1987
|
+
}
|
|
1988
|
+
get currentCalculatedPage() {
|
|
1989
|
+
return this._currentCalculatedPage$.getValue();
|
|
1990
|
+
}
|
|
1991
|
+
get totalPages$() {
|
|
1992
|
+
return this._totalPages$.asObservable();
|
|
1993
|
+
}
|
|
1994
|
+
get totalPages() {
|
|
1995
|
+
return this._totalPages$.getValue();
|
|
1996
|
+
}
|
|
1997
|
+
get itemsPerPage$() {
|
|
1998
|
+
return this._itemsPerPage$.asObservable();
|
|
1999
|
+
}
|
|
2000
|
+
get itemsPerPage() {
|
|
2001
|
+
return this._itemsPerPage$.getValue();
|
|
2002
|
+
}
|
|
2003
|
+
get data$() {
|
|
2004
|
+
return this._data$;
|
|
2005
|
+
}
|
|
2006
|
+
get currentQuery$() {
|
|
2007
|
+
return this._lastQuery$;
|
|
2008
|
+
}
|
|
2009
|
+
constructor(_config, _destroy$) {
|
|
2010
|
+
this._config = _config;
|
|
2011
|
+
this._destroy$ = _destroy$;
|
|
2012
|
+
this._queries$ = new BehaviorSubject([]);
|
|
2013
|
+
this._currentPage$ = new BehaviorSubject(null);
|
|
2014
|
+
this._currentCalculatedPage$ = new BehaviorSubject(null);
|
|
2015
|
+
this._totalPages$ = new BehaviorSubject(null);
|
|
2016
|
+
this._itemsPerPage$ = new BehaviorSubject(this._config.limitParam?.value ?? 10);
|
|
2017
|
+
this._data$ = this._queries$.pipe(switchMap((queries) => {
|
|
2018
|
+
if (!queries.length) {
|
|
2019
|
+
return of([]);
|
|
2020
|
+
}
|
|
2021
|
+
return combineLatest(queries.map((query) => query.state$.pipe(filterSuccess(), map((state) => ({ state, query })))));
|
|
2022
|
+
}), tap((stateMaps) => {
|
|
2023
|
+
const lastState = stateMaps[stateMaps.length - 1]?.state;
|
|
2024
|
+
const lastQuery = stateMaps[stateMaps.length - 1]?.query;
|
|
2025
|
+
if (!lastState || !lastQuery) {
|
|
2026
|
+
return;
|
|
2027
|
+
}
|
|
2028
|
+
const totalPages = this._config.response.totalPagesExtractor?.({
|
|
2029
|
+
response: lastState.response,
|
|
2030
|
+
itemsPerPage: this.itemsPerPage,
|
|
2031
|
+
args: lastQuery._arguments,
|
|
2032
|
+
}) ??
|
|
2033
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
2034
|
+
lastState.response?.totalPages ??
|
|
2035
|
+
null;
|
|
2036
|
+
this._totalPages$.next(totalPages);
|
|
2037
|
+
}), map((stateMaps) => {
|
|
2038
|
+
if (!stateMaps.length) {
|
|
2039
|
+
return null;
|
|
2040
|
+
}
|
|
2041
|
+
const fullData = stateMaps.reduce((acc, stateMap) => {
|
|
2042
|
+
const valExtractFn = this._config?.response?.valueExtractor;
|
|
2043
|
+
let data = valExtractFn
|
|
2044
|
+
? valExtractFn?.(stateMap.state.response)
|
|
2045
|
+
: stateMap.state.response;
|
|
2046
|
+
if (this._config.response.reverse) {
|
|
2047
|
+
data = [...data].reverse();
|
|
2048
|
+
}
|
|
2049
|
+
if (this._config.response.appendItemsTo === 'start') {
|
|
2050
|
+
acc.unshift(...data);
|
|
2051
|
+
}
|
|
2052
|
+
else {
|
|
2053
|
+
acc.push(...data);
|
|
2054
|
+
}
|
|
2055
|
+
return acc;
|
|
2056
|
+
},
|
|
2057
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
2058
|
+
[]);
|
|
2059
|
+
return fullData;
|
|
2060
|
+
}), shareReplay({ bufferSize: 1, refCount: true }));
|
|
2061
|
+
this._lastQuery$ = this._queries$.pipe(map((queries) => queries[queries.length - 1] ?? null), shareReplay({ bufferSize: 1, refCount: true }));
|
|
2062
|
+
}
|
|
2063
|
+
nextPage() {
|
|
2064
|
+
const newPage = (this._currentPage$.value ?? 0) + 1;
|
|
2065
|
+
const calculatedPage = this._config?.pageParam?.valueCalculator?.({
|
|
2066
|
+
page: newPage,
|
|
2067
|
+
totalPages: this.totalPages,
|
|
2068
|
+
itemsPerPage: this.itemsPerPage,
|
|
2069
|
+
}) ?? newPage;
|
|
2070
|
+
if (this.totalPages !== null && newPage > this.totalPages) {
|
|
2071
|
+
console.error('Cannot load more pages, already at the end. Make sure to not render the infinity query trigger using *ngIf canLoadMore');
|
|
2072
|
+
return;
|
|
2073
|
+
}
|
|
2074
|
+
const args = this._prepareArgs(this._config, calculatedPage);
|
|
2075
|
+
const query = this._config.queryCreator.prepare(args).execute();
|
|
2076
|
+
if (this._config.pollingInterval) {
|
|
2077
|
+
query.poll({ interval: this._config.pollingInterval, takeUntil: this._destroy$ });
|
|
2078
|
+
}
|
|
2079
|
+
this._queries$.next([...this._queries$.value, query]);
|
|
2080
|
+
this._currentPage$.next(newPage);
|
|
2081
|
+
this._currentCalculatedPage$.next(calculatedPage);
|
|
2082
|
+
}
|
|
2083
|
+
reset(newConfig) {
|
|
2084
|
+
this._config = { ...this._config, ...(newConfig ?? {}) };
|
|
2085
|
+
this._currentPage$.next(null);
|
|
2086
|
+
this._currentCalculatedPage$.next(null);
|
|
2087
|
+
this._totalPages$.next(null);
|
|
2088
|
+
this._itemsPerPage$.next(this._config.limitParam?.value ?? 10);
|
|
2089
|
+
this._queries$.next([]);
|
|
2090
|
+
this.nextPage();
|
|
2091
|
+
}
|
|
2092
|
+
_prepareArgs(config, page) {
|
|
2093
|
+
const pageParamLocation = this._getPageParamLocation(config?.pageParam?.location);
|
|
2094
|
+
const pageArgs = {
|
|
2095
|
+
[pageParamLocation]: {
|
|
2096
|
+
[config?.pageParam?.key ?? 'page']: page,
|
|
2097
|
+
},
|
|
2098
|
+
};
|
|
2099
|
+
const limitParamLocation = this._getPageParamLocation(config?.limitParam?.location);
|
|
2100
|
+
const limitArgs = {
|
|
2101
|
+
[limitParamLocation]: {
|
|
2102
|
+
[config?.limitParam?.key ?? 'limit']: config?.limitParam?.value ?? 10,
|
|
2103
|
+
},
|
|
2104
|
+
};
|
|
2105
|
+
const defaultConfig = config?.defaultArgs ?? {};
|
|
2106
|
+
const mergedArgs = pageParamLocation === limitParamLocation
|
|
2107
|
+
? {
|
|
2108
|
+
...defaultConfig,
|
|
2109
|
+
[pageParamLocation]: {
|
|
2110
|
+
...(config?.defaultArgs?.[pageParamLocation] ?? {}),
|
|
2111
|
+
...pageArgs[limitParamLocation],
|
|
2112
|
+
...limitArgs[limitParamLocation],
|
|
2113
|
+
},
|
|
2114
|
+
}
|
|
2115
|
+
: {
|
|
2116
|
+
...defaultConfig,
|
|
2117
|
+
[pageParamLocation]: {
|
|
2118
|
+
...(config?.defaultArgs?.[pageParamLocation] ?? {}),
|
|
2119
|
+
...pageArgs[pageParamLocation],
|
|
2120
|
+
},
|
|
2121
|
+
[limitParamLocation]: {
|
|
2122
|
+
...(config?.defaultArgs?.[limitParamLocation] ?? {}),
|
|
2123
|
+
...limitArgs[limitParamLocation],
|
|
2124
|
+
},
|
|
2125
|
+
};
|
|
2126
|
+
return mergedArgs;
|
|
2127
|
+
}
|
|
2128
|
+
_getPageParamLocation(location) {
|
|
2129
|
+
const loc = location ?? 'query';
|
|
2130
|
+
switch (loc) {
|
|
2131
|
+
case 'path':
|
|
2132
|
+
return 'pathParams';
|
|
2133
|
+
case 'query':
|
|
2134
|
+
return 'queryParams';
|
|
2135
|
+
case 'body':
|
|
2136
|
+
return 'body';
|
|
2137
|
+
case 'header':
|
|
2138
|
+
return 'headers';
|
|
2139
|
+
case 'variable':
|
|
2140
|
+
return 'variables';
|
|
2141
|
+
default:
|
|
2142
|
+
throw new Error(`Invalid pageParamLocation: ${loc}`);
|
|
2143
|
+
}
|
|
2144
|
+
}
|
|
2145
|
+
}
|
|
2146
|
+
|
|
2147
|
+
const createInfinityQueryConfig = (config) => config;
|
|
2148
|
+
const skipPaginationPageParamCalculator = ({ page, itemsPerPage }) => itemsPerPage * (page - 1);
|
|
2149
|
+
|
|
2150
|
+
/* eslint-disable @typescript-eslint/no-explicit-any */
|
|
2151
|
+
const INFINITY_QUERY_TOKEN = new InjectionToken('INFINITY_QUERY_TOKEN');
|
|
2152
|
+
class InfinityQueryDirective {
|
|
2153
|
+
get infinityQuery() {
|
|
2154
|
+
return this._infinityQuery;
|
|
2155
|
+
}
|
|
2156
|
+
set infinityQuery(v) {
|
|
2157
|
+
this._cleanup();
|
|
2158
|
+
this._infinityQuery = v;
|
|
2159
|
+
this._infinityQueryInstance = this._setupInfinityQuery(v);
|
|
2160
|
+
this._infinityQueryInstance.nextPage();
|
|
2161
|
+
}
|
|
2162
|
+
get context() {
|
|
2163
|
+
return this._viewContext;
|
|
2164
|
+
}
|
|
2165
|
+
get instance() {
|
|
2166
|
+
return this._infinityQueryInstance;
|
|
2167
|
+
}
|
|
2168
|
+
get data$() {
|
|
2169
|
+
return this._data$.asObservable();
|
|
2170
|
+
}
|
|
2171
|
+
static ngTemplateContextGuard(dir, ctx) {
|
|
2172
|
+
return true;
|
|
2173
|
+
}
|
|
2174
|
+
constructor() {
|
|
2175
|
+
this._queryConfigChanged$ = new Subject();
|
|
2176
|
+
this._viewContext = {
|
|
2177
|
+
$implicit: null,
|
|
2178
|
+
etInfinityQuery: null,
|
|
2179
|
+
loading: false,
|
|
2180
|
+
error: null,
|
|
2181
|
+
isFirstLoad: false,
|
|
2182
|
+
canLoadMore: false,
|
|
2183
|
+
currentPage: null,
|
|
2184
|
+
currentCalculatedPage: null,
|
|
2185
|
+
totalPages: null,
|
|
2186
|
+
itemsPerPage: null,
|
|
2187
|
+
currentQuery: null,
|
|
2188
|
+
};
|
|
2189
|
+
this._infinityQueryInstance = null;
|
|
2190
|
+
this._destroy$ = createDestroy();
|
|
2191
|
+
this._cdr = inject(ChangeDetectorRef);
|
|
2192
|
+
this._viewContainerRef = inject(ViewContainerRef);
|
|
2193
|
+
this._mainTemplateRef = inject((TemplateRef));
|
|
2194
|
+
this._errorHandler = inject(ErrorHandler);
|
|
2195
|
+
this._delayable = inject(DelayableDirective, { host: true });
|
|
2196
|
+
this._data$ = new BehaviorSubject([]);
|
|
2197
|
+
this.data = toSignal(this._data$, { requireSync: true });
|
|
2198
|
+
this._viewContainerRef.createEmbeddedView(this._mainTemplateRef, this._viewContext);
|
|
2199
|
+
}
|
|
2200
|
+
_setupInfinityQuery(config) {
|
|
2201
|
+
const instance = new InfinityQuery(config, this._destroy$);
|
|
2202
|
+
combineLatest([
|
|
2203
|
+
instance.currentQuery$.pipe(switchQueryState(), withLatestFrom(instance.currentQuery$)),
|
|
2204
|
+
this._delayable.isDelayed$,
|
|
2205
|
+
instance.data$,
|
|
2206
|
+
])
|
|
2207
|
+
.pipe(tap(([[state, currentQuery], isDelayed, infinityArray]) => {
|
|
2208
|
+
this._viewContext.currentPage = instance.currentPage;
|
|
2209
|
+
this._viewContext.totalPages = instance.totalPages;
|
|
2210
|
+
this._viewContext.itemsPerPage = instance.itemsPerPage;
|
|
2211
|
+
this._viewContext.canLoadMore =
|
|
2212
|
+
(instance.totalPages !== null &&
|
|
2213
|
+
instance.currentPage !== null &&
|
|
2214
|
+
instance.totalPages > instance.currentPage) ||
|
|
2215
|
+
false;
|
|
2216
|
+
this._viewContext.currentCalculatedPage = instance.currentCalculatedPage;
|
|
2217
|
+
this._viewContext.currentQuery = currentQuery;
|
|
2218
|
+
if (isQueryStateLoading(state) || isDelayed || !infinityArray) {
|
|
2219
|
+
this._viewContext.loading = state ? state.meta.triggeredVia !== 'poll' : true;
|
|
2220
|
+
this._viewContext.error = null;
|
|
2221
|
+
this._viewContext.isFirstLoad = this.context.etInfinityQuery === null;
|
|
2222
|
+
}
|
|
2223
|
+
else if (isQueryStateFailure(state)) {
|
|
2224
|
+
this._viewContext.loading = false;
|
|
2225
|
+
this._viewContext.error = state.error;
|
|
2226
|
+
this._viewContext.isFirstLoad = false;
|
|
2227
|
+
this._errorHandler.handleError(state.error);
|
|
2228
|
+
}
|
|
2229
|
+
else if (isQueryStateSuccess(state)) {
|
|
2230
|
+
this._viewContext.loading = false;
|
|
2231
|
+
this._viewContext.error = null;
|
|
2232
|
+
this._viewContext.isFirstLoad = false;
|
|
2233
|
+
this._viewContext.etInfinityQuery = this._viewContext.$implicit =
|
|
2234
|
+
infinityArray;
|
|
2235
|
+
this._data$.next(infinityArray);
|
|
2236
|
+
}
|
|
2237
|
+
this._cdr.markForCheck();
|
|
2238
|
+
}), takeUntil(this._destroy$), takeUntil(this._queryConfigChanged$))
|
|
2239
|
+
.subscribe();
|
|
2240
|
+
return instance;
|
|
2241
|
+
}
|
|
2242
|
+
loadNextPage() {
|
|
2243
|
+
if (!this._infinityQueryInstance) {
|
|
2244
|
+
return;
|
|
2245
|
+
}
|
|
2246
|
+
if (this._viewContext.loading) {
|
|
2247
|
+
return;
|
|
2248
|
+
}
|
|
2249
|
+
this._infinityQueryInstance.nextPage();
|
|
2250
|
+
}
|
|
2251
|
+
reset(newConfig) {
|
|
2252
|
+
if (!this._infinityQueryInstance) {
|
|
2253
|
+
return;
|
|
2254
|
+
}
|
|
2255
|
+
this._infinityQueryInstance.reset(newConfig);
|
|
2256
|
+
}
|
|
2257
|
+
_cleanup() {
|
|
2258
|
+
this._queryConfigChanged$.next(true);
|
|
2259
|
+
this._viewContext.loading = false;
|
|
2260
|
+
this._viewContext.error = null;
|
|
2261
|
+
this._viewContext.etInfinityQuery = null;
|
|
2262
|
+
this._viewContext.$implicit = null;
|
|
2263
|
+
this._viewContext.isFirstLoad = false;
|
|
2264
|
+
this._viewContext.canLoadMore = false;
|
|
2265
|
+
this._viewContext.currentPage = null;
|
|
2266
|
+
this._viewContext.itemsPerPage = null;
|
|
2267
|
+
this._viewContext.totalPages = null;
|
|
2268
|
+
this._viewContext.currentQuery = null;
|
|
2269
|
+
this._data$.next([]);
|
|
2270
|
+
}
|
|
2271
|
+
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "18.1.1", ngImport: i0, type: InfinityQueryDirective, deps: [], target: i0.ɵɵFactoryTarget.Directive }); }
|
|
2272
|
+
static { this.ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "18.1.1", type: InfinityQueryDirective, isStandalone: true, selector: "[etInfinityQuery]", inputs: { infinityQuery: ["etInfinityQuery", "infinityQuery"] }, providers: [{ provide: INFINITY_QUERY_TOKEN, useExisting: InfinityQueryDirective }], exportAs: ["etInfinityQuery"], hostDirectives: [{ directive: i1.DelayableDirective }], ngImport: i0 }); }
|
|
2273
|
+
}
|
|
2274
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.1.1", ngImport: i0, type: InfinityQueryDirective, decorators: [{
|
|
2275
|
+
type: Directive,
|
|
2276
|
+
args: [{
|
|
2277
|
+
// eslint-disable-next-line @angular-eslint/directive-selector
|
|
2278
|
+
selector: '[etInfinityQuery]',
|
|
2279
|
+
exportAs: 'etInfinityQuery',
|
|
2280
|
+
standalone: true,
|
|
2281
|
+
providers: [{ provide: INFINITY_QUERY_TOKEN, useExisting: InfinityQueryDirective }],
|
|
2282
|
+
hostDirectives: [DelayableDirective],
|
|
2283
|
+
}]
|
|
2284
|
+
}], ctorParameters: () => [], propDecorators: { infinityQuery: [{
|
|
2285
|
+
type: Input,
|
|
2286
|
+
args: ['etInfinityQuery']
|
|
2287
|
+
}] } });
|
|
2288
|
+
|
|
2289
|
+
class InfinityQueryTriggerDirective {
|
|
2290
|
+
constructor() {
|
|
2291
|
+
this._elementRef = inject((ElementRef));
|
|
2292
|
+
this._infinityQuery = inject(INFINITY_QUERY_TOKEN);
|
|
2293
|
+
this._destroy = new Subject();
|
|
2294
|
+
this._observer = null;
|
|
2295
|
+
this.click$ = fromEvent(this._elementRef.nativeElement, 'click');
|
|
2296
|
+
this.scrollContainerSelector = null;
|
|
2297
|
+
}
|
|
2298
|
+
ngOnInit() {
|
|
2299
|
+
const isInteractive = this._elementRef.nativeElement.tagName === 'BUTTON';
|
|
2300
|
+
if (isInteractive) {
|
|
2301
|
+
this.click$.pipe(takeUntil(this._destroy)).subscribe(() => this._infinityQuery.loadNextPage());
|
|
2302
|
+
}
|
|
2303
|
+
else {
|
|
2304
|
+
this._setupIntersectionObserver();
|
|
2305
|
+
}
|
|
2306
|
+
}
|
|
2307
|
+
ngOnDestroy() {
|
|
2308
|
+
this._destroy.next(true);
|
|
2309
|
+
this._destroy.unsubscribe();
|
|
2310
|
+
this._observer?.disconnect();
|
|
2311
|
+
}
|
|
2312
|
+
_setupIntersectionObserver() {
|
|
2313
|
+
this._observer = new IntersectionObserver((entries) => {
|
|
2314
|
+
const entry = entries[0];
|
|
2315
|
+
if (!entry)
|
|
2316
|
+
return;
|
|
2317
|
+
if (entry.isIntersecting && !this._infinityQuery.context.loading) {
|
|
2318
|
+
this._infinityQuery.loadNextPage();
|
|
2319
|
+
}
|
|
2320
|
+
}, {
|
|
2321
|
+
root: this.scrollContainerSelector ? document.querySelector(this.scrollContainerSelector) : null,
|
|
2322
|
+
rootMargin: '0px',
|
|
2323
|
+
threshold: [0.25, 0.5, 0.75, 1],
|
|
2324
|
+
});
|
|
2325
|
+
this._observer.observe(this._elementRef.nativeElement);
|
|
2326
|
+
}
|
|
2327
|
+
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "18.1.1", ngImport: i0, type: InfinityQueryTriggerDirective, deps: [], target: i0.ɵɵFactoryTarget.Directive }); }
|
|
2328
|
+
static { this.ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "18.1.1", type: InfinityQueryTriggerDirective, isStandalone: true, selector: "[etInfinityQueryTrigger], et-infinity-query-trigger", inputs: { scrollContainerSelector: "scrollContainerSelector" }, ngImport: i0 }); }
|
|
2329
|
+
}
|
|
2330
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.1.1", ngImport: i0, type: InfinityQueryTriggerDirective, decorators: [{
|
|
2331
|
+
type: Directive,
|
|
2332
|
+
args: [{
|
|
2333
|
+
// eslint-disable-next-line @angular-eslint/directive-selector
|
|
2334
|
+
selector: '[etInfinityQueryTrigger], et-infinity-query-trigger',
|
|
2335
|
+
standalone: true,
|
|
2336
|
+
}]
|
|
2337
|
+
}], propDecorators: { scrollContainerSelector: [{
|
|
2338
|
+
type: Input
|
|
2339
|
+
}] } });
|
|
2340
|
+
|
|
2341
|
+
class QueryShortNamePipe {
|
|
2342
|
+
transform(value) {
|
|
2343
|
+
const route = value._routeWithParams;
|
|
2344
|
+
if (typeof route === 'string') {
|
|
2345
|
+
// given an url that looks like this: https://api.github.com/repos/EladBezalel/etimo-achievements or https://api.github.com/repos/EladBezalel/etimo-achievements?test=1&test2=2
|
|
2346
|
+
// we want to extract the last 3 parts of the url, so we get: repos/EladBezalel/etimo-achievements
|
|
2347
|
+
const parts = route.split('/');
|
|
2348
|
+
if (parts.length > 3) {
|
|
2349
|
+
return `.../${parts.slice(parts.length - 3).join('/')}`;
|
|
2350
|
+
}
|
|
2351
|
+
return route;
|
|
2352
|
+
}
|
|
2353
|
+
return route;
|
|
2354
|
+
}
|
|
2355
|
+
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "18.1.1", ngImport: i0, type: QueryShortNamePipe, deps: [], target: i0.ɵɵFactoryTarget.Pipe }); }
|
|
2356
|
+
static { this.ɵpipe = i0.ɵɵngDeclarePipe({ minVersion: "14.0.0", version: "18.1.1", ngImport: i0, type: QueryShortNamePipe, isStandalone: true, name: "queryShortName" }); }
|
|
2357
|
+
}
|
|
2358
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.1.1", ngImport: i0, type: QueryShortNamePipe, decorators: [{
|
|
2359
|
+
type: Pipe,
|
|
2360
|
+
args: [{ name: 'queryShortName', standalone: true }]
|
|
2361
|
+
}] });
|
|
2362
|
+
|
|
2363
|
+
const QUERY_CLIENT_DEVTOOLS_TOKEN = new InjectionToken('QUERY_CLIENT_DEVTOOLS_TOKEN');
|
|
2364
|
+
const provideQueryClientForDevtools = (queryClient) => {
|
|
2365
|
+
if (!isDevMode()) {
|
|
2366
|
+
console.warn('You are using the Query Devtools in production mode. This increases the size of your bundle and should only be used for development purposes.');
|
|
2367
|
+
}
|
|
2368
|
+
return {
|
|
2369
|
+
provide: QUERY_CLIENT_DEVTOOLS_TOKEN,
|
|
2370
|
+
useValue: queryClient,
|
|
2371
|
+
multi: true,
|
|
2372
|
+
};
|
|
2373
|
+
};
|
|
2374
|
+
|
|
2375
|
+
class QueryDevtoolsComponent {
|
|
2376
|
+
constructor() {
|
|
2377
|
+
this._cdr = inject(ChangeDetectorRef);
|
|
2378
|
+
this._destroy$ = createDestroy();
|
|
2379
|
+
this.queryClientConfigs = inject(QUERY_CLIENT_DEVTOOLS_TOKEN);
|
|
2380
|
+
this.isOpen = signal(false);
|
|
2381
|
+
this.isTranslucent = signal(false);
|
|
2382
|
+
this.snapLayout = signal('full');
|
|
2383
|
+
this.queryListMode = signal('live');
|
|
2384
|
+
this.viewMode = signal('query');
|
|
2385
|
+
this.showResponse = signal(false);
|
|
2386
|
+
this.showRawResponse = signal(false);
|
|
2387
|
+
this.showQueryConfig = signal(false);
|
|
2388
|
+
this.showEntityStoreValue = signal(false);
|
|
2389
|
+
this.showArguments = signal(false);
|
|
2390
|
+
this.selectedClientIdCtrl = new FormControl(0);
|
|
2391
|
+
this.selectedClientId = toSignal(this.selectedClientIdCtrl.valueChanges.pipe(startWith(this.selectedClientIdCtrl.value)));
|
|
2392
|
+
this.selectedQueryId = signal(null);
|
|
2393
|
+
this.selectedClientConfig = computed(() => {
|
|
2394
|
+
if (this.queryClientConfigs.length === 1) {
|
|
2395
|
+
return this.queryClientConfigs[0];
|
|
2396
|
+
}
|
|
2397
|
+
const index = this.selectedClientId() ?? 0;
|
|
2398
|
+
const cfg = this.queryClientConfigs[index];
|
|
2399
|
+
if (!cfg) {
|
|
2400
|
+
return this.queryClientConfigs[0];
|
|
2401
|
+
}
|
|
2402
|
+
return cfg;
|
|
2403
|
+
});
|
|
2404
|
+
this.queryStore = computed(() => {
|
|
2405
|
+
const config = this.selectedClientConfig();
|
|
2406
|
+
return config.client._store;
|
|
2407
|
+
});
|
|
2408
|
+
this.queryStore$ = toObservable(this.queryStore);
|
|
2409
|
+
this._queries$ = new BehaviorSubject([]);
|
|
2410
|
+
this.queries = toSignal(this._queries$);
|
|
2411
|
+
this.selectedQuery = computed(() => {
|
|
2412
|
+
const id = this.selectedQueryId();
|
|
2413
|
+
const mode = this.queryListMode();
|
|
2414
|
+
if (id === null || !mode) {
|
|
2415
|
+
return null;
|
|
2416
|
+
}
|
|
2417
|
+
if (mode === 'live') {
|
|
2418
|
+
return this.queries()?.find((q) => q._id === id) ?? null;
|
|
2419
|
+
}
|
|
2420
|
+
return this.queryHistory$.getValue()?.find((q) => q._id === id) ?? null;
|
|
2421
|
+
});
|
|
2422
|
+
this.selectedQuery$ = toObservable(this.selectedQuery);
|
|
2423
|
+
this.selectedQueryEntityStore = computed(() => {
|
|
2424
|
+
const query = this.selectedQuery();
|
|
2425
|
+
if (!query)
|
|
2426
|
+
return null;
|
|
2427
|
+
return query.store;
|
|
2428
|
+
});
|
|
2429
|
+
this.selectedQueryEntityStore$ = toObservable(this.selectedQueryEntityStore);
|
|
2430
|
+
this.selectedQueryEntityStoreValue$ = this.selectedQueryEntityStore$.pipe(switchMap((s) => s?._change$.pipe(startWith(''), map(() => {
|
|
2431
|
+
if (!s._dictionary)
|
|
2432
|
+
return null;
|
|
2433
|
+
return Array.from(s._dictionary).reduce((obj, [key, value]) => {
|
|
2434
|
+
obj[key] = value;
|
|
2435
|
+
return obj;
|
|
2436
|
+
}, {});
|
|
2437
|
+
})) ?? of(null)));
|
|
2438
|
+
this.queryHistory$ = new BehaviorSubject([]);
|
|
2439
|
+
this.stringifiedQueryConfig = computed(() => {
|
|
2440
|
+
const query = this.selectedQuery();
|
|
2441
|
+
if (!query)
|
|
2442
|
+
return null;
|
|
2443
|
+
const cfg = query._queryConfig;
|
|
2444
|
+
const route = typeof cfg.route === 'string' ? cfg.route : cfg.route ? cfg.route({}).replace(/undefined/g, ':param') : null;
|
|
2445
|
+
const sharedCfg = {
|
|
2446
|
+
method: cfg.method,
|
|
2447
|
+
secure: cfg.secure,
|
|
2448
|
+
route,
|
|
2449
|
+
reportProgress: cfg.reportProgress,
|
|
2450
|
+
responseType: cfg.responseType,
|
|
2451
|
+
withCredentials: cfg.withCredentials,
|
|
2452
|
+
autoRefreshOn: cfg.autoRefreshOn,
|
|
2453
|
+
enableSmartPolling: cfg.enableSmartPolling,
|
|
2454
|
+
hasEntityStore: !!cfg.entity,
|
|
2455
|
+
};
|
|
2456
|
+
if (isGqlQueryConfig(cfg)) {
|
|
2457
|
+
return {
|
|
2458
|
+
...sharedCfg,
|
|
2459
|
+
query: cfg.query,
|
|
2460
|
+
transferVia: cfg.transferVia,
|
|
2461
|
+
};
|
|
2462
|
+
}
|
|
2463
|
+
return {
|
|
2464
|
+
...sharedCfg,
|
|
2465
|
+
};
|
|
2466
|
+
});
|
|
2467
|
+
this.authProvider = toSignal(toObservable(this.selectedClientConfig).pipe(switchMap((c) => c.client.authProvider$)));
|
|
2468
|
+
this.authProviderDetails = computed(() => {
|
|
2469
|
+
const provider = this.authProvider();
|
|
2470
|
+
if (!provider)
|
|
2471
|
+
return null;
|
|
2472
|
+
if (provider instanceof CustomHeaderAuthProvider) {
|
|
2473
|
+
return {
|
|
2474
|
+
type: 'CustomHeaderAuthProvider',
|
|
2475
|
+
header: () => provider.header,
|
|
2476
|
+
provider: provider,
|
|
2477
|
+
config: {
|
|
2478
|
+
name: provider._config.name,
|
|
2479
|
+
value: provider._config.value,
|
|
2480
|
+
},
|
|
2481
|
+
};
|
|
2482
|
+
}
|
|
2483
|
+
else if (provider instanceof BasicAuthProvider) {
|
|
2484
|
+
return {
|
|
2485
|
+
type: 'BasicAuthProvider',
|
|
2486
|
+
header: () => provider.header,
|
|
2487
|
+
provider: provider,
|
|
2488
|
+
config: {
|
|
2489
|
+
username: provider._config.username,
|
|
2490
|
+
password: provider._config.password,
|
|
2491
|
+
},
|
|
2492
|
+
};
|
|
2493
|
+
}
|
|
2494
|
+
else if (isBearerAuthProvider(provider)) {
|
|
2495
|
+
const refreshCfg = provider._config.refreshConfig;
|
|
2496
|
+
return {
|
|
2497
|
+
type: 'BearerAuthProvider',
|
|
2498
|
+
header: () => provider.header,
|
|
2499
|
+
provider: provider,
|
|
2500
|
+
config: {
|
|
2501
|
+
token: provider._config.token,
|
|
2502
|
+
refreshConfig: {
|
|
2503
|
+
refreshOnUnauthorizedResponse: refreshCfg?.refreshOnUnauthorizedResponse ?? 'true (default)',
|
|
2504
|
+
token: refreshCfg?.token,
|
|
2505
|
+
cookieName: refreshCfg?.cookieName,
|
|
2506
|
+
cookieDomain: refreshCfg?.cookieDomain ?? 'current origin (default)',
|
|
2507
|
+
cookieExpiresInDays: refreshCfg?.cookieExpiresInDays ?? '30 (default)',
|
|
2508
|
+
cookiePath: refreshCfg?.cookiePath ?? '/ (default)',
|
|
2509
|
+
cookieEnabled: refreshCfg?.cookieEnabled ?? 'true (default)',
|
|
2510
|
+
cookieSameSite: refreshCfg?.cookieSameSite ?? 'lax (default)',
|
|
2511
|
+
refreshBuffer: refreshCfg?.refreshBuffer ?? '30000 (default)',
|
|
2512
|
+
expiresInPropertyName: refreshCfg?.expiresInPropertyName ?? 'exp (default)',
|
|
2513
|
+
strategy: refreshCfg?.strategy ?? 'AuthBearerRefreshStrategy.BeforeExpiration (default)',
|
|
2514
|
+
requestArgsAdapter: refreshCfg?.requestArgsAdapter ? 'custom' : 'default',
|
|
2515
|
+
responseAdapter: refreshCfg?.responseAdapter ? 'custom' : 'default',
|
|
2516
|
+
},
|
|
2517
|
+
},
|
|
2518
|
+
};
|
|
2519
|
+
}
|
|
2520
|
+
else {
|
|
2521
|
+
return {
|
|
2522
|
+
type: 'UnknownAuthProvider',
|
|
2523
|
+
header: () => provider.header,
|
|
2524
|
+
provider: provider,
|
|
2525
|
+
config: {},
|
|
2526
|
+
};
|
|
2527
|
+
}
|
|
2528
|
+
});
|
|
2529
|
+
this.trackByClient = (_, { client }) => client.config.baseRoute;
|
|
2530
|
+
this.trackByQuery = (_, { _id }) => _id;
|
|
2531
|
+
this.selectedClientIdCtrl.valueChanges
|
|
2532
|
+
.pipe(tap(() => this.clearQueryHistory()), takeUntil(this._destroy$))
|
|
2533
|
+
.subscribe();
|
|
2534
|
+
this.selectedQuery$
|
|
2535
|
+
.pipe(switchMap((q) => q?._dependentsChanged$ ?? of(null)), tap(() => this._cdr.markForCheck()), takeUntilDestroyed())
|
|
2536
|
+
.subscribe();
|
|
2537
|
+
toObservable(this.authProviderDetails)
|
|
2538
|
+
.pipe(switchMap((ap) => (ap?.type === 'BearerAuthProvider' ? ap.provider.tokens$ : of(null))))
|
|
2539
|
+
.pipe(tap(() => this._cdr.markForCheck()), takeUntilDestroyed())
|
|
2540
|
+
.subscribe();
|
|
2541
|
+
this.queryStore$
|
|
2542
|
+
.pipe(switchMap((s) => s.storeChange$.pipe(startWith(''), map(() => s._store), tap((s) => {
|
|
2543
|
+
untracked(() => {
|
|
2544
|
+
this._queries$.next(Array.from(s.values()));
|
|
2545
|
+
});
|
|
2546
|
+
}), takeUntil(this._destroy$))))
|
|
2547
|
+
.subscribe();
|
|
2548
|
+
this.queryStore$
|
|
2549
|
+
.pipe(switchMap((s) => s.queryCreated$.pipe(tap((query) => {
|
|
2550
|
+
const currentHistory = this.queryHistory$.getValue();
|
|
2551
|
+
if (!currentHistory)
|
|
2552
|
+
return;
|
|
2553
|
+
const currentHistoryClone = [...currentHistory];
|
|
2554
|
+
if (!currentHistory.some((q) => q._id === query._id)) {
|
|
2555
|
+
currentHistoryClone.unshift(query);
|
|
2556
|
+
}
|
|
2557
|
+
currentHistoryClone.splice(50);
|
|
2558
|
+
this.queryHistory$.next(currentHistoryClone);
|
|
2559
|
+
}), takeUntil(this._destroy$))))
|
|
2560
|
+
.subscribe();
|
|
2561
|
+
this._queries$
|
|
2562
|
+
.pipe(tap((queries) => {
|
|
2563
|
+
const currentHistory = this.queryHistory$.getValue();
|
|
2564
|
+
if (!queries || !currentHistory)
|
|
2565
|
+
return;
|
|
2566
|
+
const currentHistoryClone = [...currentHistory];
|
|
2567
|
+
for (const query of queries) {
|
|
2568
|
+
if (!currentHistoryClone.some((q) => q._id === query._id)) {
|
|
2569
|
+
currentHistoryClone.unshift(query);
|
|
2570
|
+
}
|
|
2571
|
+
}
|
|
2572
|
+
currentHistoryClone.splice(50);
|
|
2573
|
+
this.queryHistory$.next(currentHistoryClone);
|
|
2574
|
+
}), takeUntil(this._destroy$))
|
|
2575
|
+
.subscribe();
|
|
2576
|
+
effect(() => {
|
|
2577
|
+
const devtoolConfig = {
|
|
2578
|
+
isOpen: this.isOpen(),
|
|
2579
|
+
isTranslucent: this.isTranslucent(),
|
|
2580
|
+
snapLayout: this.snapLayout(),
|
|
2581
|
+
showResponse: this.showResponse(),
|
|
2582
|
+
showRawResponse: this.showRawResponse(),
|
|
2583
|
+
showQueryConfig: this.showQueryConfig(),
|
|
2584
|
+
showEntityStoreValue: this.showEntityStoreValue(),
|
|
2585
|
+
showArguments: this.showArguments(),
|
|
2586
|
+
selectedClientId: this.selectedClientId(),
|
|
2587
|
+
selectedQueryPath: this.selectedQueryId(),
|
|
2588
|
+
queryListMode: this.queryListMode(),
|
|
2589
|
+
viewMode: this.viewMode(),
|
|
2590
|
+
};
|
|
2591
|
+
window.localStorage.setItem('ethlete:query:devtools', JSON.stringify(devtoolConfig));
|
|
2592
|
+
}, { allowSignalWrites: true });
|
|
2593
|
+
const initialConfig = window.localStorage.getItem('ethlete:query:devtools');
|
|
2594
|
+
if (initialConfig) {
|
|
2595
|
+
const parsed = JSON.parse(initialConfig);
|
|
2596
|
+
this.isOpen.set(parsed.isOpen ?? false);
|
|
2597
|
+
this.isTranslucent.set(parsed.isTranslucent ?? false);
|
|
2598
|
+
this.snapLayout.set(parsed.snapLayout ?? 'full');
|
|
2599
|
+
this.showResponse.set(parsed.showResponse ?? false);
|
|
2600
|
+
this.showRawResponse.set(parsed.showRawResponse ?? false);
|
|
2601
|
+
this.showQueryConfig.set(parsed.showQueryConfig ?? false);
|
|
2602
|
+
this.showEntityStoreValue.set(parsed.showEntityStoreValue ?? false);
|
|
2603
|
+
this.showArguments.set(parsed.showArguments ?? false);
|
|
2604
|
+
this.selectedClientIdCtrl.setValue(parsed.selectedClientId ?? 0);
|
|
2605
|
+
this.selectedQueryId.set(parsed.selectedQueryPath ?? null);
|
|
2606
|
+
this.queryListMode.set(parsed.queryListMode ?? 'live');
|
|
2607
|
+
this.viewMode.set(parsed.viewMode ?? 'query');
|
|
2608
|
+
}
|
|
2609
|
+
}
|
|
2610
|
+
toggleOpen() {
|
|
2611
|
+
this.isOpen.set(!this.isOpen());
|
|
2612
|
+
}
|
|
2613
|
+
toggleTranslucent() {
|
|
2614
|
+
this.isTranslucent.set(!this.isTranslucent());
|
|
2615
|
+
}
|
|
2616
|
+
toggleShowResponse() {
|
|
2617
|
+
this.showResponse.set(!this.showResponse());
|
|
2618
|
+
nextFrame(() => {
|
|
2619
|
+
this._cdr.markForCheck();
|
|
2620
|
+
});
|
|
2621
|
+
}
|
|
2622
|
+
toggleShowQueryConfig() {
|
|
2623
|
+
this.showQueryConfig.set(!this.showQueryConfig());
|
|
2624
|
+
}
|
|
2625
|
+
toggleShowRawResponse() {
|
|
2626
|
+
this.showRawResponse.set(!this.showRawResponse());
|
|
2627
|
+
}
|
|
2628
|
+
toggleShowEntityStoreValue() {
|
|
2629
|
+
this.showEntityStoreValue.set(!this.showEntityStoreValue());
|
|
2630
|
+
}
|
|
2631
|
+
toggleShowArguments() {
|
|
2632
|
+
this.showArguments.set(!this.showArguments());
|
|
2633
|
+
}
|
|
2634
|
+
selectSnapLayout(layout) {
|
|
2635
|
+
this.snapLayout.set(layout);
|
|
2636
|
+
}
|
|
2637
|
+
selectQueryListMode(mode) {
|
|
2638
|
+
this.queryListMode.set(mode);
|
|
2639
|
+
}
|
|
2640
|
+
selectViewMode(mode) {
|
|
2641
|
+
this.viewMode.set(mode);
|
|
2642
|
+
}
|
|
2643
|
+
selectQuery(query) {
|
|
2644
|
+
this.selectedQueryId.set(query._id);
|
|
2645
|
+
}
|
|
2646
|
+
clearQueryHistory() {
|
|
2647
|
+
this.queryHistory$.next([]);
|
|
2648
|
+
}
|
|
2649
|
+
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "18.1.1", ngImport: i0, type: QueryDevtoolsComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
|
|
2650
|
+
static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "18.1.1", type: QueryDevtoolsComponent, isStandalone: true, selector: "et-query-devtools", host: { classAttribute: "et-query-devtools" }, ngImport: i0, template: "@if (!isOpen()) {\n <button (click)=\"toggleOpen()\" class=\"et-query-devtools-open-btn et-qd-chip\">Query Devtools</button>\n}\n\n@if (isOpen()) {\n <div\n [class.et-query-devtools-container--transparent]=\"isTranslucent()\"\n class=\"et-query-devtools-container et-query-devtools-container--snap-{{ snapLayout() }}\"\n >\n <header class=\"et-qd-header\">\n <h3 class=\"et-qd-header__title\">Query Devtools</h3>\n\n <div class=\"et-qd-header__select\">\n <label for=\"et-query-client-instance\">Client instance</label>\n <select [formControl]=\"selectedClientIdCtrl\" id=\"et-query-client-instance\">\n @for (clientConfig of queryClientConfigs; track trackByClient(i, clientConfig); let i = $index) {\n <option [value]=\"i\">\n {{ clientConfig.displayName ?? clientConfig.client.config.baseRoute }}\n </option>\n }\n </select>\n </div>\n\n <button (click)=\"toggleOpen()\" class=\"et-qd-btn et-qd-btn-secondary\">Close</button>\n </header>\n\n <div class=\"et-qd-sub-header\">\n <button\n [ngClass]=\"{ 'et-qd-chip-secondary': !isTranslucent() }\"\n (click)=\"toggleTranslucent()\"\n class=\"et-qd-chip et-qd-translucent-clickable\"\n >\n Translucent\n </button>\n <div class=\"et-qd-sub-header__divider\"></div>\n <button\n [ngClass]=\"{ 'et-qd-chip-secondary': snapLayout() !== 'full' }\"\n (click)=\"selectSnapLayout('full')\"\n class=\"et-qd-chip\"\n >\n Fullscreen\n </button>\n <button\n [ngClass]=\"{ 'et-qd-chip-secondary': snapLayout() !== 'top' }\"\n (click)=\"selectSnapLayout('top')\"\n class=\"et-qd-chip\"\n >\n Top\n </button>\n <button\n [ngClass]=\"{ 'et-qd-chip-secondary': snapLayout() !== 'bottom' }\"\n (click)=\"selectSnapLayout('bottom')\"\n class=\"et-qd-chip\"\n >\n Bottom\n </button>\n <button\n [ngClass]=\"{ 'et-qd-chip-secondary': snapLayout() !== 'left' }\"\n (click)=\"selectSnapLayout('left')\"\n class=\"et-qd-chip\"\n >\n Left\n </button>\n <button\n [ngClass]=\"{ 'et-qd-chip-secondary': snapLayout() !== 'right' }\"\n (click)=\"selectSnapLayout('right')\"\n class=\"et-qd-chip\"\n >\n Right\n </button>\n </div>\n\n @if (selectedClientConfig(); as cfg) {\n <div class=\"et-qd-client\">\n <p class=\"et-qd-client__title\">{{ cfg.displayName ?? cfg.client.config.baseRoute }}</p>\n\n <div class=\"et-qd-client__actions\">\n <button (click)=\"cfg.client._store.refreshQueriesInUse()\" class=\"et-qd-chip\">Refresh all</button>\n <button (click)=\"cfg.client._store.refreshQueriesInUse({ ignoreCacheValidity: true })\" class=\"et-qd-chip\">\n Refresh all (ignore caches)\n </button>\n <button\n (click)=\"cfg.client._store.refreshQueriesInUse({ ignoreCacheValidity: true, purgeUnused: true })\"\n class=\"et-qd-chip\"\n >\n Refresh all (ignore caches & purge unused)\n </button>\n </div>\n </div>\n }\n\n @if (selectedClientConfig(); as cfg) {\n <div class=\"et-qd-queries\">\n <p class=\"et-qd-queries__title\">Inspector</p>\n <div class=\"et-qd-queries__actions\">\n <button\n [ngClass]=\"{ 'et-qd-chip-secondary': queryListMode() !== 'live' || viewMode() !== 'query' }\"\n (click)=\"selectQueryListMode('live'); selectViewMode('query')\"\n class=\"et-qd-chip et-qd-chip-bordered\"\n >\n Live Queries ({{ queries()?.length ?? 0 }})\n </button>\n <button\n [ngClass]=\"{ 'et-qd-chip-secondary': queryListMode() !== 'history' || viewMode() !== 'query' }\"\n (click)=\"selectQueryListMode('history'); selectViewMode('query')\"\n class=\"et-qd-chip et-qd-chip-bordered\"\n >\n Query History ({{ (queryHistory$ | async)?.length ?? 0 }}/50)\n </button>\n @if (queryListMode() === 'history' && viewMode() === 'query') {\n <div class=\"et-qd-queries__divider\"></div>\n <button (click)=\"clearQueryHistory()\" class=\"et-qd-chip et-qd-chip-bordered\">Clear history</button>\n }\n\n <div class=\"et-qd-queries__divider\"></div>\n <button\n [ngClass]=\"{ 'et-qd-chip-secondary': viewMode() !== 'authProvider' }\"\n (click)=\"selectViewMode('authProvider')\"\n class=\"et-qd-chip et-qd-chip-bordered\"\n >\n Auth Provider\n </button>\n </div>\n\n @if (viewMode() === 'query') {\n <div class=\"et-qd-queries__browser\">\n @if (queryListMode() === 'live') {\n <ul class=\"et-qd-queries__browser-list\">\n @for (query of queries(); track trackByQuery($index, query)) {\n <li>\n <button\n [class.et-qd-query--active]=\"selectedQuery()?._id === query._id\"\n (click)=\"selectQuery(query)\"\n >\n <span> {{ query._queryConfig.method }}</span>\n <span> {{ query | queryShortName }}</span>\n </button>\n </li>\n }\n </ul>\n }\n @if (queryListMode() === 'history') {\n <ul class=\"et-qd-queries__browser-list\">\n @for (query of queryHistory$ | async; track trackByQuery($index, query)) {\n <li>\n <button\n [class.et-qd-query--active]=\"selectedQuery()?._id === query._id\"\n (click)=\"selectQuery(query)\"\n >\n <span> {{ query._queryConfig.method }}</span>\n <span> {{ query | queryShortName }}</span>\n </button>\n </li>\n }\n </ul>\n }\n <div class=\"et-qd-queries__browser-detail\">\n @if (selectedQuery(); as query) {\n <p class=\"et-qd-queries__browser-detail__title\">\n {{ query._queryConfig.method }} {{ query._routeWithParams }}\n </p>\n\n <div class=\"et-qd-queries__actions\">\n <button (click)=\"query.execute()\" class=\"et-qd-chip et-qd-chip-bordered\">Execute</button>\n <button (click)=\"query.execute({ skipCache: true })\" class=\"et-qd-chip et-qd-chip-bordered\">\n Execute (ignore cache)\n </button>\n\n <button (click)=\"query.abort()\" class=\"et-qd-chip et-qd-chip-bordered\">Abort</button>\n <div class=\"et-qd-queries__divider\"></div>\n <button\n [ngClass]=\"{ 'et-qd-chip-secondary': !query.isPolling }\"\n (click)=\"query.poll({ interval: 5000, takeUntil: _destroy$ })\"\n class=\"et-qd-chip et-qd-chip-bordered\"\n >\n Poll\n </button>\n\n <button\n [ngClass]=\"{ 'et-qd-chip-secondary': query.isPolling }\"\n (click)=\"query.stopPolling()\"\n class=\"et-qd-chip et-qd-chip-bordered\"\n >\n Stop polling\n </button>\n @if (query.isPolling || query._isPollingPaused) {\n <button (click)=\"query.pausePolling()\" class=\"et-qd-chip et-qd-chip-secondary et-qd-chip-bordered\">\n Pause polling\n </button>\n <button (click)=\"query.resumePolling()\" class=\"et-qd-chip et-qd-chip-secondary et-qd-chip-bordered\">\n Resume polling\n </button>\n }\n </div>\n\n <div class=\"et-qd-list\">\n <div class=\"et-qd-list-item\">\n <p>State type</p>\n <pre>{{ query.rawState.type }}</pre>\n </div>\n\n <div class=\"et-qd-list-item\">\n <p>Dependents</p>\n <pre>{{ query._dependents | json }}</pre>\n </div>\n\n <div class=\"et-qd-list-item\">\n <p>Is expired</p>\n <pre>{{ query.isExpired }}</pre>\n </div>\n\n <div class=\"et-qd-list-item\">\n <p>Is in use</p>\n <pre>{{ query.isInUse }}</pre>\n </div>\n\n <div class=\"et-qd-list-item\">\n <p>Subscriber count</p>\n <pre>{{ query._subscriberCount }}</pre>\n </div>\n\n <div class=\"et-qd-list-item\">\n <p>Is polling</p>\n <pre>{{ query.isPolling }}</pre>\n </div>\n\n <div class=\"et-qd-list-item\">\n <p>Is polling paused</p>\n <pre>{{ query._isPollingPaused }}</pre>\n </div>\n\n <div class=\"et-qd-list-item\">\n <p>Enable smart polling</p>\n <pre>{{ query._enableSmartPolling }}</pre>\n </div>\n\n <div class=\"et-qd-list-item\">\n <p>Query store key</p>\n <pre>{{ query._queryStoreKey }}</pre>\n </div>\n\n <div class=\"et-qd-list-item\">\n <p>Arguments</p>\n\n <div>\n <button\n [ngClass]=\"{ 'et-qd-chip-secondary': !showArguments() }\"\n (click)=\"toggleShowArguments()\"\n class=\"et-qd-chip et-qd-chip-bordered\"\n >\n Toggle\n </button>\n </div>\n @if (showArguments()) {\n <pre>{{ query._arguments | json }}</pre>\n }\n </div>\n\n <div class=\"et-qd-list-item\">\n <p>State</p>\n <p><i> Warning, this will cause a subscription if shown!</i></p>\n <div>\n <button\n [ngClass]=\"{ 'et-qd-chip-secondary': !showResponse() }\"\n (click)=\"toggleShowResponse()\"\n class=\"et-qd-chip et-qd-chip-bordered\"\n >\n Toggle\n </button>\n </div>\n @if (showResponse()) {\n <pre>{{ query.state$ | async | json }}</pre>\n }\n </div>\n\n <div class=\"et-qd-list-item\">\n <p>Raw state</p>\n <p><i> The query state unaffected by entity stores</i></p>\n\n <div>\n <button\n [ngClass]=\"{ 'et-qd-chip-secondary': !showRawResponse() }\"\n (click)=\"toggleShowRawResponse()\"\n class=\"et-qd-chip et-qd-chip-bordered\"\n >\n Toggle\n </button>\n </div>\n @if (showRawResponse()) {\n <pre>{{ query.rawState | json }}</pre>\n }\n </div>\n\n <div class=\"et-qd-list-item\">\n <p>Query config</p>\n\n <div>\n <button\n [ngClass]=\"{ 'et-qd-chip-secondary': !showQueryConfig() }\"\n (click)=\"toggleShowQueryConfig()\"\n class=\"et-qd-chip et-qd-chip-bordered\"\n >\n Toggle\n </button>\n </div>\n @if (showQueryConfig()) {\n <pre>{{ stringifiedQueryConfig() | json }}</pre>\n }\n </div>\n\n @if (!!query._queryConfig.entity) {\n <div class=\"et-qd-list-item\">\n <p>Entity store value</p>\n\n <div>\n <button\n [ngClass]=\"{ 'et-qd-chip-secondary': !showEntityStoreValue() }\"\n (click)=\"toggleShowEntityStoreValue()\"\n class=\"et-qd-chip et-qd-chip-bordered\"\n >\n Toggle\n </button>\n </div>\n @if (showEntityStoreValue()) {\n <pre>{{ selectedQueryEntityStoreValue$ | async | json }}</pre>\n }\n </div>\n }\n </div>\n }\n </div>\n </div>\n } @else if (viewMode() === 'authProvider') {\n <div class=\"et-qd-auth-provider__viewer\">\n @if (authProviderDetails(); as details) {\n <div class=\"et-qd-list\">\n <div class=\"et-qd-list-item\">\n <p>Type</p>\n <pre>{{ details.type }}</pre>\n </div>\n <div class=\"et-qd-list-item\">\n <p>Header value</p>\n <pre>{{ details.header() | json }}</pre>\n </div>\n <div class=\"et-qd-list-item\">\n <p>Config</p>\n <pre>{{ details.config | json }}</pre>\n </div>\n\n @if (details.type === 'BearerAuthProvider') {\n <div class=\"et-qd-list-item\">\n <p>Tokens</p>\n <pre>{{ details.provider.tokens$ | async | json }}</pre>\n </div>\n\n <div class=\"et-qd-list-item\">\n <p>Should refresh on unauthorized response</p>\n <pre>{{ details.provider.shouldRefreshOnUnauthorizedResponse }}</pre>\n </div>\n\n <ng-container\n *etQuery=\"\n details.provider.currentRefreshQuery$ | async as response;\n loading as loading;\n error as error;\n query as query\n \"\n >\n <div class=\"et-qd-list-item\">\n <p>Refresh query response</p>\n\n <div>\n <button (click)=\"details.provider._refreshQuery()\" class=\"et-qd-chip et-qd-chip-bordered\">\n Refresh\n </button>\n </div>\n <pre>{{ response | json }}</pre>\n </div>\n <div class=\"et-qd-list-item\">\n <p>Refresh query loading</p>\n <pre>{{ loading }}</pre>\n </div>\n <div class=\"et-qd-list-item\">\n <p>Refresh query error</p>\n <pre>{{ error ?? 'null' }}</pre>\n </div>\n <div class=\"et-qd-list-item\">\n <p>State</p>\n <pre>{{ query?.state$ | async | json }}</pre>\n </div>\n </ng-container>\n }\n </div>\n } @else {\n <p>No auth provider set</p>\n }\n </div>\n }\n </div>\n }\n </div>\n}\n", styles: [":host{position:fixed;top:0;z-index:1000000;font-size:14px;color:#fff}.et-query-devtools-container{position:fixed;inset:0;background-color:#25292e;z-index:10000;overflow:auto;font-family:monospace;display:grid;grid-template-rows:auto auto auto minmax(0,1fr)}.et-query-devtools-container.et-query-devtools-container--transparent{opacity:.25;pointer-events:none}.et-query-devtools-container.et-query-devtools-container--transparent .et-query-devtools-translucent-btn{pointer-events:all}.et-query-devtools-container.et-query-devtools-container--snap-left{inset-inline-start:0;inset-block-start:0;inset-block-end:0;width:50%;border-right:1px solid rgb(37,41,46)}.et-query-devtools-container.et-query-devtools-container--snap-right{inset-inline-start:unset;inset-inline-end:0;inset-block-start:0;inset-block-end:0;width:50%;border-left:1px solid rgb(37,41,46)}.et-query-devtools-container.et-query-devtools-container--snap-top{inset-inline-start:0;inset-block-start:0;inset-inline-end:0;height:50%;border-bottom:1px solid rgb(37,41,46)}.et-query-devtools-container.et-query-devtools-container--snap-bottom{inset-inline-start:0;inset-block-start:unset;inset-block-end:0;inset-inline-end:0;height:50%;border-top:1px solid rgb(37,41,46)}button.et-query-devtools-open-btn{position:fixed;inset-inline-end:20px;inset-block-end:0;z-index:9999;font-family:monospace;border-radius:3px 3px 0 0;line-height:14px;font-size:12px}.et-qd-header{background-color:#00ffa1;padding:12px;display:grid;grid-template-columns:1fr auto auto;gap:12px;align-items:center}.et-qd-header__title{color:#25292e;margin:0;font-size:22px}.et-qd-header__select{display:grid;background-color:#25292e;border-radius:10px;overflow:hidden}.et-qd-header__select select{width:250px;color:#fff;padding:3px 10px 2px;border:none;background-color:#25292e;font-family:monospace}.et-qd-header__select label{padding:3px 10px;color:#25292e;font-size:12px;font-weight:700;color:#fff;border-bottom:1px solid gray}.et-qd-sub-header{background-color:#00b371;padding:12px;display:flex;gap:12px;flex-wrap:wrap}.et-qd-sub-header__divider{width:1px;background-color:#25292e}.et-qd-btn{background-color:#00ffa1;color:#25292e;border:none;padding:12px;cursor:pointer;font-size:14px;font-weight:700;text-align:center;text-decoration:none;display:inline-block;width:100%;transition:all .3s ease-in-out;font-family:monospace;border-radius:10px}.et-qd-btn:hover{background-color:#00cc81}.et-qd-btn.et-qd-btn-secondary{background-color:#25292e;color:#00ffa1}.et-qd-btn.et-qd-btn-secondary:hover{background-color:#3c424a}.et-qd-chip{background-color:#00ffa1;color:#25292e;border:none;padding:4px 9px;cursor:pointer;font-size:12px;font-weight:700;text-align:center;text-decoration:none;display:inline-block;transition:all .3s ease-in-out;font-family:monospace;border-radius:10px}.et-qd-chip:hover{background-color:#00cc81}.et-qd-chip.et-qd-chip-secondary{background-color:#25292e;color:#00ffa1}.et-qd-chip.et-qd-chip-secondary:hover{background-color:#3c424a}.et-qd-chip.et-qd-chip-bordered{border:1px solid rgb(0,255,161)}.et-qd-translucent-clickable{pointer-events:all}.et-qd-client{border-bottom:1px solid #3c424a;padding:12px}.et-qd-client__title{margin:0;font-size:14px;color:#fff;font-weight:700}.et-qd-client__actions{display:flex;gap:12px;margin-top:12px}.et-qd-queries{padding:12px;display:grid;grid-template-rows:auto auto minmax(0,1fr)}.et-qd-queries__actions{display:flex;gap:12px;margin-top:12px;margin-bottom:16px}.et-qd-queries__title{margin:0;font-size:14px;color:#fff;font-weight:700}.et-qd-queries__divider{width:1px;background-color:#3c424a}.et-qd-queries__browser{display:grid;grid-template-columns:1fr minmax(0,3fr);margin-left:-12px;margin-right:-12px;border-top:1px solid #3c424a;margin-bottom:-12px}.et-qd-queries__browser-list{border-right:1px solid #3c424a;list-style:none;padding:0;margin:0;display:grid;grid-auto-flow:row;grid-auto-rows:55px;overflow-y:auto;overflow-x:hidden}.et-qd-queries__browser-list li{margin:0}.et-qd-queries__browser-list li button{all:unset;font-family:monospace;box-sizing:border-box;width:100%;border:none;border-bottom:1px solid #3c424a;background-color:#25292e;color:#fff;text-align:left;padding:12px;height:100%;transition:all .3s ease-in-out;cursor:pointer;display:grid;grid-template-columns:auto minmax(0,1fr);gap:2px;display:block}.et-qd-queries__browser-list li button span{white-space:nowrap;overflow:hidden;text-overflow:ellipsis}.et-qd-queries__browser-list li button span:first-of-type{font-weight:700}.et-qd-queries__browser-list li button:hover,.et-qd-queries__browser-list li button.et-qd-query--active{background-color:#30363c}.et-qd-queries__browser-detail{padding:12px;overflow-y:auto;overflow-x:hidden}.et-qd-queries__browser-detail__title{margin:0;font-size:14px;color:#fff;font-weight:700}.et-qd-auth-provider__viewer{border-top:1px solid #3c424a;margin-left:-12px;margin-right:-12px;margin-bottom:-12px;padding:12px;overflow-y:auto;overflow-x:hidden}.et-qd-list{display:grid;gap:12px}.et-qd-list-item{display:grid}.et-qd-list-item p{font-weight:700;margin:0;padding-bottom:8px}.et-qd-list-item p+p{margin-top:-8px}.et-qd-list-item div+pre{margin-top:8px}.et-qd-list-item pre{margin:0;padding:4px;background-color:#0e1012;border-radius:4px;max-height:450px;overflow:auto}\n"], dependencies: [{ kind: "pipe", type: AsyncPipe, name: "async" }, { kind: "pipe", type: JsonPipe, name: "json" }, { kind: "ngmodule", type: ReactiveFormsModule }, { kind: "directive", type: i1$1.NgSelectOption, selector: "option", inputs: ["ngValue", "value"] }, { kind: "directive", type: i1$1.ɵNgSelectMultipleOption, selector: "option", inputs: ["ngValue", "value"] }, { kind: "directive", type: i1$1.SelectControlValueAccessor, selector: "select:not([multiple])[formControlName],select:not([multiple])[formControl],select:not([multiple])[ngModel]", inputs: ["compareWith"] }, { kind: "directive", type: i1$1.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i1$1.FormControlDirective, selector: "[formControl]", inputs: ["formControl", "disabled", "ngModel"], outputs: ["ngModelChange"], exportAs: ["ngForm"] }, { kind: "directive", type: NgClass, selector: "[ngClass]", inputs: ["class", "ngClass"] }, { kind: "pipe", type: QueryShortNamePipe, name: "queryShortName" }, { kind: "directive", type: QueryDirective, selector: "[etQuery]", inputs: ["etQuery", "etQueryCache"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush, encapsulation: i0.ViewEncapsulation.ShadowDom }); }
|
|
2651
|
+
}
|
|
2652
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.1.1", ngImport: i0, type: QueryDevtoolsComponent, decorators: [{
|
|
2653
|
+
type: Component,
|
|
2654
|
+
args: [{ selector: 'et-query-devtools', standalone: true, changeDetection: ChangeDetectionStrategy.OnPush, encapsulation: ViewEncapsulation.ShadowDom, host: {
|
|
2655
|
+
class: 'et-query-devtools',
|
|
2656
|
+
}, imports: [AsyncPipe, JsonPipe, ReactiveFormsModule, NgClass, QueryShortNamePipe, QueryDirective], template: "@if (!isOpen()) {\n <button (click)=\"toggleOpen()\" class=\"et-query-devtools-open-btn et-qd-chip\">Query Devtools</button>\n}\n\n@if (isOpen()) {\n <div\n [class.et-query-devtools-container--transparent]=\"isTranslucent()\"\n class=\"et-query-devtools-container et-query-devtools-container--snap-{{ snapLayout() }}\"\n >\n <header class=\"et-qd-header\">\n <h3 class=\"et-qd-header__title\">Query Devtools</h3>\n\n <div class=\"et-qd-header__select\">\n <label for=\"et-query-client-instance\">Client instance</label>\n <select [formControl]=\"selectedClientIdCtrl\" id=\"et-query-client-instance\">\n @for (clientConfig of queryClientConfigs; track trackByClient(i, clientConfig); let i = $index) {\n <option [value]=\"i\">\n {{ clientConfig.displayName ?? clientConfig.client.config.baseRoute }}\n </option>\n }\n </select>\n </div>\n\n <button (click)=\"toggleOpen()\" class=\"et-qd-btn et-qd-btn-secondary\">Close</button>\n </header>\n\n <div class=\"et-qd-sub-header\">\n <button\n [ngClass]=\"{ 'et-qd-chip-secondary': !isTranslucent() }\"\n (click)=\"toggleTranslucent()\"\n class=\"et-qd-chip et-qd-translucent-clickable\"\n >\n Translucent\n </button>\n <div class=\"et-qd-sub-header__divider\"></div>\n <button\n [ngClass]=\"{ 'et-qd-chip-secondary': snapLayout() !== 'full' }\"\n (click)=\"selectSnapLayout('full')\"\n class=\"et-qd-chip\"\n >\n Fullscreen\n </button>\n <button\n [ngClass]=\"{ 'et-qd-chip-secondary': snapLayout() !== 'top' }\"\n (click)=\"selectSnapLayout('top')\"\n class=\"et-qd-chip\"\n >\n Top\n </button>\n <button\n [ngClass]=\"{ 'et-qd-chip-secondary': snapLayout() !== 'bottom' }\"\n (click)=\"selectSnapLayout('bottom')\"\n class=\"et-qd-chip\"\n >\n Bottom\n </button>\n <button\n [ngClass]=\"{ 'et-qd-chip-secondary': snapLayout() !== 'left' }\"\n (click)=\"selectSnapLayout('left')\"\n class=\"et-qd-chip\"\n >\n Left\n </button>\n <button\n [ngClass]=\"{ 'et-qd-chip-secondary': snapLayout() !== 'right' }\"\n (click)=\"selectSnapLayout('right')\"\n class=\"et-qd-chip\"\n >\n Right\n </button>\n </div>\n\n @if (selectedClientConfig(); as cfg) {\n <div class=\"et-qd-client\">\n <p class=\"et-qd-client__title\">{{ cfg.displayName ?? cfg.client.config.baseRoute }}</p>\n\n <div class=\"et-qd-client__actions\">\n <button (click)=\"cfg.client._store.refreshQueriesInUse()\" class=\"et-qd-chip\">Refresh all</button>\n <button (click)=\"cfg.client._store.refreshQueriesInUse({ ignoreCacheValidity: true })\" class=\"et-qd-chip\">\n Refresh all (ignore caches)\n </button>\n <button\n (click)=\"cfg.client._store.refreshQueriesInUse({ ignoreCacheValidity: true, purgeUnused: true })\"\n class=\"et-qd-chip\"\n >\n Refresh all (ignore caches & purge unused)\n </button>\n </div>\n </div>\n }\n\n @if (selectedClientConfig(); as cfg) {\n <div class=\"et-qd-queries\">\n <p class=\"et-qd-queries__title\">Inspector</p>\n <div class=\"et-qd-queries__actions\">\n <button\n [ngClass]=\"{ 'et-qd-chip-secondary': queryListMode() !== 'live' || viewMode() !== 'query' }\"\n (click)=\"selectQueryListMode('live'); selectViewMode('query')\"\n class=\"et-qd-chip et-qd-chip-bordered\"\n >\n Live Queries ({{ queries()?.length ?? 0 }})\n </button>\n <button\n [ngClass]=\"{ 'et-qd-chip-secondary': queryListMode() !== 'history' || viewMode() !== 'query' }\"\n (click)=\"selectQueryListMode('history'); selectViewMode('query')\"\n class=\"et-qd-chip et-qd-chip-bordered\"\n >\n Query History ({{ (queryHistory$ | async)?.length ?? 0 }}/50)\n </button>\n @if (queryListMode() === 'history' && viewMode() === 'query') {\n <div class=\"et-qd-queries__divider\"></div>\n <button (click)=\"clearQueryHistory()\" class=\"et-qd-chip et-qd-chip-bordered\">Clear history</button>\n }\n\n <div class=\"et-qd-queries__divider\"></div>\n <button\n [ngClass]=\"{ 'et-qd-chip-secondary': viewMode() !== 'authProvider' }\"\n (click)=\"selectViewMode('authProvider')\"\n class=\"et-qd-chip et-qd-chip-bordered\"\n >\n Auth Provider\n </button>\n </div>\n\n @if (viewMode() === 'query') {\n <div class=\"et-qd-queries__browser\">\n @if (queryListMode() === 'live') {\n <ul class=\"et-qd-queries__browser-list\">\n @for (query of queries(); track trackByQuery($index, query)) {\n <li>\n <button\n [class.et-qd-query--active]=\"selectedQuery()?._id === query._id\"\n (click)=\"selectQuery(query)\"\n >\n <span> {{ query._queryConfig.method }}</span>\n <span> {{ query | queryShortName }}</span>\n </button>\n </li>\n }\n </ul>\n }\n @if (queryListMode() === 'history') {\n <ul class=\"et-qd-queries__browser-list\">\n @for (query of queryHistory$ | async; track trackByQuery($index, query)) {\n <li>\n <button\n [class.et-qd-query--active]=\"selectedQuery()?._id === query._id\"\n (click)=\"selectQuery(query)\"\n >\n <span> {{ query._queryConfig.method }}</span>\n <span> {{ query | queryShortName }}</span>\n </button>\n </li>\n }\n </ul>\n }\n <div class=\"et-qd-queries__browser-detail\">\n @if (selectedQuery(); as query) {\n <p class=\"et-qd-queries__browser-detail__title\">\n {{ query._queryConfig.method }} {{ query._routeWithParams }}\n </p>\n\n <div class=\"et-qd-queries__actions\">\n <button (click)=\"query.execute()\" class=\"et-qd-chip et-qd-chip-bordered\">Execute</button>\n <button (click)=\"query.execute({ skipCache: true })\" class=\"et-qd-chip et-qd-chip-bordered\">\n Execute (ignore cache)\n </button>\n\n <button (click)=\"query.abort()\" class=\"et-qd-chip et-qd-chip-bordered\">Abort</button>\n <div class=\"et-qd-queries__divider\"></div>\n <button\n [ngClass]=\"{ 'et-qd-chip-secondary': !query.isPolling }\"\n (click)=\"query.poll({ interval: 5000, takeUntil: _destroy$ })\"\n class=\"et-qd-chip et-qd-chip-bordered\"\n >\n Poll\n </button>\n\n <button\n [ngClass]=\"{ 'et-qd-chip-secondary': query.isPolling }\"\n (click)=\"query.stopPolling()\"\n class=\"et-qd-chip et-qd-chip-bordered\"\n >\n Stop polling\n </button>\n @if (query.isPolling || query._isPollingPaused) {\n <button (click)=\"query.pausePolling()\" class=\"et-qd-chip et-qd-chip-secondary et-qd-chip-bordered\">\n Pause polling\n </button>\n <button (click)=\"query.resumePolling()\" class=\"et-qd-chip et-qd-chip-secondary et-qd-chip-bordered\">\n Resume polling\n </button>\n }\n </div>\n\n <div class=\"et-qd-list\">\n <div class=\"et-qd-list-item\">\n <p>State type</p>\n <pre>{{ query.rawState.type }}</pre>\n </div>\n\n <div class=\"et-qd-list-item\">\n <p>Dependents</p>\n <pre>{{ query._dependents | json }}</pre>\n </div>\n\n <div class=\"et-qd-list-item\">\n <p>Is expired</p>\n <pre>{{ query.isExpired }}</pre>\n </div>\n\n <div class=\"et-qd-list-item\">\n <p>Is in use</p>\n <pre>{{ query.isInUse }}</pre>\n </div>\n\n <div class=\"et-qd-list-item\">\n <p>Subscriber count</p>\n <pre>{{ query._subscriberCount }}</pre>\n </div>\n\n <div class=\"et-qd-list-item\">\n <p>Is polling</p>\n <pre>{{ query.isPolling }}</pre>\n </div>\n\n <div class=\"et-qd-list-item\">\n <p>Is polling paused</p>\n <pre>{{ query._isPollingPaused }}</pre>\n </div>\n\n <div class=\"et-qd-list-item\">\n <p>Enable smart polling</p>\n <pre>{{ query._enableSmartPolling }}</pre>\n </div>\n\n <div class=\"et-qd-list-item\">\n <p>Query store key</p>\n <pre>{{ query._queryStoreKey }}</pre>\n </div>\n\n <div class=\"et-qd-list-item\">\n <p>Arguments</p>\n\n <div>\n <button\n [ngClass]=\"{ 'et-qd-chip-secondary': !showArguments() }\"\n (click)=\"toggleShowArguments()\"\n class=\"et-qd-chip et-qd-chip-bordered\"\n >\n Toggle\n </button>\n </div>\n @if (showArguments()) {\n <pre>{{ query._arguments | json }}</pre>\n }\n </div>\n\n <div class=\"et-qd-list-item\">\n <p>State</p>\n <p><i> Warning, this will cause a subscription if shown!</i></p>\n <div>\n <button\n [ngClass]=\"{ 'et-qd-chip-secondary': !showResponse() }\"\n (click)=\"toggleShowResponse()\"\n class=\"et-qd-chip et-qd-chip-bordered\"\n >\n Toggle\n </button>\n </div>\n @if (showResponse()) {\n <pre>{{ query.state$ | async | json }}</pre>\n }\n </div>\n\n <div class=\"et-qd-list-item\">\n <p>Raw state</p>\n <p><i> The query state unaffected by entity stores</i></p>\n\n <div>\n <button\n [ngClass]=\"{ 'et-qd-chip-secondary': !showRawResponse() }\"\n (click)=\"toggleShowRawResponse()\"\n class=\"et-qd-chip et-qd-chip-bordered\"\n >\n Toggle\n </button>\n </div>\n @if (showRawResponse()) {\n <pre>{{ query.rawState | json }}</pre>\n }\n </div>\n\n <div class=\"et-qd-list-item\">\n <p>Query config</p>\n\n <div>\n <button\n [ngClass]=\"{ 'et-qd-chip-secondary': !showQueryConfig() }\"\n (click)=\"toggleShowQueryConfig()\"\n class=\"et-qd-chip et-qd-chip-bordered\"\n >\n Toggle\n </button>\n </div>\n @if (showQueryConfig()) {\n <pre>{{ stringifiedQueryConfig() | json }}</pre>\n }\n </div>\n\n @if (!!query._queryConfig.entity) {\n <div class=\"et-qd-list-item\">\n <p>Entity store value</p>\n\n <div>\n <button\n [ngClass]=\"{ 'et-qd-chip-secondary': !showEntityStoreValue() }\"\n (click)=\"toggleShowEntityStoreValue()\"\n class=\"et-qd-chip et-qd-chip-bordered\"\n >\n Toggle\n </button>\n </div>\n @if (showEntityStoreValue()) {\n <pre>{{ selectedQueryEntityStoreValue$ | async | json }}</pre>\n }\n </div>\n }\n </div>\n }\n </div>\n </div>\n } @else if (viewMode() === 'authProvider') {\n <div class=\"et-qd-auth-provider__viewer\">\n @if (authProviderDetails(); as details) {\n <div class=\"et-qd-list\">\n <div class=\"et-qd-list-item\">\n <p>Type</p>\n <pre>{{ details.type }}</pre>\n </div>\n <div class=\"et-qd-list-item\">\n <p>Header value</p>\n <pre>{{ details.header() | json }}</pre>\n </div>\n <div class=\"et-qd-list-item\">\n <p>Config</p>\n <pre>{{ details.config | json }}</pre>\n </div>\n\n @if (details.type === 'BearerAuthProvider') {\n <div class=\"et-qd-list-item\">\n <p>Tokens</p>\n <pre>{{ details.provider.tokens$ | async | json }}</pre>\n </div>\n\n <div class=\"et-qd-list-item\">\n <p>Should refresh on unauthorized response</p>\n <pre>{{ details.provider.shouldRefreshOnUnauthorizedResponse }}</pre>\n </div>\n\n <ng-container\n *etQuery=\"\n details.provider.currentRefreshQuery$ | async as response;\n loading as loading;\n error as error;\n query as query\n \"\n >\n <div class=\"et-qd-list-item\">\n <p>Refresh query response</p>\n\n <div>\n <button (click)=\"details.provider._refreshQuery()\" class=\"et-qd-chip et-qd-chip-bordered\">\n Refresh\n </button>\n </div>\n <pre>{{ response | json }}</pre>\n </div>\n <div class=\"et-qd-list-item\">\n <p>Refresh query loading</p>\n <pre>{{ loading }}</pre>\n </div>\n <div class=\"et-qd-list-item\">\n <p>Refresh query error</p>\n <pre>{{ error ?? 'null' }}</pre>\n </div>\n <div class=\"et-qd-list-item\">\n <p>State</p>\n <pre>{{ query?.state$ | async | json }}</pre>\n </div>\n </ng-container>\n }\n </div>\n } @else {\n <p>No auth provider set</p>\n }\n </div>\n }\n </div>\n }\n </div>\n}\n", styles: [":host{position:fixed;top:0;z-index:1000000;font-size:14px;color:#fff}.et-query-devtools-container{position:fixed;inset:0;background-color:#25292e;z-index:10000;overflow:auto;font-family:monospace;display:grid;grid-template-rows:auto auto auto minmax(0,1fr)}.et-query-devtools-container.et-query-devtools-container--transparent{opacity:.25;pointer-events:none}.et-query-devtools-container.et-query-devtools-container--transparent .et-query-devtools-translucent-btn{pointer-events:all}.et-query-devtools-container.et-query-devtools-container--snap-left{inset-inline-start:0;inset-block-start:0;inset-block-end:0;width:50%;border-right:1px solid rgb(37,41,46)}.et-query-devtools-container.et-query-devtools-container--snap-right{inset-inline-start:unset;inset-inline-end:0;inset-block-start:0;inset-block-end:0;width:50%;border-left:1px solid rgb(37,41,46)}.et-query-devtools-container.et-query-devtools-container--snap-top{inset-inline-start:0;inset-block-start:0;inset-inline-end:0;height:50%;border-bottom:1px solid rgb(37,41,46)}.et-query-devtools-container.et-query-devtools-container--snap-bottom{inset-inline-start:0;inset-block-start:unset;inset-block-end:0;inset-inline-end:0;height:50%;border-top:1px solid rgb(37,41,46)}button.et-query-devtools-open-btn{position:fixed;inset-inline-end:20px;inset-block-end:0;z-index:9999;font-family:monospace;border-radius:3px 3px 0 0;line-height:14px;font-size:12px}.et-qd-header{background-color:#00ffa1;padding:12px;display:grid;grid-template-columns:1fr auto auto;gap:12px;align-items:center}.et-qd-header__title{color:#25292e;margin:0;font-size:22px}.et-qd-header__select{display:grid;background-color:#25292e;border-radius:10px;overflow:hidden}.et-qd-header__select select{width:250px;color:#fff;padding:3px 10px 2px;border:none;background-color:#25292e;font-family:monospace}.et-qd-header__select label{padding:3px 10px;color:#25292e;font-size:12px;font-weight:700;color:#fff;border-bottom:1px solid gray}.et-qd-sub-header{background-color:#00b371;padding:12px;display:flex;gap:12px;flex-wrap:wrap}.et-qd-sub-header__divider{width:1px;background-color:#25292e}.et-qd-btn{background-color:#00ffa1;color:#25292e;border:none;padding:12px;cursor:pointer;font-size:14px;font-weight:700;text-align:center;text-decoration:none;display:inline-block;width:100%;transition:all .3s ease-in-out;font-family:monospace;border-radius:10px}.et-qd-btn:hover{background-color:#00cc81}.et-qd-btn.et-qd-btn-secondary{background-color:#25292e;color:#00ffa1}.et-qd-btn.et-qd-btn-secondary:hover{background-color:#3c424a}.et-qd-chip{background-color:#00ffa1;color:#25292e;border:none;padding:4px 9px;cursor:pointer;font-size:12px;font-weight:700;text-align:center;text-decoration:none;display:inline-block;transition:all .3s ease-in-out;font-family:monospace;border-radius:10px}.et-qd-chip:hover{background-color:#00cc81}.et-qd-chip.et-qd-chip-secondary{background-color:#25292e;color:#00ffa1}.et-qd-chip.et-qd-chip-secondary:hover{background-color:#3c424a}.et-qd-chip.et-qd-chip-bordered{border:1px solid rgb(0,255,161)}.et-qd-translucent-clickable{pointer-events:all}.et-qd-client{border-bottom:1px solid #3c424a;padding:12px}.et-qd-client__title{margin:0;font-size:14px;color:#fff;font-weight:700}.et-qd-client__actions{display:flex;gap:12px;margin-top:12px}.et-qd-queries{padding:12px;display:grid;grid-template-rows:auto auto minmax(0,1fr)}.et-qd-queries__actions{display:flex;gap:12px;margin-top:12px;margin-bottom:16px}.et-qd-queries__title{margin:0;font-size:14px;color:#fff;font-weight:700}.et-qd-queries__divider{width:1px;background-color:#3c424a}.et-qd-queries__browser{display:grid;grid-template-columns:1fr minmax(0,3fr);margin-left:-12px;margin-right:-12px;border-top:1px solid #3c424a;margin-bottom:-12px}.et-qd-queries__browser-list{border-right:1px solid #3c424a;list-style:none;padding:0;margin:0;display:grid;grid-auto-flow:row;grid-auto-rows:55px;overflow-y:auto;overflow-x:hidden}.et-qd-queries__browser-list li{margin:0}.et-qd-queries__browser-list li button{all:unset;font-family:monospace;box-sizing:border-box;width:100%;border:none;border-bottom:1px solid #3c424a;background-color:#25292e;color:#fff;text-align:left;padding:12px;height:100%;transition:all .3s ease-in-out;cursor:pointer;display:grid;grid-template-columns:auto minmax(0,1fr);gap:2px;display:block}.et-qd-queries__browser-list li button span{white-space:nowrap;overflow:hidden;text-overflow:ellipsis}.et-qd-queries__browser-list li button span:first-of-type{font-weight:700}.et-qd-queries__browser-list li button:hover,.et-qd-queries__browser-list li button.et-qd-query--active{background-color:#30363c}.et-qd-queries__browser-detail{padding:12px;overflow-y:auto;overflow-x:hidden}.et-qd-queries__browser-detail__title{margin:0;font-size:14px;color:#fff;font-weight:700}.et-qd-auth-provider__viewer{border-top:1px solid #3c424a;margin-left:-12px;margin-right:-12px;margin-bottom:-12px;padding:12px;overflow-y:auto;overflow-x:hidden}.et-qd-list{display:grid;gap:12px}.et-qd-list-item{display:grid}.et-qd-list-item p{font-weight:700;margin:0;padding-bottom:8px}.et-qd-list-item p+p{margin-top:-8px}.et-qd-list-item div+pre{margin-top:8px}.et-qd-list-item pre{margin:0;padding:4px;background-color:#0e1012;border-radius:4px;max-height:450px;overflow:auto}\n"] }]
|
|
2657
|
+
}], ctorParameters: () => [] });
|
|
2658
|
+
|
|
2659
|
+
class EntityStoreError extends Error {
|
|
2660
|
+
constructor(message, detail) {
|
|
2661
|
+
super(message);
|
|
2662
|
+
this.detail = detail;
|
|
2663
|
+
this.name = 'EntityStoreError';
|
|
2664
|
+
}
|
|
2665
|
+
}
|
|
2666
|
+
function flatten(arr) {
|
|
2667
|
+
return arr.reduce((acc, val) => acc.concat(val), []);
|
|
2668
|
+
}
|
|
2669
|
+
class EntityStore {
|
|
2670
|
+
constructor(_config) {
|
|
2671
|
+
this._config = _config;
|
|
2672
|
+
/**
|
|
2673
|
+
* @internal
|
|
2674
|
+
*/
|
|
2675
|
+
this._dictionary = new Map();
|
|
2676
|
+
/**
|
|
2677
|
+
* @internal
|
|
2678
|
+
*/
|
|
2679
|
+
this._change$ = new Subject();
|
|
2680
|
+
}
|
|
2681
|
+
select(keyOrKeys) {
|
|
2682
|
+
if (Array.isArray(keyOrKeys)) {
|
|
2683
|
+
const _keys = this._normalizeKeys(keyOrKeys);
|
|
2684
|
+
return this._selectMany(_keys);
|
|
2685
|
+
}
|
|
2686
|
+
return this._select(keyOrKeys);
|
|
2687
|
+
}
|
|
2688
|
+
/**
|
|
2689
|
+
* **Note:** Prefer `select` over `selectWhere` when possible.
|
|
2690
|
+
*/
|
|
2691
|
+
selectWhere(predicate) {
|
|
2692
|
+
return this._change$.pipe(startWith(null), debounceTime(0), map(() => Array.from(this._dictionary.values()).filter(predicate)), shareReplay({ bufferSize: 1, refCount: true }));
|
|
2693
|
+
}
|
|
2694
|
+
set(keyOrKeys, valueOrValues) {
|
|
2695
|
+
const _keys = this._normalizeKeys(keyOrKeys);
|
|
2696
|
+
if (Array.isArray(_keys)) {
|
|
2697
|
+
if (!Array.isArray(valueOrValues)) {
|
|
2698
|
+
throw new EntityStoreError('When setting multiple keys, values must be an array', {
|
|
2699
|
+
keyOrValues: _keys,
|
|
2700
|
+
valueOrValues,
|
|
2701
|
+
});
|
|
2702
|
+
}
|
|
2703
|
+
const _values = (Array.isArray(valueOrValues[0]) ? flatten(valueOrValues) : valueOrValues);
|
|
2704
|
+
for (let i = 0; i < _keys.length; i++) {
|
|
2705
|
+
const k = _keys[i];
|
|
2706
|
+
const v = _values[i];
|
|
2707
|
+
if (k === undefined || v === undefined)
|
|
2708
|
+
continue;
|
|
2709
|
+
this._set(k, v);
|
|
2710
|
+
}
|
|
2711
|
+
}
|
|
2712
|
+
else {
|
|
2713
|
+
this._set(_keys, valueOrValues);
|
|
2714
|
+
}
|
|
2715
|
+
this._change$.next(_keys);
|
|
2716
|
+
}
|
|
2717
|
+
remove(keyOrKeys) {
|
|
2718
|
+
const _keys = this._normalizeKeys(keyOrKeys);
|
|
2719
|
+
if (Array.isArray(_keys)) {
|
|
2720
|
+
for (const key of _keys) {
|
|
2721
|
+
this._remove(key);
|
|
2722
|
+
}
|
|
2723
|
+
}
|
|
2724
|
+
else {
|
|
2725
|
+
this._remove(_keys);
|
|
2726
|
+
}
|
|
2727
|
+
this._change$.next(_keys);
|
|
2728
|
+
}
|
|
2729
|
+
logState() {
|
|
2730
|
+
console.log('EntityStore:', this._config.name, {
|
|
2731
|
+
dictionary: this._dictionary,
|
|
2732
|
+
});
|
|
2733
|
+
}
|
|
2734
|
+
_select(key) {
|
|
2735
|
+
if (this._config.logActions) {
|
|
2736
|
+
console.log('EntityStore: select', this._config.name, key);
|
|
2737
|
+
}
|
|
2738
|
+
return this._change$.pipe(startWith(null), filter((v) => {
|
|
2739
|
+
if (v === null) {
|
|
2740
|
+
return true;
|
|
2741
|
+
}
|
|
2742
|
+
if (Array.isArray(v)) {
|
|
2743
|
+
return v.includes(key);
|
|
2744
|
+
}
|
|
2745
|
+
return v === key;
|
|
2746
|
+
}), map(() => this._dictionary.get(key) ?? null));
|
|
2747
|
+
}
|
|
2748
|
+
_selectMany(keys) {
|
|
2749
|
+
if (this._config.logActions) {
|
|
2750
|
+
console.log('EntityStore: selectMany', this._config.name, keys);
|
|
2751
|
+
}
|
|
2752
|
+
return this._change$.pipe(startWith(null), filter((v) => {
|
|
2753
|
+
if (v === null) {
|
|
2754
|
+
return true;
|
|
2755
|
+
}
|
|
2756
|
+
if (Array.isArray(v)) {
|
|
2757
|
+
return keys.some((key) => v.includes(key));
|
|
2758
|
+
}
|
|
2759
|
+
return keys.includes(v);
|
|
2760
|
+
}), map(() => keys.map((key) => this._dictionary.get(key)).filter((value) => value !== undefined)));
|
|
2761
|
+
}
|
|
2762
|
+
_set(key, value) {
|
|
2763
|
+
if (this._config.logActions) {
|
|
2764
|
+
console.log('EntityStore: set', this._config.name, key, value);
|
|
2765
|
+
}
|
|
2766
|
+
this._dictionary.set(key, value);
|
|
2767
|
+
}
|
|
2768
|
+
_remove(key) {
|
|
2769
|
+
if (this._config.logActions) {
|
|
2770
|
+
console.log('EntityStore: remove', this._config.name, key);
|
|
2771
|
+
}
|
|
2772
|
+
this._dictionary.delete(key);
|
|
2773
|
+
}
|
|
2774
|
+
_normalizeKeys(keys) {
|
|
2775
|
+
if (Array.isArray(keys)) {
|
|
2776
|
+
if (Array.isArray(keys[0])) {
|
|
2777
|
+
return flatten(keys);
|
|
2778
|
+
}
|
|
2779
|
+
return keys;
|
|
2780
|
+
}
|
|
2781
|
+
return keys;
|
|
2782
|
+
}
|
|
2783
|
+
}
|
|
2784
|
+
|
|
2785
|
+
function mapToPaginated(response) {
|
|
2786
|
+
return map((items) => ({ ...response, items }));
|
|
2787
|
+
}
|
|
2788
|
+
// FIXME: This breaks if SubStoreType is an array
|
|
2789
|
+
/**
|
|
2790
|
+
* Use an other store to represent a property of the current entity.
|
|
2791
|
+
*/
|
|
2792
|
+
const insertFrom = (store, { for: key, id }) => switchMap((value) => {
|
|
2793
|
+
if (!value)
|
|
2794
|
+
return of(null);
|
|
2795
|
+
const ids = id(value);
|
|
2796
|
+
if (Array.isArray(ids)) {
|
|
2797
|
+
return store.select(ids).pipe(map((data) => ({ ...value, [key]: data })));
|
|
2798
|
+
}
|
|
2799
|
+
else {
|
|
2800
|
+
return store.select(ids).pipe(map((data) => ({ ...value, [key]: data })));
|
|
2801
|
+
}
|
|
2802
|
+
});
|
|
2803
|
+
/**
|
|
2804
|
+
* Remove entities from a store based on a where function.
|
|
2805
|
+
*/
|
|
2806
|
+
const removeFrom = (store, { where, id }) => {
|
|
2807
|
+
store
|
|
2808
|
+
.selectWhere(where)
|
|
2809
|
+
.pipe(tap((entities) => store.remove(entities.map(id))), take(1))
|
|
2810
|
+
.subscribe();
|
|
2811
|
+
};
|
|
2812
|
+
|
|
2813
|
+
const transformToString = (value) => {
|
|
2814
|
+
if (typeof value === 'string') {
|
|
2815
|
+
return value;
|
|
2816
|
+
}
|
|
2817
|
+
return null;
|
|
2818
|
+
};
|
|
2819
|
+
const transformToStringArray = (value) => {
|
|
2820
|
+
if (Array.isArray(value)) {
|
|
2821
|
+
return value.map(transformToString).filter(Boolean);
|
|
2822
|
+
}
|
|
2823
|
+
else if (typeof value === 'string') {
|
|
2824
|
+
return [transformToString(value)];
|
|
2825
|
+
}
|
|
2826
|
+
return null;
|
|
2827
|
+
};
|
|
2828
|
+
const transformToNumber = (value) => {
|
|
2829
|
+
if (typeof value === 'number') {
|
|
2830
|
+
return value;
|
|
2831
|
+
}
|
|
2832
|
+
if (typeof value === 'string') {
|
|
2833
|
+
return Number(value);
|
|
2834
|
+
}
|
|
2835
|
+
return null;
|
|
2836
|
+
};
|
|
2837
|
+
const transformToNumberArray = (value) => {
|
|
2838
|
+
if (Array.isArray(value)) {
|
|
2839
|
+
return value.map(transformToNumber).filter((item) => item !== null);
|
|
2840
|
+
}
|
|
2841
|
+
else if (typeof value === 'number') {
|
|
2842
|
+
return [value];
|
|
2843
|
+
}
|
|
2844
|
+
return null;
|
|
2845
|
+
};
|
|
2846
|
+
const transformToBoolean = (value) => {
|
|
2847
|
+
if (typeof value === 'boolean') {
|
|
2848
|
+
return value;
|
|
2849
|
+
}
|
|
2850
|
+
if (typeof value === 'string') {
|
|
2851
|
+
return value === 'true' || value === '1';
|
|
2852
|
+
}
|
|
2853
|
+
return null;
|
|
2854
|
+
};
|
|
2855
|
+
const transformToBooleanArray = (value) => {
|
|
2856
|
+
if (Array.isArray(value)) {
|
|
2857
|
+
return value.map(transformToBoolean).filter((item) => item !== null);
|
|
2858
|
+
}
|
|
2859
|
+
else if (typeof value === 'string' || typeof value === 'boolean') {
|
|
2860
|
+
return [transformToBoolean(value)];
|
|
2861
|
+
}
|
|
2862
|
+
return null;
|
|
2863
|
+
};
|
|
2864
|
+
const transformToDate = (value) => {
|
|
2865
|
+
if (value instanceof Date) {
|
|
2866
|
+
return value;
|
|
2867
|
+
}
|
|
2868
|
+
if (typeof value === 'string') {
|
|
2869
|
+
const date = new Date(value);
|
|
2870
|
+
if (isNaN(date.getTime())) {
|
|
2871
|
+
return null;
|
|
2872
|
+
}
|
|
2873
|
+
return date;
|
|
2874
|
+
}
|
|
2875
|
+
return null;
|
|
2876
|
+
};
|
|
2877
|
+
const transformToDateArray = (value) => {
|
|
2878
|
+
if (Array.isArray(value)) {
|
|
2879
|
+
return value.map(transformToDate).filter((item) => item !== null);
|
|
2880
|
+
}
|
|
2881
|
+
else if (typeof value === 'string' || value instanceof Date) {
|
|
2882
|
+
return [transformToDate(value)];
|
|
2883
|
+
}
|
|
2884
|
+
return null;
|
|
2885
|
+
};
|
|
2886
|
+
const transformToSort = (value) => {
|
|
2887
|
+
if (typeof value === 'string') {
|
|
2888
|
+
const [active, direction] = value.split(':');
|
|
2889
|
+
if (!active) {
|
|
2890
|
+
return null;
|
|
2891
|
+
}
|
|
2892
|
+
return {
|
|
2893
|
+
active,
|
|
2894
|
+
direction: direction,
|
|
2895
|
+
};
|
|
2896
|
+
}
|
|
2897
|
+
return null;
|
|
2898
|
+
};
|
|
2899
|
+
const transformToSortQueryParam = (value) => {
|
|
2900
|
+
if (typeof value === 'string') {
|
|
2901
|
+
return value;
|
|
2902
|
+
}
|
|
2903
|
+
if (value && typeof value === 'object' && 'active' in value && 'direction' in value) {
|
|
2904
|
+
const valAsSort = value;
|
|
2905
|
+
return valAsSort.direction ? `${valAsSort.active}:${valAsSort.direction}` : null;
|
|
2906
|
+
}
|
|
2907
|
+
return null;
|
|
2908
|
+
};
|
|
2909
|
+
|
|
2910
|
+
const ET_ARR_PREFIX = 'ET_ARR__';
|
|
2911
|
+
class QueryField {
|
|
2912
|
+
get control() {
|
|
2913
|
+
return this.data.control;
|
|
2914
|
+
}
|
|
2915
|
+
constructor(data) {
|
|
2916
|
+
this.data = data;
|
|
2917
|
+
}
|
|
2918
|
+
}
|
|
2919
|
+
const IGNORED_FILTER_COUNT_FIELDS = ['page', 'skip', 'take', 'limit', 'sort', 'sortBy', 'sortOrder', 'query', 'search'];
|
|
2920
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
2921
|
+
class QueryForm {
|
|
2922
|
+
get _formValue() {
|
|
2923
|
+
return this.form.getRawValue();
|
|
2924
|
+
}
|
|
2925
|
+
get _form() {
|
|
2926
|
+
return this.form;
|
|
2927
|
+
}
|
|
2928
|
+
get controls() {
|
|
2929
|
+
return this.form.controls;
|
|
2930
|
+
}
|
|
2931
|
+
get value() {
|
|
2932
|
+
return this._changes$.value.currentValue;
|
|
2933
|
+
}
|
|
2934
|
+
get defaultFormValue() {
|
|
2935
|
+
return Object.entries(this._defaultValues).reduce((acc, [key]) => {
|
|
2936
|
+
acc[key] = this._getDefaultValue(key);
|
|
2937
|
+
return acc;
|
|
2938
|
+
}, {});
|
|
2939
|
+
}
|
|
2940
|
+
// with prefix eg. page should become ${prefix}-page
|
|
2941
|
+
constructor(_fields, _options) {
|
|
2942
|
+
this._fields = _fields;
|
|
2943
|
+
this._options = _options;
|
|
2944
|
+
this._destroy$ = createDestroy();
|
|
2945
|
+
this._unobserveTrigger$ = new Subject();
|
|
2946
|
+
this._router = inject(Router);
|
|
2947
|
+
this._activatedRoute = inject(ActivatedRoute);
|
|
2948
|
+
this._defaultValues = this._extractDefaultValues();
|
|
2949
|
+
this._routerStateService = inject(RouterStateService);
|
|
2950
|
+
this._zone = inject(NgZone);
|
|
2951
|
+
this._didValueChanges$ = new Subject();
|
|
2952
|
+
this._isObserving = false;
|
|
2953
|
+
this._skipNextResets = false;
|
|
2954
|
+
/**
|
|
2955
|
+
* The angular form group that contains all the fields.
|
|
2956
|
+
*
|
|
2957
|
+
* **Do not** use any of the following methods on this form group:
|
|
2958
|
+
* - `setValue`: Use `QueryForm.setValue` instead.
|
|
2959
|
+
* - `patchValue`: Use `QueryForm.patchValue` instead.
|
|
2960
|
+
* - `valueChanges`: Use `QueryForm.changes$` instead.
|
|
2961
|
+
* - `value`: Use `QueryForm.value` instead.
|
|
2962
|
+
* - `controls`: Use `QueryForm.controls` instead.
|
|
2963
|
+
*/
|
|
2964
|
+
this.form = this._setupFormGroup();
|
|
2965
|
+
this._lastFormValue$ = new BehaviorSubject(null);
|
|
2966
|
+
this._currentFormValue$ = new BehaviorSubject(this._formValue);
|
|
2967
|
+
this._changes$ = new BehaviorSubject({
|
|
2968
|
+
previousValue: null,
|
|
2969
|
+
currentValue: this.form.getRawValue(),
|
|
2970
|
+
});
|
|
2971
|
+
this.changes$ = this._changes$.asObservable();
|
|
2972
|
+
this.currentValue$ = this._changes$.pipe(map(({ currentValue }) => currentValue));
|
|
2973
|
+
this.previousValue$ = this._changes$.pipe(map(({ previousValue }) => previousValue));
|
|
2974
|
+
this.changes = toSignal(this.changes$);
|
|
2975
|
+
this.currentValue = toSignal(this.currentValue$);
|
|
2976
|
+
this.previousValue = toSignal(this.previousValue$);
|
|
2977
|
+
/**
|
|
2978
|
+
* The number of active filters.
|
|
2979
|
+
*
|
|
2980
|
+
* Excludes the following fields by default:
|
|
2981
|
+
* - `page`
|
|
2982
|
+
* - `skip`
|
|
2983
|
+
* - `take`
|
|
2984
|
+
* - `limit`
|
|
2985
|
+
* - `sort`
|
|
2986
|
+
* - `sortBy`
|
|
2987
|
+
* - `sortOrder`
|
|
2988
|
+
* - `query`
|
|
2989
|
+
* - `search`
|
|
2990
|
+
*/
|
|
2991
|
+
this.activeFilterCount$ = this.changes$.pipe(map(({ currentValue }) => Object.entries(currentValue)
|
|
2992
|
+
.map(([key, value]) => {
|
|
2993
|
+
if (IGNORED_FILTER_COUNT_FIELDS.includes(key) || this._fields[key]?.data.skipInFilterCount) {
|
|
2994
|
+
return true;
|
|
2995
|
+
}
|
|
2996
|
+
return this._isDefaultValue(key, value);
|
|
2997
|
+
})
|
|
2998
|
+
.filter((v) => v === false).length));
|
|
2999
|
+
assertInInjectionContext(QueryForm);
|
|
3000
|
+
}
|
|
3001
|
+
observe(options) {
|
|
3002
|
+
if (this._isObserving) {
|
|
3003
|
+
if (isDevMode()) {
|
|
3004
|
+
console.warn('QueryForm.observe() was called multiple times. This is not supported.');
|
|
3005
|
+
}
|
|
3006
|
+
return this;
|
|
3007
|
+
}
|
|
3008
|
+
this._isObserving = true;
|
|
3009
|
+
if (options?.syncOnNavigation !== false) {
|
|
3010
|
+
const didChanges = this.setFormValueFromUrlQueryParams({
|
|
3011
|
+
queryParams: this._routerStateService.queryParams,
|
|
3012
|
+
});
|
|
3013
|
+
if (didChanges) {
|
|
3014
|
+
this._handleFormChange(true);
|
|
3015
|
+
}
|
|
3016
|
+
}
|
|
3017
|
+
merge(...Object.values(this._fields).map((field) => field.control.valueChanges), this._didValueChanges$)
|
|
3018
|
+
.pipe(debounceTime(0), tap(() => {
|
|
3019
|
+
this._handleFormChange();
|
|
3020
|
+
}), takeUntil(this._destroy$), takeUntil(this._unobserveTrigger$))
|
|
3021
|
+
.subscribe();
|
|
3022
|
+
let changedFieldsInLastResetLoop = [];
|
|
3023
|
+
let currentUniqueChangedFields = [];
|
|
3024
|
+
this._currentFormValue$
|
|
3025
|
+
.pipe(map((currentValue) => {
|
|
3026
|
+
return {
|
|
3027
|
+
previousValue: clone(this._lastFormValue$.value),
|
|
3028
|
+
currentValue: clone(currentValue),
|
|
3029
|
+
};
|
|
3030
|
+
}), tap(({ currentValue, previousValue }) => {
|
|
3031
|
+
if (options?.writeToQueryParams !== false) {
|
|
3032
|
+
this._syncViaUrlQueryParams(currentValue, options?.replaceUrl);
|
|
3033
|
+
}
|
|
3034
|
+
const didResetValues = this._skipNextResets
|
|
3035
|
+
? false
|
|
3036
|
+
: this._handleQueryFormResets(previousValue ?? null, currentValue);
|
|
3037
|
+
this._skipNextResets = false;
|
|
3038
|
+
const changedFields = Object.keys(currentValue).filter((key) => !equal(previousValue?.[key], currentValue[key]));
|
|
3039
|
+
if (changedFieldsInLastResetLoop.length) {
|
|
3040
|
+
changedFields.push(...changedFieldsInLastResetLoop);
|
|
3041
|
+
changedFieldsInLastResetLoop = [];
|
|
3042
|
+
}
|
|
3043
|
+
if (didResetValues) {
|
|
3044
|
+
this._didValueChanges$.next(true);
|
|
3045
|
+
changedFieldsInLastResetLoop = changedFields;
|
|
3046
|
+
}
|
|
3047
|
+
currentUniqueChangedFields = [...new Set(changedFields)];
|
|
3048
|
+
}), switchMap(({ currentValue, previousValue }) => {
|
|
3049
|
+
if (changedFieldsInLastResetLoop.length)
|
|
3050
|
+
return of(null).pipe(map(() => ({ currentValue, previousValue })));
|
|
3051
|
+
const debounceValues = currentUniqueChangedFields.map((key) => {
|
|
3052
|
+
const field = this._fields[key];
|
|
3053
|
+
if (!field)
|
|
3054
|
+
return null;
|
|
3055
|
+
if (field.data.disableDebounceIfFalsy === true && !currentValue[key]) {
|
|
3056
|
+
return null;
|
|
3057
|
+
}
|
|
3058
|
+
return field.data.debounce ?? null;
|
|
3059
|
+
});
|
|
3060
|
+
currentUniqueChangedFields = [];
|
|
3061
|
+
if (debounceValues.some((v) => v === null) || !debounceValues.length) {
|
|
3062
|
+
return of(null).pipe(map(() => ({ currentValue, previousValue })));
|
|
3063
|
+
}
|
|
3064
|
+
const shortestDebounceTime = Math.min(...debounceValues.filter((v) => v !== null));
|
|
3065
|
+
return timer(shortestDebounceTime).pipe(map(() => ({ currentValue, previousValue })));
|
|
3066
|
+
}), tap(({ currentValue, previousValue }) => {
|
|
3067
|
+
if (changedFieldsInLastResetLoop.length)
|
|
3068
|
+
return;
|
|
3069
|
+
this._changes$.next({
|
|
3070
|
+
previousValue: previousValue ?? null,
|
|
3071
|
+
currentValue: currentValue,
|
|
3072
|
+
});
|
|
3073
|
+
}), takeUntil(this._destroy$), takeUntil(this._unobserveTrigger$))
|
|
3074
|
+
.subscribe();
|
|
3075
|
+
if (options?.syncOnNavigation !== false) {
|
|
3076
|
+
this._routerStateService.queryParamChanges$
|
|
3077
|
+
.pipe(takeUntil(this._destroy$), takeUntil(this._unobserveTrigger$), tap((changes) => {
|
|
3078
|
+
const didValueChanges = this.setFormValueFromUrlQueryParams({ queryParams: changes });
|
|
3079
|
+
if (didValueChanges) {
|
|
3080
|
+
this._didValueChanges$.next(true);
|
|
3081
|
+
}
|
|
3082
|
+
}))
|
|
3083
|
+
.subscribe();
|
|
3084
|
+
}
|
|
3085
|
+
return this;
|
|
3086
|
+
}
|
|
3087
|
+
_handleQueryFormResets(previousValue, currentValue) {
|
|
3088
|
+
let didResetValues = false;
|
|
3089
|
+
for (const formFieldKey in this._fields) {
|
|
3090
|
+
const field = this._fields[formFieldKey];
|
|
3091
|
+
if (!field)
|
|
3092
|
+
continue;
|
|
3093
|
+
const resets = field.data.isResetBy;
|
|
3094
|
+
if (!resets)
|
|
3095
|
+
continue;
|
|
3096
|
+
const resetConditionKeys = Array.isArray(resets) ? resets : [resets];
|
|
3097
|
+
for (const resetConditionKey of resetConditionKeys) {
|
|
3098
|
+
if (!(resetConditionKey in this._fields)) {
|
|
3099
|
+
if (isDevMode()) {
|
|
3100
|
+
console.warn(`The field "${resetConditionKey}" is not defined in the QueryForm. Is it a typo?`, this);
|
|
3101
|
+
}
|
|
3102
|
+
continue;
|
|
3103
|
+
}
|
|
3104
|
+
if (previousValue &&
|
|
3105
|
+
currentValue &&
|
|
3106
|
+
!equal(previousValue[resetConditionKey], currentValue[resetConditionKey])) {
|
|
3107
|
+
const defaultValueForKeyToReset = this._getDefaultValue(formFieldKey);
|
|
3108
|
+
const currentValueForKeyToReset = currentValue[formFieldKey];
|
|
3109
|
+
if (equal(defaultValueForKeyToReset, currentValueForKeyToReset)) {
|
|
3110
|
+
continue;
|
|
3111
|
+
}
|
|
3112
|
+
this.form.controls[formFieldKey].setValue(defaultValueForKeyToReset);
|
|
3113
|
+
didResetValues = true;
|
|
3114
|
+
break;
|
|
3115
|
+
}
|
|
3116
|
+
}
|
|
3117
|
+
}
|
|
3118
|
+
return didResetValues;
|
|
3119
|
+
}
|
|
3120
|
+
unobserve() {
|
|
3121
|
+
this._unobserveTrigger$.next(true);
|
|
3122
|
+
}
|
|
3123
|
+
setFormValueFromUrlQueryParams(options) {
|
|
3124
|
+
let didValueChanges = false;
|
|
3125
|
+
for (const [key, field] of Object.entries(this._fields)) {
|
|
3126
|
+
const value = options.queryParams[this._transformKeyToQueryParam(key)];
|
|
3127
|
+
const valueDoesNotExist = value === undefined;
|
|
3128
|
+
if (valueDoesNotExist)
|
|
3129
|
+
continue;
|
|
3130
|
+
const valueGotRemoved = value === ET_PROPERTY_REMOVED;
|
|
3131
|
+
if (valueGotRemoved) {
|
|
3132
|
+
field.control.setValue(this._getDefaultValue(key), { emitEvent: false });
|
|
3133
|
+
didValueChanges = true;
|
|
3134
|
+
continue;
|
|
3135
|
+
}
|
|
3136
|
+
let deserializedValue = value;
|
|
3137
|
+
if (field.data.queryParamToValueTransformFn) {
|
|
3138
|
+
deserializedValue = field.data.queryParamToValueTransformFn(value);
|
|
3139
|
+
}
|
|
3140
|
+
else if (typeof this._getDefaultValue(key) === 'number' || !isNaN(Number(value))) {
|
|
3141
|
+
deserializedValue = transformToNumber(value);
|
|
3142
|
+
}
|
|
3143
|
+
else if (typeof this._getDefaultValue(key) === 'boolean' || value === 'true' || value === 'false') {
|
|
3144
|
+
deserializedValue = transformToBoolean(value);
|
|
3145
|
+
}
|
|
3146
|
+
const valueIsEqualToCurrent = equal(deserializedValue, field.control.value);
|
|
3147
|
+
if (valueIsEqualToCurrent)
|
|
3148
|
+
continue;
|
|
3149
|
+
field.control.setValue(deserializedValue, { emitEvent: false });
|
|
3150
|
+
didValueChanges = true;
|
|
3151
|
+
}
|
|
3152
|
+
return didValueChanges;
|
|
3153
|
+
}
|
|
3154
|
+
setValue(value, options) {
|
|
3155
|
+
this._form._setValue(value, options);
|
|
3156
|
+
if (options?.skipResets) {
|
|
3157
|
+
this._skipNextResets = true;
|
|
3158
|
+
}
|
|
3159
|
+
}
|
|
3160
|
+
patchValue(value, options) {
|
|
3161
|
+
this._form._patchValue(value, options);
|
|
3162
|
+
if (options?.skipResets) {
|
|
3163
|
+
this._skipNextResets = true;
|
|
3164
|
+
}
|
|
3165
|
+
}
|
|
3166
|
+
resetFieldToDefault(key, options) {
|
|
3167
|
+
const defaultValue = this._getDefaultValue(key);
|
|
3168
|
+
this.form.controls[key].setValue(defaultValue);
|
|
3169
|
+
if (options?.skipResets) {
|
|
3170
|
+
this._skipNextResets = true;
|
|
3171
|
+
}
|
|
3172
|
+
}
|
|
3173
|
+
resetFieldsToDefault(keys, options) {
|
|
3174
|
+
const defaults = keys.reduce((acc, key) => {
|
|
3175
|
+
acc[key] = this._getDefaultValue(key);
|
|
3176
|
+
return acc;
|
|
3177
|
+
}, {});
|
|
3178
|
+
this.patchValue(defaults);
|
|
3179
|
+
if (options?.skipResets) {
|
|
3180
|
+
this._skipNextResets = true;
|
|
3181
|
+
}
|
|
3182
|
+
}
|
|
3183
|
+
resetAllFieldsToDefault(options) {
|
|
3184
|
+
const keys = Object.keys(this._fields);
|
|
3185
|
+
if (options?.skipFields) {
|
|
3186
|
+
for (const key of options.skipFields) {
|
|
3187
|
+
const index = keys.indexOf(key);
|
|
3188
|
+
if (index !== -1) {
|
|
3189
|
+
keys.splice(index, 1);
|
|
3190
|
+
}
|
|
3191
|
+
}
|
|
3192
|
+
}
|
|
3193
|
+
this.resetFieldsToDefault(keys, options);
|
|
3194
|
+
}
|
|
3195
|
+
_getDefaultValue(key) {
|
|
3196
|
+
const val = this._defaultValues[key];
|
|
3197
|
+
if (typeof val === 'string' && val.startsWith(ET_ARR_PREFIX)) {
|
|
3198
|
+
return JSON.parse(val.slice(ET_ARR_PREFIX.length));
|
|
3199
|
+
}
|
|
3200
|
+
return val ?? null;
|
|
3201
|
+
}
|
|
3202
|
+
_transformKeyToQueryParam(key) {
|
|
3203
|
+
if (!this._options?.queryParamPrefix)
|
|
3204
|
+
return key;
|
|
3205
|
+
const prefix = typeof this._options?.queryParamPrefix === 'string'
|
|
3206
|
+
? this._options.queryParamPrefix
|
|
3207
|
+
: this._options.queryParamPrefix();
|
|
3208
|
+
return `${prefix}-${key}`;
|
|
3209
|
+
}
|
|
3210
|
+
_isDefaultValue(key, value) {
|
|
3211
|
+
const normalizedValue = Array.isArray(value) ? `${ET_ARR_PREFIX}${JSON.stringify(value)}` : value;
|
|
3212
|
+
return this._getDefaultValue(key) === normalizedValue;
|
|
3213
|
+
}
|
|
3214
|
+
_setupFormGroup() {
|
|
3215
|
+
const group = new FormGroup({});
|
|
3216
|
+
for (const [key, field] of Object.entries(this._fields)) {
|
|
3217
|
+
group.addControl(key, field.control);
|
|
3218
|
+
}
|
|
3219
|
+
group._patchValue = group.patchValue;
|
|
3220
|
+
group._setValue = group.setValue;
|
|
3221
|
+
group.patchValue = this.patchValue.bind(this);
|
|
3222
|
+
group.setValue = this.setValue.bind(this);
|
|
3223
|
+
return group;
|
|
3224
|
+
}
|
|
3225
|
+
_extractDefaultValues() {
|
|
3226
|
+
const defaultValues = {};
|
|
3227
|
+
for (const [key, field] of Object.entries(this._fields)) {
|
|
3228
|
+
const value = field.data.defaultValue !== undefined ? field.data.defaultValue : field.control.value;
|
|
3229
|
+
if (Array.isArray(value)) {
|
|
3230
|
+
defaultValues[key] = `${ET_ARR_PREFIX}${JSON.stringify(value)}`;
|
|
3231
|
+
}
|
|
3232
|
+
else {
|
|
3233
|
+
defaultValues[key] = value;
|
|
3234
|
+
}
|
|
3235
|
+
}
|
|
3236
|
+
return defaultValues;
|
|
3237
|
+
}
|
|
3238
|
+
_syncViaUrlQueryParams(values, replaceUrl) {
|
|
3239
|
+
const queryParams = { ...clone(this._activatedRoute.snapshot.queryParams) };
|
|
3240
|
+
for (const [key, value] of Object.entries(values)) {
|
|
3241
|
+
const queryParamKey = this._transformKeyToQueryParam(key);
|
|
3242
|
+
const field = this._fields[key];
|
|
3243
|
+
if (!field) {
|
|
3244
|
+
continue;
|
|
3245
|
+
}
|
|
3246
|
+
const isDefault = this._isDefaultValue(key, value);
|
|
3247
|
+
const writeDefaultToUrl = field.data.appendDefaultValueToUrl === true;
|
|
3248
|
+
const writeToUrl = field.data.appendToUrl !== false;
|
|
3249
|
+
if (!writeToUrl || (isDefault && !writeDefaultToUrl)) {
|
|
3250
|
+
queryParams[queryParamKey] = undefined;
|
|
3251
|
+
}
|
|
3252
|
+
else {
|
|
3253
|
+
queryParams[queryParamKey] = field.data.valueToQueryParamTransformFn
|
|
3254
|
+
? field.data.valueToQueryParamTransformFn?.(value)
|
|
3255
|
+
: value;
|
|
3256
|
+
}
|
|
3257
|
+
}
|
|
3258
|
+
this._zone.run(() => {
|
|
3259
|
+
this._router.navigate([], {
|
|
3260
|
+
queryParams,
|
|
3261
|
+
replaceUrl,
|
|
3262
|
+
queryParamsHandling: 'merge',
|
|
3263
|
+
});
|
|
3264
|
+
});
|
|
3265
|
+
}
|
|
3266
|
+
_handleFormChange(forceOverwrite = false) {
|
|
3267
|
+
const currentVal = clone(this._currentFormValue$.value);
|
|
3268
|
+
const newVal = clone(this._formValue);
|
|
3269
|
+
if (equal(currentVal, newVal)) {
|
|
3270
|
+
return;
|
|
3271
|
+
}
|
|
3272
|
+
if (forceOverwrite) {
|
|
3273
|
+
this._lastFormValue$.next(newVal);
|
|
3274
|
+
}
|
|
3275
|
+
else {
|
|
3276
|
+
this._lastFormValue$.next(currentVal);
|
|
3277
|
+
}
|
|
3278
|
+
this._currentFormValue$.next(newVal);
|
|
3279
|
+
}
|
|
3280
|
+
}
|
|
3281
|
+
|
|
3282
|
+
// Burst cache
|
|
3283
|
+
|
|
3284
|
+
/**
|
|
3285
|
+
* Generated bundle index. Do not edit.
|
|
3286
|
+
*/
|
|
3287
|
+
|
|
3288
|
+
export { BasicAuthProvider, BearerAuthProvider, CustomHeaderAuthProvider, EntityStore, INFINITY_QUERY_TOKEN, InfinityQuery, InfinityQueryDirective, InfinityQueryTriggerDirective, QUERY_CLIENT_DEVTOOLS_TOKEN, Query, QueryClient, QueryCreator, QueryDevtoolsComponent, QueryDirective, QueryError, QueryField, QueryForm, QueryShortNamePipe, QueryStore, addQueryContainerHandling, buildErrorMessage, buildQueryCacheKey, buildQueryString, buildRoute, buildTimestampFromSeconds, castQueryCreatorTypes, computeQueryAuthHeader, computeQueryBody, computeQueryHeaders, computeQueryMethod, computeQueryQueryParams, createInfinityQueryConfig, createQueryCollection, createQueryCollectionSignal, createQueryCollectionSubject, decryptBearer, deepFreeze, def, detectContentTypeHeader, extractExpiresInSeconds, extractQuery, filterFailure, filterNull, filterQueryStates, filterSuccess, forEachHeader, getDefaultHeaders, getResponseUrl, gql, hasHeader, ignoreAutoRefresh, insertFrom, invalidBaseRouteError, invalidBodyError, invalidRouteError, isAutoRefresh, isBasicAuthProvider, isBearerAuthProvider, isClassValidatorError, isCustomHeaderAuthProvider, isEmptyString, isGqlQueryConfig, isNaN$1 as isNaN, isQuery, isQueryCollection, isQueryStateCancelled, isQueryStateFailure, isQueryStateLoading, isQueryStatePrepared, isQueryStateSuccess, isRequestError, isSymfonyError, isSymfonyFormViolationListError, isSymfonyListError, isSymfonyPagerfantaOutOfRangeError, mapToPaginated, mergeHeaders, paginatedEntityValueUpdater, parseAllXhrResponseHeaders, pathParamsMissingInRouteFunctionError, provideQueryClientForDevtools, queryArrayComputed, queryComputed, queryComputedTillTruthy, queryStateAlreadyHasKeyError, queryStateCannotTransformError, queryStateDoesNotContainKeyError, queryStateErrorSignal, queryStateLoadingSignal, queryStateResponseSignal, queryStateSignal, removeFrom, request, resetPageOnError, serializeBody, shouldCacheQuery, shouldRetryRequest, skipPaginationPageParamCalculator, switchQueryCollectionState, switchQueryState, takeUntilResponse, toQuerySignal, toQuerySubject, transformGql, transformMethod, transformToBoolean, transformToBooleanArray, transformToDate, transformToDateArray, transformToNumber, transformToNumberArray, transformToSort, transformToSortQueryParam, transformToString, transformToStringArray };
|
|
3289
|
+
//# sourceMappingURL=ethlete-query.mjs.map
|