@ethlete/query 0.1.0-next.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (87) hide show
  1. package/README.md +7 -0
  2. package/esm2020/ethlete-query.mjs +5 -0
  3. package/esm2020/index.mjs +12 -0
  4. package/esm2020/lib/auth/auth-provider.types.mjs +2 -0
  5. package/esm2020/lib/auth/auth-provider.utils.mjs +22 -0
  6. package/esm2020/lib/auth/basic-auth-provider.mjs +16 -0
  7. package/esm2020/lib/auth/bearer-auth-provider.mjs +85 -0
  8. package/esm2020/lib/auth/custom-header-auth-provider.mjs +13 -0
  9. package/esm2020/lib/auth/index.mjs +6 -0
  10. package/esm2020/lib/directives/index.mjs +4 -0
  11. package/esm2020/lib/directives/infinity-query-trigger.directive.mjs +54 -0
  12. package/esm2020/lib/directives/infinity-query.directive.mjs +129 -0
  13. package/esm2020/lib/directives/query.directive.mjs +101 -0
  14. package/esm2020/lib/gql/gql.mjs +23 -0
  15. package/esm2020/lib/gql/index.mjs +2 -0
  16. package/esm2020/lib/infinite-query/index.mjs +4 -0
  17. package/esm2020/lib/infinite-query/infinity-query.mjs +161 -0
  18. package/esm2020/lib/infinite-query/infinity-query.types.mjs +2 -0
  19. package/esm2020/lib/infinite-query/infinity-query.utils.mjs +3 -0
  20. package/esm2020/lib/logger/index.mjs +2 -0
  21. package/esm2020/lib/logger/logger.mjs +16 -0
  22. package/esm2020/lib/query/index.mjs +4 -0
  23. package/esm2020/lib/query/query.mjs +164 -0
  24. package/esm2020/lib/query/query.types.mjs +2 -0
  25. package/esm2020/lib/query/query.utils.mjs +42 -0
  26. package/esm2020/lib/query-client/index.mjs +4 -0
  27. package/esm2020/lib/query-client/query-client.mjs +88 -0
  28. package/esm2020/lib/query-client/query-client.types.mjs +2 -0
  29. package/esm2020/lib/query-client/query-client.utils.mjs +16 -0
  30. package/esm2020/lib/query-form/index.mjs +4 -0
  31. package/esm2020/lib/query-form/query-form.mjs +121 -0
  32. package/esm2020/lib/query-form/query-form.types.mjs +2 -0
  33. package/esm2020/lib/query-form/query-form.utils.mjs +74 -0
  34. package/esm2020/lib/query-store/index.mjs +2 -0
  35. package/esm2020/lib/query-store/query-store.mjs +64 -0
  36. package/esm2020/lib/request/index.mjs +4 -0
  37. package/esm2020/lib/request/request.mjs +19 -0
  38. package/esm2020/lib/request/request.types.mjs +2 -0
  39. package/esm2020/lib/request/request.util.mjs +206 -0
  40. package/esm2020/lib/utils/deep-freeze.util.mjs +13 -0
  41. package/esm2020/lib/utils/def.util.mjs +8 -0
  42. package/esm2020/lib/utils/index.mjs +3 -0
  43. package/fesm2015/ethlete-query.mjs +1393 -0
  44. package/fesm2015/ethlete-query.mjs.map +1 -0
  45. package/fesm2020/ethlete-query.mjs +1412 -0
  46. package/fesm2020/ethlete-query.mjs.map +1 -0
  47. package/index.d.ts +11 -0
  48. package/lib/auth/auth-provider.types.d.ts +103 -0
  49. package/lib/auth/auth-provider.utils.d.ts +8 -0
  50. package/lib/auth/basic-auth-provider.d.ts +12 -0
  51. package/lib/auth/bearer-auth-provider.d.ts +21 -0
  52. package/lib/auth/custom-header-auth-provider.d.ts +11 -0
  53. package/lib/auth/index.d.ts +5 -0
  54. package/lib/directives/index.d.ts +3 -0
  55. package/lib/directives/infinity-query-trigger.directive.d.ts +15 -0
  56. package/lib/directives/infinity-query.directive.d.ts +43 -0
  57. package/lib/directives/query.directive.d.ts +36 -0
  58. package/lib/gql/gql.d.ts +2 -0
  59. package/lib/gql/index.d.ts +1 -0
  60. package/lib/infinite-query/index.d.ts +3 -0
  61. package/lib/infinite-query/infinity-query.d.ts +32 -0
  62. package/lib/infinite-query/infinity-query.types.d.ts +91 -0
  63. package/lib/infinite-query/infinity-query.utils.d.ts +5 -0
  64. package/lib/logger/index.d.ts +1 -0
  65. package/lib/logger/logger.d.ts +12 -0
  66. package/lib/query/index.d.ts +3 -0
  67. package/lib/query/query.d.ts +24 -0
  68. package/lib/query/query.types.d.ts +128 -0
  69. package/lib/query/query.utils.d.ts +14 -0
  70. package/lib/query-client/index.d.ts +3 -0
  71. package/lib/query-client/query-client.d.ts +22 -0
  72. package/lib/query-client/query-client.types.d.ts +47 -0
  73. package/lib/query-client/query-client.utils.d.ts +4 -0
  74. package/lib/query-form/index.d.ts +3 -0
  75. package/lib/query-form/query-form.d.ts +30 -0
  76. package/lib/query-form/query-form.types.d.ts +43 -0
  77. package/lib/query-form/query-form.utils.d.ts +8 -0
  78. package/lib/query-store/index.d.ts +1 -0
  79. package/lib/query-store/query-store.d.ts +18 -0
  80. package/lib/request/index.d.ts +3 -0
  81. package/lib/request/request.d.ts +9 -0
  82. package/lib/request/request.types.d.ts +16 -0
  83. package/lib/request/request.util.d.ts +20 -0
  84. package/lib/utils/deep-freeze.util.d.ts +1 -0
  85. package/lib/utils/def.util.d.ts +7 -0
  86. package/lib/utils/index.d.ts +2 -0
  87. package/package.json +34 -0
@@ -0,0 +1,1412 @@
1
+ import { Subject, timer, filter, takeWhile, BehaviorSubject, interval, takeUntil, tap, switchMap, of, fromEvent, startWith, concat, take, debounceTime, combineLatest, map, distinctUntilChanged } from 'rxjs';
2
+ import * as i0 from '@angular/core';
3
+ import { Directive, Input, InjectionToken, inject, ChangeDetectorRef, ViewContainerRef, TemplateRef, ErrorHandler, ElementRef } from '@angular/core';
4
+ import { FormGroup } from '@angular/forms';
5
+ import { Router, ActivatedRoute } from '@angular/router';
6
+
7
+ class CustomHeaderAuthProvider {
8
+ constructor(_config) {
9
+ this._config = _config;
10
+ this.queryClient = null;
11
+ }
12
+ get header() {
13
+ return { [this._config.name]: this._config.value };
14
+ }
15
+ cleanUp() {
16
+ // noop
17
+ }
18
+ }
19
+
20
+ class BasicAuthProvider {
21
+ constructor(_config) {
22
+ this._config = _config;
23
+ this.queryClient = null;
24
+ }
25
+ get header() {
26
+ return { Authorization: `Basic ${this._basicAuthString}` };
27
+ }
28
+ get _basicAuthString() {
29
+ return btoa(`${this._config.username}:${this._config.password}`);
30
+ }
31
+ cleanUp() {
32
+ // noop
33
+ }
34
+ }
35
+
36
+ const buildErrorMessage = (code, message) => `[@ethlete\\query:${code}] ${message}`;
37
+ class QueryError extends Error {
38
+ constructor(code, message, data) {
39
+ super(buildErrorMessage(code, message));
40
+ this.name = 'QueryError';
41
+ this.data = data;
42
+ }
43
+ }
44
+ const invalidBaseRouteError = (data) => new QueryError('001', 'The baseRoute must not end with a slash', data);
45
+ const invalidRouteError = (data) => new QueryError('002', 'The route must start with a slash', data);
46
+ const pathParamsMissingInRouteFunctionError = (data) => new QueryError('003', 'The route is a function but pathParams are missing', data);
47
+ const queryStateAlreadyHasKeyError = (data) => new QueryError('004', 'The query state already contains the provided key', data);
48
+ const queryStateDoesNotContainKeyError = (data) => new QueryError('005', 'The query state does not contain the provided key', data);
49
+ const queryStateCannotTransformError = (data) => new QueryError('006', 'The query state cannot be transformed because it is not loading', data);
50
+ const invalidBodyError = (data) => new QueryError('007', 'The body is not valid or upsupported', data);
51
+
52
+ const isRequestError = (error) => error instanceof Object && 'code' in error && 'message' in error;
53
+ const isAbortRequestError = (error) => isRequestError(error) && error.code === -1;
54
+ const buildRoute = (options) => {
55
+ if (options.base.endsWith('/')) {
56
+ throw invalidBaseRouteError(options.base);
57
+ }
58
+ let route = null;
59
+ if (typeof options.route === 'function') {
60
+ if (!options.pathParams) {
61
+ throw pathParamsMissingInRouteFunctionError(options.route({}));
62
+ }
63
+ route = options.route(options.pathParams);
64
+ }
65
+ else {
66
+ route = options.route ?? null;
67
+ }
68
+ if (route && !route.startsWith('/')) {
69
+ throw invalidRouteError(route);
70
+ }
71
+ if (options.queryParams) {
72
+ const queryString = buildQueryString(options.queryParams);
73
+ if (queryString) {
74
+ route = route ? `${route}?${queryString}` : `/?${queryString}`;
75
+ }
76
+ }
77
+ return `${options.base}${route ?? ''}`;
78
+ };
79
+ const buildQueryString = (params) => {
80
+ const validParams = filterInvalidParams(params);
81
+ const queryString = Object.keys(validParams)
82
+ .map((key) => Array.isArray(validParams[key])
83
+ ? buildQueryArrayString(key, validParams[key])
84
+ : `${key}=${encodeURIComponent(validParams[key])}`)
85
+ .join('&');
86
+ return queryString || null;
87
+ };
88
+ const buildQueryArrayString = (key, array) => {
89
+ const uriBrackets = encodeURIComponent('[]');
90
+ return array
91
+ .slice()
92
+ .map((item) => `${key}${uriBrackets}=${encodeURIComponent(item)}`)
93
+ .join('&');
94
+ };
95
+ const isFetchResponse = (response) => typeof response === 'object' &&
96
+ response !== null &&
97
+ 'status' in response &&
98
+ 'statusText' in response &&
99
+ 'json' in response &&
100
+ typeof response['json'] === 'function';
101
+ const filterInvalidParams = (params) => {
102
+ const filteredParams = Object.entries(params)
103
+ .map(([key, value]) => {
104
+ if (!Array.isArray(value)) {
105
+ return [key, value];
106
+ }
107
+ return [key, value.filter((v) => isParamValid(v))];
108
+ })
109
+ .filter(([, value]) => isParamValid(value))
110
+ .reduce((acc, [key, value]) => ({ ...acc, [key]: value }), {});
111
+ return filteredParams;
112
+ };
113
+ const isParamValid = (primitive) => {
114
+ if (primitive === undefined || primitive === null || primitive === '') {
115
+ return false;
116
+ }
117
+ if (typeof primitive === 'string' && primitive.trim() === '') {
118
+ return false;
119
+ }
120
+ if (typeof primitive === 'number' && isNaN(primitive)) {
121
+ return false;
122
+ }
123
+ return true;
124
+ };
125
+ const extractExpiresInSeconds = (headers) => {
126
+ const cacheControl = headers.get('cache-control');
127
+ const age = headers.get('age');
128
+ const expires = headers.get('expires');
129
+ // In seconds
130
+ let expiresIn = null;
131
+ let maxAge = null;
132
+ if (cacheControl?.includes('no-cache')) {
133
+ return null;
134
+ }
135
+ if (cacheControl?.includes('max-age')) {
136
+ maxAge = parseInt(cacheControl.split('max-age=')[1]);
137
+ }
138
+ else if (cacheControl?.includes('s-maxage')) {
139
+ maxAge = parseInt(cacheControl.split('s-maxage=')[1]);
140
+ }
141
+ if (maxAge && age) {
142
+ const ageSeconds = parseInt(age);
143
+ expiresIn = maxAge - ageSeconds;
144
+ }
145
+ else if (expires) {
146
+ // Used by some apis to tell the response will never expire
147
+ // In this case we let the response expire after 1 hour
148
+ if (expires === '-1') {
149
+ expiresIn = 3600;
150
+ }
151
+ else {
152
+ const expiresDate = new Date(expires);
153
+ // check if the date is valid
154
+ if (expiresDate.toString() !== 'Invalid Date') {
155
+ expiresIn = Math.floor((expiresDate.getTime() - Date.now()) / 1000);
156
+ }
157
+ }
158
+ }
159
+ return expiresIn;
160
+ };
161
+ const buildTimestampFromSeconds = (seconds) => {
162
+ if (seconds === null) {
163
+ return null;
164
+ }
165
+ return new Date(Date.now() + seconds * 1000).getTime();
166
+ };
167
+ const buildRequestError = async (error) => {
168
+ if (error instanceof DOMException && error.code === error.ABORT_ERR) {
169
+ const err = {
170
+ code: -1,
171
+ message: 'Request aborted',
172
+ detail: null,
173
+ raw: error,
174
+ };
175
+ return err;
176
+ }
177
+ if (error instanceof SyntaxError) {
178
+ const err = {
179
+ code: -3,
180
+ message: 'Syntax error',
181
+ detail: null,
182
+ raw: error,
183
+ };
184
+ return err;
185
+ }
186
+ if (error instanceof Error) {
187
+ const err = {
188
+ code: -2,
189
+ message: `${error.name}: ${error.message}`,
190
+ detail: null,
191
+ raw: error,
192
+ };
193
+ return err;
194
+ }
195
+ if (isFetchResponse(error)) {
196
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
197
+ let detail;
198
+ try {
199
+ detail = await error.text();
200
+ }
201
+ catch (err) {
202
+ detail = null;
203
+ }
204
+ const err = {
205
+ code: error.status,
206
+ message: error.statusText,
207
+ detail,
208
+ raw: error,
209
+ };
210
+ return err;
211
+ }
212
+ const err = {
213
+ code: 0,
214
+ message: 'Unknown error',
215
+ detail: null,
216
+ raw: error,
217
+ };
218
+ return err;
219
+ };
220
+ const buildBody = (body) => {
221
+ if (body === undefined || body === null) {
222
+ return null;
223
+ }
224
+ if (typeof body === 'string') {
225
+ return body;
226
+ }
227
+ if (typeof body === 'object') {
228
+ return JSON.stringify(body);
229
+ }
230
+ if (body instanceof FormData) {
231
+ return body;
232
+ }
233
+ throw invalidBodyError(body);
234
+ };
235
+ const transformMethod = (method) => {
236
+ if (method === 'GQL_QUERY' || method === 'GQL_MUTATE') {
237
+ return 'POST';
238
+ }
239
+ return method;
240
+ };
241
+ const guessContentType = (body) => {
242
+ if (body === null || body === undefined) {
243
+ return null;
244
+ }
245
+ if (typeof body === 'string') {
246
+ return 'text/plain';
247
+ }
248
+ if (body instanceof FormData) {
249
+ return 'multipart/form-data';
250
+ }
251
+ if (typeof body === 'object') {
252
+ return 'application/json';
253
+ }
254
+ return null;
255
+ };
256
+
257
+ const request = async (options) => {
258
+ try {
259
+ const response = await fetch(options.route, options.init);
260
+ if (!response.ok) {
261
+ throw response;
262
+ }
263
+ const data = (await response.json());
264
+ const expiresInSeconds = options.cacheAdapter
265
+ ? options.cacheAdapter(response.headers)
266
+ : extractExpiresInSeconds(response.headers);
267
+ const expiresInTimestamp = buildTimestampFromSeconds(expiresInSeconds);
268
+ return { data, expiresInTimestamp };
269
+ }
270
+ catch (error) {
271
+ throw await buildRequestError(error);
272
+ }
273
+ };
274
+
275
+ const isBasicAuthProvider = (authProvider) => authProvider instanceof BasicAuthProvider;
276
+ const isBearerAuthProvider = (authProvider) => authProvider instanceof BearerAuthProvider;
277
+ const isCustomHeaderAuthProvider = (authProvider) => authProvider instanceof CustomHeaderAuthProvider;
278
+ const decryptBearer = (token) => {
279
+ try {
280
+ const base64Url = token.split('.')[1];
281
+ const base64 = base64Url.replace(/-/g, '+').replace(/_/g, '/');
282
+ const jsonPayload = decodeURIComponent(atob(base64)
283
+ .split('')
284
+ .map((c) => '%' + ('00' + c.charCodeAt(0).toString(16)).slice(-2))
285
+ .join(''));
286
+ return JSON.parse(jsonPayload);
287
+ }
288
+ catch (error) {
289
+ console.error(`Invalid bearer token: ${token}`, error);
290
+ return null;
291
+ }
292
+ };
293
+
294
+ class BearerAuthProvider {
295
+ constructor(_config) {
296
+ this._config = _config;
297
+ this._refreshToken = null;
298
+ this._refreshTimerSubscription = null;
299
+ this.queryClient = null;
300
+ this.onRefreshInitiation$ = new Subject();
301
+ this.onRefreshSuccess$ = new Subject();
302
+ this.onRefreshFailure$ = new Subject();
303
+ this._token = _config.token;
304
+ this._refreshToken = _config.refreshConfig?.token ?? null;
305
+ if (this._config.refreshConfig) {
306
+ this._setupRefresh(this._config.refreshConfig);
307
+ }
308
+ }
309
+ get header() {
310
+ return { Authorization: `Bearer ${this._token}` };
311
+ }
312
+ cleanUp() {
313
+ this._refreshTimerSubscription?.unsubscribe();
314
+ this._refreshTimerSubscription = null;
315
+ }
316
+ _setupRefresh(config) {
317
+ const bearer = decryptBearer(this._token);
318
+ if (!bearer) {
319
+ return;
320
+ }
321
+ const expiresInPropertyName = config.expiresInPropertyName || 'exp';
322
+ const expiresIn = bearer[expiresInPropertyName];
323
+ const fiveMinutes = 5 * 60 * 1000;
324
+ if (expiresIn === undefined) {
325
+ throw new Error(`Bearer token does not contain an '${expiresInPropertyName}' property`);
326
+ }
327
+ const remainingTime = new Date(expiresIn * 1000).getTime() - fiveMinutes - new Date().getTime();
328
+ const strategy = config.strategy ?? "beforeExpiration" /* AuthBearerRefreshStrategy.BeforeExpiration */;
329
+ switch (strategy) {
330
+ case "beforeExpiration" /* AuthBearerRefreshStrategy.BeforeExpiration */:
331
+ this._refreshTimerSubscription = timer(remainingTime).subscribe(() => this._refresh(config));
332
+ break;
333
+ }
334
+ }
335
+ async _refresh(config) {
336
+ if (!this._refreshToken) {
337
+ throw new Error('No refresh token found');
338
+ }
339
+ if (!this.queryClient) {
340
+ throw new Error('Query client instance is null');
341
+ }
342
+ const { method, route, bodyAdapter, responseAdapter } = config;
343
+ const fullRoute = buildRoute({
344
+ base: this.queryClient.config.baseRoute,
345
+ route,
346
+ });
347
+ const body = bodyAdapter ? bodyAdapter(this._refreshToken) : { refreshToken: this._refreshToken };
348
+ this.onRefreshInitiation$.next();
349
+ try {
350
+ const result = await request({
351
+ route: fullRoute,
352
+ init: { body: buildBody(body), method },
353
+ });
354
+ const tokens = responseAdapter ? responseAdapter(result.data) : result.data;
355
+ if (!tokens.token) {
356
+ throw new Error('Token not found in response');
357
+ }
358
+ if (!tokens.refreshToken) {
359
+ throw new Error('Refresh token not found in response');
360
+ }
361
+ this._token = tokens.token;
362
+ this._refreshToken = tokens.refreshToken;
363
+ this.onRefreshSuccess$.next(tokens);
364
+ }
365
+ catch (error) {
366
+ if (error instanceof Error) {
367
+ this.onRefreshFailure$.next(await buildRequestError(error));
368
+ }
369
+ else {
370
+ this.onRefreshFailure$.next(error);
371
+ }
372
+ }
373
+ }
374
+ }
375
+
376
+ const getOpName = /(query|mutation) ?([\w\d-_]+)? ?\(.*?\)? \{/;
377
+ const transformGql = (str) => {
378
+ str = Array.isArray(str) ? str.join('') : str;
379
+ const name = getOpName.exec(str);
380
+ return function (variables) {
381
+ const data = { query: str };
382
+ if (variables)
383
+ data['variables'] = JSON.stringify(variables);
384
+ if (name && name.length) {
385
+ const operationName = name[2];
386
+ if (operationName)
387
+ data['operationName'] = name[2];
388
+ }
389
+ return JSON.stringify(data);
390
+ };
391
+ };
392
+ const gql = (strings, ...values) => {
393
+ const str = strings.reduce((acc, cur, i) => {
394
+ return acc + cur + (values[i] || '');
395
+ }, '');
396
+ return str;
397
+ };
398
+
399
+ const deepFreeze = (object) => {
400
+ // Retrieve the property names defined on object
401
+ const propNames = Object.getOwnPropertyNames(object);
402
+ // Freeze properties before freezing self
403
+ for (const name of propNames) {
404
+ const value = object[name];
405
+ if (value && typeof value === 'object') {
406
+ deepFreeze(value);
407
+ }
408
+ }
409
+ return Object.freeze(object);
410
+ };
411
+
412
+ /**
413
+ * Dummy function for providing types while not breaking inference.
414
+ * **This function always returns null.**
415
+ * @see https://github.com/microsoft/TypeScript/issues/26242
416
+ * @returns null
417
+ */
418
+ const def = () => null;
419
+
420
+ function filterSuccess() {
421
+ return function (source) {
422
+ return source.pipe(filter((value) => isQueryStateSuccess(value)));
423
+ };
424
+ }
425
+ function filterFailure() {
426
+ return function (source) {
427
+ return source.pipe(filter((value) => isQueryStateFailure(value)));
428
+ };
429
+ }
430
+ function takeUntilResponse() {
431
+ return function (source) {
432
+ return source.pipe(takeWhile((value) => isQueryStateLoading(value), true));
433
+ };
434
+ }
435
+ const isQueryStateLoading = (state) => state?.type === "LOADING" /* QueryStateType.Loading */;
436
+ const isQueryStateSuccess = (state) => state?.type === "SUCCESS" /* QueryStateType.Success */;
437
+ const isQueryStateFailure = (state) => state?.type === "FAILURE" /* QueryStateType.Failure */;
438
+ const isQueryStateCancelled = (state) => state?.type === "CANCELLED" /* QueryStateType.Cancelled */;
439
+ const isQueryStatePrepared = (state) => state?.type === "PREPARED" /* QueryStateType.Prepared */;
440
+ const mergeHeaders = (...headers) => {
441
+ return headers.reduce((acc, headers) => {
442
+ if (!headers) {
443
+ return acc;
444
+ }
445
+ return {
446
+ ...acc,
447
+ ...headers,
448
+ };
449
+ }, {});
450
+ };
451
+ const isGqlQueryConfig = (config) => {
452
+ if (!config || typeof config !== 'object' || Array.isArray(config)) {
453
+ return false;
454
+ }
455
+ if (!('query' in config)) {
456
+ return false;
457
+ }
458
+ return true;
459
+ };
460
+
461
+ class Query {
462
+ constructor(_client, _queryConfig, _route, _args) {
463
+ this._client = _client;
464
+ this._queryConfig = _queryConfig;
465
+ this._route = _route;
466
+ this._args = _args;
467
+ this._currentId = 0;
468
+ this._abortController = new AbortController();
469
+ this._pollingSubscription = null;
470
+ this._state$ = new BehaviorSubject({
471
+ type: "PREPARED" /* QueryStateType.Prepared */,
472
+ meta: { id: this._currentId },
473
+ });
474
+ }
475
+ get _nextId() {
476
+ return this._currentId++;
477
+ }
478
+ get state$() {
479
+ return this._state$.asObservable();
480
+ }
481
+ get state() {
482
+ return this._state$.value;
483
+ }
484
+ get isExpired() {
485
+ if (!isQueryStateSuccess(this.state)) {
486
+ return false;
487
+ }
488
+ const ts = this.state.meta.expiresAt;
489
+ if (!ts) {
490
+ return true;
491
+ }
492
+ return ts < Date.now();
493
+ }
494
+ clone() {
495
+ return this._client.fetch(this._queryConfig);
496
+ }
497
+ execute(options) {
498
+ if (!this.isExpired && !options?.skipCache && isQueryStateSuccess(this.state)) {
499
+ return this;
500
+ }
501
+ if (this._queryConfig.secure && !this._client.authProvider) {
502
+ throw new Error('Cannot execute secure query without auth provider');
503
+ }
504
+ const id = this._nextId;
505
+ if (isQueryStateLoading(this._state$.value)) {
506
+ this.abort();
507
+ }
508
+ const meta = deepFreeze({ id });
509
+ this._state$.next({
510
+ type: "LOADING" /* QueryStateType.Loading */,
511
+ meta,
512
+ });
513
+ let body = null;
514
+ let contentType = null;
515
+ if (isGqlQueryConfig(this._queryConfig)) {
516
+ const queryTemplate = this._queryConfig.query;
517
+ const query = transformGql(queryTemplate);
518
+ contentType = 'application/json';
519
+ body = query(this._args?.variables);
520
+ }
521
+ else {
522
+ body = buildBody(this._args?.body);
523
+ contentType = guessContentType(body);
524
+ }
525
+ let authHeader = null;
526
+ if (this._queryConfig.secure) {
527
+ const header = this._client.authProvider?.header;
528
+ if (header) {
529
+ authHeader = header;
530
+ }
531
+ }
532
+ else if (this._client.authProvider?.header) {
533
+ if (this._queryConfig.secure === undefined || this._queryConfig.secure) {
534
+ authHeader = this._client.authProvider.header;
535
+ }
536
+ }
537
+ let headers = mergeHeaders(authHeader, this._args?.headers);
538
+ if (contentType && !headers?.['Content-Type'] && !headers?.['content-type']) {
539
+ if (headers) {
540
+ headers['Content-Type'] = contentType;
541
+ }
542
+ else {
543
+ headers = {
544
+ 'Content-Type': contentType,
545
+ };
546
+ }
547
+ }
548
+ request({
549
+ route: this._route,
550
+ init: {
551
+ method: transformMethod(this._queryConfig.method),
552
+ signal: this._abortController.signal,
553
+ body,
554
+ headers: headers || undefined,
555
+ },
556
+ cacheAdapter: this._client.config.request?.cacheAdapter,
557
+ })
558
+ .then((response) => {
559
+ const isResponseObject = typeof response === 'object';
560
+ const isGql = isGqlQueryConfig(this._queryConfig);
561
+ let responseData = null;
562
+ if (isGql && isResponseObject && 'data' in response.data) {
563
+ responseData = response.data['data'];
564
+ }
565
+ else {
566
+ responseData = response.data;
567
+ }
568
+ const transformedResponse = this._queryConfig.responseTransformer
569
+ ? this._queryConfig.responseTransformer(responseData)
570
+ : responseData;
571
+ this._state$.next({
572
+ type: "SUCCESS" /* QueryStateType.Success */,
573
+ response: deepFreeze(transformedResponse),
574
+ rawResponse: deepFreeze(responseData),
575
+ meta: deepFreeze({ ...meta, expiresAt: response.expiresInTimestamp }),
576
+ });
577
+ })
578
+ .catch((error) => this._handleExecuteError(error, meta));
579
+ return this;
580
+ }
581
+ abort() {
582
+ this._abortController.abort();
583
+ this._abortController = new AbortController();
584
+ return this;
585
+ }
586
+ poll(config) {
587
+ if (this._pollingSubscription) {
588
+ return this;
589
+ }
590
+ this._pollingSubscription = interval(config.interval)
591
+ .pipe(takeUntil(config.takeUntil))
592
+ .subscribe(() => this.execute({ skipCache: true }));
593
+ return this;
594
+ }
595
+ stopPolling() {
596
+ this._pollingSubscription?.unsubscribe();
597
+ this._pollingSubscription = null;
598
+ return this;
599
+ }
600
+ _handleExecuteError(error, meta) {
601
+ if (isAbortRequestError(error)) {
602
+ this._state$.next({
603
+ type: "CANCELLED" /* QueryStateType.Cancelled */,
604
+ meta,
605
+ });
606
+ }
607
+ else if (isRequestError(error)) {
608
+ this._state$.next({
609
+ type: "FAILURE" /* QueryStateType.Failure */,
610
+ error,
611
+ meta,
612
+ });
613
+ }
614
+ else {
615
+ throw error;
616
+ }
617
+ }
618
+ }
619
+
620
+ class QueryDirective {
621
+ constructor(_mainTemplateRef, _viewContainerRef, _errorHandler, _cdr) {
622
+ this._mainTemplateRef = _mainTemplateRef;
623
+ this._viewContainerRef = _viewContainerRef;
624
+ this._errorHandler = _errorHandler;
625
+ this._cdr = _cdr;
626
+ this._isMainViewCreated = false;
627
+ this._subscription = null;
628
+ this._viewContext = {
629
+ $implicit: null,
630
+ query: null,
631
+ raw: null,
632
+ loading: false,
633
+ error: null,
634
+ };
635
+ this._cache = false;
636
+ }
637
+ get query() {
638
+ return this._query;
639
+ }
640
+ set query(v) {
641
+ this._query = v;
642
+ this._subscribeToQuery();
643
+ }
644
+ get cache() {
645
+ return this._cache;
646
+ }
647
+ set cache(v) {
648
+ this._cache = v;
649
+ }
650
+ static ngTemplateContextGuard(dir, ctx) {
651
+ return true;
652
+ }
653
+ ngOnInit() {
654
+ this._renderMainView();
655
+ }
656
+ ngOnDestroy() {
657
+ this._subscription?.unsubscribe();
658
+ }
659
+ _subscribeToQuery() {
660
+ this._subscription?.unsubscribe();
661
+ this._subscription = null;
662
+ if (!this.query) {
663
+ return;
664
+ }
665
+ const sub = this.query.state$.pipe(tap((state) => this._updateView(state))).subscribe();
666
+ this._subscription = sub;
667
+ }
668
+ _updateView(state) {
669
+ if (isQueryStateLoading(state)) {
670
+ this._viewContext.loading = true;
671
+ }
672
+ else {
673
+ this._viewContext.loading = false;
674
+ }
675
+ if (isQueryStateSuccess(state)) {
676
+ this._viewContext.query = state.response;
677
+ this._viewContext.$implicit = state.response;
678
+ this._viewContext.raw = state.rawResponse;
679
+ }
680
+ else if (!this.cache) {
681
+ this._viewContext.query = null;
682
+ this._viewContext.$implicit = null;
683
+ this._viewContext.raw = null;
684
+ }
685
+ if (isQueryStateFailure(state)) {
686
+ this._viewContext.error = state.error;
687
+ this._errorHandler.handleError(state.error);
688
+ }
689
+ else {
690
+ this._viewContext.error = null;
691
+ }
692
+ this._cdr.markForCheck();
693
+ }
694
+ _renderMainView() {
695
+ if (!this._isMainViewCreated) {
696
+ this._isMainViewCreated = true;
697
+ this._viewContainerRef.createEmbeddedView(this._mainTemplateRef, this._viewContext);
698
+ }
699
+ }
700
+ }
701
+ QueryDirective.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "14.2.8", ngImport: i0, type: QueryDirective, deps: [{ token: i0.TemplateRef }, { token: i0.ViewContainerRef }, { token: i0.ErrorHandler }, { token: i0.ChangeDetectorRef }], target: i0.ɵɵFactoryTarget.Directive });
702
+ QueryDirective.ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "14.2.8", type: QueryDirective, isStandalone: true, selector: "[query]", inputs: { query: "query", cache: ["queryCache", "cache"] }, ngImport: i0 });
703
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "14.2.8", ngImport: i0, type: QueryDirective, decorators: [{
704
+ type: Directive,
705
+ args: [{
706
+ // eslint-disable-next-line @angular-eslint/directive-selector
707
+ selector: '[query]',
708
+ standalone: true,
709
+ }]
710
+ }], ctorParameters: function () { return [{ type: i0.TemplateRef }, { type: i0.ViewContainerRef }, { type: i0.ErrorHandler }, { type: i0.ChangeDetectorRef }]; }, propDecorators: { query: [{
711
+ type: Input
712
+ }], cache: [{
713
+ type: Input,
714
+ args: ['queryCache']
715
+ }] } });
716
+
717
+ /* eslint-disable @typescript-eslint/no-explicit-any */
718
+ class InfinityQuery {
719
+ constructor(_config) {
720
+ this._config = _config;
721
+ this._currentPage$ = new BehaviorSubject(null);
722
+ this._currentCalculatedPage$ = new BehaviorSubject(null);
723
+ this._totalPages$ = new BehaviorSubject(null);
724
+ this._itemsPerPage$ = new BehaviorSubject(this._config.limitParam?.value ?? 10);
725
+ this._currentQuery$ = new BehaviorSubject(null);
726
+ this._data$ = new BehaviorSubject([]);
727
+ this._subscriptions = [];
728
+ }
729
+ get currentPage$() {
730
+ return this._currentPage$.asObservable();
731
+ }
732
+ get currentPage() {
733
+ return this._currentPage$.getValue();
734
+ }
735
+ get currentCalculatedPage$() {
736
+ return this._currentCalculatedPage$.asObservable();
737
+ }
738
+ get currentCalculatedPage() {
739
+ return this._currentCalculatedPage$.getValue();
740
+ }
741
+ get totalPages$() {
742
+ return this._totalPages$.asObservable();
743
+ }
744
+ get totalPages() {
745
+ return this._totalPages$.getValue();
746
+ }
747
+ get itemsPerPage$() {
748
+ return this._itemsPerPage$.asObservable();
749
+ }
750
+ get itemsPerPage() {
751
+ return this._itemsPerPage$.getValue();
752
+ }
753
+ get currentQuery$() {
754
+ return this._currentQuery$.asObservable();
755
+ }
756
+ get currentQuery() {
757
+ return this._currentQuery$.getValue();
758
+ }
759
+ get data$() {
760
+ return this._data$.asObservable();
761
+ }
762
+ get data() {
763
+ return this._data$.getValue();
764
+ }
765
+ nextPage() {
766
+ const newPage = (this._currentPage$.value ?? 0) + 1;
767
+ const calculatedPage = this._config?.pageParam?.valueCalculator?.({
768
+ page: newPage,
769
+ totalPages: this.totalPages,
770
+ itemsPerPage: this.itemsPerPage,
771
+ }) ?? newPage;
772
+ const args = this._prepareArgs(this._config, calculatedPage);
773
+ const query = this._config.queryCreator.prepare(args).execute();
774
+ this._handleNewQuery(query);
775
+ this._currentPage$.next(newPage);
776
+ this._currentCalculatedPage$.next(calculatedPage);
777
+ this._currentQuery$.next(query);
778
+ }
779
+ reset(newConfig) {
780
+ this._destroy();
781
+ this._config = { ...this._config, ...(newConfig ?? {}) };
782
+ this._currentPage$.next(null);
783
+ this._currentCalculatedPage$.next(null);
784
+ this._totalPages$.next(null);
785
+ this._itemsPerPage$.next(this._config.limitParam?.value ?? 10);
786
+ this._currentQuery$.next(null);
787
+ this._data$.next([]);
788
+ }
789
+ _destroy() {
790
+ this._subscriptions.forEach((sub) => sub.unsubscribe());
791
+ this._subscriptions = [];
792
+ }
793
+ _handleNewQuery(query) {
794
+ const stateChangesSub = query.state$.pipe(takeUntilResponse(), filterSuccess()).subscribe({
795
+ next: (state) => {
796
+ let newData = this._config?.response.valueExtractor(state.response);
797
+ if (this._config.response.reverse) {
798
+ newData = [...newData].reverse();
799
+ }
800
+ if (this._config.response.appendItemsTo === 'start') {
801
+ newData = [...newData, ...this.data];
802
+ }
803
+ else {
804
+ newData = [...this.data, ...newData];
805
+ }
806
+ this._data$.next(newData);
807
+ const totalPages = this._config.response.totalPagesExtractor?.({
808
+ response: state.response,
809
+ itemsPerPage: this.itemsPerPage,
810
+ }) ??
811
+ state.response?.totalPages ??
812
+ null;
813
+ this._totalPages$.next(totalPages);
814
+ },
815
+ complete: () => {
816
+ this._subscriptions = this._subscriptions.filter((sub) => sub !== stateChangesSub);
817
+ },
818
+ });
819
+ this._subscriptions.push(stateChangesSub);
820
+ }
821
+ _prepareArgs(config, page) {
822
+ const pageParamLocation = this._getPageParamLocation(config?.pageParam?.location);
823
+ const pageArgs = {
824
+ [pageParamLocation]: {
825
+ [config?.pageParam?.key ?? 'page']: page,
826
+ },
827
+ };
828
+ const limitParamLocation = this._getPageParamLocation(config?.limitParam?.location);
829
+ const limitArgs = {
830
+ [limitParamLocation]: {
831
+ [config?.limitParam?.key ?? 'limit']: config?.limitParam?.value ?? 10,
832
+ },
833
+ };
834
+ const defaultConfig = config?.defaultArgs ?? {};
835
+ const mergedArgs = pageParamLocation === limitParamLocation
836
+ ? {
837
+ ...defaultConfig,
838
+ [pageParamLocation]: {
839
+ ...(config?.defaultArgs?.[pageParamLocation] ?? {}),
840
+ ...pageArgs[limitParamLocation],
841
+ ...limitArgs[limitParamLocation],
842
+ },
843
+ }
844
+ : {
845
+ ...defaultConfig,
846
+ [pageParamLocation]: {
847
+ ...(config?.defaultArgs?.[pageParamLocation] ?? {}),
848
+ ...pageArgs[pageParamLocation],
849
+ },
850
+ [limitParamLocation]: {
851
+ ...(config?.defaultArgs?.[limitParamLocation] ?? {}),
852
+ ...limitArgs[limitParamLocation],
853
+ },
854
+ };
855
+ return mergedArgs;
856
+ }
857
+ _getPageParamLocation(location) {
858
+ const loc = location ?? 'query';
859
+ switch (loc) {
860
+ case 'path':
861
+ return 'pathParams';
862
+ case 'query':
863
+ return 'queryParams';
864
+ case 'body':
865
+ return 'body';
866
+ case 'header':
867
+ return 'headers';
868
+ case 'variable':
869
+ return 'variables';
870
+ default:
871
+ throw new Error(`Invalid pageParamLocation: ${loc}`);
872
+ }
873
+ }
874
+ }
875
+
876
+ const createInfinityQueryConfig = (config) => config;
877
+ const skipPaginationPageParamCalculator = ({ page, itemsPerPage }) => itemsPerPage * (page - 1);
878
+
879
+ /* eslint-disable @typescript-eslint/no-explicit-any */
880
+ const INFINITY_QUERY_TOKEN = new InjectionToken('INFINITY_QUERY_TOKEN');
881
+ class InfinityQueryDirective {
882
+ constructor() {
883
+ this._viewContext = {
884
+ $implicit: null,
885
+ infinityQuery: null,
886
+ loading: false,
887
+ error: null,
888
+ isFirstLoad: false,
889
+ canLoadMore: false,
890
+ currentPage: null,
891
+ currentCalculatedPage: null,
892
+ totalPages: null,
893
+ itemsPerPage: null,
894
+ };
895
+ this._infinityQueryInstance = null;
896
+ this._destroy$ = new Subject();
897
+ this._cdr = inject(ChangeDetectorRef);
898
+ this._viewContainerRef = inject(ViewContainerRef);
899
+ this._mainTemplateRef = inject((TemplateRef));
900
+ this._errorHandler = inject(ErrorHandler);
901
+ this._isMainViewCreated = false;
902
+ }
903
+ get infinityQuery() {
904
+ return this._infinityQuery;
905
+ }
906
+ set infinityQuery(v) {
907
+ this._infinityQuery = v;
908
+ this._infinityQueryInstance = this._setupInfinityQuery(v);
909
+ this._loadNextPage();
910
+ }
911
+ get context() {
912
+ return this._viewContext;
913
+ }
914
+ get instance() {
915
+ return this._infinityQueryInstance;
916
+ }
917
+ static ngTemplateContextGuard(dir, ctx) {
918
+ return true;
919
+ }
920
+ ngOnInit() {
921
+ this._renderMainView();
922
+ }
923
+ ngOnDestroy() {
924
+ this._destroy$.next(true);
925
+ this._destroy$.unsubscribe();
926
+ this.instance?._destroy();
927
+ }
928
+ _setupInfinityQuery(config) {
929
+ const instance = new InfinityQuery(config);
930
+ instance.currentQuery$
931
+ .pipe(switchMap((q) => q?.state$ ?? of(null)), tap((state) => {
932
+ this._viewContext.currentPage = instance.currentPage;
933
+ this._viewContext.totalPages = instance.totalPages;
934
+ this._viewContext.itemsPerPage = instance.itemsPerPage;
935
+ this._viewContext.canLoadMore =
936
+ (instance.totalPages && instance.currentPage && instance.totalPages > instance.currentPage) || false;
937
+ this._viewContext.currentCalculatedPage = instance.currentCalculatedPage;
938
+ if (isQueryStateLoading(state)) {
939
+ this._viewContext.loading = true;
940
+ this._viewContext.error = null;
941
+ this._viewContext.isFirstLoad = this.context.infinityQuery === null;
942
+ }
943
+ else if (isQueryStateFailure(state)) {
944
+ this._viewContext.loading = false;
945
+ this._viewContext.error = state.error;
946
+ this._viewContext.isFirstLoad = false;
947
+ this._errorHandler.handleError(state.error);
948
+ }
949
+ else if (isQueryStateSuccess(state)) {
950
+ this._viewContext.loading = false;
951
+ this._viewContext.error = null;
952
+ this._viewContext.isFirstLoad = false;
953
+ }
954
+ else {
955
+ this._viewContext.loading = false;
956
+ this._viewContext.error = null;
957
+ this._viewContext.infinityQuery = null;
958
+ this._viewContext.$implicit = null;
959
+ this._viewContext.isFirstLoad = false;
960
+ this._viewContext.canLoadMore = false;
961
+ this._viewContext.currentPage = null;
962
+ this._viewContext.itemsPerPage = null;
963
+ this._viewContext.totalPages = null;
964
+ }
965
+ this._cdr.markForCheck();
966
+ }), takeUntil(this._destroy$))
967
+ .subscribe();
968
+ instance.data$
969
+ .pipe(tap((data) => {
970
+ this._viewContext.infinityQuery = this._viewContext.$implicit = data;
971
+ this._cdr.markForCheck();
972
+ }), takeUntil(this._destroy$))
973
+ .subscribe();
974
+ return instance;
975
+ }
976
+ _loadNextPage() {
977
+ if (!this._infinityQueryInstance) {
978
+ return;
979
+ }
980
+ this._infinityQueryInstance.nextPage();
981
+ }
982
+ _renderMainView() {
983
+ if (!this._isMainViewCreated) {
984
+ this._isMainViewCreated = true;
985
+ this._viewContainerRef.createEmbeddedView(this._mainTemplateRef, this._viewContext);
986
+ }
987
+ }
988
+ }
989
+ InfinityQueryDirective.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "14.2.8", ngImport: i0, type: InfinityQueryDirective, deps: [], target: i0.ɵɵFactoryTarget.Directive });
990
+ InfinityQueryDirective.ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "14.2.8", type: InfinityQueryDirective, isStandalone: true, selector: "[infinityQuery]", inputs: { infinityQuery: "infinityQuery" }, providers: [{ provide: INFINITY_QUERY_TOKEN, useExisting: InfinityQueryDirective }], ngImport: i0 });
991
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "14.2.8", ngImport: i0, type: InfinityQueryDirective, decorators: [{
992
+ type: Directive,
993
+ args: [{
994
+ // eslint-disable-next-line @angular-eslint/directive-selector
995
+ selector: '[infinityQuery]',
996
+ standalone: true,
997
+ providers: [{ provide: INFINITY_QUERY_TOKEN, useExisting: InfinityQueryDirective }],
998
+ }]
999
+ }], propDecorators: { infinityQuery: [{
1000
+ type: Input
1001
+ }] } });
1002
+
1003
+ class InfinityQueryTriggerDirective {
1004
+ constructor() {
1005
+ this._elementRef = inject((ElementRef));
1006
+ this._infinityQuery = inject(INFINITY_QUERY_TOKEN);
1007
+ this._destroy = new Subject();
1008
+ this._observer = null;
1009
+ this.click$ = fromEvent(this._elementRef.nativeElement, 'click');
1010
+ this.scrollContainerSelector = null;
1011
+ }
1012
+ ngOnInit() {
1013
+ const isInteractive = this._elementRef.nativeElement.hasAttribute('infinityQueryTrigger');
1014
+ if (isInteractive) {
1015
+ this.click$.pipe(takeUntil(this._destroy)).subscribe(() => this._infinityQuery._loadNextPage());
1016
+ }
1017
+ else {
1018
+ this._setupIntersectionObserver();
1019
+ }
1020
+ }
1021
+ ngOnDestroy() {
1022
+ this._destroy.next(true);
1023
+ this._destroy.unsubscribe();
1024
+ this._observer?.disconnect();
1025
+ }
1026
+ _setupIntersectionObserver() {
1027
+ this._observer = new IntersectionObserver((entries) => {
1028
+ const entry = entries[0];
1029
+ if (entry.isIntersecting && !this._infinityQuery.context.loading) {
1030
+ this._infinityQuery._loadNextPage();
1031
+ }
1032
+ }, {
1033
+ root: this.scrollContainerSelector ? document.querySelector(this.scrollContainerSelector) : null,
1034
+ rootMargin: '0px',
1035
+ threshold: 1.0,
1036
+ });
1037
+ this._observer.observe(this._elementRef.nativeElement);
1038
+ }
1039
+ }
1040
+ InfinityQueryTriggerDirective.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "14.2.8", ngImport: i0, type: InfinityQueryTriggerDirective, deps: [], target: i0.ɵɵFactoryTarget.Directive });
1041
+ InfinityQueryTriggerDirective.ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "14.2.8", type: InfinityQueryTriggerDirective, isStandalone: true, selector: "[infinityQueryTrigger], infinity-query-trigger", inputs: { scrollContainerSelector: "scrollContainerSelector" }, ngImport: i0 });
1042
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "14.2.8", ngImport: i0, type: InfinityQueryTriggerDirective, decorators: [{
1043
+ type: Directive,
1044
+ args: [{
1045
+ // eslint-disable-next-line @angular-eslint/directive-selector
1046
+ selector: '[infinityQueryTrigger], infinity-query-trigger',
1047
+ standalone: true,
1048
+ }]
1049
+ }], propDecorators: { scrollContainerSelector: [{
1050
+ type: Input
1051
+ }] } });
1052
+
1053
+ class QueryStore {
1054
+ constructor(_config) {
1055
+ this._config = _config;
1056
+ this._store = new Map();
1057
+ this._garbageCollector = null;
1058
+ }
1059
+ add(id, query) {
1060
+ this._store.set(id, query);
1061
+ this._initGarbageCollector();
1062
+ this._logState(id, query, 'SET');
1063
+ }
1064
+ get(id) {
1065
+ return this._store.get(id) ?? null;
1066
+ }
1067
+ remove(id) {
1068
+ this._store.delete(id);
1069
+ this._logState(id, null, 'REMOVE');
1070
+ }
1071
+ _logState(key, item, operation) {
1072
+ if (!this._config?.enableChangeLogging)
1073
+ return;
1074
+ const stateAsJson = {};
1075
+ this._store.forEach((value, key) => {
1076
+ stateAsJson[key] = value;
1077
+ });
1078
+ console.log(`%c[${operation}] ${key}`, 'font-weight: bold');
1079
+ console.log({ operation, key, item });
1080
+ console.log(stateAsJson);
1081
+ }
1082
+ _initGarbageCollector() {
1083
+ if (this._garbageCollector !== null) {
1084
+ return;
1085
+ }
1086
+ this._logGarbageCollector('Start');
1087
+ this._garbageCollector = window.setInterval(() => {
1088
+ this._runGarbageCollector();
1089
+ }, 15000);
1090
+ }
1091
+ _stopGarbageCollector() {
1092
+ if (this._garbageCollector !== null) {
1093
+ window.clearInterval(this._garbageCollector);
1094
+ this._garbageCollector = null;
1095
+ }
1096
+ }
1097
+ _runGarbageCollector() {
1098
+ this._logGarbageCollector('Collecting...');
1099
+ this._store.forEach((item, key) => {
1100
+ if (item.isExpired) {
1101
+ this.remove(key);
1102
+ }
1103
+ });
1104
+ this._logGarbageCollector('Collection done');
1105
+ if (!this._store.size) {
1106
+ this._stopGarbageCollector();
1107
+ this._logGarbageCollector('Stop');
1108
+ }
1109
+ }
1110
+ _logGarbageCollector(action) {
1111
+ if (!this._config?.enableGarbageCollectorLogging)
1112
+ return;
1113
+ console.log(`%cGC: ${action}`, 'color: yellow; font-weight: bold');
1114
+ }
1115
+ }
1116
+
1117
+ const shouldCacheQuery = (method) => {
1118
+ return method === 'GET' || method === 'OPTIONS' || method === 'HEAD' || method === 'GQL_QUERY';
1119
+ };
1120
+ const buildGqlCacheKey = (
1121
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
1122
+ config, args) => {
1123
+ const { query } = config;
1124
+ const variables = args?.variables || {};
1125
+ const queryWithoutNewLinesAndWhitespace = query.replace(/\s/g, '');
1126
+ const queryFirst50Chars = queryWithoutNewLinesAndWhitespace.slice(0, 50);
1127
+ const queryMiddle25Chars = queryWithoutNewLinesAndWhitespace.slice(queryWithoutNewLinesAndWhitespace.length / 2 - 25, queryWithoutNewLinesAndWhitespace.length / 2 + 25);
1128
+ const queryLast50Chars = queryWithoutNewLinesAndWhitespace.slice(-50);
1129
+ const vars = JSON.stringify(variables);
1130
+ return `${queryFirst50Chars}...${queryMiddle25Chars}...${queryLast50Chars}...${vars}`;
1131
+ };
1132
+
1133
+ class QueryClient {
1134
+ constructor(_clientConfig) {
1135
+ this._clientConfig = _clientConfig;
1136
+ this._authProvider = null;
1137
+ this.get = (queryConfig) => this.fetch({
1138
+ ...queryConfig,
1139
+ method: 'GET',
1140
+ });
1141
+ this.post = (queryConfig) => this.fetch({
1142
+ ...queryConfig,
1143
+ method: 'POST',
1144
+ });
1145
+ this.put = (queryConfig) => this.fetch({
1146
+ ...queryConfig,
1147
+ method: 'PUT',
1148
+ });
1149
+ this.patch = (queryConfig) => this.fetch({
1150
+ ...queryConfig,
1151
+ method: 'PATCH',
1152
+ });
1153
+ this.delete = (queryConfig) => this.fetch({
1154
+ ...queryConfig,
1155
+ method: 'DELETE',
1156
+ });
1157
+ this.gqlQuery = (queryConfig) => this.fetch({
1158
+ ...queryConfig,
1159
+ method: 'GQL_QUERY',
1160
+ });
1161
+ this.gqlMutate = (queryConfig) => this.fetch({
1162
+ ...queryConfig,
1163
+ method: 'GQL_MUTATE',
1164
+ });
1165
+ this.fetch = (queryConfig) => {
1166
+ const prepare = (args) => {
1167
+ const route = buildRoute({
1168
+ base: this._clientConfig.baseRoute,
1169
+ route: queryConfig.route,
1170
+ pathParams: args?.pathParams,
1171
+ queryParams: args?.queryParams,
1172
+ });
1173
+ const cacheKey = isGqlQueryConfig(queryConfig) ? buildGqlCacheKey(queryConfig, args) : route;
1174
+ if (shouldCacheQuery(queryConfig.method)) {
1175
+ const existingQuery = this._store.get(cacheKey);
1176
+ if (existingQuery) {
1177
+ return existingQuery;
1178
+ }
1179
+ }
1180
+ const query = new Query(this, queryConfig, route, args);
1181
+ if (shouldCacheQuery(queryConfig.method)) {
1182
+ this._store.add(cacheKey, query);
1183
+ }
1184
+ return query;
1185
+ };
1186
+ const behaviorSubject = (initialValue = null) => new BehaviorSubject(initialValue);
1187
+ return {
1188
+ prepare,
1189
+ behaviorSubject,
1190
+ };
1191
+ };
1192
+ this.setAuthProvider = (authProvider) => {
1193
+ if (this._authProvider) {
1194
+ throw new Error('The auth provider is already set. Please call clearAuthProvider() first.');
1195
+ }
1196
+ this._authProvider = authProvider;
1197
+ authProvider.queryClient = this;
1198
+ };
1199
+ this.clearAuthProvider = () => {
1200
+ this._authProvider?.cleanUp();
1201
+ this._authProvider = null;
1202
+ };
1203
+ this._store = new QueryStore({
1204
+ enableChangeLogging: _clientConfig.logging?.queryStateChanges,
1205
+ enableGarbageCollectorLogging: _clientConfig.logging?.queryStateGarbageCollector,
1206
+ });
1207
+ }
1208
+ get config() {
1209
+ return this._clientConfig;
1210
+ }
1211
+ get authProvider() {
1212
+ return this._authProvider;
1213
+ }
1214
+ }
1215
+
1216
+ class QueryField {
1217
+ constructor(data) {
1218
+ this.data = data;
1219
+ this.changes$ = this._setupChangesObservable();
1220
+ this._currentDebounceTimeout = null;
1221
+ }
1222
+ get control() {
1223
+ return this.data.control;
1224
+ }
1225
+ setValue(value) {
1226
+ if (this._currentDebounceTimeout !== null) {
1227
+ clearTimeout(this._currentDebounceTimeout);
1228
+ }
1229
+ if (this.data.debounce) {
1230
+ this._currentDebounceTimeout = window.setTimeout(() => {
1231
+ this.control.setValue(value);
1232
+ }, this.data.debounce);
1233
+ }
1234
+ else {
1235
+ this.control.setValue(value);
1236
+ }
1237
+ }
1238
+ _setupChangesObservable() {
1239
+ const obs = this.control.valueChanges.pipe(startWith(this.control.value));
1240
+ if (!this.data.debounce) {
1241
+ return obs;
1242
+ }
1243
+ // The initial value should get emitted immediately
1244
+ // concat switches to the debounced observable after the first observable is completed
1245
+ return concat(obs.pipe(take(1)), this.control.valueChanges.pipe(debounceTime(this.data.debounce)));
1246
+ }
1247
+ }
1248
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
1249
+ class QueryForm {
1250
+ constructor(_fields) {
1251
+ this._fields = _fields;
1252
+ this._router = inject(Router, { optional: true });
1253
+ this._activatedRoute = inject(ActivatedRoute, { optional: true });
1254
+ this._defaultValues = this._extractDefaultValues();
1255
+ this.form = this._setupFormGroup();
1256
+ }
1257
+ observe(options) {
1258
+ return combineLatest(Object.values(this._fields).map((field) => field.changes$)).pipe(map(() => this.form.getRawValue()), distinctUntilChanged((a, b) => JSON.stringify(a) === JSON.stringify(b)), tap((values) => {
1259
+ if (options?.syncViaUrlQueryParams !== false) {
1260
+ this._syncViaUrlQueryParams(values);
1261
+ }
1262
+ }));
1263
+ }
1264
+ setFormValueFromUrlQueryParams() {
1265
+ if (!this._activatedRoute) {
1266
+ throw new Error('Cannot set form value from url query params without ActivatedRoute');
1267
+ }
1268
+ const queryParams = this._activatedRoute.snapshot.queryParams;
1269
+ for (const [key, field] of Object.entries(this._fields)) {
1270
+ const value = queryParams[key];
1271
+ if (value === undefined) {
1272
+ continue;
1273
+ }
1274
+ const deserializedValue = field.data.queryParamTransformFn?.(value) ?? value;
1275
+ field.setValue(deserializedValue);
1276
+ }
1277
+ }
1278
+ updateFormOnUrlQueryParamsChange() {
1279
+ if (!this._activatedRoute) {
1280
+ throw new Error('Cannot update form on url query params change without ActivatedRoute');
1281
+ }
1282
+ return this._activatedRoute.queryParams.pipe(tap(() => this.setFormValueFromUrlQueryParams()));
1283
+ }
1284
+ _getDefaultValue(key) {
1285
+ return this._defaultValues[key] ?? null;
1286
+ }
1287
+ _isDefaultValue(key, value) {
1288
+ const normalizedValue = Array.isArray(value) ? JSON.stringify(value) : value;
1289
+ return this._getDefaultValue(key) === normalizedValue;
1290
+ }
1291
+ _setupFormGroup() {
1292
+ const group = new FormGroup({});
1293
+ for (const [key, field] of Object.entries(this._fields)) {
1294
+ group.addControl(key, field.control);
1295
+ }
1296
+ return group;
1297
+ }
1298
+ _extractDefaultValues() {
1299
+ const defaultValues = {};
1300
+ for (const [key, field] of Object.entries(this._fields)) {
1301
+ const value = field.control.value;
1302
+ if (value === null || value === undefined) {
1303
+ continue;
1304
+ }
1305
+ if (Array.isArray(value)) {
1306
+ defaultValues[key] = JSON.stringify(value);
1307
+ }
1308
+ else {
1309
+ defaultValues[key] = value;
1310
+ }
1311
+ }
1312
+ return defaultValues;
1313
+ }
1314
+ _syncViaUrlQueryParams(values) {
1315
+ if (!this._router || !this._activatedRoute) {
1316
+ return;
1317
+ }
1318
+ const queryParams = structuredClone(this._activatedRoute.snapshot.queryParams);
1319
+ for (const [key, value] of Object.entries(values)) {
1320
+ if (this._isDefaultValue(key, value) || this._fields[key].data.appendToUrl === false) {
1321
+ delete queryParams[key];
1322
+ }
1323
+ else {
1324
+ queryParams[key] = value;
1325
+ }
1326
+ }
1327
+ this._router.navigate([], {
1328
+ queryParams,
1329
+ });
1330
+ }
1331
+ }
1332
+
1333
+ const transformToString = (value) => {
1334
+ if (typeof value === 'string') {
1335
+ return value;
1336
+ }
1337
+ return null;
1338
+ };
1339
+ const transformToStringArray = (value) => {
1340
+ if (Array.isArray(value)) {
1341
+ return value.map(transformToString).filter(Boolean);
1342
+ }
1343
+ else if (typeof value === 'string') {
1344
+ return [transformToString(value)];
1345
+ }
1346
+ return null;
1347
+ };
1348
+ const transformToNumber = (value) => {
1349
+ if (typeof value === 'number') {
1350
+ return value;
1351
+ }
1352
+ if (typeof value === 'string') {
1353
+ return Number(value);
1354
+ }
1355
+ return null;
1356
+ };
1357
+ const transformToNumberArray = (value) => {
1358
+ if (Array.isArray(value)) {
1359
+ return value.map(transformToNumber).filter((item) => item !== null);
1360
+ }
1361
+ else if (typeof value === 'number') {
1362
+ return [value];
1363
+ }
1364
+ return null;
1365
+ };
1366
+ const transformToBoolean = (value) => {
1367
+ if (typeof value === 'boolean') {
1368
+ return value;
1369
+ }
1370
+ if (typeof value === 'string') {
1371
+ return value === 'true' || value === '1';
1372
+ }
1373
+ return null;
1374
+ };
1375
+ const transformToBooleanArray = (value) => {
1376
+ if (Array.isArray(value)) {
1377
+ return value.map(transformToBoolean).filter((item) => item !== null);
1378
+ }
1379
+ else if (typeof value === 'string' || typeof value === 'boolean') {
1380
+ return [transformToBoolean(value)];
1381
+ }
1382
+ return null;
1383
+ };
1384
+ const transformToDate = (value) => {
1385
+ if (value instanceof Date) {
1386
+ return value;
1387
+ }
1388
+ if (typeof value === 'string') {
1389
+ const date = new Date(value);
1390
+ if (isNaN(date.getTime())) {
1391
+ return null;
1392
+ }
1393
+ return date;
1394
+ }
1395
+ return null;
1396
+ };
1397
+ const transformToDateArray = (value) => {
1398
+ if (Array.isArray(value)) {
1399
+ return value.map(transformToDate).filter((item) => item !== null);
1400
+ }
1401
+ else if (typeof value === 'string' || value instanceof Date) {
1402
+ return [transformToDate(value)];
1403
+ }
1404
+ return null;
1405
+ };
1406
+
1407
+ /**
1408
+ * Generated bundle index. Do not edit.
1409
+ */
1410
+
1411
+ export { BasicAuthProvider, BearerAuthProvider, CustomHeaderAuthProvider, INFINITY_QUERY_TOKEN, InfinityQuery, InfinityQueryDirective, InfinityQueryTriggerDirective, Query, QueryClient, QueryDirective, QueryError, QueryField, QueryForm, QueryStore, buildBody, buildErrorMessage, buildGqlCacheKey, buildQueryArrayString, buildQueryString, buildRequestError, buildRoute, buildTimestampFromSeconds, createInfinityQueryConfig, decryptBearer, deepFreeze, def, extractExpiresInSeconds, filterFailure, filterInvalidParams, filterSuccess, gql, guessContentType, invalidBaseRouteError, invalidBodyError, invalidRouteError, isAbortRequestError, isBasicAuthProvider, isBearerAuthProvider, isCustomHeaderAuthProvider, isFetchResponse, isGqlQueryConfig, isParamValid, isQueryStateCancelled, isQueryStateFailure, isQueryStateLoading, isQueryStatePrepared, isQueryStateSuccess, isRequestError, mergeHeaders, pathParamsMissingInRouteFunctionError, queryStateAlreadyHasKeyError, queryStateCannotTransformError, queryStateDoesNotContainKeyError, request, shouldCacheQuery, skipPaginationPageParamCalculator, takeUntilResponse, transformGql, transformMethod, transformToBoolean, transformToBooleanArray, transformToDate, transformToDateArray, transformToNumber, transformToNumberArray, transformToString, transformToStringArray };
1412
+ //# sourceMappingURL=ethlete-query.mjs.map