@ethlete/query 6.0.0-next.2 → 6.0.0-next.21
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 +227 -0
- package/fesm2022/ethlete-query-testing.mjs.map +1 -1
- package/fesm2022/ethlete-query.mjs +1879 -301
- package/fesm2022/ethlete-query.mjs.map +1 -1
- package/generators/migrate-to-query-v3/auth-provider-scaffold.js +131 -0
- package/generators/migrate-to-query-v3/cleanup-migration.js +293 -0
- package/generators/migrate-to-query-v3/http-client-check.js +93 -0
- package/generators/migrate-to-query-v3/legacy-prepare-migration.js +772 -0
- package/generators/migrate-to-query-v3/legacy-query-creator-migration.js +607 -0
- package/generators/migrate-to-query-v3/migration-scope.js +41 -0
- package/generators/migrate-to-query-v3/migration.js +28 -2932
- package/generators/migrate-to-query-v3/module-graph.js +153 -0
- package/generators/migrate-to-query-v3/query-client-migration.js +525 -0
- package/generators/migrate-to-query-v3/rename-symbols.js +193 -0
- package/generators/migrate-to-query-v3/report.js +136 -0
- package/generators/migrate-to-query-v3/schema.json +12 -0
- package/generators/migrate-to-query-v3/shared.js +97 -0
- package/generators/prep-for-query-v3/migration.js +3 -3
- package/package.json +12 -12
- package/types/ethlete-query.d.ts +1068 -36
|
@@ -1,16 +1,137 @@
|
|
|
1
1
|
import * as i0 from '@angular/core';
|
|
2
|
-
import { isDevMode, DestroyRef, InjectionToken, inject, Injector, EnvironmentInjector,
|
|
3
|
-
import { createStaticProvider, RuntimeError, clamp, createDestroy, computedTillTruthy, getCookie, setCookie, deleteCookie, createProvider, nextFrame, createRootProvider, isObject, injectRoute, getDomain, injectQueryParamChanges, injectQueryParams, clone, equal, ET_PROPERTY_REMOVED, previousSignalValue } from '@ethlete/core';
|
|
4
|
-
import {
|
|
2
|
+
import { signal, isDevMode, makeEnvironmentProviders, DestroyRef, InjectionToken, inject, Injector, EnvironmentInjector, ElementRef, ErrorHandler, createEnvironmentInjector, effect, untracked, linkedSignal, computed, runInInjectionContext, assertInInjectionContext, ChangeDetectorRef, ViewContainerRef, TemplateRef, Input, Directive, input, Pipe, ViewEncapsulation, Component, resource, PLATFORM_ID, DOCUMENT, NgZone } from '@angular/core';
|
|
3
|
+
import { createStaticProvider, RuntimeError, clamp, createDestroy, computedTillTruthy, getCookie, setCookie, deleteCookie, createProvider, nextFrame, randomId, createRootProvider, isObject, injectRoute, getDomain, injectQueryParamChanges, injectQueryParams, clone, equal, ET_PROPERTY_REMOVED, previousSignalValue } from '@ethlete/core';
|
|
4
|
+
import { filter, Subject, Subscription, Observable, tap, retry, throwError, timer, catchError, takeUntil, startWith, pairwise, switchMap, of, BehaviorSubject, fromEvent, take, ReplaySubject, map, shareReplay, interval, skip, takeWhile, combineLatest, EMPTY, withLatestFrom, debounceTime, firstValueFrom, merge, throttleTime } from 'rxjs';
|
|
5
5
|
import { toObservable, takeUntilDestroyed, toSignal } from '@angular/core/rxjs-interop';
|
|
6
|
-
import { HttpClient,
|
|
6
|
+
import { HttpClient, HttpEventType, HttpErrorResponse, HttpHeaders, HttpStatusCode } from '@angular/common/http';
|
|
7
7
|
import { getActiveConsumer, setActiveConsumer } from '@angular/core/primitives/signals';
|
|
8
|
-
import { NgClass, AsyncPipe, JsonPipe } from '@angular/common';
|
|
8
|
+
import { NgClass, AsyncPipe, JsonPipe, isPlatformBrowser } from '@angular/common';
|
|
9
9
|
import * as i1 from '@angular/forms';
|
|
10
10
|
import { FormControl, ReactiveFormsModule, FormGroup } from '@angular/forms';
|
|
11
|
+
import { validateAsync, form } from '@angular/forms/signals';
|
|
11
12
|
import { Router, ActivatedRoute } from '@angular/router';
|
|
12
13
|
import { io } from 'socket.io-client';
|
|
13
14
|
|
|
15
|
+
const noop = () => undefined;
|
|
16
|
+
let devtoolsEnabled = false;
|
|
17
|
+
/**
|
|
18
|
+
* Whether {@link provideQueryDevtools} has been called. When `false`, every `register*` call is a
|
|
19
|
+
* no-op so the devtools instrumentation retains no references and adds no overhead in production.
|
|
20
|
+
* @internal
|
|
21
|
+
*/
|
|
22
|
+
const isQueryDevtoolsEnabled = () => devtoolsEnabled;
|
|
23
|
+
const entries = signal([], /* @ts-ignore */
|
|
24
|
+
...(ngDevMode ? [{ debugName: "entries" }] : /* istanbul ignore next */ []));
|
|
25
|
+
/**
|
|
26
|
+
* The reactive list of every registered devtools entry, consumed by the `<et-query-devtools>` UI.
|
|
27
|
+
*/
|
|
28
|
+
const queryDevtoolsEntries = entries.asReadonly();
|
|
29
|
+
// Per-descriptor counter used to derive stable, reload-deterministic ids. Reset on page load (module
|
|
30
|
+
// re-eval), so the same queries created in the same order get the same ids across reloads — which is
|
|
31
|
+
// what lets the devtools restore the selected query after a reload.
|
|
32
|
+
const idCounters = new Map();
|
|
33
|
+
const descriptorOf = (entry) => {
|
|
34
|
+
const { kind, meta } = entry;
|
|
35
|
+
if (kind === 'query')
|
|
36
|
+
return `query|${meta.clientName ?? ''}|${meta.method ?? ''}|${meta.route ?? ''}`;
|
|
37
|
+
if (kind === 'auth-provider')
|
|
38
|
+
return `auth-provider|${meta.name ?? ''}`;
|
|
39
|
+
if (kind === 'ws-client')
|
|
40
|
+
return `ws-client|${meta.name ?? ''}`;
|
|
41
|
+
return kind;
|
|
42
|
+
};
|
|
43
|
+
/**
|
|
44
|
+
* Registers an entry with the devtools registry. Returns an unregister callback. A no-op (returning
|
|
45
|
+
* a no-op callback) unless {@link provideQueryDevtools} has been called.
|
|
46
|
+
*
|
|
47
|
+
* Ids are derived deterministically from a stable descriptor + a per-descriptor sequence number, so
|
|
48
|
+
* they survive a page reload (letting the UI restore the selected entry) instead of being random.
|
|
49
|
+
* @internal
|
|
50
|
+
*/
|
|
51
|
+
const registerQueryDevtoolsEntry = (entry) => {
|
|
52
|
+
if (!devtoolsEnabled)
|
|
53
|
+
return noop;
|
|
54
|
+
let id = entry.id;
|
|
55
|
+
if (!id) {
|
|
56
|
+
const descriptor = descriptorOf(entry);
|
|
57
|
+
const seq = idCounters.get(descriptor) ?? 0;
|
|
58
|
+
idCounters.set(descriptor, seq + 1);
|
|
59
|
+
id = `${descriptor}#${seq}`;
|
|
60
|
+
}
|
|
61
|
+
const fullEntry = { ...entry, id, createdAt: Date.now() };
|
|
62
|
+
entries.update((list) => [...list, fullEntry]);
|
|
63
|
+
return () => entries.update((list) => list.filter((e) => e.id !== fullEntry.id));
|
|
64
|
+
};
|
|
65
|
+
let suppressStackRegistration = false;
|
|
66
|
+
/**
|
|
67
|
+
* Suppresses devtools registration for the next {@link createQueryStack} call. Used by
|
|
68
|
+
* `createPagedQueryStack` so its backing stack is not registered as a separate `query-stack` entry.
|
|
69
|
+
* @internal
|
|
70
|
+
*/
|
|
71
|
+
const suppressNextQueryStackDevtools = () => {
|
|
72
|
+
suppressStackRegistration = true;
|
|
73
|
+
};
|
|
74
|
+
/**
|
|
75
|
+
* Reads and clears the suppress-next-stack flag. Always call this from `createQueryStack` (even when
|
|
76
|
+
* devtools are disabled) so a suppression request never leaks into a later stack.
|
|
77
|
+
* @internal
|
|
78
|
+
*/
|
|
79
|
+
const consumeSuppressQueryStackDevtools = () => {
|
|
80
|
+
const value = suppressStackRegistration;
|
|
81
|
+
suppressStackRegistration = false;
|
|
82
|
+
return value;
|
|
83
|
+
};
|
|
84
|
+
/**
|
|
85
|
+
* Stringifies a query creator route. Function routes are invoked with an empty args object and any
|
|
86
|
+
* resulting `undefined` path segments are rendered as `:param`, matching the legacy devtools.
|
|
87
|
+
* @internal
|
|
88
|
+
*/
|
|
89
|
+
const stringifyQueryRoute = (route) => {
|
|
90
|
+
if (route === undefined || route === null)
|
|
91
|
+
return '';
|
|
92
|
+
try {
|
|
93
|
+
if (typeof route === 'function') {
|
|
94
|
+
return route({}).replace(/undefined/g, ':param');
|
|
95
|
+
}
|
|
96
|
+
return String(route);
|
|
97
|
+
}
|
|
98
|
+
catch {
|
|
99
|
+
return typeof route === 'function' ? '(dynamic route)' : String(route);
|
|
100
|
+
}
|
|
101
|
+
};
|
|
102
|
+
/**
|
|
103
|
+
* Extracts the human readable client name from a query client tuple. The client's DI token is named
|
|
104
|
+
* `QueryClient_<name>`, so we strip the prefix to recover the original name.
|
|
105
|
+
* @internal
|
|
106
|
+
*/
|
|
107
|
+
const getQueryClientName = (client) => {
|
|
108
|
+
const token = client[2];
|
|
109
|
+
const desc = token?.toString?.() ?? '';
|
|
110
|
+
return desc.replace('InjectionToken ', '').replace('QueryClient_', '') || 'unknown';
|
|
111
|
+
};
|
|
112
|
+
/**
|
|
113
|
+
* Enables the `@ethlete/query` devtools. Add this to your application providers (e.g. in
|
|
114
|
+
* `bootstrapApplication`) to make query clients, queries, stacks, sequences and auth providers
|
|
115
|
+
* appear in the `<et-query-devtools>` panel.
|
|
116
|
+
*
|
|
117
|
+
* When omitted, all devtools instrumentation is a no-op — no references are retained and there is no
|
|
118
|
+
* runtime overhead — so it is safe to leave out of production builds.
|
|
119
|
+
*
|
|
120
|
+
* @example
|
|
121
|
+
* ```ts
|
|
122
|
+
* bootstrapApplication(AppComponent, {
|
|
123
|
+
* providers: [provideQueryDevtools()],
|
|
124
|
+
* });
|
|
125
|
+
* ```
|
|
126
|
+
*/
|
|
127
|
+
const provideQueryDevtools = () => {
|
|
128
|
+
devtoolsEnabled = true;
|
|
129
|
+
if (!isDevMode()) {
|
|
130
|
+
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.');
|
|
131
|
+
}
|
|
132
|
+
return makeEnvironmentProviders([]);
|
|
133
|
+
};
|
|
134
|
+
|
|
14
135
|
const getOpName = /(query|mutation) ?([\w\d-_]+)? ?\(.*?\)? \{/;
|
|
15
136
|
const transformGql = (str) => {
|
|
16
137
|
const normalizedStr = Array.isArray(str) ? str.join('') : str;
|
|
@@ -46,9 +167,6 @@ const createGqlExecuteFn = (executeOptions) => {
|
|
|
46
167
|
const reset = () => resetExecuteState({ executeState, executeOptions });
|
|
47
168
|
const exec = (executeArgs) => {
|
|
48
169
|
circularChecker.check();
|
|
49
|
-
// Make sure all effects are flushed before reading the args.
|
|
50
|
-
// A withArgs feature effect might still be pending otherwise resulting in the wrong args being read.
|
|
51
|
-
executeOptions.deps.effectScheduler.flush();
|
|
52
170
|
const { args = executeOptions.state.args(), options } = executeArgs ?? {};
|
|
53
171
|
const query = transformGql(executeOptions.creatorInternals.query);
|
|
54
172
|
let gqlParams = query(args?.variables);
|
|
@@ -133,9 +251,9 @@ const setupQueryDependencies = (options) => {
|
|
|
133
251
|
scopeDestroyRef: hostInjector.get(DestroyRef),
|
|
134
252
|
client: hostInjector.get(clientToken),
|
|
135
253
|
injector: undefined, // Will be set after injector creation
|
|
136
|
-
effectScheduler: hostInjector.get(_EffectScheduler),
|
|
137
254
|
ngErrorHandler: hostInjector.get(ErrorHandler),
|
|
138
255
|
httpClient: hostInjector.get(HttpClient),
|
|
256
|
+
hostElement: hostInjector.get(ElementRef, null, { optional: true })?.nativeElement ?? null,
|
|
139
257
|
};
|
|
140
258
|
const queryContext = {
|
|
141
259
|
deps: dependencies,
|
|
@@ -198,6 +316,10 @@ const QueryRuntimeErrorCode = {
|
|
|
198
316
|
INVALID_STATE_INSIDE_SECURE_EXECUTE_FACTORY: 701,
|
|
199
317
|
// Query Execution
|
|
200
318
|
CIRCULAR_QUERY_DEPENDENCY: 800,
|
|
319
|
+
// Query Sequence
|
|
320
|
+
QUERY_SEQUENCE_ALREADY_RUNNING: 900,
|
|
321
|
+
// Legacy interop
|
|
322
|
+
LEGACY_PREPARE_WITHOUT_INJECTION_CONTEXT: 950,
|
|
201
323
|
};
|
|
202
324
|
const queryFeatureUsedMultipleTimes = (type) => {
|
|
203
325
|
return new RuntimeError(QueryRuntimeErrorCode.QUERY_FEATURE_USED_MULTIPLE_TIMES, `The query feature "${type}()" was used multiple times.`);
|
|
@@ -266,6 +388,52 @@ const invalidStateInsideSecureExecuteFactory = () => {
|
|
|
266
388
|
const circularQueryDependency = () => {
|
|
267
389
|
return new RuntimeError(QueryRuntimeErrorCode.CIRCULAR_QUERY_DEPENDENCY, `Query was executed more than 5 times in less than 100ms. This is usually a sign of a circular dependency.`);
|
|
268
390
|
};
|
|
391
|
+
const legacyPrepareWithoutInjectionContext = (creatorName) => {
|
|
392
|
+
const label = creatorName ? `"${creatorName}"` : 'A legacy query creator';
|
|
393
|
+
return new RuntimeError(QueryRuntimeErrorCode.LEGACY_PREPARE_WITHOUT_INJECTION_CONTEXT, `${label}.prepare() was called outside of an injection context. Unlike the v2 client, the legacy interop layer needs an injector to build the underlying query.\n\n` +
|
|
394
|
+
`Capture one where a context does exist (field initializer, constructor) and pass it along:\n\n` +
|
|
395
|
+
` private injector = inject(Injector);\n` +
|
|
396
|
+
` search = (term: string) => myQuery.prepare({ queryParams: { term }, injector: this.injector });\n\n` +
|
|
397
|
+
`Prefer an injector that outlives the call site. A component injector passed from a callback that can still fire after the component is gone leaves prepare() with a destroyed injector, which returns an inert query instead of executing.`);
|
|
398
|
+
};
|
|
399
|
+
const querySequenceAlreadyRunning = () => {
|
|
400
|
+
return new RuntimeError(QueryRuntimeErrorCode.QUERY_SEQUENCE_ALREADY_RUNNING, `run() was called on a query sequence that is already running. Wait for the current run to settle before starting another.`);
|
|
401
|
+
};
|
|
402
|
+
|
|
403
|
+
const isClassValidatorError = (error) => {
|
|
404
|
+
return typeof error === 'object' && error !== null && 'statusCode' in error && 'message' in error && 'error' in error;
|
|
405
|
+
};
|
|
406
|
+
const isSymfonyError = (error) => {
|
|
407
|
+
if (!error || typeof error !== 'object') {
|
|
408
|
+
return false;
|
|
409
|
+
}
|
|
410
|
+
const symfonyError = error;
|
|
411
|
+
return (symfonyError.class !== undefined &&
|
|
412
|
+
symfonyError.detail !== undefined &&
|
|
413
|
+
symfonyError.status !== undefined &&
|
|
414
|
+
symfonyError.title !== undefined &&
|
|
415
|
+
symfonyError.trace !== undefined &&
|
|
416
|
+
symfonyError.type !== undefined);
|
|
417
|
+
};
|
|
418
|
+
const isSymfonyPagerfantaOutOfRangeError = (error) => {
|
|
419
|
+
if (!isSymfonyError(error)) {
|
|
420
|
+
return false;
|
|
421
|
+
}
|
|
422
|
+
return error.class.startsWith('Pagerfanta') && error.class.endsWith('OutOfRangeCurrentPageException');
|
|
423
|
+
};
|
|
424
|
+
const isSymfonyFormViolationListError = (error) => {
|
|
425
|
+
return typeof error === 'object' && error !== null && 'violations' in error;
|
|
426
|
+
};
|
|
427
|
+
const isSymfonyListError = (error) => {
|
|
428
|
+
return (!!error &&
|
|
429
|
+
typeof error === 'object' &&
|
|
430
|
+
Array.isArray(error) &&
|
|
431
|
+
!!error.length &&
|
|
432
|
+
typeof error[0] === 'object' &&
|
|
433
|
+
'message' in error[0] &&
|
|
434
|
+
'propertyPath' in error[0] &&
|
|
435
|
+
'invalidValue' in error[0]);
|
|
436
|
+
};
|
|
269
437
|
|
|
270
438
|
/**
|
|
271
439
|
* Returning this inside a withArgs feature will reset the query args to null.
|
|
@@ -290,6 +458,7 @@ const QueryFeatureType = {
|
|
|
290
458
|
WITH_POLLING: 'WITH_POLLING',
|
|
291
459
|
WITH_AUTO_REFRESH: 'WITH_AUTO_REFRESH',
|
|
292
460
|
WITH_RESPONSE_UPDATE: 'WITH_RESPONSE_UPDATE',
|
|
461
|
+
WITH_PAGE_RESET_ON_ERROR: 'WITH_PAGE_RESET_ON_ERROR',
|
|
293
462
|
};
|
|
294
463
|
const createQueryFeature = (config) => {
|
|
295
464
|
return config;
|
|
@@ -307,17 +476,27 @@ const withArgs = (args) => {
|
|
|
307
476
|
return createQueryFeature({
|
|
308
477
|
type: QueryFeatureType.WITH_ARGS,
|
|
309
478
|
fn: (context) => {
|
|
310
|
-
|
|
479
|
+
// `state.args` is reactively backed by this source, so every reader (exec, polling,
|
|
480
|
+
// auto-refresh) always sees the latest value with no pending-effect window. Sentinels are
|
|
481
|
+
// resolved here: CLEAR -> null, `null` ("don't change") -> keep the previous value.
|
|
482
|
+
let previous = null;
|
|
483
|
+
context.state.subtle.setArgsSource(() => {
|
|
484
|
+
const value = args();
|
|
485
|
+
if (value === CLEAR_QUERY_ARGS) {
|
|
486
|
+
previous = null;
|
|
487
|
+
return null;
|
|
488
|
+
}
|
|
489
|
+
if (value === null)
|
|
490
|
+
return previous;
|
|
491
|
+
previous = value;
|
|
492
|
+
return value;
|
|
493
|
+
});
|
|
494
|
+
// Trigger (auto-)execution whenever the resolved args change.
|
|
311
495
|
nestedEffect(() => {
|
|
312
|
-
const currArgsNow =
|
|
496
|
+
const currArgsNow = context.state.args();
|
|
313
497
|
if (currArgsNow === null)
|
|
314
498
|
return;
|
|
315
499
|
untracked(() => {
|
|
316
|
-
if (currArgsNow === CLEAR_QUERY_ARGS) {
|
|
317
|
-
context.state.args.set(null);
|
|
318
|
-
return;
|
|
319
|
-
}
|
|
320
|
-
context.state.args.set(currArgsNow);
|
|
321
500
|
if (context.flags.shouldAutoExecute)
|
|
322
501
|
context.execute({ args: currArgsNow });
|
|
323
502
|
});
|
|
@@ -366,14 +545,9 @@ const withLogging = (options) => {
|
|
|
366
545
|
return createQueryFeature({
|
|
367
546
|
type: QueryFeatureType.WITH_LOGGING,
|
|
368
547
|
fn: (context) => {
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
return;
|
|
373
|
-
untracked(() => {
|
|
374
|
-
options.logFn(event);
|
|
375
|
-
});
|
|
376
|
-
}, { injector: context.deps.injector });
|
|
548
|
+
context.state.events$.pipe(takeUntilDestroyed(context.deps.destroyRef)).subscribe((event) => {
|
|
549
|
+
options.logFn(event);
|
|
550
|
+
});
|
|
377
551
|
},
|
|
378
552
|
});
|
|
379
553
|
};
|
|
@@ -382,14 +556,11 @@ const withErrorHandling = (options) => {
|
|
|
382
556
|
return createQueryFeature({
|
|
383
557
|
type: QueryFeatureType.WITH_ERROR_HANDLING,
|
|
384
558
|
fn: (context) => {
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
options.handler(error);
|
|
391
|
-
});
|
|
392
|
-
}, { injector: context.deps.injector });
|
|
559
|
+
context.state.events$
|
|
560
|
+
.pipe(filter((event) => event.type === 'error'), takeUntilDestroyed(context.deps.destroyRef))
|
|
561
|
+
.subscribe((event) => {
|
|
562
|
+
options.handler(event.error);
|
|
563
|
+
});
|
|
393
564
|
},
|
|
394
565
|
});
|
|
395
566
|
};
|
|
@@ -398,14 +569,11 @@ const withSuccessHandling = (options) => {
|
|
|
398
569
|
return createQueryFeature({
|
|
399
570
|
type: QueryFeatureType.WITH_SUCCESS_HANDLING,
|
|
400
571
|
fn: (context) => {
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
options.handler(response);
|
|
407
|
-
});
|
|
408
|
-
}, { injector: context.deps.injector });
|
|
572
|
+
context.state.events$
|
|
573
|
+
.pipe(filter((event) => event.type === HttpEventType.Response), takeUntilDestroyed(context.deps.destroyRef))
|
|
574
|
+
.subscribe(() => {
|
|
575
|
+
options.handler(context.state.response());
|
|
576
|
+
});
|
|
409
577
|
},
|
|
410
578
|
});
|
|
411
579
|
};
|
|
@@ -456,22 +624,97 @@ const withResponseUpdate = (options) => {
|
|
|
456
624
|
},
|
|
457
625
|
});
|
|
458
626
|
};
|
|
627
|
+
/**
|
|
628
|
+
* Whether an error means the requested page is out of range: HTTP `416`, or a
|
|
629
|
+
* `500` carrying a Symfony/Pagerfanta out-of-range detail (the dev-mode shape).
|
|
630
|
+
* Exported so it can also be used as the predicate of a `withErrorHandling` handler.
|
|
631
|
+
*/
|
|
632
|
+
const isPageOutOfRangeError = (error) => {
|
|
633
|
+
if (error.code === 416)
|
|
634
|
+
return true;
|
|
635
|
+
return error.code === 500 && isSymfonyPagerfantaOutOfRangeError(error.raw.error);
|
|
636
|
+
};
|
|
637
|
+
/**
|
|
638
|
+
* A query feature that resets the page when the current page becomes out of range
|
|
639
|
+
* — e.g. after a filter shrinks the result set below the current page. It reacts
|
|
640
|
+
* to the query's error events and resets the page source; the normal reactive
|
|
641
|
+
* re-execution then runs with the corrected page.
|
|
642
|
+
*
|
|
643
|
+
* Because query args are reactively sourced, this fixes the *source* (a signal or
|
|
644
|
+
* query form field) rather than patching args directly — so the change sticks.
|
|
645
|
+
*
|
|
646
|
+
* @example
|
|
647
|
+
* // signal-driven page
|
|
648
|
+
* const users = getUsers(
|
|
649
|
+
* withArgs(() => ({ queryParams: { page: page() } })),
|
|
650
|
+
* withPageResetOnError({ page }),
|
|
651
|
+
* );
|
|
652
|
+
*
|
|
653
|
+
* @example
|
|
654
|
+
* // query-form-driven page
|
|
655
|
+
* const users = getUsers(
|
|
656
|
+
* withArgs(() => ({ queryParams: qf.value() })),
|
|
657
|
+
* withPageResetOnError({ reset: () => qf.resetFieldToDefault('page') }),
|
|
658
|
+
* );
|
|
659
|
+
*/
|
|
660
|
+
const withPageResetOnError = (options) => {
|
|
661
|
+
const matches = options.when ?? isPageOutOfRangeError;
|
|
662
|
+
return createQueryFeature({
|
|
663
|
+
type: QueryFeatureType.WITH_PAGE_RESET_ON_ERROR,
|
|
664
|
+
fn: (context) => {
|
|
665
|
+
context.state.events$
|
|
666
|
+
.pipe(filter((event) => event.type === 'error'), takeUntilDestroyed(context.deps.destroyRef))
|
|
667
|
+
.subscribe((event) => {
|
|
668
|
+
if (!matches(event.error))
|
|
669
|
+
return;
|
|
670
|
+
if (options.reset) {
|
|
671
|
+
options.reset();
|
|
672
|
+
}
|
|
673
|
+
else {
|
|
674
|
+
options.page.set(options.resetTo ?? 1);
|
|
675
|
+
}
|
|
676
|
+
});
|
|
677
|
+
},
|
|
678
|
+
});
|
|
679
|
+
};
|
|
459
680
|
|
|
460
681
|
const setupQueryState = (options) => {
|
|
461
|
-
const request = signal(null,
|
|
462
|
-
|
|
682
|
+
const request = signal(null, /* @ts-ignore */
|
|
683
|
+
...(ngDevMode ? [{ debugName: "request" }] : /* istanbul ignore next */ []));
|
|
684
|
+
const rawResponse = linkedSignal(() => request()?.response() ?? null, /* @ts-ignore */
|
|
685
|
+
...(ngDevMode ? [{ debugName: "rawResponse" }] : /* istanbul ignore next */ []));
|
|
463
686
|
const response = computed(() => {
|
|
464
687
|
const raw = rawResponse();
|
|
465
688
|
if (raw === null)
|
|
466
689
|
return null;
|
|
467
690
|
return options.transformResponse ? options.transformResponse(raw) : raw;
|
|
468
|
-
},
|
|
469
|
-
|
|
470
|
-
const
|
|
471
|
-
|
|
472
|
-
const
|
|
473
|
-
|
|
474
|
-
const
|
|
691
|
+
}, /* @ts-ignore */
|
|
692
|
+
...(ngDevMode ? [{ debugName: "response" }] : /* istanbul ignore next */ []));
|
|
693
|
+
const loading = linkedSignal(() => request()?.loading() ?? null, /* @ts-ignore */
|
|
694
|
+
...(ngDevMode ? [{ debugName: "loading" }] : /* istanbul ignore next */ []));
|
|
695
|
+
const error = linkedSignal(() => request()?.error() ?? null, /* @ts-ignore */
|
|
696
|
+
...(ngDevMode ? [{ debugName: "error" }] : /* istanbul ignore next */ []));
|
|
697
|
+
const latestHttpEvent = linkedSignal(() => request()?.currentEvent() ?? null, /* @ts-ignore */
|
|
698
|
+
...(ngDevMode ? [{ debugName: "latestHttpEvent" }] : /* istanbul ignore next */ []));
|
|
699
|
+
// `args` is reactively sourced: reads always reflect the latest value from the installed source
|
|
700
|
+
// (e.g. the `withArgs` feature), so there is no stale window between a source change and an
|
|
701
|
+
// effect writing it. It stays writable (reset / auth-error clear it via `.set(null)`); a manual
|
|
702
|
+
// set is overridden again the next time the source's dependencies change.
|
|
703
|
+
const argsSource = signal(() => null, /* @ts-ignore */
|
|
704
|
+
...(ngDevMode ? [{ debugName: "argsSource" }] : /* istanbul ignore next */ []));
|
|
705
|
+
const args = linkedSignal(() => argsSource()(), /* @ts-ignore */
|
|
706
|
+
...(ngDevMode ? [{ debugName: "args" }] : /* istanbul ignore next */ []));
|
|
707
|
+
const setArgsSource = (source) => argsSource.set(source);
|
|
708
|
+
const lastTimeExecutedAt = signal(null, /* @ts-ignore */
|
|
709
|
+
...(ngDevMode ? [{ debugName: "lastTimeExecutedAt" }] : /* istanbul ignore next */ []));
|
|
710
|
+
const lastTriggeredBy = signal(null, /* @ts-ignore */
|
|
711
|
+
...(ngDevMode ? [{ debugName: "lastTriggeredBy" }] : /* istanbul ignore next */ []));
|
|
712
|
+
const requestEvents$ = new Subject();
|
|
713
|
+
let requestEventsSubscription = Subscription.EMPTY;
|
|
714
|
+
const bindRequestEvents = (request) => {
|
|
715
|
+
requestEventsSubscription.unsubscribe();
|
|
716
|
+
requestEventsSubscription = request.events$.subscribe((event) => requestEvents$.next(event));
|
|
717
|
+
};
|
|
475
718
|
const executionState = computed(() => {
|
|
476
719
|
const currentResponse = response();
|
|
477
720
|
const currentError = error();
|
|
@@ -506,7 +749,8 @@ const setupQueryState = (options) => {
|
|
|
506
749
|
else {
|
|
507
750
|
return null;
|
|
508
751
|
}
|
|
509
|
-
},
|
|
752
|
+
}, /* @ts-ignore */
|
|
753
|
+
...(ngDevMode ? [{ debugName: "executionState" }] : /* istanbul ignore next */ []));
|
|
510
754
|
const state = {
|
|
511
755
|
rawResponse,
|
|
512
756
|
response,
|
|
@@ -517,9 +761,12 @@ const setupQueryState = (options) => {
|
|
|
517
761
|
lastTimeExecutedAt,
|
|
518
762
|
lastTriggeredBy,
|
|
519
763
|
executionState,
|
|
764
|
+
events$: requestEvents$.asObservable(),
|
|
520
765
|
subtle: {
|
|
521
766
|
request,
|
|
522
767
|
rawResponse,
|
|
768
|
+
bindRequestEvents,
|
|
769
|
+
setArgsSource,
|
|
523
770
|
},
|
|
524
771
|
};
|
|
525
772
|
return state;
|
|
@@ -530,7 +777,8 @@ const createQuerySnapshotFn = (options) => {
|
|
|
530
777
|
const context = injectQueryContext();
|
|
531
778
|
const snapshotFn = () => {
|
|
532
779
|
const snapshotState = setupQueryState({});
|
|
533
|
-
const isAlive = signal(true,
|
|
780
|
+
const isAlive = signal(true, /* @ts-ignore */
|
|
781
|
+
...(ngDevMode ? [{ debugName: "isAlive" }] : /* istanbul ignore next */ []));
|
|
534
782
|
const killEffectRef = effect(() => {
|
|
535
783
|
const currentLoading = state.loading();
|
|
536
784
|
const currentError = state.error();
|
|
@@ -549,13 +797,14 @@ const createQuerySnapshotFn = (options) => {
|
|
|
549
797
|
snapshotState.rawResponse.set(currentResponse);
|
|
550
798
|
if (currentLoading)
|
|
551
799
|
return;
|
|
552
|
-
|
|
800
|
+
const hasCompletedResponse = currentLatestHttpEvent?.type === HttpEventType.Response;
|
|
801
|
+
if (!hasCompletedResponse && !currentError)
|
|
553
802
|
return;
|
|
554
803
|
// kill the effect once loading is done and we either have a response or an error
|
|
555
804
|
killEffectRef.destroy();
|
|
556
805
|
isAlive.set(false);
|
|
557
806
|
});
|
|
558
|
-
}, { ...(ngDevMode ? { debugName: "killEffectRef" } : {}), injector: context.deps.injector });
|
|
807
|
+
}, { ...(ngDevMode ? { debugName: "killEffectRef" } : /* istanbul ignore next */ {}), injector: context.deps.injector });
|
|
559
808
|
const snapshot = {
|
|
560
809
|
args: wrapAsObservableSignal(snapshotState.args.asReadonly(), context.deps.injector),
|
|
561
810
|
response: wrapAsObservableSignal(snapshotState.response, context.deps.injector),
|
|
@@ -628,6 +877,8 @@ const createQueryObject = (options) => {
|
|
|
628
877
|
const { state, execute, deps } = options;
|
|
629
878
|
const destroy = () => deps.injector.destroy();
|
|
630
879
|
const setResponse = (response) => state.rawResponse.set(response);
|
|
880
|
+
const setLoading = (loading) => state.loading.set(loading);
|
|
881
|
+
const setError = (error) => state.error.set(error);
|
|
631
882
|
const createSnapshot = createQuerySnapshotFn({ state, execute, deps });
|
|
632
883
|
// Pre-wrap all public signals once so both `query` and `roQuery` share the same instances.
|
|
633
884
|
const wrappedArgs = wrapAsObservableSignal(state.args.asReadonly(), deps.injector);
|
|
@@ -672,6 +923,8 @@ const createQueryObject = (options) => {
|
|
|
672
923
|
subtle: {
|
|
673
924
|
destroy,
|
|
674
925
|
setResponse,
|
|
926
|
+
setLoading,
|
|
927
|
+
setError,
|
|
675
928
|
request: wrappedRequest,
|
|
676
929
|
destroyRef: deps.scopeDestroyRef,
|
|
677
930
|
injector: deps.injector,
|
|
@@ -705,7 +958,28 @@ const createBaseQuery = (options) => {
|
|
|
705
958
|
};
|
|
706
959
|
applyQueryFeatures(options.features, featureFnContext);
|
|
707
960
|
maybeExecute({ execute, flags });
|
|
708
|
-
|
|
961
|
+
const query = createQueryObject({ state, execute, deps });
|
|
962
|
+
if (isQueryDevtoolsEnabled()) {
|
|
963
|
+
const unregister = registerQueryDevtoolsEntry({
|
|
964
|
+
kind: 'query',
|
|
965
|
+
handle: query,
|
|
966
|
+
meta: {
|
|
967
|
+
clientName: getQueryClientName(client),
|
|
968
|
+
clientBaseUrl: deps.client.baseUrl,
|
|
969
|
+
route: stringifyQueryRoute(options.creatorInternals.route ??
|
|
970
|
+
options.creator?.route),
|
|
971
|
+
method: flags.method,
|
|
972
|
+
featureTypes: options.features.map((f) => f.type),
|
|
973
|
+
queryConfig: options.queryConfig,
|
|
974
|
+
creator: options.creator,
|
|
975
|
+
repository: deps.client.repository,
|
|
976
|
+
element: deps.hostElement,
|
|
977
|
+
gqlQuery: options.creatorInternals.query,
|
|
978
|
+
},
|
|
979
|
+
});
|
|
980
|
+
deps.destroyRef.onDestroy(unregister);
|
|
981
|
+
}
|
|
982
|
+
return query;
|
|
709
983
|
});
|
|
710
984
|
};
|
|
711
985
|
|
|
@@ -740,11 +1014,13 @@ const queryExecute = (options) => {
|
|
|
740
1014
|
previousKey: executeState.previousKey(),
|
|
741
1015
|
runQueryOptions,
|
|
742
1016
|
isSecure,
|
|
1017
|
+
silenceUncacheableAllowCacheError: queryConfig.silenceUncacheableAllowCacheError,
|
|
743
1018
|
});
|
|
744
1019
|
executeState.previousKey.set(key);
|
|
745
1020
|
state.lastTimeExecutedAt.set(Date.now());
|
|
746
1021
|
state.lastTriggeredBy.set(runQueryOptions?.triggeredBy ?? null);
|
|
747
1022
|
state.subtle.request.set(request);
|
|
1023
|
+
state.subtle.bindRequestEvents(request);
|
|
748
1024
|
};
|
|
749
1025
|
const circularQueryDependencyChecker = () => {
|
|
750
1026
|
let lastTriggerTs = 0;
|
|
@@ -778,9 +1054,6 @@ const createExecuteFn = (executeOptions) => {
|
|
|
778
1054
|
const reset = () => resetExecuteState({ executeState, executeOptions });
|
|
779
1055
|
const exec = (executeArgs) => {
|
|
780
1056
|
circularChecker.check();
|
|
781
|
-
// Make sure all effects are flushed before reading the args.
|
|
782
|
-
// A withArgs feature effect might still be pending otherwise resulting in the wrong args being read.
|
|
783
|
-
executeOptions.deps.effectScheduler.flush();
|
|
784
1057
|
const { args = executeOptions.state.args(), options } = executeArgs ?? {};
|
|
785
1058
|
queryExecute({ executeOptions, executeState, args, options });
|
|
786
1059
|
};
|
|
@@ -790,6 +1063,19 @@ const createExecuteFn = (executeOptions) => {
|
|
|
790
1063
|
};
|
|
791
1064
|
|
|
792
1065
|
/* eslint-disable @typescript-eslint/no-explicit-any */
|
|
1066
|
+
/**
|
|
1067
|
+
* The empty request-args object, typed to satisfy `RequestArgs<TArgs>` for a query that takes no
|
|
1068
|
+
* arguments — its `QueryArgs` is just `{ response: T }`, so `RequestArgs` resolves to `{}`.
|
|
1069
|
+
*
|
|
1070
|
+
* Only needed in generic code, where `TArgs` is still a type parameter and TS therefore cannot see
|
|
1071
|
+
* that `{}` is the whole of it. Against a concrete query, omit the argument instead: both
|
|
1072
|
+
* `query.execute()` and the auth registry's `execute()` already take none.
|
|
1073
|
+
*
|
|
1074
|
+
* @example
|
|
1075
|
+
* const runAnything = <TArgs extends QueryArgs>(query: Query<TArgs>) =>
|
|
1076
|
+
* query.execute({ args: emptyQueryArgs<TArgs>() });
|
|
1077
|
+
*/
|
|
1078
|
+
const emptyQueryArgs = () => ({});
|
|
793
1079
|
const createQuery = (options) => createBaseQuery({
|
|
794
1080
|
creator: options.creator,
|
|
795
1081
|
creatorInternals: options.creatorInternals,
|
|
@@ -839,41 +1125,6 @@ const createBaseQueryCreator = (config) => {
|
|
|
839
1125
|
return queryCreator;
|
|
840
1126
|
};
|
|
841
1127
|
|
|
842
|
-
const isClassValidatorError = (error) => {
|
|
843
|
-
return typeof error === 'object' && error !== null && 'statusCode' in error && 'message' in error && 'error' in error;
|
|
844
|
-
};
|
|
845
|
-
const isSymfonyError = (error) => {
|
|
846
|
-
if (!error || typeof error !== 'object') {
|
|
847
|
-
return false;
|
|
848
|
-
}
|
|
849
|
-
const symfonyError = error;
|
|
850
|
-
return (symfonyError.class !== undefined &&
|
|
851
|
-
symfonyError.detail !== undefined &&
|
|
852
|
-
symfonyError.status !== undefined &&
|
|
853
|
-
symfonyError.title !== undefined &&
|
|
854
|
-
symfonyError.trace !== undefined &&
|
|
855
|
-
symfonyError.type !== undefined);
|
|
856
|
-
};
|
|
857
|
-
const isSymfonyPagerfantaOutOfRangeError = (error) => {
|
|
858
|
-
if (!isSymfonyError(error)) {
|
|
859
|
-
return false;
|
|
860
|
-
}
|
|
861
|
-
return error.class.startsWith('Pagerfanta') && error.class.endsWith('OutOfRangeCurrentPageException');
|
|
862
|
-
};
|
|
863
|
-
const isSymfonyFormViolationListError = (error) => {
|
|
864
|
-
return typeof error === 'object' && error !== null && 'violations' in error;
|
|
865
|
-
};
|
|
866
|
-
const isSymfonyListError = (error) => {
|
|
867
|
-
return (!!error &&
|
|
868
|
-
typeof error === 'object' &&
|
|
869
|
-
Array.isArray(error) &&
|
|
870
|
-
!!error.length &&
|
|
871
|
-
typeof error[0] === 'object' &&
|
|
872
|
-
'message' in error[0] &&
|
|
873
|
-
'propertyPath' in error[0] &&
|
|
874
|
-
'invalidValue' in error[0]);
|
|
875
|
-
};
|
|
876
|
-
|
|
877
1128
|
const buildErrorMessage = (code, message) => `[@ethlete\\query:${code}] ${message}`;
|
|
878
1129
|
class QueryError extends Error {
|
|
879
1130
|
constructor(code, message, data) {
|
|
@@ -1245,7 +1496,7 @@ const request = (config) => {
|
|
|
1245
1496
|
body = text;
|
|
1246
1497
|
}
|
|
1247
1498
|
}
|
|
1248
|
-
catch
|
|
1499
|
+
catch {
|
|
1249
1500
|
// Ignore the error.
|
|
1250
1501
|
}
|
|
1251
1502
|
}
|
|
@@ -1471,6 +1722,34 @@ const shouldRetryRequest = (options) => {
|
|
|
1471
1722
|
return { retry: false };
|
|
1472
1723
|
};
|
|
1473
1724
|
|
|
1725
|
+
const isQueryErrorResponse = (error) => {
|
|
1726
|
+
return typeof error === 'object' && error !== null && 'raw' in error && 'retryState' in error && 'isList' in error;
|
|
1727
|
+
};
|
|
1728
|
+
/**
|
|
1729
|
+
* Every message carried by an error, flattened into a plain list — one entry for a single error,
|
|
1730
|
+
* one per violation for a list (a form response with several field violations), empty for `null`.
|
|
1731
|
+
*
|
|
1732
|
+
* The single/list split exists because that is how APIs answer; UI almost never wants to branch on
|
|
1733
|
+
* it. Reach for this when rendering, and keep {@link QueryErrorResponse} for anything that needs
|
|
1734
|
+
* the status code or the raw response.
|
|
1735
|
+
*
|
|
1736
|
+
* @example
|
|
1737
|
+
* ```html
|
|
1738
|
+
* @for (message of queryErrorMessages(myQuery.error()); track message) {
|
|
1739
|
+
* <p class="error">{{ message }}</p>
|
|
1740
|
+
* }
|
|
1741
|
+
* ```
|
|
1742
|
+
*/
|
|
1743
|
+
const queryErrorMessages = (error) => {
|
|
1744
|
+
if (!error)
|
|
1745
|
+
return [];
|
|
1746
|
+
return error.isList ? error.errors.map((item) => item.message) : [error.error.message];
|
|
1747
|
+
};
|
|
1748
|
+
/**
|
|
1749
|
+
* The first message of an error, or `null` when there is none — the single-line counterpart to
|
|
1750
|
+
* {@link queryErrorMessages}, for a toast or a form-field hint.
|
|
1751
|
+
*/
|
|
1752
|
+
const queryErrorMessage = (error) => queryErrorMessages(error)[0] ?? null;
|
|
1474
1753
|
const createQueryErrorResponse = (error) => {
|
|
1475
1754
|
let err = error instanceof HttpErrorResponse ? error : null;
|
|
1476
1755
|
if (!err) {
|
|
@@ -1538,20 +1817,46 @@ const SPEED_BUFFER_TIME_IN_MS = 2000;
|
|
|
1538
1817
|
const createHttpRequest = (options) => {
|
|
1539
1818
|
let currentStreamSubscription = Subscription.EMPTY;
|
|
1540
1819
|
const { args, clientOptions, dependencies } = options;
|
|
1541
|
-
const currentEvent = signal(null,
|
|
1542
|
-
|
|
1543
|
-
const
|
|
1544
|
-
const
|
|
1545
|
-
|
|
1546
|
-
const
|
|
1547
|
-
|
|
1548
|
-
const
|
|
1549
|
-
|
|
1820
|
+
const currentEvent = signal(null, /* @ts-ignore */
|
|
1821
|
+
...(ngDevMode ? [{ debugName: "currentEvent" }] : /* istanbul ignore next */ []));
|
|
1822
|
+
const event$ = new Subject();
|
|
1823
|
+
const loading = signal(null, /* @ts-ignore */
|
|
1824
|
+
...(ngDevMode ? [{ debugName: "loading" }] : /* istanbul ignore next */ []));
|
|
1825
|
+
const error = signal(null, /* @ts-ignore */
|
|
1826
|
+
...(ngDevMode ? [{ debugName: "error" }] : /* istanbul ignore next */ []));
|
|
1827
|
+
const response = signal(null, /* @ts-ignore */
|
|
1828
|
+
...(ngDevMode ? [{ debugName: "response" }] : /* istanbul ignore next */ []));
|
|
1829
|
+
const lastLoadEventTime = signal(0, /* @ts-ignore */
|
|
1830
|
+
...(ngDevMode ? [{ debugName: "lastLoadEventTime" }] : /* istanbul ignore next */ []));
|
|
1831
|
+
const lastLoadEventAmount = signal(0, /* @ts-ignore */
|
|
1832
|
+
...(ngDevMode ? [{ debugName: "lastLoadEventAmount" }] : /* istanbul ignore next */ []));
|
|
1833
|
+
const lastExecuteTime = signal(0, /* @ts-ignore */
|
|
1834
|
+
...(ngDevMode ? [{ debugName: "lastExecuteTime" }] : /* istanbul ignore next */ []));
|
|
1835
|
+
const expiresIn = signal(null, /* @ts-ignore */
|
|
1836
|
+
...(ngDevMode ? [{ debugName: "expiresIn" }] : /* istanbul ignore next */ []));
|
|
1837
|
+
// NOTE: This must be a plain function, not a `computed`. The freshness check compares against
|
|
1838
|
+
// `Date.now()`, which is not reactive, so a memoized computed would only ever recompute when
|
|
1839
|
+
// `expiresIn` changes — once it evaluated to `false` (fresh) it would stay `false` forever, even
|
|
1840
|
+
// after the window elapsed, turning every `allowCache` execute into a permanent cache hit.
|
|
1841
|
+
const isStale = () => {
|
|
1550
1842
|
const expiresInTs = expiresIn();
|
|
1551
1843
|
return expiresInTs === null || expiresInTs < Date.now();
|
|
1552
|
-
}
|
|
1844
|
+
};
|
|
1845
|
+
// Resolved per execution rather than once at creation, so a client whose headers are a function
|
|
1846
|
+
// reading a signal (a preview token, a tenant id) sees the current value on every re-run.
|
|
1847
|
+
const resolveHeaders = () => {
|
|
1848
|
+
const clientHeaders = typeof options.clientHeaders === 'function' ? options.clientHeaders() : options.clientHeaders;
|
|
1849
|
+
const argHeaders = typeof args?.headers === 'function' ? args.headers() : args?.headers;
|
|
1850
|
+
if (!clientHeaders)
|
|
1851
|
+
return argHeaders;
|
|
1852
|
+
if (!argHeaders)
|
|
1853
|
+
return clientHeaders;
|
|
1854
|
+
// `set` replaces every value of that name, so a per-request header fully overrides the
|
|
1855
|
+
// client-level one instead of being appended next to it.
|
|
1856
|
+
return argHeaders.keys().reduce((merged, key) => merged.set(key, argHeaders.getAll(key) ?? []), clientHeaders);
|
|
1857
|
+
};
|
|
1553
1858
|
const createStream = () => {
|
|
1554
|
-
const headers =
|
|
1859
|
+
const headers = resolveHeaders();
|
|
1555
1860
|
return dependencies.httpClient
|
|
1556
1861
|
.request(options.method, options.fullPath, {
|
|
1557
1862
|
observe: 'events',
|
|
@@ -1577,7 +1882,7 @@ const createHttpRequest = (options) => {
|
|
|
1577
1882
|
}));
|
|
1578
1883
|
};
|
|
1579
1884
|
const execute = (options) => {
|
|
1580
|
-
if (loading() || (!isStale() && options?.allowCache)) {
|
|
1885
|
+
if (!options?.force && (loading() || (!isStale() && options?.allowCache))) {
|
|
1581
1886
|
// Do not execute if there is already a request in progress or caching is allowed
|
|
1582
1887
|
return false;
|
|
1583
1888
|
}
|
|
@@ -1600,14 +1905,12 @@ const createHttpRequest = (options) => {
|
|
|
1600
1905
|
return true;
|
|
1601
1906
|
};
|
|
1602
1907
|
const destroy = () => {
|
|
1603
|
-
|
|
1604
|
-
return false;
|
|
1605
|
-
}
|
|
1908
|
+
const wasActive = !currentStreamSubscription.closed;
|
|
1606
1909
|
currentStreamSubscription.unsubscribe();
|
|
1607
|
-
|
|
1910
|
+
event$.complete();
|
|
1911
|
+
return wasActive;
|
|
1608
1912
|
};
|
|
1609
1913
|
const updateState = (event) => {
|
|
1610
|
-
currentEvent.set(event);
|
|
1611
1914
|
switch (event.type) {
|
|
1612
1915
|
case HttpEventType.Response:
|
|
1613
1916
|
{
|
|
@@ -1634,12 +1937,16 @@ const createHttpRequest = (options) => {
|
|
|
1634
1937
|
}
|
|
1635
1938
|
break;
|
|
1636
1939
|
}
|
|
1940
|
+
currentEvent.set(event);
|
|
1941
|
+
event$.next(event);
|
|
1637
1942
|
};
|
|
1638
1943
|
const updateErrorState = (errorResponse) => {
|
|
1639
1944
|
const errorRes = createQueryErrorResponse(errorResponse);
|
|
1640
1945
|
error.set(errorRes);
|
|
1641
1946
|
loading.set(null);
|
|
1642
|
-
|
|
1947
|
+
const errorEvent = { type: 'error', error: errorRes };
|
|
1948
|
+
currentEvent.set(errorEvent);
|
|
1949
|
+
event$.next(errorEvent);
|
|
1643
1950
|
options.dependencies.ngErrorHandler.handleError(errorRes.raw);
|
|
1644
1951
|
};
|
|
1645
1952
|
const updateLoadingState = (event) => {
|
|
@@ -1673,13 +1980,17 @@ const createHttpRequest = (options) => {
|
|
|
1673
1980
|
loading.set(state);
|
|
1674
1981
|
};
|
|
1675
1982
|
const httpRequest = {
|
|
1983
|
+
method: options.method,
|
|
1984
|
+
url: options.fullPath,
|
|
1676
1985
|
execute,
|
|
1677
1986
|
destroy,
|
|
1678
1987
|
loading: loading.asReadonly(),
|
|
1679
1988
|
error: error.asReadonly(),
|
|
1680
1989
|
response: response.asReadonly(),
|
|
1681
1990
|
currentEvent: currentEvent.asReadonly(),
|
|
1991
|
+
events$: event$.asObservable(),
|
|
1682
1992
|
isStale,
|
|
1993
|
+
expiresAt: expiresIn.asReadonly(),
|
|
1683
1994
|
};
|
|
1684
1995
|
return httpRequest;
|
|
1685
1996
|
};
|
|
@@ -1696,9 +2007,12 @@ const createQueryStack = (options) => {
|
|
|
1696
2007
|
return { queries, lastQuery };
|
|
1697
2008
|
}, transform, argsKeyFn, deduplicateArgs, maxQueries = Infinity, removeStrategy = 'oldest', } = options ?? {};
|
|
1698
2009
|
const injector = inject(Injector);
|
|
1699
|
-
const queries = signal([],
|
|
1700
|
-
|
|
1701
|
-
const
|
|
2010
|
+
const queries = signal([], /* @ts-ignore */
|
|
2011
|
+
...(ngDevMode ? [{ debugName: "queries" }] : /* istanbul ignore next */ []));
|
|
2012
|
+
const lastQuery = signal(null, /* @ts-ignore */
|
|
2013
|
+
...(ngDevMode ? [{ debugName: "lastQuery" }] : /* istanbul ignore next */ []));
|
|
2014
|
+
const firstQuery = computed(() => queries()[0] ?? null, /* @ts-ignore */
|
|
2015
|
+
...(ngDevMode ? [{ debugName: "firstQuery" }] : /* istanbul ignore next */ []));
|
|
1702
2016
|
const hasWithArgsFeature = features.some((f) => f.type == QueryFeatureType.WITH_ARGS);
|
|
1703
2017
|
const hasWithOptimisticUpdateFeature = features.some((f) => f.type == QueryFeatureType.WITH_RESPONSE_UPDATE);
|
|
1704
2018
|
if (hasWithArgsFeature) {
|
|
@@ -1780,23 +2094,29 @@ const createQueryStack = (options) => {
|
|
|
1780
2094
|
untracked(() => clear());
|
|
1781
2095
|
});
|
|
1782
2096
|
effect(() => runWithArgs(args(dependencies?.())));
|
|
1783
|
-
const anyLoading = computed(() => queries().some((q) => q.loading()),
|
|
1784
|
-
|
|
2097
|
+
const anyLoading = computed(() => queries().some((q) => q.loading()), /* @ts-ignore */
|
|
2098
|
+
...(ngDevMode ? [{ debugName: "anyLoading" }] : /* istanbul ignore next */ []));
|
|
2099
|
+
const loadingCount = computed(() => queries().filter((q) => q.loading()).length, /* @ts-ignore */
|
|
2100
|
+
...(ngDevMode ? [{ debugName: "loadingCount" }] : /* istanbul ignore next */ []));
|
|
1785
2101
|
const allLoading = computed(() => {
|
|
1786
2102
|
const qs = queries();
|
|
1787
2103
|
return qs.length > 0 && qs.every((q) => q.loading());
|
|
1788
|
-
},
|
|
2104
|
+
}, /* @ts-ignore */
|
|
2105
|
+
...(ngDevMode ? [{ debugName: "allLoading" }] : /* istanbul ignore next */ []));
|
|
1789
2106
|
const loadingProgress = computed(() => {
|
|
1790
2107
|
const total = queries().length;
|
|
1791
2108
|
const loaded = queries().filter((q) => !q.loading()).length;
|
|
1792
2109
|
return { loaded, total };
|
|
1793
|
-
},
|
|
2110
|
+
}, /* @ts-ignore */
|
|
2111
|
+
...(ngDevMode ? [{ debugName: "loadingProgress" }] : /* istanbul ignore next */ []));
|
|
1794
2112
|
const anyError = computed(() => queries()
|
|
1795
2113
|
.map((q) => q.error())
|
|
1796
|
-
.find((e) => e !== null) ?? null,
|
|
2114
|
+
.find((e) => e !== null) ?? null, /* @ts-ignore */
|
|
2115
|
+
...(ngDevMode ? [{ debugName: "anyError" }] : /* istanbul ignore next */ []));
|
|
1797
2116
|
const errors = computed(() => queries()
|
|
1798
2117
|
.map((q) => q.error())
|
|
1799
|
-
.filter((e) => e !== null),
|
|
2118
|
+
.filter((e) => e !== null), /* @ts-ignore */
|
|
2119
|
+
...(ngDevMode ? [{ debugName: "errors" }] : /* istanbul ignore next */ []));
|
|
1800
2120
|
const response = computed(() => {
|
|
1801
2121
|
const responses = queries().map((q) => q.response());
|
|
1802
2122
|
return transform?.(responses) ?? responses;
|
|
@@ -1831,6 +2151,17 @@ const createQueryStack = (options) => {
|
|
|
1831
2151
|
runWithArgs,
|
|
1832
2152
|
},
|
|
1833
2153
|
};
|
|
2154
|
+
const skipDevtools = consumeSuppressQueryStackDevtools();
|
|
2155
|
+
if (isQueryDevtoolsEnabled() && !skipDevtools) {
|
|
2156
|
+
const unregister = registerQueryDevtoolsEntry({
|
|
2157
|
+
kind: 'query-stack',
|
|
2158
|
+
handle: stack,
|
|
2159
|
+
meta: {
|
|
2160
|
+
featureTypes: features.map((f) => f.type),
|
|
2161
|
+
},
|
|
2162
|
+
});
|
|
2163
|
+
inject(DestroyRef).onDestroy(unregister);
|
|
2164
|
+
}
|
|
1834
2165
|
return stack;
|
|
1835
2166
|
};
|
|
1836
2167
|
|
|
@@ -1891,12 +2222,19 @@ const fakePaginationAdapter = (totalHits = 10) => {
|
|
|
1891
2222
|
};
|
|
1892
2223
|
const createPagedQueryStack = (options) => {
|
|
1893
2224
|
const { responseNormalizer, queryCreator, features } = options;
|
|
1894
|
-
const currentPageArgs = signal(null,
|
|
1895
|
-
|
|
1896
|
-
const
|
|
1897
|
-
|
|
1898
|
-
const
|
|
1899
|
-
|
|
2225
|
+
const currentPageArgs = signal(null, /* @ts-ignore */
|
|
2226
|
+
...(ngDevMode ? [{ debugName: "currentPageArgs" }] : /* istanbul ignore next */ []));
|
|
2227
|
+
const initialPage = signal(options.initialPage ?? 1, /* @ts-ignore */
|
|
2228
|
+
...(ngDevMode ? [{ debugName: "initialPage" }] : /* istanbul ignore next */ []));
|
|
2229
|
+
const loadedMinPage = signal(initialPage(), /* @ts-ignore */
|
|
2230
|
+
...(ngDevMode ? [{ debugName: "loadedMinPage" }] : /* istanbul ignore next */ []));
|
|
2231
|
+
const loadedMaxPage = signal(initialPage(), /* @ts-ignore */
|
|
2232
|
+
...(ngDevMode ? [{ debugName: "loadedMaxPage" }] : /* istanbul ignore next */ []));
|
|
2233
|
+
const resetTrigger = signal(0, /* @ts-ignore */
|
|
2234
|
+
...(ngDevMode ? [{ debugName: "resetTrigger" }] : /* istanbul ignore next */ []));
|
|
2235
|
+
const pageDirection = signal('next', /* @ts-ignore */
|
|
2236
|
+
...(ngDevMode ? [{ debugName: "pageDirection" }] : /* istanbul ignore next */ []));
|
|
2237
|
+
suppressNextQueryStackDevtools();
|
|
1900
2238
|
const stack = createQueryStack({
|
|
1901
2239
|
args: () => null,
|
|
1902
2240
|
queryCreator,
|
|
@@ -1919,7 +2257,7 @@ const createPagedQueryStack = (options) => {
|
|
|
1919
2257
|
transform: transformArrayResponse,
|
|
1920
2258
|
});
|
|
1921
2259
|
effect(() => {
|
|
1922
|
-
|
|
2260
|
+
resetTrigger();
|
|
1923
2261
|
const args = options.args(initialPage(), []);
|
|
1924
2262
|
untracked(() => {
|
|
1925
2263
|
stack.clear();
|
|
@@ -1935,22 +2273,26 @@ const createPagedQueryStack = (options) => {
|
|
|
1935
2273
|
if (!res)
|
|
1936
2274
|
return null;
|
|
1937
2275
|
return responseNormalizer(res, all);
|
|
1938
|
-
},
|
|
2276
|
+
}, /* @ts-ignore */
|
|
2277
|
+
...(ngDevMode ? [{ debugName: "maxPagination" }] : /* istanbul ignore next */ []));
|
|
1939
2278
|
const minPagination = computed(() => {
|
|
1940
2279
|
const res = stack.firstQuery()?.response();
|
|
1941
2280
|
const all = stack.response();
|
|
1942
2281
|
if (!res)
|
|
1943
2282
|
return null;
|
|
1944
2283
|
return responseNormalizer(res, all);
|
|
1945
|
-
},
|
|
2284
|
+
}, /* @ts-ignore */
|
|
2285
|
+
...(ngDevMode ? [{ debugName: "minPagination" }] : /* istanbul ignore next */ []));
|
|
1946
2286
|
const isFirstPageLoaded = computed(() => {
|
|
1947
2287
|
const min = minPagination();
|
|
1948
2288
|
return min ? loadedMinPage() === min.currentPage : false;
|
|
1949
|
-
},
|
|
2289
|
+
}, /* @ts-ignore */
|
|
2290
|
+
...(ngDevMode ? [{ debugName: "isFirstPageLoaded" }] : /* istanbul ignore next */ []));
|
|
1950
2291
|
const isLastPageLoaded = computed(() => {
|
|
1951
2292
|
const max = maxPagination();
|
|
1952
2293
|
return max ? loadedMaxPage() === max.totalPages : false;
|
|
1953
|
-
},
|
|
2294
|
+
}, /* @ts-ignore */
|
|
2295
|
+
...(ngDevMode ? [{ debugName: "isLastPageLoaded" }] : /* istanbul ignore next */ []));
|
|
1954
2296
|
const fetchPreviousPage = () => {
|
|
1955
2297
|
const page = loadedMinPage() - 1;
|
|
1956
2298
|
const allResponses = stack.response();
|
|
@@ -2019,25 +2361,29 @@ const createPagedQueryStack = (options) => {
|
|
|
2019
2361
|
const page = resetOptions?.initialPage ?? initialPage();
|
|
2020
2362
|
initialPage.set(page);
|
|
2021
2363
|
pageDirection.set('next');
|
|
2022
|
-
|
|
2364
|
+
resetTrigger.update((v) => v + 1);
|
|
2023
2365
|
};
|
|
2024
2366
|
const canFetchPreviousPage = computed(() => {
|
|
2025
2367
|
const currentMinPagination = minPagination();
|
|
2026
2368
|
if (!currentMinPagination)
|
|
2027
2369
|
return false;
|
|
2028
2370
|
return loadedMinPage() > 1;
|
|
2029
|
-
},
|
|
2371
|
+
}, /* @ts-ignore */
|
|
2372
|
+
...(ngDevMode ? [{ debugName: "canFetchPreviousPage" }] : /* istanbul ignore next */ []));
|
|
2030
2373
|
const canFetchNextPage = computed(() => {
|
|
2031
2374
|
const currentMaxPagination = maxPagination();
|
|
2032
2375
|
return currentMaxPagination ? loadedMaxPage() < currentMaxPagination.totalPages : false;
|
|
2033
|
-
},
|
|
2376
|
+
}, /* @ts-ignore */
|
|
2377
|
+
...(ngDevMode ? [{ debugName: "canFetchNextPage" }] : /* istanbul ignore next */ []));
|
|
2034
2378
|
const items = computed(() => {
|
|
2035
2379
|
const res = stack.response();
|
|
2036
2380
|
return res.map((r) => responseNormalizer(r, res).items).flat();
|
|
2037
|
-
},
|
|
2381
|
+
}, /* @ts-ignore */
|
|
2382
|
+
...(ngDevMode ? [{ debugName: "items" }] : /* istanbul ignore next */ []));
|
|
2038
2383
|
const isFirstLoad = computed(() => {
|
|
2039
2384
|
return stack.queries().length === 1 && !!stack.lastQuery()?.loading();
|
|
2040
|
-
},
|
|
2385
|
+
}, /* @ts-ignore */
|
|
2386
|
+
...(ngDevMode ? [{ debugName: "isFirstLoad" }] : /* istanbul ignore next */ []));
|
|
2041
2387
|
const execute = (options) => {
|
|
2042
2388
|
const whereFn = options?.where;
|
|
2043
2389
|
if (whereFn) {
|
|
@@ -2096,6 +2442,16 @@ const createPagedQueryStack = (options) => {
|
|
|
2096
2442
|
reset,
|
|
2097
2443
|
execute,
|
|
2098
2444
|
};
|
|
2445
|
+
if (isQueryDevtoolsEnabled()) {
|
|
2446
|
+
const unregister = registerQueryDevtoolsEntry({
|
|
2447
|
+
kind: 'paged-query-stack',
|
|
2448
|
+
handle: pagedQuery,
|
|
2449
|
+
meta: {
|
|
2450
|
+
featureTypes: features?.map((f) => f.type),
|
|
2451
|
+
},
|
|
2452
|
+
});
|
|
2453
|
+
inject(DestroyRef).onDestroy(unregister);
|
|
2454
|
+
}
|
|
2099
2455
|
return pagedQuery;
|
|
2100
2456
|
};
|
|
2101
2457
|
|
|
@@ -2210,7 +2566,8 @@ function effectComputed(computation, injector) {
|
|
|
2210
2566
|
// Ignore errors in the initial computation
|
|
2211
2567
|
// Angular might throw an error if required inputs are read but not available yet
|
|
2212
2568
|
}
|
|
2213
|
-
const lastResult = signal(initialData,
|
|
2569
|
+
const lastResult = signal(initialData, /* @ts-ignore */
|
|
2570
|
+
...(ngDevMode ? [{ debugName: "lastResult" }] : /* istanbul ignore next */ []));
|
|
2214
2571
|
effect(() => {
|
|
2215
2572
|
const data = runInInjectionContext(injector, () => computation());
|
|
2216
2573
|
untracked(() => {
|
|
@@ -2261,7 +2618,8 @@ function queryStateSignal(source, options) {
|
|
|
2261
2618
|
})), {
|
|
2262
2619
|
initialValue: null,
|
|
2263
2620
|
});
|
|
2264
|
-
const rwSignal = linkedSignal(roSignal,
|
|
2621
|
+
const rwSignal = linkedSignal(roSignal, /* @ts-ignore */
|
|
2622
|
+
...(ngDevMode ? [{ debugName: "rwSignal" }] : /* istanbul ignore next */ []));
|
|
2265
2623
|
return rwSignal;
|
|
2266
2624
|
}
|
|
2267
2625
|
function queryStateResponseSignal(source, options) {
|
|
@@ -2269,7 +2627,8 @@ function queryStateResponseSignal(source, options) {
|
|
|
2269
2627
|
const dataSignal = computed(() => {
|
|
2270
2628
|
const state = s();
|
|
2271
2629
|
return isQueryStateSuccess(state) ? state.response : null;
|
|
2272
|
-
},
|
|
2630
|
+
}, /* @ts-ignore */
|
|
2631
|
+
...(ngDevMode ? [{ debugName: "dataSignal" }] : /* istanbul ignore next */ []));
|
|
2273
2632
|
dataSignal['reset'] = () => {
|
|
2274
2633
|
s.set(null);
|
|
2275
2634
|
};
|
|
@@ -2352,7 +2711,8 @@ class V2QueryCreator {
|
|
|
2352
2711
|
};
|
|
2353
2712
|
this.createSignal = (initialValue, config) => {
|
|
2354
2713
|
assertInInjectionContext(this.createSignal);
|
|
2355
|
-
const _signal = signal(initialValue ?? null,
|
|
2714
|
+
const _signal = signal(initialValue ?? null, /* @ts-ignore */
|
|
2715
|
+
...(ngDevMode ? [{ debugName: "_signal" }] : /* istanbul ignore next */ []));
|
|
2356
2716
|
addQueryContainerHandling(toObservable(_signal), () => _signal(), config);
|
|
2357
2717
|
return _signal;
|
|
2358
2718
|
};
|
|
@@ -2622,16 +2982,25 @@ const createSecureExecuteFactory = (options) => {
|
|
|
2622
2982
|
const circularChecker = circularQueryDependencyChecker();
|
|
2623
2983
|
let authQuerySubscription = Subscription.EMPTY;
|
|
2624
2984
|
let tokenRefreshSubscription = Subscription.EMPTY;
|
|
2985
|
+
let tokenWaitSubscription = Subscription.EMPTY;
|
|
2625
2986
|
const reset = () => {
|
|
2626
2987
|
authQuerySubscription.unsubscribe();
|
|
2627
2988
|
authQuerySubscription = Subscription.EMPTY;
|
|
2628
2989
|
tokenRefreshSubscription.unsubscribe();
|
|
2629
2990
|
tokenRefreshSubscription = Subscription.EMPTY;
|
|
2991
|
+
tokenWaitSubscription.unsubscribe();
|
|
2992
|
+
tokenWaitSubscription = Subscription.EMPTY;
|
|
2630
2993
|
resetExecuteState({
|
|
2631
2994
|
executeState,
|
|
2632
2995
|
executeOptions: { deps: options.deps, state: options.state },
|
|
2633
2996
|
});
|
|
2634
2997
|
};
|
|
2998
|
+
// A logout tears down the secure entries in the repository, but the query object holding one keeps
|
|
2999
|
+
// its own `response`/`error` signals — so without this a component still mounted after logout goes
|
|
3000
|
+
// on rendering the previous user's data until something calls `reset()` by hand.
|
|
3001
|
+
options.deps.client.repository.events$
|
|
3002
|
+
.pipe(filter((event) => event.type === 'unbind-all-secure'), takeUntilDestroyed(options.deps.destroyRef))
|
|
3003
|
+
.subscribe(() => reset());
|
|
2635
3004
|
const error = (query) => {
|
|
2636
3005
|
const state = options.state;
|
|
2637
3006
|
state.error.set(query.error());
|
|
@@ -2667,11 +3036,27 @@ const createSecureExecuteFactory = (options) => {
|
|
|
2667
3036
|
},
|
|
2668
3037
|
}, tokens, headers, executeState);
|
|
2669
3038
|
};
|
|
3039
|
+
// The auth query completing and the access token being populated happen on two
|
|
3040
|
+
// different reactive timelines: the token is set by a separate token-extraction
|
|
3041
|
+
// effect (see `setupBearerQueryRegistry`), and for cross-client / secure auth
|
|
3042
|
+
// queries the completion is delivered through several nested `toObservable` hops.
|
|
3043
|
+
// Calling `authAndExec` purely because the auth query is "done" therefore races
|
|
3044
|
+
// the token and can run while it is still null. Gate on the token signal instead:
|
|
3045
|
+
// execute immediately if it is already available, otherwise wait for it.
|
|
3046
|
+
const authAndExecWhenTokenReady = (executeArgs) => {
|
|
3047
|
+
if (options.authProvider.accessToken()) {
|
|
3048
|
+
authAndExec(executeArgs);
|
|
3049
|
+
return;
|
|
3050
|
+
}
|
|
3051
|
+
tokenWaitSubscription.unsubscribe();
|
|
3052
|
+
tokenWaitSubscription = toObservable(options.authProvider.accessToken, {
|
|
3053
|
+
injector: options.deps.injector,
|
|
3054
|
+
})
|
|
3055
|
+
.pipe(filter(Boolean), take(1), tap(() => authAndExec(executeArgs)))
|
|
3056
|
+
.subscribe();
|
|
3057
|
+
};
|
|
2670
3058
|
const exec = (executeArgs) => {
|
|
2671
3059
|
circularChecker.check();
|
|
2672
|
-
// Make sure all effects are flushed before reading the args.
|
|
2673
|
-
// A withArgs feature effect might still be pending otherwise resulting in the wrong args being read.
|
|
2674
|
-
options.deps.effectScheduler.flush();
|
|
2675
3060
|
const execArgsWithDefaults = {
|
|
2676
3061
|
args: executeArgs?.args ?? options.state.args(),
|
|
2677
3062
|
options: executeArgs?.options,
|
|
@@ -2680,6 +3065,8 @@ const createSecureExecuteFactory = (options) => {
|
|
|
2680
3065
|
authQuerySubscription = Subscription.EMPTY;
|
|
2681
3066
|
tokenRefreshSubscription.unsubscribe();
|
|
2682
3067
|
tokenRefreshSubscription = Subscription.EMPTY;
|
|
3068
|
+
tokenWaitSubscription.unsubscribe();
|
|
3069
|
+
tokenWaitSubscription = Subscription.EMPTY;
|
|
2683
3070
|
tokenRefreshSubscription = options.authProvider.afterTokenRefresh$
|
|
2684
3071
|
.pipe(filter(() => {
|
|
2685
3072
|
const currentError = options.state.error();
|
|
@@ -2707,14 +3094,14 @@ const createSecureExecuteFactory = (options) => {
|
|
|
2707
3094
|
error(query);
|
|
2708
3095
|
}
|
|
2709
3096
|
else if (query.response()) {
|
|
2710
|
-
|
|
3097
|
+
authAndExecWhenTokenReady(execArgsWithDefaults);
|
|
2711
3098
|
}
|
|
2712
3099
|
}));
|
|
2713
3100
|
}))
|
|
2714
3101
|
.subscribe();
|
|
2715
3102
|
}
|
|
2716
3103
|
else if (authQuery.response()) {
|
|
2717
|
-
|
|
3104
|
+
authAndExecWhenTokenReady(execArgsWithDefaults);
|
|
2718
3105
|
}
|
|
2719
3106
|
else if (authQuery.error()) {
|
|
2720
3107
|
error(authQuery);
|
|
@@ -2931,10 +3318,18 @@ class LegacyQuery {
|
|
|
2931
3318
|
get _isInMockMode() {
|
|
2932
3319
|
return false;
|
|
2933
3320
|
}
|
|
2934
|
-
constructor(newQuery, _arguments, entity
|
|
3321
|
+
constructor(newQuery, _arguments, entity,
|
|
3322
|
+
/**
|
|
3323
|
+
* Wraps an inert query — see `createInertQuery`. Skips everything that needs a live injector,
|
|
3324
|
+
* so the wrapper is safe to build during teardown.
|
|
3325
|
+
*
|
|
3326
|
+
* @internal
|
|
3327
|
+
*/
|
|
3328
|
+
_isInert = false) {
|
|
2935
3329
|
this.newQuery = newQuery;
|
|
2936
3330
|
this._arguments = _arguments;
|
|
2937
3331
|
this.entity = entity;
|
|
3332
|
+
this._isInert = _isInert;
|
|
2938
3333
|
/**
|
|
2939
3334
|
* @internal
|
|
2940
3335
|
*/
|
|
@@ -2944,22 +3339,11 @@ class LegacyQuery {
|
|
|
2944
3339
|
*/
|
|
2945
3340
|
this._currentPollConfig = null;
|
|
2946
3341
|
/**
|
|
3342
|
+
* `null` for an inert query — there is no live injector to own an effect.
|
|
3343
|
+
*
|
|
2947
3344
|
* @internal
|
|
2948
3345
|
*/
|
|
2949
|
-
this.storeSyncEffect =
|
|
2950
|
-
const res = this.newQuery.response();
|
|
2951
|
-
untracked(() => {
|
|
2952
|
-
if (this.entity?.set) {
|
|
2953
|
-
const id = this.entity.id({ args: this._arguments, response: res });
|
|
2954
|
-
this.entity.set({
|
|
2955
|
-
args: this._arguments,
|
|
2956
|
-
response: res,
|
|
2957
|
-
id,
|
|
2958
|
-
store: this.entity.store,
|
|
2959
|
-
});
|
|
2960
|
-
}
|
|
2961
|
-
});
|
|
2962
|
-
}, ...(ngDevMode ? [{ debugName: "storeSyncEffect" }] : []));
|
|
3346
|
+
this.storeSyncEffect = null;
|
|
2963
3347
|
/**
|
|
2964
3348
|
* @internal
|
|
2965
3349
|
*/
|
|
@@ -2972,18 +3356,57 @@ class LegacyQuery {
|
|
|
2972
3356
|
* @internal
|
|
2973
3357
|
*/
|
|
2974
3358
|
this._isPollingPaused = false;
|
|
3359
|
+
this._isDestroyed = false;
|
|
3360
|
+
if (this._isInert) {
|
|
3361
|
+
this.state$ = of(transformExecStateToQueryState(null));
|
|
3362
|
+
this._isDestroyed = true;
|
|
3363
|
+
return;
|
|
3364
|
+
}
|
|
2975
3365
|
this.state$ = toObservable(this.newQuery.executionState, { injector: this.newQuery.subtle.injector }).pipe(map((execState) => transformExecStateToQueryState(execState)), switchMap((s) => this._transformState(s)), shareReplay({ bufferSize: 1, refCount: true }));
|
|
2976
|
-
this.
|
|
2977
|
-
|
|
2978
|
-
|
|
3366
|
+
this.storeSyncEffect = effect(() => {
|
|
3367
|
+
const res = this.newQuery.response();
|
|
3368
|
+
untracked(() => {
|
|
3369
|
+
if (this.entity?.set) {
|
|
3370
|
+
const id = this.entity.id({ args: this._arguments, response: res });
|
|
3371
|
+
this.entity.set({
|
|
3372
|
+
args: this._arguments,
|
|
3373
|
+
response: res,
|
|
3374
|
+
id,
|
|
3375
|
+
store: this.entity.store,
|
|
3376
|
+
});
|
|
3377
|
+
}
|
|
3378
|
+
});
|
|
3379
|
+
}, /* @ts-ignore */
|
|
3380
|
+
...(ngDevMode ? [{ debugName: "storeSyncEffect" }] : /* istanbul ignore next */ []));
|
|
3381
|
+
// Tear down only what this wrapper owns. The underlying query is already being destroyed when this fires, so
|
|
3382
|
+
// destroying it again would call `R3Injector.destroy()` on an injector that is mid-teardown, which throws
|
|
3383
|
+
// NG0205 ("Injector has already been destroyed") from inside the view's cleanup phase.
|
|
3384
|
+
this.newQuery.subtle.destroyRef.onDestroy(() => this._teardown());
|
|
3385
|
+
}
|
|
3386
|
+
_teardown() {
|
|
3387
|
+
if (this._isDestroyed) {
|
|
3388
|
+
return;
|
|
3389
|
+
}
|
|
3390
|
+
this._isDestroyed = true;
|
|
2979
3391
|
this._pollingSubscription?.unsubscribe();
|
|
2980
3392
|
this._pollingSubscription = null;
|
|
2981
3393
|
this._dependentsChanged$.complete();
|
|
3394
|
+
this.storeSyncEffect?.destroy();
|
|
3395
|
+
}
|
|
3396
|
+
destroy() {
|
|
3397
|
+
if (this._isDestroyed) {
|
|
3398
|
+
return;
|
|
3399
|
+
}
|
|
3400
|
+
this._teardown();
|
|
3401
|
+
// Re-enters `_teardown` through the query's `destroyRef`, which the `_isDestroyed` guard absorbs.
|
|
2982
3402
|
this.newQuery.subtle.destroy();
|
|
2983
|
-
this.storeSyncEffect.destroy();
|
|
2984
3403
|
}
|
|
2985
3404
|
execute(options = {}) {
|
|
2986
|
-
untracked(() =>
|
|
3405
|
+
untracked(() =>
|
|
3406
|
+
// v2 had a single `skipCache` for every method, so this cannot tell whether the underlying request is
|
|
3407
|
+
// cacheable. The creator is built with `silenceUncacheableAllowCacheError`, which makes `allowCache` inert
|
|
3408
|
+
// on a mutation instead of throwing ET301.
|
|
3409
|
+
this.newQuery.execute({ args: this._arguments, options: { allowCache: options.skipCache !== true } }));
|
|
2987
3410
|
return this;
|
|
2988
3411
|
}
|
|
2989
3412
|
abort() {
|
|
@@ -2994,7 +3417,8 @@ class LegacyQuery {
|
|
|
2994
3417
|
return this;
|
|
2995
3418
|
}
|
|
2996
3419
|
poll(config) {
|
|
2997
|
-
|
|
3420
|
+
// An inert or already destroyed query has nothing to poll, and the interval would outlive it.
|
|
3421
|
+
if (this._pollingSubscription || this._isDestroyed) {
|
|
2998
3422
|
return this;
|
|
2999
3423
|
}
|
|
3000
3424
|
this._currentPollConfig = config;
|
|
@@ -3079,11 +3503,89 @@ class LegacyQuery {
|
|
|
3079
3503
|
}
|
|
3080
3504
|
}
|
|
3081
3505
|
|
|
3506
|
+
/**
|
|
3507
|
+
* A query that never executes and never leaves its initial state.
|
|
3508
|
+
*
|
|
3509
|
+
* `LegacyQueryCreator.prepare()` hands one back when the injector it was given is already destroyed
|
|
3510
|
+
* — the shape v2 call sites still fire into during teardown (a debounced search resolving after the
|
|
3511
|
+
* user navigated away, an RxJS callback on a stream that has not been unsubscribed yet). v2 had no
|
|
3512
|
+
* injector at all there, so the same code used to be harmless; building a real query would now
|
|
3513
|
+
* throw NG0205 from inside the view's cleanup phase, with no hint about which query caused it.
|
|
3514
|
+
*
|
|
3515
|
+
* Everything reads as "prepared but never executed": `execute()` and friends are no-ops, signals
|
|
3516
|
+
* stay `null`, observables complete immediately. Nothing here touches Angular's DI, which is the
|
|
3517
|
+
* point — there is no live injector left to bind to.
|
|
3518
|
+
*/
|
|
3519
|
+
const createInertQuery = () => {
|
|
3520
|
+
const inertSignal = (value) => Object.assign(signal(value), { asObservable: () => of(value) });
|
|
3521
|
+
const noop = () => {
|
|
3522
|
+
/* the query is inert — there is nothing to execute, reset or destroy */
|
|
3523
|
+
};
|
|
3524
|
+
const base = {
|
|
3525
|
+
args: inertSignal(null),
|
|
3526
|
+
response: inertSignal(null),
|
|
3527
|
+
latestHttpEvent: inertSignal(null),
|
|
3528
|
+
loading: inertSignal(null),
|
|
3529
|
+
error: inertSignal(null),
|
|
3530
|
+
lastTimeExecutedAt: inertSignal(null),
|
|
3531
|
+
triggeredBy: inertSignal(null),
|
|
3532
|
+
id: inertSignal(null),
|
|
3533
|
+
executionState: inertSignal(null),
|
|
3534
|
+
};
|
|
3535
|
+
const createSnapshot = () => ({ ...base, isAlive: inertSignal(false) });
|
|
3536
|
+
// A DestroyRef that is already destroyed: callbacks registered on it are dropped rather than
|
|
3537
|
+
// stored, so nothing keeps this object (or its registrant) alive.
|
|
3538
|
+
const destroyRef = {
|
|
3539
|
+
destroyed: true,
|
|
3540
|
+
onDestroy: () => noop,
|
|
3541
|
+
};
|
|
3542
|
+
return {
|
|
3543
|
+
...base,
|
|
3544
|
+
execute: noop,
|
|
3545
|
+
createSnapshot,
|
|
3546
|
+
reset: noop,
|
|
3547
|
+
asReadonly: () => ({ ...base, createSnapshot }),
|
|
3548
|
+
subtle: {
|
|
3549
|
+
destroy: noop,
|
|
3550
|
+
setResponse: noop,
|
|
3551
|
+
setLoading: noop,
|
|
3552
|
+
setError: noop,
|
|
3553
|
+
request: inertSignal(null),
|
|
3554
|
+
destroyRef,
|
|
3555
|
+
injector: Injector.NULL,
|
|
3556
|
+
},
|
|
3557
|
+
};
|
|
3558
|
+
};
|
|
3559
|
+
|
|
3560
|
+
/**
|
|
3561
|
+
* Whether an injector can still be used. A destroyed `R3Injector` throws on `get()` rather than
|
|
3562
|
+
* answering, so the throw itself is the answer; a node injector hands back a `DestroyRef` that
|
|
3563
|
+
* reports its own state.
|
|
3564
|
+
*/
|
|
3565
|
+
const isInjectorUsable = (injector) => {
|
|
3566
|
+
try {
|
|
3567
|
+
return injector.get(DestroyRef, null, { optional: true })?.destroyed !== true;
|
|
3568
|
+
}
|
|
3569
|
+
catch {
|
|
3570
|
+
return false;
|
|
3571
|
+
}
|
|
3572
|
+
};
|
|
3082
3573
|
class LegacyQueryCreator {
|
|
3083
3574
|
constructor(options) {
|
|
3084
3575
|
this.options = options;
|
|
3085
3576
|
this.prepare = (args) => {
|
|
3086
|
-
|
|
3577
|
+
// v2's `prepare()` needed no injection context, so plenty of call sites are plain callbacks.
|
|
3578
|
+
// Angular's raw NG0203 names neither the query nor the call site, which makes those a bisection
|
|
3579
|
+
// exercise — replace it with an error that does.
|
|
3580
|
+
const injector = args?.injector ??
|
|
3581
|
+
(() => {
|
|
3582
|
+
try {
|
|
3583
|
+
return inject(Injector);
|
|
3584
|
+
}
|
|
3585
|
+
catch {
|
|
3586
|
+
throw legacyPrepareWithoutInjectionContext(this.options.name);
|
|
3587
|
+
}
|
|
3588
|
+
})();
|
|
3087
3589
|
let headers = new HttpHeaders();
|
|
3088
3590
|
if (args?.headers) {
|
|
3089
3591
|
Object.entries(args.headers).forEach(([key, value]) => {
|
|
@@ -3098,12 +3600,25 @@ class LegacyQueryCreator {
|
|
|
3098
3600
|
...(args?.queryParams ? { queryParams: args.queryParams } : {}),
|
|
3099
3601
|
headers,
|
|
3100
3602
|
};
|
|
3603
|
+
// A captured component injector routinely outlives its component here: a debounced search
|
|
3604
|
+
// resolving after navigation, an RxJS callback on a stream torn down a tick later. That is not
|
|
3605
|
+
// a programming error the way it would be in new code — v2 had no injector to destroy — so it
|
|
3606
|
+
// yields a query that does nothing instead of an NG0205 raised from the view's cleanup phase.
|
|
3607
|
+
if (!isInjectorUsable(injector)) {
|
|
3608
|
+
if (isDevMode()) {
|
|
3609
|
+
console.warn(`${this.options.name ? `"${this.options.name}"` : 'A legacy query'}.prepare() was called with a destroyed injector, so an inert query was returned. ` +
|
|
3610
|
+
`This usually means a callback outlived the component whose injector it captured — guard the call site with DestroyRef.onDestroy, or pass an injector that outlives it.`);
|
|
3611
|
+
}
|
|
3612
|
+
return new LegacyQuery(createInertQuery(), queryArgs, this.options.entity, true);
|
|
3613
|
+
}
|
|
3101
3614
|
return runInInjectionContext(injector, () => {
|
|
3102
3615
|
return untracked(() => {
|
|
3103
3616
|
const newQuery = this.options.creator({
|
|
3104
3617
|
onlyManualExecution: true,
|
|
3105
3618
|
injector,
|
|
3106
3619
|
silenceMissingWithArgsFeatureError: true,
|
|
3620
|
+
// `LegacyQuery.execute()` forwards v2's `skipCache` as `allowCache` for every method, cacheable or not.
|
|
3621
|
+
silenceUncacheableAllowCacheError: true,
|
|
3107
3622
|
});
|
|
3108
3623
|
const legacyQuery = new LegacyQuery(newQuery, queryArgs, this.options.entity);
|
|
3109
3624
|
if (args?.config?.destroyOnResponse) {
|
|
@@ -3112,7 +3627,8 @@ class LegacyQueryCreator {
|
|
|
3112
3627
|
legacyQuery.destroy();
|
|
3113
3628
|
destroyEffect.destroy();
|
|
3114
3629
|
}
|
|
3115
|
-
},
|
|
3630
|
+
}, /* @ts-ignore */
|
|
3631
|
+
...(ngDevMode ? [{ debugName: "destroyEffect" }] : /* istanbul ignore next */ []));
|
|
3116
3632
|
}
|
|
3117
3633
|
return legacyQuery;
|
|
3118
3634
|
});
|
|
@@ -3124,7 +3640,8 @@ class LegacyQueryCreator {
|
|
|
3124
3640
|
return subject;
|
|
3125
3641
|
};
|
|
3126
3642
|
this.createSignal = (initialValue, config) => {
|
|
3127
|
-
const _signal = signal(initialValue ?? null,
|
|
3643
|
+
const _signal = signal(initialValue ?? null, /* @ts-ignore */
|
|
3644
|
+
...(ngDevMode ? [{ debugName: "_signal" }] : /* istanbul ignore next */ []));
|
|
3128
3645
|
addQueryContainerHandling(toObservable(_signal), () => _signal(), config);
|
|
3129
3646
|
return _signal;
|
|
3130
3647
|
};
|
|
@@ -3263,15 +3780,20 @@ const isQueryCollection = (query) => {
|
|
|
3263
3780
|
const createQueryCollection = (
|
|
3264
3781
|
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
3265
3782
|
queryMap) => new BehaviorSubject(null);
|
|
3266
|
-
const createQueryCollectionSubject = (
|
|
3783
|
+
const createQueryCollectionSubject = (
|
|
3784
|
+
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
3785
|
+
queryMap, config) => {
|
|
3267
3786
|
assertInInjectionContext(createQueryCollectionSubject);
|
|
3268
3787
|
const subject = new BehaviorSubject(null);
|
|
3269
3788
|
addQueryContainerHandling(subject.pipe(map((s) => s?.query ?? null)), () => subject.getValue()?.query ?? null, config);
|
|
3270
3789
|
return subject;
|
|
3271
3790
|
};
|
|
3272
|
-
const createQueryCollectionSignal = (
|
|
3791
|
+
const createQueryCollectionSignal = (
|
|
3792
|
+
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
3793
|
+
queryMap, config) => {
|
|
3273
3794
|
assertInInjectionContext(createQueryCollectionSignal);
|
|
3274
|
-
const _signal = signal(null,
|
|
3795
|
+
const _signal = signal(null, /* @ts-ignore */
|
|
3796
|
+
...(ngDevMode ? [{ debugName: "_signal" }] : /* istanbul ignore next */ []));
|
|
3275
3797
|
addQueryContainerHandling(toObservable(_signal).pipe(map((s) => s?.query ?? null)), () => _signal()?.query ?? null, config);
|
|
3276
3798
|
return _signal;
|
|
3277
3799
|
};
|
|
@@ -4005,7 +4527,8 @@ class V2BearerAuthProvider {
|
|
|
4005
4527
|
}
|
|
4006
4528
|
|
|
4007
4529
|
const [provideInfinityQueryResponseDelay, injectInfinityQueryResponseDelay] = createProvider(() => {
|
|
4008
|
-
const enabled = signal(false,
|
|
4530
|
+
const enabled = signal(false, /* @ts-ignore */
|
|
4531
|
+
...(ngDevMode ? [{ debugName: "enabled" }] : /* istanbul ignore next */ []));
|
|
4009
4532
|
const enabled$ = toObservable(enabled);
|
|
4010
4533
|
return {
|
|
4011
4534
|
enabled,
|
|
@@ -4214,7 +4737,7 @@ class InfinityQueryDirective {
|
|
|
4214
4737
|
get data$() {
|
|
4215
4738
|
return this._data$.asObservable();
|
|
4216
4739
|
}
|
|
4217
|
-
static ngTemplateContextGuard(
|
|
4740
|
+
static ngTemplateContextGuard(_dir, _ctx) {
|
|
4218
4741
|
return true;
|
|
4219
4742
|
}
|
|
4220
4743
|
constructor() {
|
|
@@ -4316,13 +4839,13 @@ class InfinityQueryDirective {
|
|
|
4316
4839
|
this._viewContext.currentQuery = null;
|
|
4317
4840
|
this._data$.next([]);
|
|
4318
4841
|
}
|
|
4319
|
-
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "
|
|
4320
|
-
static { this.ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "
|
|
4842
|
+
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.7", ngImport: i0, type: InfinityQueryDirective, deps: [], target: i0.ɵɵFactoryTarget.Directive }); }
|
|
4843
|
+
static { this.ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "22.0.7", type: InfinityQueryDirective, isStandalone: true, selector: "[etInfinityQuery]", inputs: { infinityQuery: ["etInfinityQuery", "infinityQuery"] }, providers: [
|
|
4321
4844
|
{ provide: INFINITY_QUERY_TOKEN, useExisting: InfinityQueryDirective },
|
|
4322
4845
|
provideInfinityQueryResponseDelay(),
|
|
4323
4846
|
], exportAs: ["etInfinityQuery"], ngImport: i0 }); }
|
|
4324
4847
|
}
|
|
4325
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "
|
|
4848
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.7", ngImport: i0, type: InfinityQueryDirective, decorators: [{
|
|
4326
4849
|
type: Directive,
|
|
4327
4850
|
args: [{
|
|
4328
4851
|
selector: '[etInfinityQuery]',
|
|
@@ -4344,10 +4867,13 @@ class InfinityQueryTriggerDirective {
|
|
|
4344
4867
|
this._destroy = new Subject();
|
|
4345
4868
|
this._observer = null;
|
|
4346
4869
|
this.click$ = fromEvent(this._elementRef.nativeElement, 'click');
|
|
4347
|
-
this.scrollContainerSelector = input(null,
|
|
4870
|
+
this.scrollContainerSelector = input(null, /* @ts-ignore */
|
|
4871
|
+
...(ngDevMode ? [{ debugName: "scrollContainerSelector" }] : /* istanbul ignore next */ []));
|
|
4348
4872
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
4349
|
-
this.infinityQueryOverride = input(null,
|
|
4350
|
-
|
|
4873
|
+
this.infinityQueryOverride = input(null, /* @ts-ignore */
|
|
4874
|
+
...(ngDevMode ? [{ debugName: "infinityQueryOverride" }] : /* istanbul ignore next */ []));
|
|
4875
|
+
this.infinityQuery = computed(() => this.infinityQueryOverride() ?? this._infinityQuery, /* @ts-ignore */
|
|
4876
|
+
...(ngDevMode ? [{ debugName: "infinityQuery" }] : /* istanbul ignore next */ []));
|
|
4351
4877
|
}
|
|
4352
4878
|
ngOnInit() {
|
|
4353
4879
|
const isInteractive = this._elementRef.nativeElement.tagName === 'BUTTON';
|
|
@@ -4379,10 +4905,10 @@ class InfinityQueryTriggerDirective {
|
|
|
4379
4905
|
});
|
|
4380
4906
|
this._observer.observe(this._elementRef.nativeElement);
|
|
4381
4907
|
}
|
|
4382
|
-
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "
|
|
4383
|
-
static { this.ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "17.1.0", version: "
|
|
4908
|
+
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.7", ngImport: i0, type: InfinityQueryTriggerDirective, deps: [], target: i0.ɵɵFactoryTarget.Directive }); }
|
|
4909
|
+
static { this.ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "17.1.0", version: "22.0.7", type: InfinityQueryTriggerDirective, isStandalone: true, selector: "[etInfinityQueryTrigger], et-infinity-query-trigger", inputs: { scrollContainerSelector: { classPropertyName: "scrollContainerSelector", publicName: "scrollContainerSelector", isSignal: true, isRequired: false, transformFunction: null }, infinityQueryOverride: { classPropertyName: "infinityQueryOverride", publicName: "infinityQueryOverride", isSignal: true, isRequired: false, transformFunction: null } }, ngImport: i0 }); }
|
|
4384
4910
|
}
|
|
4385
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "
|
|
4911
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.7", ngImport: i0, type: InfinityQueryTriggerDirective, decorators: [{
|
|
4386
4912
|
type: Directive,
|
|
4387
4913
|
args: [{
|
|
4388
4914
|
selector: '[etInfinityQueryTrigger], et-infinity-query-trigger',
|
|
@@ -4390,7 +4916,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.1", ngImpor
|
|
|
4390
4916
|
}], propDecorators: { scrollContainerSelector: [{ type: i0.Input, args: [{ isSignal: true, alias: "scrollContainerSelector", required: false }] }], infinityQueryOverride: [{ type: i0.Input, args: [{ isSignal: true, alias: "infinityQueryOverride", required: false }] }] } });
|
|
4391
4917
|
|
|
4392
4918
|
class QueryDirective {
|
|
4393
|
-
static ngTemplateContextGuard(
|
|
4919
|
+
static ngTemplateContextGuard(_dir, _ctx) {
|
|
4394
4920
|
return true;
|
|
4395
4921
|
}
|
|
4396
4922
|
constructor() {
|
|
@@ -4406,8 +4932,8 @@ class QueryDirective {
|
|
|
4406
4932
|
scope: null,
|
|
4407
4933
|
query: null,
|
|
4408
4934
|
};
|
|
4409
|
-
this.query = input.required({ ...(ngDevMode ? { debugName: "query" } : {}), alias: 'etQuery' });
|
|
4410
|
-
this.cache = input(false, { ...(ngDevMode ? { debugName: "cache" } : {}), alias: 'etQueryCache' });
|
|
4935
|
+
this.query = input.required({ ...(ngDevMode ? { debugName: "query" } : /* istanbul ignore next */ {}), alias: 'etQuery' });
|
|
4936
|
+
this.cache = input(false, { ...(ngDevMode ? { debugName: "cache" } : /* istanbul ignore next */ {}), alias: 'etQueryCache' });
|
|
4411
4937
|
this.queryState = queryStateSignal(this.query);
|
|
4412
4938
|
inject(ViewContainerRef).createEmbeddedView(inject(TemplateRef), this._viewContext);
|
|
4413
4939
|
effect(() => {
|
|
@@ -4468,10 +4994,10 @@ class QueryDirective {
|
|
|
4468
4994
|
});
|
|
4469
4995
|
});
|
|
4470
4996
|
}
|
|
4471
|
-
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "
|
|
4472
|
-
static { this.ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "17.1.0", version: "
|
|
4997
|
+
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.7", ngImport: i0, type: QueryDirective, deps: [], target: i0.ɵɵFactoryTarget.Directive }); }
|
|
4998
|
+
static { this.ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "17.1.0", version: "22.0.7", type: QueryDirective, isStandalone: true, selector: "[etQuery]", inputs: { query: { classPropertyName: "query", publicName: "etQuery", isSignal: true, isRequired: true, transformFunction: null }, cache: { classPropertyName: "cache", publicName: "etQueryCache", isSignal: true, isRequired: false, transformFunction: null } }, ngImport: i0 }); }
|
|
4473
4999
|
}
|
|
4474
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "
|
|
5000
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.7", ngImport: i0, type: QueryDirective, decorators: [{
|
|
4475
5001
|
type: Directive,
|
|
4476
5002
|
args: [{
|
|
4477
5003
|
selector: '[etQuery]',
|
|
@@ -4492,10 +5018,10 @@ class QueryShortNamePipe {
|
|
|
4492
5018
|
}
|
|
4493
5019
|
return route;
|
|
4494
5020
|
}
|
|
4495
|
-
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "
|
|
4496
|
-
static { this.ɵpipe = i0.ɵɵngDeclarePipe({ minVersion: "14.0.0", version: "
|
|
5021
|
+
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.7", ngImport: i0, type: QueryShortNamePipe, deps: [], target: i0.ɵɵFactoryTarget.Pipe }); }
|
|
5022
|
+
static { this.ɵpipe = i0.ɵɵngDeclarePipe({ minVersion: "14.0.0", version: "22.0.7", ngImport: i0, type: QueryShortNamePipe, isStandalone: true, name: "queryShortName" }); }
|
|
4497
5023
|
}
|
|
4498
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "
|
|
5024
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.7", ngImport: i0, type: QueryShortNamePipe, decorators: [{
|
|
4499
5025
|
type: Pipe,
|
|
4500
5026
|
args: [{ name: 'queryShortName' }]
|
|
4501
5027
|
}] });
|
|
@@ -4517,34 +5043,45 @@ class QueryDevtoolsComponent {
|
|
|
4517
5043
|
this._cdr = inject(ChangeDetectorRef);
|
|
4518
5044
|
this._destroy$ = createDestroy();
|
|
4519
5045
|
this.queryClientConfigs = inject(QUERY_CLIENT_DEVTOOLS_TOKEN);
|
|
4520
|
-
this.isOpen = signal(false,
|
|
4521
|
-
|
|
4522
|
-
this.
|
|
4523
|
-
|
|
4524
|
-
this.
|
|
4525
|
-
|
|
4526
|
-
this.
|
|
4527
|
-
|
|
4528
|
-
this.
|
|
4529
|
-
|
|
5046
|
+
this.isOpen = signal(false, /* @ts-ignore */
|
|
5047
|
+
...(ngDevMode ? [{ debugName: "isOpen" }] : /* istanbul ignore next */ []));
|
|
5048
|
+
this.isTranslucent = signal(false, /* @ts-ignore */
|
|
5049
|
+
...(ngDevMode ? [{ debugName: "isTranslucent" }] : /* istanbul ignore next */ []));
|
|
5050
|
+
this.snapLayout = signal('full', /* @ts-ignore */
|
|
5051
|
+
...(ngDevMode ? [{ debugName: "snapLayout" }] : /* istanbul ignore next */ []));
|
|
5052
|
+
this.queryListMode = signal('live', /* @ts-ignore */
|
|
5053
|
+
...(ngDevMode ? [{ debugName: "queryListMode" }] : /* istanbul ignore next */ []));
|
|
5054
|
+
this.viewMode = signal('query', /* @ts-ignore */
|
|
5055
|
+
...(ngDevMode ? [{ debugName: "viewMode" }] : /* istanbul ignore next */ []));
|
|
5056
|
+
this.showResponse = signal(false, /* @ts-ignore */
|
|
5057
|
+
...(ngDevMode ? [{ debugName: "showResponse" }] : /* istanbul ignore next */ []));
|
|
5058
|
+
this.showRawResponse = signal(false, /* @ts-ignore */
|
|
5059
|
+
...(ngDevMode ? [{ debugName: "showRawResponse" }] : /* istanbul ignore next */ []));
|
|
5060
|
+
this.showQueryConfig = signal(false, /* @ts-ignore */
|
|
5061
|
+
...(ngDevMode ? [{ debugName: "showQueryConfig" }] : /* istanbul ignore next */ []));
|
|
5062
|
+
this.showEntityStoreValue = signal(false, /* @ts-ignore */
|
|
5063
|
+
...(ngDevMode ? [{ debugName: "showEntityStoreValue" }] : /* istanbul ignore next */ []));
|
|
5064
|
+
this.showArguments = signal(false, /* @ts-ignore */
|
|
5065
|
+
...(ngDevMode ? [{ debugName: "showArguments" }] : /* istanbul ignore next */ []));
|
|
4530
5066
|
this.selectedClientIdCtrl = new FormControl(0);
|
|
4531
5067
|
this.selectedClientId = toSignal(this.selectedClientIdCtrl.valueChanges.pipe(startWith(this.selectedClientIdCtrl.value)));
|
|
4532
|
-
this.selectedQueryId = signal(null,
|
|
5068
|
+
this.selectedQueryId = signal(null, /* @ts-ignore */
|
|
5069
|
+
...(ngDevMode ? [{ debugName: "selectedQueryId" }] : /* istanbul ignore next */ []));
|
|
4533
5070
|
this.selectedClientConfig = computed(() => {
|
|
4534
|
-
|
|
4535
|
-
|
|
4536
|
-
|
|
4537
|
-
|
|
4538
|
-
|
|
4539
|
-
|
|
4540
|
-
|
|
4541
|
-
|
|
4542
|
-
|
|
4543
|
-
}, ...(ngDevMode ? [{ debugName: "selectedClientConfig" }] : []));
|
|
5071
|
+
const configs = this.queryClientConfigs;
|
|
5072
|
+
const index = configs.length === 1 ? 0 : (this.selectedClientId() ?? 0);
|
|
5073
|
+
const config = configs[index] ?? configs[0];
|
|
5074
|
+
if (!config) {
|
|
5075
|
+
throw new Error('QueryDevtools: no query client configs registered');
|
|
5076
|
+
}
|
|
5077
|
+
return config;
|
|
5078
|
+
}, /* @ts-ignore */
|
|
5079
|
+
...(ngDevMode ? [{ debugName: "selectedClientConfig" }] : /* istanbul ignore next */ []));
|
|
4544
5080
|
this.queryStore = computed(() => {
|
|
4545
5081
|
const config = this.selectedClientConfig();
|
|
4546
5082
|
return config.client._store;
|
|
4547
|
-
},
|
|
5083
|
+
}, /* @ts-ignore */
|
|
5084
|
+
...(ngDevMode ? [{ debugName: "queryStore" }] : /* istanbul ignore next */ []));
|
|
4548
5085
|
this.queryStore$ = toObservable(this.queryStore);
|
|
4549
5086
|
this._queries$ = new BehaviorSubject([]);
|
|
4550
5087
|
this.queries = toSignal(this._queries$);
|
|
@@ -4558,14 +5095,16 @@ class QueryDevtoolsComponent {
|
|
|
4558
5095
|
return this.queries()?.find((q) => q._id === id) ?? null;
|
|
4559
5096
|
}
|
|
4560
5097
|
return this.queryHistory$.getValue()?.find((q) => q._id === id) ?? null;
|
|
4561
|
-
},
|
|
5098
|
+
}, /* @ts-ignore */
|
|
5099
|
+
...(ngDevMode ? [{ debugName: "selectedQuery" }] : /* istanbul ignore next */ []));
|
|
4562
5100
|
this.selectedQuery$ = toObservable(this.selectedQuery);
|
|
4563
5101
|
this.selectedQueryEntityStore = computed(() => {
|
|
4564
5102
|
const query = this.selectedQuery();
|
|
4565
5103
|
if (!query)
|
|
4566
5104
|
return null;
|
|
4567
5105
|
return query.store;
|
|
4568
|
-
},
|
|
5106
|
+
}, /* @ts-ignore */
|
|
5107
|
+
...(ngDevMode ? [{ debugName: "selectedQueryEntityStore" }] : /* istanbul ignore next */ []));
|
|
4569
5108
|
this.selectedQueryEntityStore$ = toObservable(this.selectedQueryEntityStore);
|
|
4570
5109
|
this.selectedQueryEntityStoreValue$ = this.selectedQueryEntityStore$.pipe(switchMap((s) => s?._change$.pipe(startWith(''), map(() => {
|
|
4571
5110
|
if (!s._dictionary)
|
|
@@ -4603,7 +5142,8 @@ class QueryDevtoolsComponent {
|
|
|
4603
5142
|
return {
|
|
4604
5143
|
...sharedCfg,
|
|
4605
5144
|
};
|
|
4606
|
-
},
|
|
5145
|
+
}, /* @ts-ignore */
|
|
5146
|
+
...(ngDevMode ? [{ debugName: "stringifiedQueryConfig" }] : /* istanbul ignore next */ []));
|
|
4607
5147
|
this.authProvider = toSignal(toObservable(this.selectedClientConfig).pipe(switchMap((c) => c.client.authProvider$)));
|
|
4608
5148
|
this.authProviderDetails = computed(() => {
|
|
4609
5149
|
const provider = this.authProvider();
|
|
@@ -4665,7 +5205,8 @@ class QueryDevtoolsComponent {
|
|
|
4665
5205
|
config: {},
|
|
4666
5206
|
};
|
|
4667
5207
|
}
|
|
4668
|
-
},
|
|
5208
|
+
}, /* @ts-ignore */
|
|
5209
|
+
...(ngDevMode ? [{ debugName: "authProviderDetails" }] : /* istanbul ignore next */ []));
|
|
4669
5210
|
this.trackByClient = (_, { client }) => client.config.baseRoute;
|
|
4670
5211
|
this.trackByQuery = (_, { _id }) => _id;
|
|
4671
5212
|
this.selectedClientIdCtrl.valueChanges
|
|
@@ -4786,14 +5327,14 @@ class QueryDevtoolsComponent {
|
|
|
4786
5327
|
clearQueryHistory() {
|
|
4787
5328
|
this.queryHistory$.next([]);
|
|
4788
5329
|
}
|
|
4789
|
-
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "
|
|
4790
|
-
static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.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 rgb(59.734939759,66.1927710843,74.265060241);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 rgb(59.734939759,66.1927710843,74.265060241);margin-bottom:-12px}.et-qd-queries__browser-list{border-right:1px solid rgb(59.734939759,66.1927710843,74.265060241);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 rgb(59.734939759,66.1927710843,74.265060241);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 rgb(59.734939759,66.1927710843,74.265060241);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: "ngmodule", type: ReactiveFormsModule }, { kind: "directive", type: i1.NgSelectOption, selector: "option", inputs: ["ngValue", "value"] }, { kind: "directive", type: i1.ɵNgSelectMultipleOption, selector: "option", inputs: ["ngValue", "value"] }, { kind: "directive", type: i1.SelectControlValueAccessor, selector: "select:not([multiple])[formControlName],select:not([multiple])[formControl],select:not([multiple])[ngModel]", inputs: ["compareWith"] }, { kind: "directive", type: i1.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i1.FormControlDirective, selector: "[formControl]", inputs: ["formControl", "disabled", "ngModel"], outputs: ["ngModelChange"], exportAs: ["ngForm"] }, { kind: "directive", type: NgClass, selector: "[ngClass]", inputs: ["class", "ngClass"] }, { kind: "directive", type: QueryDirective, selector: "[etQuery]", inputs: ["etQuery", "etQueryCache"] }, { kind: "pipe", type: AsyncPipe, name: "async" }, { kind: "pipe", type: JsonPipe, name: "json" }, { kind: "pipe", type: QueryShortNamePipe, name: "queryShortName" }], changeDetection: i0.ChangeDetectionStrategy.OnPush, encapsulation: i0.ViewEncapsulation.ShadowDom }); }
|
|
5330
|
+
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.7", ngImport: i0, type: QueryDevtoolsComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
|
|
5331
|
+
static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.0.7", 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>{{ $safeNavigationMigration(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 rgb(59.734939759,66.1927710843,74.265060241);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 rgb(59.734939759,66.1927710843,74.265060241);margin-bottom:-12px}.et-qd-queries__browser-list{border-right:1px solid rgb(59.734939759,66.1927710843,74.265060241);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 rgb(59.734939759,66.1927710843,74.265060241);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 rgb(59.734939759,66.1927710843,74.265060241);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: "ngmodule", type: ReactiveFormsModule }, { kind: "directive", type: i1.NgSelectOption, selector: "option", inputs: ["ngValue", "value"] }, { kind: "directive", type: i1.ɵNgSelectMultipleOption, selector: "option", inputs: ["ngValue", "value"] }, { kind: "directive", type: i1.SelectControlValueAccessor, selector: "select:not([multiple]):not([ngNoCva])[formControlName],select:not([multiple]):not([ngNoCva])[formControl],select:not([multiple]):not([ngNoCva])[ngModel]", inputs: ["compareWith"] }, { kind: "directive", type: i1.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i1.FormControlDirective, selector: "[formControl]", inputs: ["formControl", "disabled", "ngModel"], outputs: ["ngModelChange"], exportAs: ["ngForm"] }, { kind: "directive", type: NgClass, selector: "[ngClass]", inputs: ["class", "ngClass"] }, { kind: "directive", type: QueryDirective, selector: "[etQuery]", inputs: ["etQuery", "etQueryCache"] }, { kind: "pipe", type: AsyncPipe, name: "async" }, { kind: "pipe", type: JsonPipe, name: "json" }, { kind: "pipe", type: QueryShortNamePipe, name: "queryShortName" }], encapsulation: i0.ViewEncapsulation.ShadowDom }); }
|
|
4791
5332
|
}
|
|
4792
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "
|
|
5333
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.7", ngImport: i0, type: QueryDevtoolsComponent, decorators: [{
|
|
4793
5334
|
type: Component,
|
|
4794
|
-
args: [{ selector: 'et-query-devtools',
|
|
5335
|
+
args: [{ selector: 'et-query-devtools', encapsulation: ViewEncapsulation.ShadowDom, host: {
|
|
4795
5336
|
class: 'et-query-devtools',
|
|
4796
|
-
}, 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 rgb(59.734939759,66.1927710843,74.265060241);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 rgb(59.734939759,66.1927710843,74.265060241);margin-bottom:-12px}.et-qd-queries__browser-list{border-right:1px solid rgb(59.734939759,66.1927710843,74.265060241);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 rgb(59.734939759,66.1927710843,74.265060241);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 rgb(59.734939759,66.1927710843,74.265060241);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"] }]
|
|
5337
|
+
}, 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>{{ $safeNavigationMigration(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 rgb(59.734939759,66.1927710843,74.265060241);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 rgb(59.734939759,66.1927710843,74.265060241);margin-bottom:-12px}.et-qd-queries__browser-list{border-right:1px solid rgb(59.734939759,66.1927710843,74.265060241);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 rgb(59.734939759,66.1927710843,74.265060241);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 rgb(59.734939759,66.1927710843,74.265060241);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"] }]
|
|
4797
5338
|
}], ctorParameters: () => [] });
|
|
4798
5339
|
|
|
4799
5340
|
class EntityStoreError extends Error {
|
|
@@ -4950,10 +5491,238 @@ const removeFrom = (store, { where, id }) => {
|
|
|
4950
5491
|
.subscribe();
|
|
4951
5492
|
};
|
|
4952
5493
|
|
|
4953
|
-
|
|
5494
|
+
/** `kind` of a `ValidationError` created from a single server violation. */
|
|
5495
|
+
const SERVER_VIOLATION_ERROR_KIND = 'etServerViolation';
|
|
5496
|
+
/** `kind` of the form-level fallback error created when a failed request carries no violations. */
|
|
5497
|
+
const SERVER_ERROR_KIND = 'etServerError';
|
|
5498
|
+
/**
|
|
5499
|
+
* Extracts the violation list from a failed request in any of the shapes it may reach you:
|
|
5500
|
+
* a `QueryErrorResponse` (from `query.error()` or a settled snapshot), a raw `HttpErrorResponse`,
|
|
5501
|
+
* an already-unwrapped error body, or a plain violation array. Returns `[]` when the error
|
|
5502
|
+
* carries no violations.
|
|
5503
|
+
*/
|
|
5504
|
+
const extractFormViolations = (error) => {
|
|
5505
|
+
if (!error)
|
|
5506
|
+
return [];
|
|
5507
|
+
const body = isQueryErrorResponse(error) ? error.raw.error : error instanceof HttpErrorResponse ? error.error : error;
|
|
5508
|
+
if (isSymfonyFormViolationListError(body)) {
|
|
5509
|
+
return body.violations;
|
|
5510
|
+
}
|
|
5511
|
+
if (isSymfonyListError(body)) {
|
|
5512
|
+
return body;
|
|
5513
|
+
}
|
|
5514
|
+
return [];
|
|
5515
|
+
};
|
|
5516
|
+
/**
|
|
5517
|
+
* Maps a failed request's violations onto a signal-forms field tree as validation errors —
|
|
5518
|
+
* the bridge between an API's violation list (`@ethlete/types` `FormViolationView`) and
|
|
5519
|
+
* signal forms.
|
|
5520
|
+
*
|
|
5521
|
+
* Each violation's `propertyPath` (e.g. `items[2].name`) is resolved against `fieldTree`; a
|
|
5522
|
+
* resolved violation becomes a `ServerViolationValidationError` bound to that field, an
|
|
5523
|
+
* unresolved one becomes a form-level error (customizable via `onUnmappedViolation`). A failed
|
|
5524
|
+
* request that carries no violations at all degrades to form-level `ServerValidationError`s
|
|
5525
|
+
* built from the normalized error message — so returning this function's result from a
|
|
5526
|
+
* `submit()` action never silently succeeds on failure.
|
|
5527
|
+
*
|
|
5528
|
+
* ```ts
|
|
5529
|
+
* await submit(this.form, async (field) => {
|
|
5530
|
+
* const snapshot = await executeUntilSettled(this.createPost, { args: { body: field().value() } });
|
|
5531
|
+
* const error = snapshot.error();
|
|
5532
|
+
*
|
|
5533
|
+
* if (!error) return;
|
|
5534
|
+
*
|
|
5535
|
+
* return mapViolationsToFormErrors({ fieldTree: field, error });
|
|
5536
|
+
* });
|
|
5537
|
+
* ```
|
|
5538
|
+
*/
|
|
5539
|
+
const mapViolationsToFormErrors = (options) => {
|
|
5540
|
+
const { fieldTree, error, rewritePath, onUnmappedViolation } = options;
|
|
5541
|
+
if (error === null || error === undefined)
|
|
5542
|
+
return [];
|
|
5543
|
+
const violations = extractFormViolations(error);
|
|
5544
|
+
if (!violations.length) {
|
|
5545
|
+
// An empty violation array is an explicit "nothing to map" — only unrecognized error
|
|
5546
|
+
// shapes degrade to the form-level fallback.
|
|
5547
|
+
if (Array.isArray(error) && !error.length)
|
|
5548
|
+
return [];
|
|
5549
|
+
return createFallbackErrors(error);
|
|
5550
|
+
}
|
|
5551
|
+
const errors = [];
|
|
5552
|
+
for (const violation of violations) {
|
|
5553
|
+
const rawPath = violation.propertyPath;
|
|
5554
|
+
const path = rawPath && rewritePath ? rewritePath(rawPath, violation) : rawPath;
|
|
5555
|
+
const resolvedFieldTree = path ? resolveFieldTreePath(fieldTree, path) : null;
|
|
5556
|
+
if (resolvedFieldTree) {
|
|
5557
|
+
const mappedError = {
|
|
5558
|
+
kind: SERVER_VIOLATION_ERROR_KIND,
|
|
5559
|
+
message: violation.message,
|
|
5560
|
+
fieldTree: resolvedFieldTree,
|
|
5561
|
+
violation,
|
|
5562
|
+
};
|
|
5563
|
+
errors.push(mappedError);
|
|
5564
|
+
}
|
|
5565
|
+
else if (onUnmappedViolation) {
|
|
5566
|
+
const customError = onUnmappedViolation(violation);
|
|
5567
|
+
if (customError) {
|
|
5568
|
+
errors.push(customError);
|
|
5569
|
+
}
|
|
5570
|
+
}
|
|
5571
|
+
else {
|
|
5572
|
+
const unmappedError = {
|
|
5573
|
+
kind: SERVER_VIOLATION_ERROR_KIND,
|
|
5574
|
+
message: violation.message,
|
|
5575
|
+
violation,
|
|
5576
|
+
};
|
|
5577
|
+
errors.push(unmappedError);
|
|
5578
|
+
}
|
|
5579
|
+
}
|
|
5580
|
+
return errors;
|
|
5581
|
+
};
|
|
5582
|
+
const createFallbackErrors = (error) => {
|
|
5583
|
+
const queryError = isQueryErrorResponse(error) ? error : createQueryErrorResponse(error);
|
|
5584
|
+
if (queryError.isList) {
|
|
5585
|
+
return queryError.errors.map((item) => ({ kind: SERVER_ERROR_KIND, message: item.message }));
|
|
5586
|
+
}
|
|
5587
|
+
return [{ kind: SERVER_ERROR_KIND, message: queryError.error.message }];
|
|
5588
|
+
};
|
|
5589
|
+
/** Splits a property path like `items[2].name` or `translations['de'].title` into its segments. */
|
|
5590
|
+
const parsePropertyPath = (path) => {
|
|
5591
|
+
return path
|
|
5592
|
+
.replace(/\[(['"]?)([^\]]*?)\1\]/g, '.$2')
|
|
5593
|
+
.split('.')
|
|
5594
|
+
.filter((segment) => segment.length > 0);
|
|
5595
|
+
};
|
|
5596
|
+
const resolveFieldTreePath = (fieldTree, path) => {
|
|
5597
|
+
let current = fieldTree;
|
|
5598
|
+
for (const segment of parsePropertyPath(path)) {
|
|
5599
|
+
if (current === null || (typeof current !== 'object' && typeof current !== 'function')) {
|
|
5600
|
+
return null;
|
|
5601
|
+
}
|
|
5602
|
+
current = current[segment];
|
|
5603
|
+
}
|
|
5604
|
+
// A field tree node is callable (calling it returns the field state) — anything else means the
|
|
5605
|
+
// path left the tree (e.g. an index past the array's current length).
|
|
5606
|
+
return typeof current === 'function' ? current : null;
|
|
5607
|
+
};
|
|
5608
|
+
|
|
5609
|
+
// Shared core for the query-backed async validators (`validateWithQuery` /
|
|
5610
|
+
// `validateWithV2Query`). Everything except how the query is executed — the `factory` that turns
|
|
5611
|
+
// the debounced params into a `Resource` — is identical between the v2 and v3 variants, so it is
|
|
5612
|
+
// factored here and imported by both. This module is intentionally NOT part of the `http` barrel;
|
|
5613
|
+
// it is internal plumbing consumed by direct path (the legacy variant already imports from `http`).
|
|
5614
|
+
/** Default debounce (ms) applied before a validate request runs, mirroring `selectOptionsFrom*Query`. */
|
|
5615
|
+
const DEFAULT_QUERY_VALIDATION_DEBOUNCE = 300;
|
|
5616
|
+
/**
|
|
5617
|
+
* Turns a failed validate request into field errors — the default `onError` mapping shared by both
|
|
5618
|
+
* validators. Reuses the existing `mapViolationsToFormErrors` bridge: a `422` violation list is
|
|
5619
|
+
* resolved onto the child fields by `propertyPath`, while a network / non-violation error degrades
|
|
5620
|
+
* to a non-swallowed form-level error. A caller-supplied `mapViolations` overrides only the
|
|
5621
|
+
* violation → error step (it receives the already-extracted violations).
|
|
5622
|
+
*/
|
|
5623
|
+
const mapQueryValidationError = (error, ctx, mapViolations) => {
|
|
5624
|
+
// Angular's `resource()` wraps a thrown non-`Error` (our `QueryErrorResponse` / `HttpErrorResponse`)
|
|
5625
|
+
// into an `Error` and stashes the original on `.cause`, so unwrap it before extraction.
|
|
5626
|
+
const cause = error instanceof Error && error.cause !== undefined ? error.cause : error;
|
|
5627
|
+
if (mapViolations) {
|
|
5628
|
+
return mapViolations(extractFormViolations(cause), ctx);
|
|
5629
|
+
}
|
|
5630
|
+
// `ctx.fieldTree` is the (read-only) tree of the validated field; `propertyPath`s resolve against
|
|
5631
|
+
// it. The cast bridges `ReadonlyFieldTree` → `FieldTree` — `mapViolationsToFormErrors` only walks
|
|
5632
|
+
// and reads the tree, never mutates it.
|
|
5633
|
+
return mapViolationsToFormErrors({ fieldTree: ctx.fieldTree, error: cause });
|
|
5634
|
+
};
|
|
5635
|
+
/**
|
|
5636
|
+
* Binds a query-backed async validator to `path` via `validateAsync`. Success (`204` / any `2xx`
|
|
5637
|
+
* with no violation body) reports no errors; a failed request is mapped by
|
|
5638
|
+
* {@link mapQueryValidationError}. Async validation only runs once the field's synchronous
|
|
5639
|
+
* validators pass and while `when` holds — signal forms feeds the `factory` `undefined` params when
|
|
5640
|
+
* the request shouldn't run, so the resource stays idle.
|
|
5641
|
+
*/
|
|
5642
|
+
const applyQueryAsyncValidator = (path, config) => {
|
|
5643
|
+
validateAsync(path, {
|
|
5644
|
+
params: config.params,
|
|
5645
|
+
debounce: config.debounce ?? DEFAULT_QUERY_VALIDATION_DEBOUNCE,
|
|
5646
|
+
factory: config.factory,
|
|
5647
|
+
when: config.when,
|
|
5648
|
+
onSuccess: () => null,
|
|
5649
|
+
onError: (error, ctx) => mapQueryValidationError(error, ctx, config.mapViolations),
|
|
5650
|
+
});
|
|
5651
|
+
};
|
|
5652
|
+
|
|
5653
|
+
/**
|
|
5654
|
+
* Binds a query-backed async validator to a signal-forms field, adapting a **legacy v2**
|
|
5655
|
+
* `@ethlete/query` query into what `validateAsync` consumes — the `V2QueryClient` counterpart of
|
|
5656
|
+
* {@link validateWithQuery}, so apps that haven't migrated yet can still validate against the
|
|
5657
|
+
* server. The query runs through the client (bearer auth, base route, caching, Symfony error
|
|
5658
|
+
* normalization), where a raw `httpResource` would fire unauthenticated and bypass the pipeline.
|
|
5659
|
+
*
|
|
5660
|
+
* The query is prepared and executed (debounced) as the field value changes, only after the
|
|
5661
|
+
* field's synchronous validators pass. A `204` / successful response reports no errors; a
|
|
5662
|
+
* `422 FormViolationListView` maps each violation onto its child field by `propertyPath`; a
|
|
5663
|
+
* network / other error degrades to a non-swallowed form-level error.
|
|
5664
|
+
*
|
|
5665
|
+
* Call it from a schema definition, the same place you'd call `validateAsync`:
|
|
5666
|
+
*
|
|
5667
|
+
* ```ts
|
|
5668
|
+
* const schema = schema<OpportunityEmail>((p) => {
|
|
5669
|
+
* validateWithV2Query(p, {
|
|
5670
|
+
* queryCreator: postOpportunityEmailValidate, // hubApiClient.post({ ... })
|
|
5671
|
+
* args: (ctx) => ({ pathParams: { opportunityId }, body: { ...ctx.value() } }),
|
|
5672
|
+
* });
|
|
5673
|
+
* });
|
|
5674
|
+
* ```
|
|
5675
|
+
*/
|
|
5676
|
+
const validateWithV2Query = (path, config) => {
|
|
5677
|
+
applyQueryAsyncValidator(path, {
|
|
5678
|
+
params: config.args,
|
|
5679
|
+
debounce: config.debounce,
|
|
5680
|
+
when: config.when,
|
|
5681
|
+
mapViolations: config.mapViolations,
|
|
5682
|
+
factory: (params) => resource({
|
|
5683
|
+
params: () => params(),
|
|
5684
|
+
loader: ({ params: prepareArgs, abortSignal }) => {
|
|
5685
|
+
// Prepared per request — v2 caches by the prepared args, so a fresh `prepare()` is the
|
|
5686
|
+
// legacy idiom (the sibling `selectOptionsFromV2Query` does the same via `queryComputed`).
|
|
5687
|
+
const query = config.queryCreator.prepare(prepareArgs);
|
|
5688
|
+
abortSignal.addEventListener('abort', () => query.abort());
|
|
5689
|
+
query.execute();
|
|
5690
|
+
// Resolve once the query settles. A failure is thrown so it lands in `onError`, where the
|
|
5691
|
+
// violations are mapped onto the fields; the underlying `HttpErrorResponse` is thrown so
|
|
5692
|
+
// `extractFormViolations` can unwrap the 422 body.
|
|
5693
|
+
return firstValueFrom(query.state$.pipe(filter((state) => isQueryStateSuccess(state) || isQueryStateFailure(state)), take(1))).then((state) => {
|
|
5694
|
+
if (isQueryStateFailure(state)) {
|
|
5695
|
+
throw state.error.httpErrorResponse;
|
|
5696
|
+
}
|
|
5697
|
+
return state.response;
|
|
5698
|
+
});
|
|
5699
|
+
},
|
|
5700
|
+
}),
|
|
5701
|
+
});
|
|
5702
|
+
};
|
|
5703
|
+
|
|
5704
|
+
const generateUuid = () => randomId();
|
|
5705
|
+
/** @see CreateQueryClientConfigOptions.keepUnusedFor */
|
|
5706
|
+
const DEFAULT_KEEP_UNUSED_FOR = 300_000;
|
|
5707
|
+
/**
|
|
5708
|
+
* Hard cap on entries kept without consumers, per client. Retention is opportunistic, so a runaway
|
|
5709
|
+
* count (a search-as-you-type query produces a new cache key per keystroke, and each one goes through
|
|
5710
|
+
* `unbind`) must never grow unbounded — the least recently orphaned entries are dropped first.
|
|
5711
|
+
*/
|
|
5712
|
+
const MAX_UNUSED_ENTRIES = 50;
|
|
4954
5713
|
const createQueryRepository = (config) => {
|
|
4955
5714
|
const cache = new Map();
|
|
4956
5715
|
const eventsSubject = new Subject();
|
|
5716
|
+
// Bumped on every cache mutation so the devtools cache view can react. Cheap enough to keep
|
|
5717
|
+
// unconditional — it is a single integer signal with no readers unless the devtools are open.
|
|
5718
|
+
const cacheVersion = signal(0, /* @ts-ignore */
|
|
5719
|
+
...(ngDevMode ? [{ debugName: "cacheVersion" }] : /* istanbul ignore next */ []));
|
|
5720
|
+
const bumpCacheVersion = () => cacheVersion.update((v) => v + 1);
|
|
5721
|
+
const resolveKeepUnusedFor = (creatorOptions, shouldCache) => {
|
|
5722
|
+
if (config.retentionEnabled === false || !shouldCache)
|
|
5723
|
+
return 0;
|
|
5724
|
+
return Math.max(0, creatorOptions?.keepUnusedFor ?? config.keepUnusedFor ?? DEFAULT_KEEP_UNUSED_FOR);
|
|
5725
|
+
};
|
|
4957
5726
|
const request = (options) => {
|
|
4958
5727
|
const { args, creatorOptions, runQueryOptions } = options;
|
|
4959
5728
|
const shouldCache = creatorOptions?.subtle?.useQueryRepositoryCache === false
|
|
@@ -4961,8 +5730,11 @@ const createQueryRepository = (config) => {
|
|
|
4961
5730
|
: shouldCacheQuery(options.method) || creatorOptions?.subtle?.useQueryRepositoryCache === true;
|
|
4962
5731
|
if (!shouldCache && options.key)
|
|
4963
5732
|
throw uncacheableRequestHasCacheKeyParam(options.key);
|
|
4964
|
-
|
|
5733
|
+
// `allowCache` is never read on the uncacheable path below — there is no cache entry to reuse — so this throw
|
|
5734
|
+
// is purely a guard against a mistake in hand-written code. The legacy interop opts out of it.
|
|
5735
|
+
if (!shouldCache && runQueryOptions?.allowCache && !options.silenceUncacheableAllowCacheError) {
|
|
4965
5736
|
throw uncacheableRequestHasAllowCacheParam();
|
|
5737
|
+
}
|
|
4966
5738
|
const route = buildRoute({
|
|
4967
5739
|
base: config.baseUrl,
|
|
4968
5740
|
route: options.route,
|
|
@@ -4979,6 +5751,7 @@ const createQueryRepository = (config) => {
|
|
|
4979
5751
|
})
|
|
4980
5752
|
: false;
|
|
4981
5753
|
const trackingKey = cacheKey || generateUuid();
|
|
5754
|
+
const keepUnusedFor = resolveKeepUnusedFor(creatorOptions, shouldCache);
|
|
4982
5755
|
const previousKey = options.previousKey;
|
|
4983
5756
|
if (cacheKey !== previousKey && previousKey) {
|
|
4984
5757
|
unbind(previousKey, options.consumerDestroyRef);
|
|
@@ -4986,7 +5759,10 @@ const createQueryRepository = (config) => {
|
|
|
4986
5759
|
if (shouldCache && cacheKey) {
|
|
4987
5760
|
const cacheEntry = cache.get(cacheKey);
|
|
4988
5761
|
if (cacheEntry) {
|
|
4989
|
-
|
|
5762
|
+
// The entry may have been sitting out its `keepUnusedFor` window — a consumer binding again
|
|
5763
|
+
// makes it live, so the pending eviction must go.
|
|
5764
|
+
cancelEviction(cacheEntry);
|
|
5765
|
+
bind(cacheKey, options.consumerDestroyRef, cacheEntry.request, options.isSecure ?? false, true, keepUnusedFor);
|
|
4990
5766
|
if (!runQueryOptions?.allowCache || cacheEntry.request.isStale()) {
|
|
4991
5767
|
cacheEntry.request.execute({ allowCache: runQueryOptions?.allowCache });
|
|
4992
5768
|
}
|
|
@@ -4999,13 +5775,43 @@ const createQueryRepository = (config) => {
|
|
|
4999
5775
|
method: options.method,
|
|
5000
5776
|
dependencies: config.dependencies,
|
|
5001
5777
|
clientOptions: creatorOptions,
|
|
5778
|
+
clientHeaders: config.headers,
|
|
5002
5779
|
cacheAdapter: config.cacheAdapter,
|
|
5003
5780
|
retryFn: options.retryFn ?? config.retryFn,
|
|
5004
5781
|
});
|
|
5005
5782
|
request.execute();
|
|
5006
|
-
bind(trackingKey, options.consumerDestroyRef, request, options.isSecure ?? false, shouldCache);
|
|
5783
|
+
bind(trackingKey, options.consumerDestroyRef, request, options.isSecure ?? false, shouldCache, keepUnusedFor);
|
|
5007
5784
|
return { key: trackingKey, request };
|
|
5008
5785
|
};
|
|
5786
|
+
/** Tears an entry down for good, whether it currently has consumers or not. */
|
|
5787
|
+
const destroyEntry = (key, cacheEntry) => {
|
|
5788
|
+
clearTimeout(cacheEntry.evictTimer);
|
|
5789
|
+
cacheEntry.request.destroy();
|
|
5790
|
+
cacheEntry.eventSubscription?.unsubscribe();
|
|
5791
|
+
cache.delete(key);
|
|
5792
|
+
};
|
|
5793
|
+
const cancelEviction = (cacheEntry) => {
|
|
5794
|
+
if (cacheEntry.evictTimer === undefined)
|
|
5795
|
+
return;
|
|
5796
|
+
clearTimeout(cacheEntry.evictTimer);
|
|
5797
|
+
cacheEntry.evictTimer = undefined;
|
|
5798
|
+
cacheEntry.unusedSince = undefined;
|
|
5799
|
+
};
|
|
5800
|
+
/** Drops the least recently orphaned unused entries until the cap is respected again. */
|
|
5801
|
+
const enforceUnusedEntryLimit = () => {
|
|
5802
|
+
const unused = Array.from(cache.entries()).filter(([, entry]) => entry.consumers.size === 0);
|
|
5803
|
+
if (unused.length <= MAX_UNUSED_ENTRIES)
|
|
5804
|
+
return;
|
|
5805
|
+
unused.sort(([, a], [, b]) => (a.unusedSince ?? 0) - (b.unusedSince ?? 0));
|
|
5806
|
+
for (const [key, entry] of unused.slice(0, unused.length - MAX_UNUSED_ENTRIES)) {
|
|
5807
|
+
destroyEntry(key, entry);
|
|
5808
|
+
}
|
|
5809
|
+
};
|
|
5810
|
+
const retain = (key, cacheEntry) => {
|
|
5811
|
+
cacheEntry.unusedSince = Date.now();
|
|
5812
|
+
cacheEntry.evictTimer = setTimeout(() => evict(key), cacheEntry.keepUnusedFor);
|
|
5813
|
+
enforceUnusedEntryLimit();
|
|
5814
|
+
};
|
|
5009
5815
|
const unbind = (key, consumerDestroyRef) => {
|
|
5010
5816
|
if (key === null)
|
|
5011
5817
|
return false;
|
|
@@ -5014,24 +5820,41 @@ const createQueryRepository = (config) => {
|
|
|
5014
5820
|
return false;
|
|
5015
5821
|
cacheEntry.consumers.delete(consumerDestroyRef);
|
|
5016
5822
|
if (cacheEntry.consumers.size === 0) {
|
|
5017
|
-
|
|
5018
|
-
|
|
5019
|
-
|
|
5823
|
+
// Only data is worth keeping around: an entry that never produced a response has nothing to
|
|
5824
|
+
// hand back to a returning consumer, so it is aborted immediately as it always was.
|
|
5825
|
+
if (cacheEntry.keepUnusedFor > 0 && cacheEntry.request.response() !== null) {
|
|
5826
|
+
retain(key, cacheEntry);
|
|
5827
|
+
}
|
|
5828
|
+
else {
|
|
5829
|
+
destroyEntry(key, cacheEntry);
|
|
5830
|
+
}
|
|
5020
5831
|
}
|
|
5832
|
+
bumpCacheVersion();
|
|
5021
5833
|
return true;
|
|
5022
5834
|
};
|
|
5023
5835
|
const unbindAllSecure = () => {
|
|
5024
5836
|
for (const [key, cacheEntry] of cache.entries()) {
|
|
5025
|
-
if (cacheEntry.isSecure)
|
|
5026
|
-
|
|
5027
|
-
|
|
5028
|
-
|
|
5029
|
-
|
|
5030
|
-
|
|
5031
|
-
}
|
|
5837
|
+
if (!cacheEntry.isSecure)
|
|
5838
|
+
continue;
|
|
5839
|
+
// Force the teardown instead of routing through `unbind`: a logged out session must not leave a
|
|
5840
|
+
// retained response body behind waiting out its `keepUnusedFor` window.
|
|
5841
|
+
cacheEntry.consumers.clear();
|
|
5842
|
+
destroyEntry(key, cacheEntry);
|
|
5032
5843
|
}
|
|
5844
|
+
bumpCacheVersion();
|
|
5845
|
+
eventsSubject.next({ type: 'unbind-all-secure' });
|
|
5033
5846
|
};
|
|
5034
|
-
const
|
|
5847
|
+
const refreshInUse = () => {
|
|
5848
|
+
for (const cacheEntry of cache.values()) {
|
|
5849
|
+
if (cacheEntry.consumers.size === 0)
|
|
5850
|
+
continue;
|
|
5851
|
+
// Re-firing a mutation would be a side effect nobody asked for, so only reads are refreshed.
|
|
5852
|
+
if (!shouldCacheQuery(cacheEntry.request.method))
|
|
5853
|
+
continue;
|
|
5854
|
+
cacheEntry.request.execute({ force: true });
|
|
5855
|
+
}
|
|
5856
|
+
};
|
|
5857
|
+
const bind = (key, consumerDestroyRef, request, isSecure, allowReuse, keepUnusedFor) => {
|
|
5035
5858
|
const destroyListener = consumerDestroyRef.onDestroy(() => unbind(key, consumerDestroyRef));
|
|
5036
5859
|
const cacheEntry = cache.get(key);
|
|
5037
5860
|
if (cacheEntry && allowReuse) {
|
|
@@ -5040,26 +5863,17 @@ const createQueryRepository = (config) => {
|
|
|
5040
5863
|
else {
|
|
5041
5864
|
const consumers = new Map([]);
|
|
5042
5865
|
consumers.set(consumerDestroyRef, destroyListener);
|
|
5043
|
-
|
|
5044
|
-
|
|
5045
|
-
|
|
5046
|
-
|
|
5047
|
-
if (
|
|
5048
|
-
|
|
5049
|
-
type: 'request-error',
|
|
5050
|
-
|
|
5051
|
-
key,
|
|
5052
|
-
isSecure,
|
|
5053
|
-
request,
|
|
5054
|
-
});
|
|
5866
|
+
// Drive repository events off the request's discrete, terminal event stream rather than
|
|
5867
|
+
// recombining the `error` + `response` signals via combineLatest (which could observe stale
|
|
5868
|
+
// pairings). Each settle emits exactly one terminal event.
|
|
5869
|
+
const eventSubscription = request.events$.subscribe((event) => {
|
|
5870
|
+
if (event.type === 'error') {
|
|
5871
|
+
if (event.error.raw instanceof HttpErrorResponse) {
|
|
5872
|
+
eventsSubject.next({ type: 'request-error', error: event.error.raw, key, isSecure, request });
|
|
5873
|
+
}
|
|
5055
5874
|
}
|
|
5056
|
-
else if (
|
|
5057
|
-
eventsSubject.next({
|
|
5058
|
-
type: 'request-success',
|
|
5059
|
-
key,
|
|
5060
|
-
isSecure,
|
|
5061
|
-
request,
|
|
5062
|
-
});
|
|
5875
|
+
else if (event.type === HttpEventType.Response) {
|
|
5876
|
+
eventsSubject.next({ type: 'request-success', key, isSecure, request });
|
|
5063
5877
|
}
|
|
5064
5878
|
});
|
|
5065
5879
|
cache.set(key, {
|
|
@@ -5067,14 +5881,36 @@ const createQueryRepository = (config) => {
|
|
|
5067
5881
|
request,
|
|
5068
5882
|
isSecure,
|
|
5069
5883
|
eventSubscription,
|
|
5884
|
+
keepUnusedFor,
|
|
5070
5885
|
});
|
|
5071
5886
|
}
|
|
5887
|
+
bumpCacheVersion();
|
|
5888
|
+
};
|
|
5889
|
+
const cacheEntries = () => Array.from(cache.entries()).map(([key, entry]) => ({
|
|
5890
|
+
key,
|
|
5891
|
+
isSecure: entry.isSecure,
|
|
5892
|
+
consumerCount: entry.consumers.size,
|
|
5893
|
+
isUnused: entry.consumers.size === 0,
|
|
5894
|
+
request: entry.request,
|
|
5895
|
+
}));
|
|
5896
|
+
const evict = (key) => {
|
|
5897
|
+
const entry = cache.get(key);
|
|
5898
|
+
if (!entry)
|
|
5899
|
+
return;
|
|
5900
|
+
destroyEntry(key, entry);
|
|
5901
|
+
bumpCacheVersion();
|
|
5072
5902
|
};
|
|
5073
5903
|
const repository = {
|
|
5074
5904
|
request,
|
|
5075
5905
|
unbind,
|
|
5076
5906
|
unbindAllSecure,
|
|
5907
|
+
refreshInUse,
|
|
5077
5908
|
events$: eventsSubject.asObservable(),
|
|
5909
|
+
subtle: {
|
|
5910
|
+
cacheEntries,
|
|
5911
|
+
cacheVersion: cacheVersion.asReadonly(),
|
|
5912
|
+
evict,
|
|
5913
|
+
},
|
|
5078
5914
|
};
|
|
5079
5915
|
return repository;
|
|
5080
5916
|
};
|
|
@@ -5085,10 +5921,15 @@ const createQueryClient = (options) => createRootProvider(() => {
|
|
|
5085
5921
|
const injector = inject(Injector);
|
|
5086
5922
|
const repository = createQueryRepository({
|
|
5087
5923
|
...options,
|
|
5924
|
+
// Retaining unused entries on the server would pin response bodies (and a pending timer) inside
|
|
5925
|
+
// a per-request injector for the whole window, so retention is browser only.
|
|
5926
|
+
retentionEnabled: isPlatformBrowser(inject(PLATFORM_ID)),
|
|
5088
5927
|
dependencies: { httpClient, ngErrorHandler, injector },
|
|
5089
5928
|
});
|
|
5090
5929
|
const client = {
|
|
5091
5930
|
repository,
|
|
5931
|
+
baseUrl: options.baseUrl,
|
|
5932
|
+
refreshQueriesInUse: () => repository.refreshInUse(),
|
|
5092
5933
|
};
|
|
5093
5934
|
return client;
|
|
5094
5935
|
}, {
|
|
@@ -5157,6 +5998,191 @@ const createPatchQuery = createQueryTemplate('PATCH');
|
|
|
5157
5998
|
/** A query creator that creates a secure PATCH query (requires authentication) */
|
|
5158
5999
|
const createSecurePatchQuery = createSecureQueryTemplate('PATCH');
|
|
5159
6000
|
|
|
6001
|
+
/**
|
|
6002
|
+
* Executes the query and resolves once that execution has settled (response or error received).
|
|
6003
|
+
* The returned snapshot is frozen to the awaited execution, so a later execution can't swap the
|
|
6004
|
+
* `response()` / `error()` you read from it.
|
|
6005
|
+
*
|
|
6006
|
+
* Designed for imperative flows that need a mutation's outcome in place — e.g. a signal-forms
|
|
6007
|
+
* `submit()` action mapping server violations onto the form via `mapViolationsToFormErrors`.
|
|
6008
|
+
*
|
|
6009
|
+
* The promise stays pending if the execution is cancelled (e.g. `reset()`) and the query is never
|
|
6010
|
+
* executed again — the snapshot settles with whichever execution completes next.
|
|
6011
|
+
*/
|
|
6012
|
+
const executeUntilSettled = async (query, executeArgs) => {
|
|
6013
|
+
query.execute(executeArgs);
|
|
6014
|
+
const snapshot = query.createSnapshot();
|
|
6015
|
+
await firstValueFrom(snapshot.isAlive.asObservable().pipe(filter((isAlive) => !isAlive)));
|
|
6016
|
+
return snapshot;
|
|
6017
|
+
};
|
|
6018
|
+
|
|
6019
|
+
/* eslint-disable @typescript-eslint/no-explicit-any */
|
|
6020
|
+
const createSequenceState = () => ({
|
|
6021
|
+
status: signal('idle'),
|
|
6022
|
+
running: signal(false),
|
|
6023
|
+
currentStep: signal(0),
|
|
6024
|
+
error: signal(null),
|
|
6025
|
+
failedAt: signal(null),
|
|
6026
|
+
snapshots: signal([]),
|
|
6027
|
+
stepArgs: signal([]),
|
|
6028
|
+
responses: signal([]),
|
|
6029
|
+
});
|
|
6030
|
+
const buildSequence = (steps, state, devtoolsHandle) => {
|
|
6031
|
+
const run = async () => {
|
|
6032
|
+
if (state.running())
|
|
6033
|
+
throw querySequenceAlreadyRunning();
|
|
6034
|
+
state.status.set('running');
|
|
6035
|
+
state.running.set(true);
|
|
6036
|
+
state.currentStep.set(0);
|
|
6037
|
+
state.error.set(null);
|
|
6038
|
+
state.failedAt.set(null);
|
|
6039
|
+
state.snapshots.set([]);
|
|
6040
|
+
state.stepArgs.set([]);
|
|
6041
|
+
state.responses.set([]);
|
|
6042
|
+
const snapshots = [];
|
|
6043
|
+
const stepArgs = [];
|
|
6044
|
+
const responses = [];
|
|
6045
|
+
let previousResponse = undefined;
|
|
6046
|
+
for (const [i, step] of steps.entries()) {
|
|
6047
|
+
state.currentStep.set(i + 1);
|
|
6048
|
+
const { args } = step.produceArgs(previousResponse, responses);
|
|
6049
|
+
stepArgs.push(args);
|
|
6050
|
+
state.stepArgs.set([...stepArgs]);
|
|
6051
|
+
const snapshot = await executeUntilSettled(step.query, { args });
|
|
6052
|
+
snapshots.push(snapshot);
|
|
6053
|
+
state.snapshots.set([...snapshots]);
|
|
6054
|
+
const error = snapshot.error();
|
|
6055
|
+
if (error) {
|
|
6056
|
+
state.status.set('error');
|
|
6057
|
+
state.error.set(error);
|
|
6058
|
+
state.failedAt.set(i);
|
|
6059
|
+
state.running.set(false);
|
|
6060
|
+
return { ok: false, failedAt: i, error, snapshots };
|
|
6061
|
+
}
|
|
6062
|
+
const response = snapshot.response();
|
|
6063
|
+
responses.push(response);
|
|
6064
|
+
state.responses.set([...responses]);
|
|
6065
|
+
previousResponse = response;
|
|
6066
|
+
}
|
|
6067
|
+
state.status.set('success');
|
|
6068
|
+
state.running.set(false);
|
|
6069
|
+
return { ok: true, responses: responses, snapshots };
|
|
6070
|
+
};
|
|
6071
|
+
const sequence = {
|
|
6072
|
+
then: (query, mapArgs) => buildSequence([
|
|
6073
|
+
...steps,
|
|
6074
|
+
{ query, produceArgs: (previousResponse, responses) => mapArgs(previousResponse, responses) },
|
|
6075
|
+
], state, devtoolsHandle),
|
|
6076
|
+
status: state.status.asReadonly(),
|
|
6077
|
+
running: state.running.asReadonly(),
|
|
6078
|
+
currentStep: state.currentStep.asReadonly(),
|
|
6079
|
+
total: steps.length,
|
|
6080
|
+
queries: steps.map((s) => s.query),
|
|
6081
|
+
error: state.error.asReadonly(),
|
|
6082
|
+
failedAt: state.failedAt.asReadonly(),
|
|
6083
|
+
snapshots: state.snapshots.asReadonly(),
|
|
6084
|
+
stepArgs: state.stepArgs.asReadonly(),
|
|
6085
|
+
responses: state.responses.asReadonly(),
|
|
6086
|
+
run,
|
|
6087
|
+
};
|
|
6088
|
+
if (devtoolsHandle)
|
|
6089
|
+
devtoolsHandle.current = sequence;
|
|
6090
|
+
return sequence;
|
|
6091
|
+
};
|
|
6092
|
+
/**
|
|
6093
|
+
* Starts an imperative waterfall of dependent queries. Chain the remaining steps with `.then()`
|
|
6094
|
+
* and trigger it with `.run()` — see {@link QuerySequence}.
|
|
6095
|
+
*
|
|
6096
|
+
* The seed step's `args` are produced lazily (at `run()` time), so the sequence can safely live as
|
|
6097
|
+
* a component field while still reading current signal values on each run.
|
|
6098
|
+
*
|
|
6099
|
+
* @example
|
|
6100
|
+
* ```ts
|
|
6101
|
+
* readonly checkout = querySequence(this.createOrder, () => ({ args: { body: this.order() } }))
|
|
6102
|
+
* .then(this.createPayment, (order) => ({ args: { body: { orderId: order.id } } }))
|
|
6103
|
+
* .then(this.confirmOrder, (payment, [order]) => ({
|
|
6104
|
+
* args: { pathParams: { paymentId: payment.id }, body: { orderRef: order.id } },
|
|
6105
|
+
* }));
|
|
6106
|
+
*
|
|
6107
|
+
* async submit() {
|
|
6108
|
+
* const result = await this.checkout.run();
|
|
6109
|
+
* if (!result.ok) return this.handle(result.error);
|
|
6110
|
+
* const [order, payment, confirmation] = result.responses;
|
|
6111
|
+
* }
|
|
6112
|
+
* ```
|
|
6113
|
+
*/
|
|
6114
|
+
const querySequence = (query, seedArgs) => {
|
|
6115
|
+
const devtoolsHandle = isQueryDevtoolsEnabled()
|
|
6116
|
+
? { current: null }
|
|
6117
|
+
: undefined;
|
|
6118
|
+
const seed = buildSequence([{ query, produceArgs: () => seedArgs() }], createSequenceState(), devtoolsHandle);
|
|
6119
|
+
if (devtoolsHandle) {
|
|
6120
|
+
const unregister = registerQueryDevtoolsEntry({
|
|
6121
|
+
kind: 'query-sequence',
|
|
6122
|
+
handle: devtoolsHandle,
|
|
6123
|
+
meta: {},
|
|
6124
|
+
});
|
|
6125
|
+
// Sequences have no destroy hook; tie cleanup to the creating scope when there is one.
|
|
6126
|
+
try {
|
|
6127
|
+
inject(DestroyRef).onDestroy(unregister);
|
|
6128
|
+
}
|
|
6129
|
+
catch {
|
|
6130
|
+
// Created outside an injection context — the entry lives for the app's lifetime.
|
|
6131
|
+
}
|
|
6132
|
+
}
|
|
6133
|
+
return seed;
|
|
6134
|
+
};
|
|
6135
|
+
|
|
6136
|
+
/**
|
|
6137
|
+
* Binds a query-backed async validator to a signal-forms field, adapting an `@ethlete/query` v3
|
|
6138
|
+
* query into what `validateAsync` consumes. The query runs through the query client — so bearer
|
|
6139
|
+
* auth, base route, caching and Symfony error normalization all apply — where a raw `httpResource`
|
|
6140
|
+
* would fire unauthenticated and bypass the pipeline.
|
|
6141
|
+
*
|
|
6142
|
+
* On the current v3 `QueryClient` / `createPostQuery` creators; the `validateWithV2Query` twin
|
|
6143
|
+
* covers the legacy `V2QueryClient`. The query is created once and re-executed (debounced) as the
|
|
6144
|
+
* field value changes, only after the field's synchronous validators pass. A `204` / successful
|
|
6145
|
+
* response reports no errors; a `422 FormViolationListView` maps each violation onto its child
|
|
6146
|
+
* field by `propertyPath`; a network / other error degrades to a non-swallowed form-level error.
|
|
6147
|
+
*
|
|
6148
|
+
* Call it from a schema definition, the same place you'd call `validateAsync`:
|
|
6149
|
+
*
|
|
6150
|
+
* ```ts
|
|
6151
|
+
* const schema = schema<OpportunityEmail>((p) => {
|
|
6152
|
+
* validateWithQuery(p, {
|
|
6153
|
+
* queryCreator: postOpportunityEmailValidate,
|
|
6154
|
+
* args: (ctx) => ({ pathParams: { opportunityId }, body: { ...ctx.value() } }),
|
|
6155
|
+
* });
|
|
6156
|
+
* });
|
|
6157
|
+
* ```
|
|
6158
|
+
*/
|
|
6159
|
+
const validateWithQuery = (path, config) => {
|
|
6160
|
+
applyQueryAsyncValidator(path, {
|
|
6161
|
+
params: config.args,
|
|
6162
|
+
debounce: config.debounce,
|
|
6163
|
+
when: config.when,
|
|
6164
|
+
mapViolations: config.mapViolations,
|
|
6165
|
+
factory: (params) => {
|
|
6166
|
+
// Created once, exactly like a query stack. A POST validate route doesn't auto-execute, so
|
|
6167
|
+
// the resource loader drives it — each params change re-executes with the new args.
|
|
6168
|
+
const query = config.queryCreator();
|
|
6169
|
+
return resource({
|
|
6170
|
+
params: () => params(),
|
|
6171
|
+
loader: async ({ params: requestArgs }) => {
|
|
6172
|
+
const snapshot = await executeUntilSettled(query, { args: requestArgs });
|
|
6173
|
+
const error = snapshot.error();
|
|
6174
|
+
// A failed request (e.g. 422) is thrown so it lands in `onError`, where the violations are
|
|
6175
|
+
// mapped onto the fields. A `204` / success settles with no error and no violations.
|
|
6176
|
+
if (error) {
|
|
6177
|
+
throw error;
|
|
6178
|
+
}
|
|
6179
|
+
return snapshot.response() ?? undefined;
|
|
6180
|
+
},
|
|
6181
|
+
});
|
|
6182
|
+
},
|
|
6183
|
+
});
|
|
6184
|
+
};
|
|
6185
|
+
|
|
5160
6186
|
const setupLeaderElection = () => {
|
|
5161
6187
|
const destroyRef = inject(DestroyRef);
|
|
5162
6188
|
const channelName = 'ethlete-auth-leader';
|
|
@@ -5164,8 +6190,10 @@ const setupLeaderElection = () => {
|
|
|
5164
6190
|
const leaderTimeout = 3000;
|
|
5165
6191
|
const storageKey = 'ethlete-auth-leader-heartbeat';
|
|
5166
6192
|
const tabId = `tab-${Date.now()}-${Math.random().toString(36).substring(2, 9)}`;
|
|
5167
|
-
const isLeader = signal(false,
|
|
5168
|
-
|
|
6193
|
+
const isLeader = signal(false, /* @ts-ignore */
|
|
6194
|
+
...(ngDevMode ? [{ debugName: "isLeader" }] : /* istanbul ignore next */ []));
|
|
6195
|
+
const instanceCount = signal(1, /* @ts-ignore */
|
|
6196
|
+
...(ngDevMode ? [{ debugName: "instanceCount" }] : /* istanbul ignore next */ []));
|
|
5169
6197
|
const knownTabs = new Map();
|
|
5170
6198
|
knownTabs.set(tabId, Date.now());
|
|
5171
6199
|
let channel = null;
|
|
@@ -5539,11 +6567,21 @@ const defaultExtractTokens = (response) => {
|
|
|
5539
6567
|
}
|
|
5540
6568
|
return { accessToken: response['accessToken'], refreshToken: response['refreshToken'] };
|
|
5541
6569
|
};
|
|
5542
|
-
const
|
|
6570
|
+
const deriveExecutionStateType = (builder, triggeredBy) => {
|
|
6571
|
+
if (triggeredBy === 'persistent-auth')
|
|
6572
|
+
return 'autoLogin';
|
|
6573
|
+
if (builder._type === 'tokenRefreshQuery')
|
|
6574
|
+
return 'tokenRefresh';
|
|
6575
|
+
if (triggeredBy === 'token-revocation')
|
|
6576
|
+
return 'revocation';
|
|
6577
|
+
return builder.key;
|
|
6578
|
+
};
|
|
6579
|
+
const setupBearerQueryRegistry = (builders, injector, latestExecutedQuery, latestNonInternalQuery, setTokens, executionState) => {
|
|
5543
6580
|
const queries = {};
|
|
5544
6581
|
const querySnapshots = new Map();
|
|
5545
6582
|
for (const builder of builders) {
|
|
5546
|
-
const querySnapshot = signal(null,
|
|
6583
|
+
const querySnapshot = signal(null, /* @ts-ignore */
|
|
6584
|
+
...(ngDevMode ? [{ debugName: "querySnapshot" }] : /* istanbul ignore next */ []));
|
|
5547
6585
|
querySnapshots.set(builder.key, querySnapshot);
|
|
5548
6586
|
const extractTokens = builder.config.extractTokens ?? defaultExtractTokens;
|
|
5549
6587
|
effect(() => {
|
|
@@ -5572,10 +6610,27 @@ const setupBearerQueryRegistry = (builders, injector, latestExecutedQuery, lates
|
|
|
5572
6610
|
});
|
|
5573
6611
|
query.execute({ args, options });
|
|
5574
6612
|
const snapshot = query.createSnapshot();
|
|
5575
|
-
|
|
5576
|
-
if (
|
|
5577
|
-
|
|
6613
|
+
const stateType = deriveExecutionStateType(builder, options?.triggeredBy);
|
|
6614
|
+
if (stateType !== 'revocation') {
|
|
6615
|
+
latestExecutedQuery.set({ key: builder.key, snapshot });
|
|
6616
|
+
if (!snapshot.triggeredBy()) {
|
|
6617
|
+
latestNonInternalQuery.set({ key: builder.key, snapshot });
|
|
6618
|
+
}
|
|
5578
6619
|
}
|
|
6620
|
+
executionState.set({ type: stateType, state: 'loading' });
|
|
6621
|
+
effect(() => {
|
|
6622
|
+
const response = snapshot.response();
|
|
6623
|
+
const loading = snapshot.loading();
|
|
6624
|
+
const error = snapshot.error();
|
|
6625
|
+
if (loading)
|
|
6626
|
+
return;
|
|
6627
|
+
if (error) {
|
|
6628
|
+
executionState.set({ type: stateType, state: 'error', error });
|
|
6629
|
+
}
|
|
6630
|
+
else if (response) {
|
|
6631
|
+
executionState.set({ type: stateType, state: 'success', response });
|
|
6632
|
+
}
|
|
6633
|
+
}, { injector });
|
|
5579
6634
|
querySnapshot.set(snapshot);
|
|
5580
6635
|
return snapshot;
|
|
5581
6636
|
};
|
|
@@ -5635,8 +6690,10 @@ const createBearerAuthProviderImpl = (config) => {
|
|
|
5635
6690
|
const injector = inject(Injector);
|
|
5636
6691
|
const destroyRef = inject(DestroyRef);
|
|
5637
6692
|
const queryClient = config.queryClientRef[1]();
|
|
5638
|
-
const accessToken = signal(null,
|
|
5639
|
-
|
|
6693
|
+
const accessToken = signal(null, /* @ts-ignore */
|
|
6694
|
+
...(ngDevMode ? [{ debugName: "accessToken" }] : /* istanbul ignore next */ []));
|
|
6695
|
+
const refreshToken = signal(null, /* @ts-ignore */
|
|
6696
|
+
...(ngDevMode ? [{ debugName: "refreshToken" }] : /* istanbul ignore next */ []));
|
|
5640
6697
|
const afterTokenRefresh$ = new Subject();
|
|
5641
6698
|
const bearerData = computed(() => {
|
|
5642
6699
|
const token = accessToken();
|
|
@@ -5651,20 +6708,29 @@ const createBearerAuthProviderImpl = (config) => {
|
|
|
5651
6708
|
}
|
|
5652
6709
|
return null;
|
|
5653
6710
|
}
|
|
5654
|
-
},
|
|
5655
|
-
|
|
5656
|
-
const
|
|
5657
|
-
|
|
6711
|
+
}, /* @ts-ignore */
|
|
6712
|
+
...(ngDevMode ? [{ debugName: "bearerData" }] : /* istanbul ignore next */ []));
|
|
6713
|
+
const isAuthenticated = computed(() => !!accessToken(), /* @ts-ignore */
|
|
6714
|
+
...(ngDevMode ? [{ debugName: "isAuthenticated" }] : /* istanbul ignore next */ []));
|
|
6715
|
+
const latestExecutedQuery = signal(null, /* @ts-ignore */
|
|
6716
|
+
...(ngDevMode ? [{ debugName: "latestExecutedQuery" }] : /* istanbul ignore next */ []));
|
|
6717
|
+
const latestNonInternalQuery = signal(null, /* @ts-ignore */
|
|
6718
|
+
...(ngDevMode ? [{ debugName: "latestNonInternalQuery" }] : /* istanbul ignore next */ []));
|
|
6719
|
+
const executionState = signal(null, /* @ts-ignore */
|
|
6720
|
+
...(ngDevMode ? [{ debugName: "executionState" }] : /* istanbul ignore next */ []));
|
|
5658
6721
|
const setTokens = (access, refresh) => {
|
|
5659
6722
|
accessToken.set(access);
|
|
5660
6723
|
refreshToken.set(refresh);
|
|
5661
6724
|
afterTokenRefresh$.next();
|
|
5662
6725
|
};
|
|
5663
|
-
const { queries } = setupBearerQueryRegistry(config.queries, injector, latestExecutedQuery, latestNonInternalQuery, setTokens);
|
|
6726
|
+
const { queries } = setupBearerQueryRegistry(config.queries, injector, latestExecutedQuery, latestNonInternalQuery, setTokens, executionState);
|
|
5664
6727
|
const logout = () => {
|
|
5665
6728
|
accessToken.set(null);
|
|
5666
6729
|
refreshToken.set(null);
|
|
5667
6730
|
queryClient.repository.unbindAllSecure();
|
|
6731
|
+
latestExecutedQuery.set(null);
|
|
6732
|
+
latestNonInternalQuery.set(null);
|
|
6733
|
+
executionState.set({ type: 'logout', state: 'success' });
|
|
5668
6734
|
};
|
|
5669
6735
|
const isLeader = createLeaderElection(config);
|
|
5670
6736
|
const querySetupContext = {
|
|
@@ -5692,10 +6758,11 @@ const createBearerAuthProviderImpl = (config) => {
|
|
|
5692
6758
|
leaderElection: isLeader.leaderElectionContext,
|
|
5693
6759
|
afterTokenRefresh$,
|
|
5694
6760
|
queries: queries,
|
|
6761
|
+
executionState,
|
|
5695
6762
|
};
|
|
5696
6763
|
const features = setupFeatures(config.features, featureSetupContext);
|
|
5697
6764
|
setupMultiTabSyncIfEnabled(config, accessToken, refreshToken, queryClient);
|
|
5698
|
-
|
|
6765
|
+
const provider = {
|
|
5699
6766
|
queries,
|
|
5700
6767
|
features: features,
|
|
5701
6768
|
accessToken: accessToken.asReadonly(),
|
|
@@ -5704,9 +6771,25 @@ const createBearerAuthProviderImpl = (config) => {
|
|
|
5704
6771
|
isAuthenticated,
|
|
5705
6772
|
latestExecutedQuery: latestExecutedQuery.asReadonly(),
|
|
5706
6773
|
latestNonInternalQuery: latestNonInternalQuery.asReadonly(),
|
|
6774
|
+
executionState: executionState.asReadonly(),
|
|
6775
|
+
setTokens,
|
|
5707
6776
|
logout,
|
|
5708
6777
|
afterTokenRefresh$,
|
|
5709
6778
|
};
|
|
6779
|
+
if (isQueryDevtoolsEnabled()) {
|
|
6780
|
+
const unregister = registerQueryDevtoolsEntry({
|
|
6781
|
+
kind: 'auth-provider',
|
|
6782
|
+
handle: provider,
|
|
6783
|
+
meta: {
|
|
6784
|
+
name: config.name,
|
|
6785
|
+
clientName: getQueryClientName(config.queryClientRef),
|
|
6786
|
+
clientBaseUrl: queryClient.baseUrl,
|
|
6787
|
+
repository: queryClient.repository,
|
|
6788
|
+
},
|
|
6789
|
+
});
|
|
6790
|
+
destroyRef.onDestroy(unregister);
|
|
6791
|
+
}
|
|
6792
|
+
return provider;
|
|
5710
6793
|
};
|
|
5711
6794
|
const createBearerAuthProvider = (config) => createRootProvider(() => createBearerAuthProviderImpl(config), { name: `BearerAuthProvider_${config.name}` });
|
|
5712
6795
|
|
|
@@ -5853,8 +6936,10 @@ const withInactivityLogout = (config = {}) => {
|
|
|
5853
6936
|
const activityEvents = config.activityEvents ?? ['mousedown', 'keydown', 'scroll', 'touchstart'];
|
|
5854
6937
|
const destroyRef = inject(DestroyRef);
|
|
5855
6938
|
const document = inject(DOCUMENT);
|
|
5856
|
-
const enabled = signal(true,
|
|
5857
|
-
|
|
6939
|
+
const enabled = signal(true, /* @ts-ignore */
|
|
6940
|
+
...(ngDevMode ? [{ debugName: "enabled" }] : /* istanbul ignore next */ []));
|
|
6941
|
+
const lastActivityTime = signal(Date.now(), /* @ts-ignore */
|
|
6942
|
+
...(ngDevMode ? [{ debugName: "lastActivityTime" }] : /* istanbul ignore next */ []));
|
|
5858
6943
|
const resetTimer = () => {
|
|
5859
6944
|
lastActivityTime.set(Date.now());
|
|
5860
6945
|
};
|
|
@@ -5937,7 +7022,8 @@ const createPersistentAuthFeature = (config, context) => {
|
|
|
5937
7022
|
}
|
|
5938
7023
|
return config.defaultRememberMe ?? false;
|
|
5939
7024
|
};
|
|
5940
|
-
const rememberMeSignal = signal(initializeRememberMe(),
|
|
7025
|
+
const rememberMeSignal = signal(initializeRememberMe(), /* @ts-ignore */
|
|
7026
|
+
...(ngDevMode ? [{ debugName: "rememberMeSignal" }] : /* istanbul ignore next */ []));
|
|
5941
7027
|
effect(() => {
|
|
5942
7028
|
const token = refreshToken();
|
|
5943
7029
|
const rememberMe = rememberMeSignal();
|
|
@@ -5998,7 +7084,8 @@ const withTokenExpirationWarning = (config = {}) => {
|
|
|
5998
7084
|
if (typeof exp !== 'number')
|
|
5999
7085
|
return null;
|
|
6000
7086
|
return new Date(exp * 1000);
|
|
6001
|
-
},
|
|
7087
|
+
}, /* @ts-ignore */
|
|
7088
|
+
...(ngDevMode ? [{ debugName: "expiresAt" }] : /* istanbul ignore next */ []));
|
|
6002
7089
|
const expiresIn$ = combineLatest([interval(checkInterval), toObservable(context.bearerData)]).pipe(map(() => {
|
|
6003
7090
|
const expiry = expiresAt();
|
|
6004
7091
|
if (!expiry)
|
|
@@ -6029,23 +7116,27 @@ const withTokenExpirationWarning = (config = {}) => {
|
|
|
6029
7116
|
const withTokenRevocation = (config) => {
|
|
6030
7117
|
return (context) => {
|
|
6031
7118
|
const revokeOnLogout = config.revokeOnLogout ?? true;
|
|
6032
|
-
const enabled = signal(true,
|
|
7119
|
+
const enabled = signal(true, /* @ts-ignore */
|
|
7120
|
+
...(ngDevMode ? [{ debugName: "enabled" }] : /* istanbul ignore next */ []));
|
|
6033
7121
|
let previousAccessToken = null;
|
|
7122
|
+
let previousRefreshToken = null;
|
|
6034
7123
|
let currentRevocationSnapshot = null;
|
|
6035
|
-
const
|
|
7124
|
+
const revokeWithTokens = (accessToken, refreshToken) => {
|
|
6036
7125
|
if (currentRevocationSnapshot?.isAlive())
|
|
6037
7126
|
return currentRevocationSnapshot;
|
|
6038
|
-
const accessToken = context.accessToken();
|
|
6039
|
-
const refreshToken = context.refreshToken();
|
|
6040
7127
|
if (!accessToken && !refreshToken) {
|
|
6041
7128
|
return null;
|
|
6042
7129
|
}
|
|
6043
7130
|
const args = config.buildArgs({ accessToken, refreshToken });
|
|
7131
|
+
context.executionState.set({ type: 'revocation', state: 'loading' });
|
|
6044
7132
|
currentRevocationSnapshot = context.queries[config.queryKey].execute(args, {
|
|
6045
7133
|
triggeredBy: 'token-revocation',
|
|
6046
7134
|
});
|
|
6047
7135
|
return currentRevocationSnapshot;
|
|
6048
7136
|
};
|
|
7137
|
+
const revoke = () => {
|
|
7138
|
+
return revokeWithTokens(context.accessToken(), context.refreshToken());
|
|
7139
|
+
};
|
|
6049
7140
|
const enable = () => {
|
|
6050
7141
|
enabled.set(true);
|
|
6051
7142
|
};
|
|
@@ -6055,10 +7146,12 @@ const withTokenRevocation = (config) => {
|
|
|
6055
7146
|
if (revokeOnLogout) {
|
|
6056
7147
|
effect(() => {
|
|
6057
7148
|
const currentToken = context.accessToken();
|
|
7149
|
+
const currentRefreshToken = context.refreshToken();
|
|
6058
7150
|
if (previousAccessToken && !currentToken && enabled()) {
|
|
6059
|
-
untracked(() =>
|
|
7151
|
+
untracked(() => revokeWithTokens(previousAccessToken, previousRefreshToken));
|
|
6060
7152
|
}
|
|
6061
7153
|
previousAccessToken = currentToken;
|
|
7154
|
+
previousRefreshToken = currentRefreshToken;
|
|
6062
7155
|
}, { injector: context.injector });
|
|
6063
7156
|
}
|
|
6064
7157
|
const instance = {
|
|
@@ -6382,10 +7475,10 @@ class ParseHttpErrorCodeToTitleDePipe {
|
|
|
6382
7475
|
constructor() {
|
|
6383
7476
|
this.transform = parseHttpErrorCodeToTitleDe;
|
|
6384
7477
|
}
|
|
6385
|
-
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "
|
|
6386
|
-
static { this.ɵpipe = i0.ɵɵngDeclarePipe({ minVersion: "14.0.0", version: "
|
|
7478
|
+
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.7", ngImport: i0, type: ParseHttpErrorCodeToTitleDePipe, deps: [], target: i0.ɵɵFactoryTarget.Pipe }); }
|
|
7479
|
+
static { this.ɵpipe = i0.ɵɵngDeclarePipe({ minVersion: "14.0.0", version: "22.0.7", ngImport: i0, type: ParseHttpErrorCodeToTitleDePipe, isStandalone: true, name: "parseHttpErrorCodeToTitleDe" }); }
|
|
6387
7480
|
}
|
|
6388
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "
|
|
7481
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.7", ngImport: i0, type: ParseHttpErrorCodeToTitleDePipe, decorators: [{
|
|
6389
7482
|
type: Pipe,
|
|
6390
7483
|
args: [{
|
|
6391
7484
|
name: 'parseHttpErrorCodeToTitleDe',
|
|
@@ -6395,10 +7488,10 @@ class ParseHttpErrorCodeToMessageDePipe {
|
|
|
6395
7488
|
constructor() {
|
|
6396
7489
|
this.transform = parseHttpErrorCodeToMessageDe;
|
|
6397
7490
|
}
|
|
6398
|
-
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "
|
|
6399
|
-
static { this.ɵpipe = i0.ɵɵngDeclarePipe({ minVersion: "14.0.0", version: "
|
|
7491
|
+
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.7", ngImport: i0, type: ParseHttpErrorCodeToMessageDePipe, deps: [], target: i0.ɵɵFactoryTarget.Pipe }); }
|
|
7492
|
+
static { this.ɵpipe = i0.ɵɵngDeclarePipe({ minVersion: "14.0.0", version: "22.0.7", ngImport: i0, type: ParseHttpErrorCodeToMessageDePipe, isStandalone: true, name: "parseHttpErrorCodeToMessageDe" }); }
|
|
6400
7493
|
}
|
|
6401
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "
|
|
7494
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.7", ngImport: i0, type: ParseHttpErrorCodeToMessageDePipe, decorators: [{
|
|
6402
7495
|
type: Pipe,
|
|
6403
7496
|
args: [{
|
|
6404
7497
|
name: 'parseHttpErrorCodeToMessageDe',
|
|
@@ -6582,10 +7675,10 @@ class ParseHttpErrorCodeToTitleEnPipe {
|
|
|
6582
7675
|
constructor() {
|
|
6583
7676
|
this.transform = parseHttpErrorCodeToTitleEn;
|
|
6584
7677
|
}
|
|
6585
|
-
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "
|
|
6586
|
-
static { this.ɵpipe = i0.ɵɵngDeclarePipe({ minVersion: "14.0.0", version: "
|
|
7678
|
+
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.7", ngImport: i0, type: ParseHttpErrorCodeToTitleEnPipe, deps: [], target: i0.ɵɵFactoryTarget.Pipe }); }
|
|
7679
|
+
static { this.ɵpipe = i0.ɵɵngDeclarePipe({ minVersion: "14.0.0", version: "22.0.7", ngImport: i0, type: ParseHttpErrorCodeToTitleEnPipe, isStandalone: true, name: "parseHttpErrorCodeToTitleEn" }); }
|
|
6587
7680
|
}
|
|
6588
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "
|
|
7681
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.7", ngImport: i0, type: ParseHttpErrorCodeToTitleEnPipe, decorators: [{
|
|
6589
7682
|
type: Pipe,
|
|
6590
7683
|
args: [{
|
|
6591
7684
|
name: 'parseHttpErrorCodeToTitleEn',
|
|
@@ -6595,10 +7688,10 @@ class ParseHttpErrorCodeToMessageEnPipe {
|
|
|
6595
7688
|
constructor() {
|
|
6596
7689
|
this.transform = parseHttpErrorCodeToMessageEn;
|
|
6597
7690
|
}
|
|
6598
|
-
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "
|
|
6599
|
-
static { this.ɵpipe = i0.ɵɵngDeclarePipe({ minVersion: "14.0.0", version: "
|
|
7691
|
+
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.7", ngImport: i0, type: ParseHttpErrorCodeToMessageEnPipe, deps: [], target: i0.ɵɵFactoryTarget.Pipe }); }
|
|
7692
|
+
static { this.ɵpipe = i0.ɵɵngDeclarePipe({ minVersion: "14.0.0", version: "22.0.7", ngImport: i0, type: ParseHttpErrorCodeToMessageEnPipe, isStandalone: true, name: "parseHttpErrorCodeToMessageEn" }); }
|
|
6600
7693
|
}
|
|
6601
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "
|
|
7694
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.7", ngImport: i0, type: ParseHttpErrorCodeToMessageEnPipe, decorators: [{
|
|
6602
7695
|
type: Pipe,
|
|
6603
7696
|
args: [{
|
|
6604
7697
|
name: 'parseHttpErrorCodeToMessageEn',
|
|
@@ -6806,7 +7899,7 @@ class DateArrayQueryField {
|
|
|
6806
7899
|
};
|
|
6807
7900
|
}
|
|
6808
7901
|
}
|
|
6809
|
-
const IGNORED_FILTER_COUNT_FIELDS = ['page', 'skip', 'take', 'limit', 'sort', 'sortBy', 'sortOrder', 'query', 'search'];
|
|
7902
|
+
const IGNORED_FILTER_COUNT_FIELDS$1 = ['page', 'skip', 'take', 'limit', 'sort', 'sortBy', 'sortOrder', 'query', 'search'];
|
|
6810
7903
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
6811
7904
|
class QueryForm {
|
|
6812
7905
|
get _formValue() {
|
|
@@ -6881,7 +7974,7 @@ class QueryForm {
|
|
|
6881
7974
|
*/
|
|
6882
7975
|
this.activeFilterCount$ = this.changes$.pipe(map(({ currentValue }) => Object.entries(currentValue)
|
|
6883
7976
|
.map(([key, value]) => {
|
|
6884
|
-
if (IGNORED_FILTER_COUNT_FIELDS.includes(key) || this._fields[key]?.data.skipInFilterCount) {
|
|
7977
|
+
if (IGNORED_FILTER_COUNT_FIELDS$1.includes(key) || this._fields[key]?.data.skipInFilterCount) {
|
|
6885
7978
|
return true;
|
|
6886
7979
|
}
|
|
6887
7980
|
return this._isDefaultValue(key, value);
|
|
@@ -7022,8 +8115,11 @@ class QueryForm {
|
|
|
7022
8115
|
continue;
|
|
7023
8116
|
const valueGotRemoved = value === ET_PROPERTY_REMOVED;
|
|
7024
8117
|
if (valueGotRemoved) {
|
|
7025
|
-
|
|
7026
|
-
|
|
8118
|
+
const defaultValue = this._getDefaultValue(key);
|
|
8119
|
+
if (!equal(field.control.value, defaultValue)) {
|
|
8120
|
+
field.control.setValue(defaultValue, { emitEvent: false });
|
|
8121
|
+
didValueChanges = true;
|
|
8122
|
+
}
|
|
7027
8123
|
continue;
|
|
7028
8124
|
}
|
|
7029
8125
|
let deserializedValue = value;
|
|
@@ -7195,6 +8291,21 @@ class QueryForm {
|
|
|
7195
8291
|
});
|
|
7196
8292
|
}
|
|
7197
8293
|
_handleFormChange(forceOverwrite = false) {
|
|
8294
|
+
// Normalize values that have a valueToQueryParamTransformFn returning null/undefined to their
|
|
8295
|
+
// field default before capturing the new value. This prevents intermediate "empty object" states
|
|
8296
|
+
// (e.g. Sort emitting { active: '', direction: '' } when cleared) from being
|
|
8297
|
+
// emitted as distinct form values.
|
|
8298
|
+
for (const [key, field] of Object.entries(this._fields)) {
|
|
8299
|
+
if (!field.data.valueToQueryParamTransformFn)
|
|
8300
|
+
continue;
|
|
8301
|
+
const rawValue = this._formValue[key];
|
|
8302
|
+
if (this._isDefaultValue(key, rawValue))
|
|
8303
|
+
continue;
|
|
8304
|
+
const serialized = field.data.valueToQueryParamTransformFn(rawValue);
|
|
8305
|
+
if (serialized !== null && serialized !== undefined)
|
|
8306
|
+
continue;
|
|
8307
|
+
this.form.controls[key]?.setValue(this._getDefaultValue(key), { emitEvent: false });
|
|
8308
|
+
}
|
|
7198
8309
|
const currentVal = clone(this._currentFormValue$.value);
|
|
7199
8310
|
const newVal = clone(this._formValue);
|
|
7200
8311
|
if (equal(currentVal, newVal)) {
|
|
@@ -7229,6 +8340,433 @@ class QueryForm {
|
|
|
7229
8340
|
}
|
|
7230
8341
|
}
|
|
7231
8342
|
|
|
8343
|
+
/** URL sentinel for an explicit `null` value (a bare empty param would be ambiguous). */
|
|
8344
|
+
const ET_NULL_VALUE = 'ET_NULL__';
|
|
8345
|
+
/**
|
|
8346
|
+
* Fields excluded from `activeFilterCount` by default — pagination, sorting and
|
|
8347
|
+
* search are navigation state, not filters.
|
|
8348
|
+
*/
|
|
8349
|
+
const IGNORED_FILTER_COUNT_FIELDS = [
|
|
8350
|
+
'page',
|
|
8351
|
+
'skip',
|
|
8352
|
+
'take',
|
|
8353
|
+
'limit',
|
|
8354
|
+
'sort',
|
|
8355
|
+
'sortBy',
|
|
8356
|
+
'sortOrder',
|
|
8357
|
+
'query',
|
|
8358
|
+
'search',
|
|
8359
|
+
];
|
|
8360
|
+
const resolveDefault = (def) => {
|
|
8361
|
+
const { defaultValue } = def;
|
|
8362
|
+
return typeof defaultValue === 'function' ? defaultValue() : (defaultValue ?? null);
|
|
8363
|
+
};
|
|
8364
|
+
const buildDefaults = (fields) => {
|
|
8365
|
+
const out = {};
|
|
8366
|
+
for (const [key, def] of Object.entries(fields)) {
|
|
8367
|
+
out[key] = resolveDefault(def);
|
|
8368
|
+
}
|
|
8369
|
+
return out;
|
|
8370
|
+
};
|
|
8371
|
+
const changedKeysBetween = (previous, current) => Object.keys(current).filter((key) => !equal(previous?.[key], current[key]));
|
|
8372
|
+
/** Best-effort URL string → value coercion, mirroring the auto-transform of the legacy QueryForm. */
|
|
8373
|
+
const autoCoerce = (raw, defaultValue) => {
|
|
8374
|
+
if (raw === ET_NULL_VALUE)
|
|
8375
|
+
return null;
|
|
8376
|
+
if (typeof raw !== 'string')
|
|
8377
|
+
return raw;
|
|
8378
|
+
const defaultIsNumber = typeof defaultValue === 'number';
|
|
8379
|
+
const looksNumeric = raw.trim() === raw && !raw.startsWith('0') && !raw.endsWith('.') && !isNaN(Number(raw));
|
|
8380
|
+
if (defaultIsNumber || (looksNumeric && raw !== '')) {
|
|
8381
|
+
return transformToNumber(raw);
|
|
8382
|
+
}
|
|
8383
|
+
if (raw === 'true' || raw === 'false') {
|
|
8384
|
+
return transformToBoolean(raw);
|
|
8385
|
+
}
|
|
8386
|
+
return raw;
|
|
8387
|
+
};
|
|
8388
|
+
const computeFilterCount = (fields, value, defaults) => {
|
|
8389
|
+
let count = 0;
|
|
8390
|
+
for (const [key, def] of Object.entries(fields)) {
|
|
8391
|
+
if (IGNORED_FILTER_COUNT_FIELDS.includes(key) || def.skipInFilterCount)
|
|
8392
|
+
continue;
|
|
8393
|
+
if (!equal(value[key], defaults[key]))
|
|
8394
|
+
count++;
|
|
8395
|
+
}
|
|
8396
|
+
return count;
|
|
8397
|
+
};
|
|
8398
|
+
const createBranch = (fields, initial, injector) => {
|
|
8399
|
+
const defaults = buildDefaults(fields);
|
|
8400
|
+
const model = signal(clone(initial), /* @ts-ignore */
|
|
8401
|
+
...(ngDevMode ? [{ debugName: "model" }] : /* istanbul ignore next */ []));
|
|
8402
|
+
const tree = form(model, { injector });
|
|
8403
|
+
return {
|
|
8404
|
+
fields: tree,
|
|
8405
|
+
value: model.asReadonly(),
|
|
8406
|
+
activeFilterCount: computed(() => computeFilterCount(fields, model(), defaults)),
|
|
8407
|
+
setValue: (value) => model.set(clone(value)),
|
|
8408
|
+
patchValue: (value) => model.update((cur) => ({ ...cur, ...value })),
|
|
8409
|
+
resetFieldToDefault: (key) => model.update((cur) => ({ ...cur, [key]: defaults[key] })),
|
|
8410
|
+
resetAllFieldsToDefault: () => model.set(clone(defaults)),
|
|
8411
|
+
};
|
|
8412
|
+
};
|
|
8413
|
+
/**
|
|
8414
|
+
* A signals-first query form: binds on-screen controls to typed fields, keeps a
|
|
8415
|
+
* debounced committed value, syncs to the URL query params, and resets dependent
|
|
8416
|
+
* fields via the `isResetBy` graph — all as signals.
|
|
8417
|
+
*
|
|
8418
|
+
* @example
|
|
8419
|
+
* const qf = createQueryForm({
|
|
8420
|
+
* fields: {
|
|
8421
|
+
* search: searchQueryField(),
|
|
8422
|
+
* sort: sortQueryField(),
|
|
8423
|
+
* page: queryField<number>({ defaultValue: 1, isResetBy: ['search', 'sort'] }),
|
|
8424
|
+
* },
|
|
8425
|
+
* }).observe();
|
|
8426
|
+
* // template: <input etInput [formField]="qf.fields.search" />
|
|
8427
|
+
*/
|
|
8428
|
+
class QueryFormSignals {
|
|
8429
|
+
constructor(config) {
|
|
8430
|
+
this._router = inject(Router);
|
|
8431
|
+
this._route = inject(ActivatedRoute);
|
|
8432
|
+
this._injector = inject(Injector);
|
|
8433
|
+
this._previous = signal(null, /* @ts-ignore */
|
|
8434
|
+
...(ngDevMode ? [{ debugName: "_previous" }] : /* istanbul ignore next */ []));
|
|
8435
|
+
this._observing = signal(false, /* @ts-ignore */
|
|
8436
|
+
...(ngDevMode ? [{ debugName: "_observing" }] : /* istanbul ignore next */ []));
|
|
8437
|
+
this._queryParamChanges = injectQueryParamChanges();
|
|
8438
|
+
this._pendingTimer = null;
|
|
8439
|
+
this._skipNextResets = false;
|
|
8440
|
+
assertInInjectionContext(QueryFormSignals);
|
|
8441
|
+
this._fields = config.fields;
|
|
8442
|
+
this._prefix = config.queryParamPrefix;
|
|
8443
|
+
this._defaults = buildDefaults(config.fields);
|
|
8444
|
+
this.defaultValue = clone(this._defaults);
|
|
8445
|
+
const initial = clone(this._defaults);
|
|
8446
|
+
this._model = signal(initial, /* @ts-ignore */
|
|
8447
|
+
...(ngDevMode ? [{ debugName: "_model" }] : /* istanbul ignore next */ []));
|
|
8448
|
+
this._committed = signal(clone(initial), /* @ts-ignore */
|
|
8449
|
+
...(ngDevMode ? [{ debugName: "_committed" }] : /* istanbul ignore next */ []));
|
|
8450
|
+
this.fields = form(this._model);
|
|
8451
|
+
this.value = this._committed.asReadonly();
|
|
8452
|
+
this.previousValue = this._previous.asReadonly();
|
|
8453
|
+
this.changes = computed(() => ({ previousValue: this._previous(), currentValue: this._committed() }), /* @ts-ignore */
|
|
8454
|
+
...(ngDevMode ? [{ debugName: "changes" }] : /* istanbul ignore next */ []));
|
|
8455
|
+
this.activeFilterCount = computed(() => computeFilterCount(this._fields, this._committed(), this._defaults), /* @ts-ignore */
|
|
8456
|
+
...(ngDevMode ? [{ debugName: "activeFilterCount" }] : /* istanbul ignore next */ []));
|
|
8457
|
+
// React to live control edits: schedule a debounced commit.
|
|
8458
|
+
effect(() => {
|
|
8459
|
+
const live = this._model();
|
|
8460
|
+
untracked(() => this._onLiveChange(live));
|
|
8461
|
+
});
|
|
8462
|
+
// React to navigation (back/forward, external links): apply URL → form immediately.
|
|
8463
|
+
effect(() => {
|
|
8464
|
+
const changes = this._queryParamChanges();
|
|
8465
|
+
untracked(() => {
|
|
8466
|
+
if (!this._observing() || this._observeOptions?.syncOnNavigation === false)
|
|
8467
|
+
return;
|
|
8468
|
+
this._applyFromUrl(changes);
|
|
8469
|
+
});
|
|
8470
|
+
});
|
|
8471
|
+
inject(DestroyRef).onDestroy(() => this._cleanup());
|
|
8472
|
+
}
|
|
8473
|
+
observe(options) {
|
|
8474
|
+
if (this._observing()) {
|
|
8475
|
+
if (isDevMode()) {
|
|
8476
|
+
console.warn('QueryFormSignals.observe() was called more than once. Ignoring the extra call.');
|
|
8477
|
+
}
|
|
8478
|
+
return this;
|
|
8479
|
+
}
|
|
8480
|
+
this._observeOptions = options;
|
|
8481
|
+
this._observing.set(true);
|
|
8482
|
+
if (options?.syncOnNavigation !== false) {
|
|
8483
|
+
this._applyFromUrl(this._route.snapshot.queryParams);
|
|
8484
|
+
}
|
|
8485
|
+
// Commit whatever the model holds now (URL-restored or programmatic defaults).
|
|
8486
|
+
this._flush();
|
|
8487
|
+
return this;
|
|
8488
|
+
}
|
|
8489
|
+
unobserve() {
|
|
8490
|
+
this._cleanup();
|
|
8491
|
+
}
|
|
8492
|
+
setValue(value, options) {
|
|
8493
|
+
if (options?.skipResets)
|
|
8494
|
+
this._skipNextResets = true;
|
|
8495
|
+
this._model.set(clone(value));
|
|
8496
|
+
}
|
|
8497
|
+
patchValue(value, options) {
|
|
8498
|
+
if (options?.skipResets)
|
|
8499
|
+
this._skipNextResets = true;
|
|
8500
|
+
this._model.update((cur) => ({ ...cur, ...value }));
|
|
8501
|
+
}
|
|
8502
|
+
resetFieldToDefault(key, options) {
|
|
8503
|
+
if (options?.skipResets)
|
|
8504
|
+
this._skipNextResets = true;
|
|
8505
|
+
this._model.update((cur) => ({ ...cur, [key]: this._defaults[key] }));
|
|
8506
|
+
}
|
|
8507
|
+
resetFieldsToDefault(keys, options) {
|
|
8508
|
+
if (options?.skipResets)
|
|
8509
|
+
this._skipNextResets = true;
|
|
8510
|
+
this._model.update((cur) => {
|
|
8511
|
+
const next = { ...cur };
|
|
8512
|
+
for (const key of keys) {
|
|
8513
|
+
next[key] = this._defaults[key];
|
|
8514
|
+
}
|
|
8515
|
+
return next;
|
|
8516
|
+
});
|
|
8517
|
+
}
|
|
8518
|
+
resetAllFieldsToDefault(options) {
|
|
8519
|
+
const skip = new Set((options?.skipFields ?? []).map((key) => key));
|
|
8520
|
+
const keys = Object.keys(this._fields).filter((key) => !skip.has(key));
|
|
8521
|
+
this.resetFieldsToDefault(keys, options);
|
|
8522
|
+
}
|
|
8523
|
+
/** Create a detached editor over the same fields, seeded from the current committed value. */
|
|
8524
|
+
branch() {
|
|
8525
|
+
return createBranch(this._fields, clone(this._committed()), this._injector);
|
|
8526
|
+
}
|
|
8527
|
+
_onLiveChange(live) {
|
|
8528
|
+
if (!this._observing())
|
|
8529
|
+
return;
|
|
8530
|
+
if (equal(live, this._committed()))
|
|
8531
|
+
return;
|
|
8532
|
+
const changedKeys = changedKeysBetween(this._committed(), live);
|
|
8533
|
+
const debounceMs = this._resolveDebounce(changedKeys, live);
|
|
8534
|
+
this._clearTimer();
|
|
8535
|
+
if (debounceMs === null) {
|
|
8536
|
+
this._flush();
|
|
8537
|
+
}
|
|
8538
|
+
else {
|
|
8539
|
+
this._pendingTimer = setTimeout(() => {
|
|
8540
|
+
this._pendingTimer = null;
|
|
8541
|
+
this._flush();
|
|
8542
|
+
}, debounceMs);
|
|
8543
|
+
}
|
|
8544
|
+
}
|
|
8545
|
+
/** Shortest-debounce-wins; `null` means commit immediately. */
|
|
8546
|
+
_resolveDebounce(changedKeys, live) {
|
|
8547
|
+
if (!changedKeys.length)
|
|
8548
|
+
return null;
|
|
8549
|
+
const times = [];
|
|
8550
|
+
for (const key of changedKeys) {
|
|
8551
|
+
const def = this._fields[key];
|
|
8552
|
+
if (!def)
|
|
8553
|
+
return null;
|
|
8554
|
+
if (def.disableDebounceIfFalsy && !live[key])
|
|
8555
|
+
return null;
|
|
8556
|
+
if (def.debounce === undefined)
|
|
8557
|
+
return null;
|
|
8558
|
+
times.push(def.debounce);
|
|
8559
|
+
}
|
|
8560
|
+
return Math.min(...times);
|
|
8561
|
+
}
|
|
8562
|
+
_flush() {
|
|
8563
|
+
this._clearTimer();
|
|
8564
|
+
const live = this._model();
|
|
8565
|
+
const previous = this._committed();
|
|
8566
|
+
if (equal(live, previous)) {
|
|
8567
|
+
this._skipNextResets = false;
|
|
8568
|
+
return;
|
|
8569
|
+
}
|
|
8570
|
+
const changedKeys = changedKeysBetween(previous, live);
|
|
8571
|
+
const next = this._skipNextResets ? { ...live } : this._applyResets(live, changedKeys);
|
|
8572
|
+
this._skipNextResets = false;
|
|
8573
|
+
this._previous.set(clone(previous));
|
|
8574
|
+
this._committed.set(clone(next));
|
|
8575
|
+
// Reflect any reset overrides back into the bound controls. The effect re-runs
|
|
8576
|
+
// but no-ops because the model now equals the committed value.
|
|
8577
|
+
if (!equal(next, live)) {
|
|
8578
|
+
this._model.set(clone(next));
|
|
8579
|
+
}
|
|
8580
|
+
if (this._observing() && this._observeOptions?.writeToQueryParams !== false) {
|
|
8581
|
+
this._writeToUrl(next);
|
|
8582
|
+
}
|
|
8583
|
+
}
|
|
8584
|
+
_applyResets(live, changedKeys) {
|
|
8585
|
+
const next = { ...live };
|
|
8586
|
+
for (const [key, def] of Object.entries(this._fields)) {
|
|
8587
|
+
const resets = def.isResetBy;
|
|
8588
|
+
if (!resets?.length)
|
|
8589
|
+
continue;
|
|
8590
|
+
const triggered = resets.some((resetKey) => {
|
|
8591
|
+
if (!(resetKey in this._fields)) {
|
|
8592
|
+
if (isDevMode()) {
|
|
8593
|
+
console.warn(`QueryFormSignals: isResetBy references unknown field "${resetKey}". Is it a typo?`);
|
|
8594
|
+
}
|
|
8595
|
+
return false;
|
|
8596
|
+
}
|
|
8597
|
+
return changedKeys.includes(resetKey);
|
|
8598
|
+
});
|
|
8599
|
+
if (!triggered)
|
|
8600
|
+
continue;
|
|
8601
|
+
const defaultValue = this._defaults[key];
|
|
8602
|
+
if (!equal(next[key], defaultValue)) {
|
|
8603
|
+
next[key] = defaultValue;
|
|
8604
|
+
}
|
|
8605
|
+
}
|
|
8606
|
+
return next;
|
|
8607
|
+
}
|
|
8608
|
+
_applyFromUrl(params) {
|
|
8609
|
+
const current = { ...this._committed() };
|
|
8610
|
+
let changed = false;
|
|
8611
|
+
for (const [key, def] of Object.entries(this._fields)) {
|
|
8612
|
+
const raw = params[this._paramKey(key)];
|
|
8613
|
+
if (raw === undefined)
|
|
8614
|
+
continue;
|
|
8615
|
+
const value = raw === ET_PROPERTY_REMOVED ? this._defaults[key] : this._deserialize(def, raw);
|
|
8616
|
+
if (!equal(current[key], value)) {
|
|
8617
|
+
current[key] = value;
|
|
8618
|
+
changed = true;
|
|
8619
|
+
}
|
|
8620
|
+
}
|
|
8621
|
+
if (!changed)
|
|
8622
|
+
return;
|
|
8623
|
+
this._commitFromUrl(current);
|
|
8624
|
+
}
|
|
8625
|
+
_commitFromUrl(next) {
|
|
8626
|
+
this._clearTimer();
|
|
8627
|
+
this._previous.set(clone(this._committed()));
|
|
8628
|
+
this._committed.set(clone(next));
|
|
8629
|
+
// Feed the URL value into the live model & bound controls; the effect no-ops (model === committed).
|
|
8630
|
+
this._model.set(clone(next));
|
|
8631
|
+
}
|
|
8632
|
+
_deserialize(def, raw) {
|
|
8633
|
+
if (def.queryParamToValue)
|
|
8634
|
+
return def.queryParamToValue(raw);
|
|
8635
|
+
if (def.skipAutoTransform)
|
|
8636
|
+
return raw;
|
|
8637
|
+
return autoCoerce(raw, resolveDefault(def));
|
|
8638
|
+
}
|
|
8639
|
+
_writeToUrl(value) {
|
|
8640
|
+
const queryParams = {};
|
|
8641
|
+
for (const [key, def] of Object.entries(this._fields)) {
|
|
8642
|
+
const paramKey = this._paramKey(key);
|
|
8643
|
+
const fieldValue = value[key];
|
|
8644
|
+
const isDefault = equal(fieldValue, this._defaults[key]);
|
|
8645
|
+
const writeToUrl = def.appendToUrl !== false;
|
|
8646
|
+
const writeDefault = def.appendDefaultValueToUrl === true;
|
|
8647
|
+
if (!writeToUrl || (isDefault && !writeDefault)) {
|
|
8648
|
+
queryParams[paramKey] = undefined;
|
|
8649
|
+
}
|
|
8650
|
+
else if (def.valueToQueryParam) {
|
|
8651
|
+
queryParams[paramKey] = def.valueToQueryParam(fieldValue);
|
|
8652
|
+
}
|
|
8653
|
+
else {
|
|
8654
|
+
queryParams[paramKey] = fieldValue === null ? ET_NULL_VALUE : fieldValue;
|
|
8655
|
+
}
|
|
8656
|
+
}
|
|
8657
|
+
queueMicrotask(() => {
|
|
8658
|
+
this._router.navigate([], {
|
|
8659
|
+
queryParams,
|
|
8660
|
+
queryParamsHandling: 'merge',
|
|
8661
|
+
replaceUrl: this._observeOptions?.replaceUrl,
|
|
8662
|
+
});
|
|
8663
|
+
});
|
|
8664
|
+
}
|
|
8665
|
+
_paramKey(key) {
|
|
8666
|
+
const prefix = this._prefix;
|
|
8667
|
+
if (!prefix)
|
|
8668
|
+
return key;
|
|
8669
|
+
return `${typeof prefix === 'string' ? prefix : prefix()}-${key}`;
|
|
8670
|
+
}
|
|
8671
|
+
_clearTimer() {
|
|
8672
|
+
if (this._pendingTimer !== null) {
|
|
8673
|
+
clearTimeout(this._pendingTimer);
|
|
8674
|
+
this._pendingTimer = null;
|
|
8675
|
+
}
|
|
8676
|
+
}
|
|
8677
|
+
_cleanup() {
|
|
8678
|
+
if (!this._observing())
|
|
8679
|
+
return;
|
|
8680
|
+
this._observing.set(false);
|
|
8681
|
+
this._clearTimer();
|
|
8682
|
+
if (this._observeOptions?.writeToQueryParams === false)
|
|
8683
|
+
return;
|
|
8684
|
+
const queryParams = {};
|
|
8685
|
+
for (const key of Object.keys(this._fields)) {
|
|
8686
|
+
queryParams[this._paramKey(key)] = undefined;
|
|
8687
|
+
}
|
|
8688
|
+
queueMicrotask(() => {
|
|
8689
|
+
this._router.navigate([], { queryParams, queryParamsHandling: 'merge', replaceUrl: true });
|
|
8690
|
+
});
|
|
8691
|
+
}
|
|
8692
|
+
}
|
|
8693
|
+
/**
|
|
8694
|
+
* Create a {@link QueryFormSignals}. Call `.observe()` to start syncing with the
|
|
8695
|
+
* URL. Must be called in an injection context.
|
|
8696
|
+
*/
|
|
8697
|
+
const createQueryForm = (config) => new QueryFormSignals(config);
|
|
8698
|
+
|
|
8699
|
+
const normalizeConfig = (config) => {
|
|
8700
|
+
if (!config)
|
|
8701
|
+
return {};
|
|
8702
|
+
const { isResetBy, ...rest } = config;
|
|
8703
|
+
return {
|
|
8704
|
+
...rest,
|
|
8705
|
+
isResetBy: isResetBy === undefined ? undefined : Array.isArray(isResetBy) ? isResetBy : [isResetBy],
|
|
8706
|
+
};
|
|
8707
|
+
};
|
|
8708
|
+
/**
|
|
8709
|
+
* A generic query-form field. Defaults to `null` and, unless a transform is
|
|
8710
|
+
* given, relies on the auto-coercion when reading back from the URL.
|
|
8711
|
+
*
|
|
8712
|
+
* @example
|
|
8713
|
+
* queryField<number>({ defaultValue: 1, isResetBy: ['search'] })
|
|
8714
|
+
*/
|
|
8715
|
+
const queryField = (config) => ({
|
|
8716
|
+
defaultValue: null,
|
|
8717
|
+
...normalizeConfig(config),
|
|
8718
|
+
});
|
|
8719
|
+
/**
|
|
8720
|
+
* A debounced free-text search field. Debounces typing by 300ms but applies
|
|
8721
|
+
* clearing immediately (`disableDebounceIfFalsy`).
|
|
8722
|
+
*/
|
|
8723
|
+
const searchQueryField = (config) => ({
|
|
8724
|
+
defaultValue: null,
|
|
8725
|
+
debounce: 300,
|
|
8726
|
+
disableDebounceIfFalsy: true,
|
|
8727
|
+
...normalizeConfig(config),
|
|
8728
|
+
});
|
|
8729
|
+
/**
|
|
8730
|
+
* A sort field serialized as `"active:direction"` (e.g. `"name:asc"`). Matches
|
|
8731
|
+
* the wire format the table system's URL adapter uses, so the two interoperate.
|
|
8732
|
+
*/
|
|
8733
|
+
const sortQueryField = (config) => ({
|
|
8734
|
+
defaultValue: null,
|
|
8735
|
+
queryParamToValue: transformToSort,
|
|
8736
|
+
valueToQueryParam: transformToSortQueryParam,
|
|
8737
|
+
...normalizeConfig(config),
|
|
8738
|
+
});
|
|
8739
|
+
/** A field holding a list of strings. */
|
|
8740
|
+
const stringArrayQueryField = (config) => ({
|
|
8741
|
+
defaultValue: null,
|
|
8742
|
+
queryParamToValue: transformToStringArray,
|
|
8743
|
+
...normalizeConfig(config),
|
|
8744
|
+
});
|
|
8745
|
+
/** A field holding a list of numbers. */
|
|
8746
|
+
const numberArrayQueryField = (config) => ({
|
|
8747
|
+
defaultValue: null,
|
|
8748
|
+
queryParamToValue: transformToNumberArray,
|
|
8749
|
+
...normalizeConfig(config),
|
|
8750
|
+
});
|
|
8751
|
+
/** A field holding a list of booleans. */
|
|
8752
|
+
const booleanArrayQueryField = (config) => ({
|
|
8753
|
+
defaultValue: null,
|
|
8754
|
+
queryParamToValue: transformToBooleanArray,
|
|
8755
|
+
...normalizeConfig(config),
|
|
8756
|
+
});
|
|
8757
|
+
/** A single date field (expects an ISO/`Date`-parseable string in the URL). */
|
|
8758
|
+
const dateQueryField = (config) => ({
|
|
8759
|
+
defaultValue: null,
|
|
8760
|
+
queryParamToValue: transformToDate,
|
|
8761
|
+
...normalizeConfig(config),
|
|
8762
|
+
});
|
|
8763
|
+
/** A field holding a list of dates. */
|
|
8764
|
+
const dateArrayQueryField = (config) => ({
|
|
8765
|
+
defaultValue: null,
|
|
8766
|
+
queryParamToValue: transformToDateArray,
|
|
8767
|
+
...normalizeConfig(config),
|
|
8768
|
+
});
|
|
8769
|
+
|
|
7232
8770
|
// codes 1000-1999
|
|
7233
8771
|
const WebSocketRuntimeErrorCode = {
|
|
7234
8772
|
// Web Socket Client
|
|
@@ -7242,6 +8780,7 @@ const messageMalformed = () => {
|
|
|
7242
8780
|
return new RuntimeError(WebSocketRuntimeErrorCode.MESSAGE_MALFORMED, 'A message has been received but it is malformed (cannot be parsed as JSON).');
|
|
7243
8781
|
};
|
|
7244
8782
|
|
|
8783
|
+
const MAX_DEVTOOLS_MESSAGES = 100;
|
|
7245
8784
|
const createWebSocketClient = (options) => {
|
|
7246
8785
|
return createRootProvider(() => {
|
|
7247
8786
|
const socket = io(options.url, {
|
|
@@ -7250,21 +8789,36 @@ const createWebSocketClient = (options) => {
|
|
|
7250
8789
|
transports: options.transports,
|
|
7251
8790
|
});
|
|
7252
8791
|
const rooms = new Map();
|
|
7253
|
-
const isConnected = signal(false,
|
|
8792
|
+
const isConnected = signal(false, /* @ts-ignore */
|
|
8793
|
+
...(ngDevMode ? [{ debugName: "isConnected" }] : /* istanbul ignore next */ []));
|
|
8794
|
+
// Devtools instrumentation (no-op unless provideQueryDevtools() was called).
|
|
8795
|
+
const devtoolsEnabled = isQueryDevtoolsEnabled();
|
|
8796
|
+
const devtoolsRooms = signal([], /* @ts-ignore */
|
|
8797
|
+
...(ngDevMode ? [{ debugName: "devtoolsRooms" }] : /* istanbul ignore next */ []));
|
|
8798
|
+
const devtoolsMessages = signal([], /* @ts-ignore */
|
|
8799
|
+
...(ngDevMode ? [{ debugName: "devtoolsMessages" }] : /* istanbul ignore next */ []));
|
|
8800
|
+
let devtoolsMessageId = 0;
|
|
8801
|
+
const syncDevtoolsRooms = () => {
|
|
8802
|
+
if (devtoolsEnabled)
|
|
8803
|
+
devtoolsRooms.set([...rooms.keys()]);
|
|
8804
|
+
};
|
|
7254
8805
|
const joinRoom = (room) => {
|
|
7255
8806
|
const roomFn = typeof room === 'function' ? room : () => room;
|
|
7256
8807
|
const pre = previousSignalValue(computed(() => roomFn()));
|
|
7257
|
-
const roomData = signal(null,
|
|
8808
|
+
const roomData = signal(null, /* @ts-ignore */
|
|
8809
|
+
...(ngDevMode ? [{ debugName: "roomData" }] : /* istanbul ignore next */ []));
|
|
7258
8810
|
const join = (name) => {
|
|
7259
8811
|
socket.emit('join-room', name);
|
|
7260
8812
|
const existingRoom = rooms.get(name);
|
|
7261
8813
|
if (existingRoom)
|
|
7262
8814
|
return existingRoom;
|
|
7263
|
-
const message = signal(null,
|
|
8815
|
+
const message = signal(null, /* @ts-ignore */
|
|
8816
|
+
...(ngDevMode ? [{ debugName: "message" }] : /* istanbul ignore next */ []));
|
|
7264
8817
|
const newRoom = {
|
|
7265
8818
|
latestMessage: message,
|
|
7266
8819
|
};
|
|
7267
8820
|
rooms.set(name, newRoom);
|
|
8821
|
+
syncDevtoolsRooms();
|
|
7268
8822
|
return newRoom;
|
|
7269
8823
|
};
|
|
7270
8824
|
effect(() => {
|
|
@@ -7301,6 +8855,7 @@ const createWebSocketClient = (options) => {
|
|
|
7301
8855
|
}
|
|
7302
8856
|
socket.emit('leave-room', room);
|
|
7303
8857
|
rooms.delete(room);
|
|
8858
|
+
syncDevtoolsRooms();
|
|
7304
8859
|
};
|
|
7305
8860
|
const setupWebSocketConnectionListener = () => {
|
|
7306
8861
|
socket.on('connect', () => {
|
|
@@ -7315,6 +8870,16 @@ const createWebSocketClient = (options) => {
|
|
|
7315
8870
|
socket.onAny((data) => {
|
|
7316
8871
|
try {
|
|
7317
8872
|
const json = JSON.parse(data);
|
|
8873
|
+
if (devtoolsEnabled) {
|
|
8874
|
+
const message = {
|
|
8875
|
+
id: devtoolsMessageId++,
|
|
8876
|
+
timestamp: Date.now(),
|
|
8877
|
+
room: json.room,
|
|
8878
|
+
event: json.event,
|
|
8879
|
+
data: json.data,
|
|
8880
|
+
};
|
|
8881
|
+
devtoolsMessages.update((log) => [message, ...log].slice(0, MAX_DEVTOOLS_MESSAGES));
|
|
8882
|
+
}
|
|
7318
8883
|
const room = rooms.get(json.room);
|
|
7319
8884
|
if (room)
|
|
7320
8885
|
room.latestMessage.set(json);
|
|
@@ -7337,6 +8902,19 @@ const createWebSocketClient = (options) => {
|
|
|
7337
8902
|
leaveRoom,
|
|
7338
8903
|
},
|
|
7339
8904
|
};
|
|
8905
|
+
if (devtoolsEnabled) {
|
|
8906
|
+
const handle = {
|
|
8907
|
+
connected: isConnected.asReadonly(),
|
|
8908
|
+
rooms: devtoolsRooms.asReadonly(),
|
|
8909
|
+
messages: devtoolsMessages.asReadonly(),
|
|
8910
|
+
};
|
|
8911
|
+
const unregister = registerQueryDevtoolsEntry({
|
|
8912
|
+
kind: 'ws-client',
|
|
8913
|
+
handle,
|
|
8914
|
+
meta: { name: options.name, url: options.url },
|
|
8915
|
+
});
|
|
8916
|
+
inject(DestroyRef).onDestroy(unregister);
|
|
8917
|
+
}
|
|
7340
8918
|
return client;
|
|
7341
8919
|
}, {
|
|
7342
8920
|
name: `WebSocketClient_${options.name}`,
|
|
@@ -7347,5 +8925,5 @@ const createWebSocketClient = (options) => {
|
|
|
7347
8925
|
* Generated bundle index. Do not edit.
|
|
7348
8926
|
*/
|
|
7349
8927
|
|
|
7350
|
-
export { BasicAuthProvider, BearerAuthFeatureType, BooleanArrayQueryField, CLEAR_QUERY_ARGS, CustomHeaderAuthProvider, DateArrayQueryField, DateQueryField, EntityStore, INFINITY_QUERY_TOKEN, InfinityQuery, InfinityQueryDirective, InfinityQueryTriggerDirective, LegacyQuery, LegacyQueryCreator, NumberArrayQueryField, ParseHttpErrorCodeToMessageDePipe, ParseHttpErrorCodeToMessageEnPipe, ParseHttpErrorCodeToTitleDePipe, ParseHttpErrorCodeToTitleEnPipe, QUERY_CLIENT_DEVTOOLS_TOKEN, QueryDevtoolsComponent, QueryDirective, QueryError, QueryFeatureType, QueryField, QueryForm, QueryRuntimeErrorCode, QueryShortNamePipe, QueryStore, SPEED_BUFFER_TIME_IN_MS, SearchQueryField, SortQueryField, StringArrayQueryField, V2BearerAuthProvider, V2Query, V2QueryClient, V2QueryCreator, addQueryContainerHandling, applyQueryFeatures, authExtractTokensResponseMissingAccessToken, authExtractTokensResponseMissingRefreshToken, authExtractTokensResponseNotObject, authProviderFeatureUsedMultipleTimes, buildErrorMessage, buildQueryCacheKey, buildQueryString, buildRoute, buildTimestampFromSeconds, castQueryCreatorTypes, circularQueryDependency, circularQueryDependencyChecker, computeQueryAuthHeader, computeQueryBody, computeQueryHeaders, computeQueryMethod, computeQueryQueryParams, contentfulGqlLikePaginationAdapter, createBaseQuery, createBaseQueryCreator, createBearerAuthProvider, createDeleteQuery, createExecuteFn, createGetQuery, createGqlExecuteFn, createGqlMutationViaGet, createGqlMutationViaPost, createGqlQuery, createGqlQueryCreator, createGqlQueryViaGet, createGqlQueryViaPost, createHttpRequest, createInfinityQueryConfig, createLegacyQueryCreator, createPagedQueryStack, createPatchQuery, createPersistentAuthFeature, createPostQuery, createPutQuery, createQuery, createQueryClient, createQueryCollection, createQueryCollectionSignal, createQueryCollectionSubject, createQueryContext, createQueryCreator, createQueryErrorResponse, createQueryFeature, createQueryObject, createQueryRepository, createQuerySnapshotFn, createQueryStack, createSecureDeleteQuery, createSecureExecuteFactory, createSecureExecuteFn, createSecureGetQuery, createSecureGqlExecuteFn, createSecureGqlMutationViaGet, createSecureGqlMutationViaPost, createSecureGqlQuery, createSecureGqlQueryCreator, createSecureGqlQueryViaGet, createSecureGqlQueryViaPost, createSecurePatchQuery, createSecurePostQuery, createSecurePutQuery, createSecureQuery, createSecureQueryCreator, createTrackingFeature, createWebSocketClient, decryptBearer, deepFreeze, def, detectContentTypeHeader, dynLikePaginationAdapter, effectComputed, ethletePaginationAdapter, extractExpiresInSeconds, extractQuery, fakePaginationAdapter, filterFailure, filterNull, filterQueryStates, filterSuccess, forEachHeader, getDefaultHeaders, getQueryFeatureUsage, getResponseUrl, ggLikePaginationAdapter, gql, gqlDataPropertyMissingInResponse, hasHeader, ignoreAutoRefresh, injectInfinityQueryResponseDelay, injectQueryContext, insertFrom, invalidBaseRouteError, invalidBodyError, invalidRouteError, invalidStateInsideSecureExecuteFactory, isAutoRefresh, isBasicAuthProvider, isBearerAuthProvider, isClassValidatorError, isCreateGqlQueryOptions, isCustomHeaderAuthProvider, isEmptyString, isGqlQueryConfig, isLegacyQuery, isNaN$1 as isNaN, isQuery, isQueryCollection, isQueryStateCancelled, isQueryStateFailure, isQueryStateLoading, isQueryStatePrepared, isQueryStateSuccess, isRequestError, isSymfonyError, isSymfonyFormViolationListError, isSymfonyListError, isSymfonyPagerfantaOutOfRangeError, mapToPaginated, maybeExecute, mergeHeaders, nestedEffect, pagedQueryStackNextPageCalledWithoutPreviousPage, pagedQueryStackPageBiggerThanTotalPages, pagedQueryStackPreviousPageCalledButAlreadyAtFirstPage, paginatedEntityValueUpdater, parseAllXhrResponseHeaders, parseHttpErrorCodeToMessageDe, parseHttpErrorCodeToMessageEn, parseHttpErrorCodeToTitleDe, parseHttpErrorCodeToTitleEn, pathParamsMissingInRouteFunctionError, provideInfinityQueryResponseDelay, provideQueryClientForDevtools, queryArrayComputed, queryComputed, queryComputedTillTruthy, queryExecute, queryFeatureUsedMultipleTimes, queryStackTotalQueriesAndExpectedQueriesMismatch, queryStackWithArgsUsed, queryStackWithResponseUpdateUsed, queryStateAlreadyHasKeyError, queryStateCannotTransformError, queryStateDoesNotContainKeyError, queryStateErrorSignal, queryStateLoadingSignal, queryStateResponseSignal, queryStateSignal, removeFrom, request, resetExecuteState, resetPageOnError, serializeBody, setupQueryDependencies, setupQueryExecuteState, setupQueryState, shouldAutoExecuteGqlQuery, shouldAutoExecuteQuery, shouldCacheQuery, shouldRetryRequest, silenceMissingWithArgsFeatureErrorUsedButWithArgsPresent, skipPaginationPageParamCalculator, splitQueryConfig, switchQueryCollectionState, switchQueryState, takeUntilResponse, toQuerySignal, toQuerySubject, tokensNotAvailableInsideAuthAndExec, transformArrayResponse, transformExecStateToQueryState, transformGql, transformMethod, transformPaginatedResponse, transformToBoolean, transformToBooleanArray, transformToDate, transformToDateArray, transformToNumber, transformToNumberArray, transformToSort, transformToSortQueryParam, transformToString, transformToStringArray, uncacheableRequestHasAllowCacheParam, uncacheableRequestHasCacheKeyParam, v2BuildQueryCacheKey, v2ExtractExpiresInSeconds, v2ShouldCacheQuery, v2ShouldRetryRequest, withArgs, withArgsQueryFeatureMissingButRouteIsFunction, withAuthenticationQuery, withAutoRefresh, withAutoRefreshUsedInManualQuery, withAutoRefreshUsedOnUnsupportedHttpMethod, withErrorHandling, withInactivityLogout, withLogging, withPersistentAuth, withPolling, withPollingUsedOnUnsupportedHttpMethod, withRefreshQuery, withResponseUpdate, withSuccessHandling, withTokenExpirationWarning, withTokenRevocation, withTracking, wrapAsObservableSignal };
|
|
8928
|
+
export { BasicAuthProvider, BearerAuthFeatureType, BooleanArrayQueryField, CLEAR_QUERY_ARGS, CustomHeaderAuthProvider, DEFAULT_KEEP_UNUSED_FOR, DateArrayQueryField, DateQueryField, EntityStore, IGNORED_FILTER_COUNT_FIELDS, INFINITY_QUERY_TOKEN, InfinityQuery, InfinityQueryDirective, InfinityQueryTriggerDirective, LegacyQuery, LegacyQueryCreator, MAX_UNUSED_ENTRIES, NumberArrayQueryField, ParseHttpErrorCodeToMessageDePipe, ParseHttpErrorCodeToMessageEnPipe, ParseHttpErrorCodeToTitleDePipe, ParseHttpErrorCodeToTitleEnPipe, QUERY_CLIENT_DEVTOOLS_TOKEN, QueryDevtoolsComponent, QueryDirective, QueryError, QueryFeatureType, QueryField, QueryForm, QueryFormSignals, QueryRuntimeErrorCode, QueryShortNamePipe, QueryStore, SERVER_ERROR_KIND, SERVER_VIOLATION_ERROR_KIND, SPEED_BUFFER_TIME_IN_MS, SearchQueryField, SortQueryField, StringArrayQueryField, V2BearerAuthProvider, V2Query, V2QueryClient, V2QueryCreator, addQueryContainerHandling, applyQueryFeatures, authExtractTokensResponseMissingAccessToken, authExtractTokensResponseMissingRefreshToken, authExtractTokensResponseNotObject, authProviderFeatureUsedMultipleTimes, booleanArrayQueryField, buildErrorMessage, buildQueryCacheKey, buildQueryString, buildRoute, buildTimestampFromSeconds, castQueryCreatorTypes, circularQueryDependency, circularQueryDependencyChecker, computeQueryAuthHeader, computeQueryBody, computeQueryHeaders, computeQueryMethod, computeQueryQueryParams, consumeSuppressQueryStackDevtools, contentfulGqlLikePaginationAdapter, createBaseQuery, createBaseQueryCreator, createBearerAuthProvider, createDeleteQuery, createExecuteFn, createGetQuery, createGqlExecuteFn, createGqlMutationViaGet, createGqlMutationViaPost, createGqlQuery, createGqlQueryCreator, createGqlQueryViaGet, createGqlQueryViaPost, createHttpRequest, createInfinityQueryConfig, createLegacyQueryCreator, createPagedQueryStack, createPatchQuery, createPersistentAuthFeature, createPostQuery, createPutQuery, createQuery, createQueryClient, createQueryCollection, createQueryCollectionSignal, createQueryCollectionSubject, createQueryContext, createQueryCreator, createQueryErrorResponse, createQueryFeature, createQueryForm, createQueryObject, createQueryRepository, createQuerySnapshotFn, createQueryStack, createSecureDeleteQuery, createSecureExecuteFactory, createSecureExecuteFn, createSecureGetQuery, createSecureGqlExecuteFn, createSecureGqlMutationViaGet, createSecureGqlMutationViaPost, createSecureGqlQuery, createSecureGqlQueryCreator, createSecureGqlQueryViaGet, createSecureGqlQueryViaPost, createSecurePatchQuery, createSecurePostQuery, createSecurePutQuery, createSecureQuery, createSecureQueryCreator, createTrackingFeature, createWebSocketClient, dateArrayQueryField, dateQueryField, decryptBearer, deepFreeze, def, detectContentTypeHeader, dynLikePaginationAdapter, effectComputed, emptyQueryArgs, ethletePaginationAdapter, executeUntilSettled, extractExpiresInSeconds, extractFormViolations, extractQuery, fakePaginationAdapter, filterFailure, filterNull, filterQueryStates, filterSuccess, forEachHeader, getDefaultHeaders, getQueryClientName, getQueryFeatureUsage, getResponseUrl, ggLikePaginationAdapter, gql, gqlDataPropertyMissingInResponse, hasHeader, ignoreAutoRefresh, injectInfinityQueryResponseDelay, injectQueryContext, insertFrom, invalidBaseRouteError, invalidBodyError, invalidRouteError, invalidStateInsideSecureExecuteFactory, isAutoRefresh, isBasicAuthProvider, isBearerAuthProvider, isClassValidatorError, isCreateGqlQueryOptions, isCustomHeaderAuthProvider, isEmptyString, isGqlQueryConfig, isLegacyQuery, isNaN$1 as isNaN, isPageOutOfRangeError, isQuery, isQueryCollection, isQueryDevtoolsEnabled, isQueryErrorResponse, isQueryStateCancelled, isQueryStateFailure, isQueryStateLoading, isQueryStatePrepared, isQueryStateSuccess, isRequestError, isSymfonyError, isSymfonyFormViolationListError, isSymfonyListError, isSymfonyPagerfantaOutOfRangeError, legacyPrepareWithoutInjectionContext, mapToPaginated, mapViolationsToFormErrors, maybeExecute, mergeHeaders, nestedEffect, numberArrayQueryField, pagedQueryStackNextPageCalledWithoutPreviousPage, pagedQueryStackPageBiggerThanTotalPages, pagedQueryStackPreviousPageCalledButAlreadyAtFirstPage, paginatedEntityValueUpdater, parseAllXhrResponseHeaders, parseHttpErrorCodeToMessageDe, parseHttpErrorCodeToMessageEn, parseHttpErrorCodeToTitleDe, parseHttpErrorCodeToTitleEn, pathParamsMissingInRouteFunctionError, provideInfinityQueryResponseDelay, provideQueryClientForDevtools, provideQueryDevtools, queryArrayComputed, queryComputed, queryComputedTillTruthy, queryDevtoolsEntries, queryErrorMessage, queryErrorMessages, queryExecute, queryFeatureUsedMultipleTimes, queryField, querySequence, querySequenceAlreadyRunning, queryStackTotalQueriesAndExpectedQueriesMismatch, queryStackWithArgsUsed, queryStackWithResponseUpdateUsed, queryStateAlreadyHasKeyError, queryStateCannotTransformError, queryStateDoesNotContainKeyError, queryStateErrorSignal, queryStateLoadingSignal, queryStateResponseSignal, queryStateSignal, registerQueryDevtoolsEntry, removeFrom, request, resetExecuteState, resetPageOnError, searchQueryField, serializeBody, setupQueryDependencies, setupQueryExecuteState, setupQueryState, shouldAutoExecuteGqlQuery, shouldAutoExecuteQuery, shouldCacheQuery, shouldRetryRequest, silenceMissingWithArgsFeatureErrorUsedButWithArgsPresent, skipPaginationPageParamCalculator, sortQueryField, splitQueryConfig, stringArrayQueryField, stringifyQueryRoute, suppressNextQueryStackDevtools, switchQueryCollectionState, switchQueryState, takeUntilResponse, toQuerySignal, toQuerySubject, tokensNotAvailableInsideAuthAndExec, transformArrayResponse, transformExecStateToQueryState, transformGql, transformMethod, transformPaginatedResponse, transformToBoolean, transformToBooleanArray, transformToDate, transformToDateArray, transformToNumber, transformToNumberArray, transformToSort, transformToSortQueryParam, transformToString, transformToStringArray, uncacheableRequestHasAllowCacheParam, uncacheableRequestHasCacheKeyParam, v2BuildQueryCacheKey, v2ExtractExpiresInSeconds, v2ShouldCacheQuery, v2ShouldRetryRequest, validateWithQuery, validateWithV2Query, withArgs, withArgsQueryFeatureMissingButRouteIsFunction, withAuthenticationQuery, withAutoRefresh, withAutoRefreshUsedInManualQuery, withAutoRefreshUsedOnUnsupportedHttpMethod, withErrorHandling, withInactivityLogout, withLogging, withPageResetOnError, withPersistentAuth, withPolling, withPollingUsedOnUnsupportedHttpMethod, withRefreshQuery, withResponseUpdate, withSuccessHandling, withTokenExpirationWarning, withTokenRevocation, withTracking, wrapAsObservableSignal };
|
|
7351
8929
|
//# sourceMappingURL=ethlete-query.mjs.map
|