@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.
@@ -1,16 +1,137 @@
1
1
  import * as i0 from '@angular/core';
2
- import { isDevMode, DestroyRef, InjectionToken, inject, Injector, EnvironmentInjector, ErrorHandler, ɵEffectScheduler as _EffectScheduler, createEnvironmentInjector, effect, computed, untracked, signal, linkedSignal, runInInjectionContext, assertInInjectionContext, ChangeDetectorRef, ViewContainerRef, TemplateRef, Input, Directive, ElementRef, input, Pipe, ViewEncapsulation, ChangeDetectionStrategy, Component, DOCUMENT, NgZone } from '@angular/core';
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 { Observable, Subscription, tap, retry, throwError, timer, catchError, takeUntil, startWith, pairwise, switchMap, of, BehaviorSubject, Subject, fromEvent, take, filter, ReplaySubject, map, shareReplay, interval, skip, takeWhile, combineLatest, EMPTY, withLatestFrom, debounceTime, merge, throttleTime } from 'rxjs';
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, HttpErrorResponse, HttpEventType, HttpHeaders, HttpStatusCode } from '@angular/common/http';
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
- const currArgs = computed(() => args(), ...(ngDevMode ? [{ debugName: "currArgs" }] : []));
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 = currArgs();
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
- nestedEffect(() => {
370
- const event = context.state.latestHttpEvent();
371
- if (event === null)
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
- nestedEffect(() => {
386
- const error = context.state.error();
387
- if (error === null)
388
- return;
389
- untracked(() => {
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
- nestedEffect(() => {
402
- const response = context.state.response();
403
- if (response === null)
404
- return;
405
- untracked(() => {
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, ...(ngDevMode ? [{ debugName: "request" }] : []));
462
- const rawResponse = linkedSignal(() => request()?.response() ?? null, ...(ngDevMode ? [{ debugName: "rawResponse" }] : []));
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
- }, ...(ngDevMode ? [{ debugName: "response" }] : []));
469
- const loading = linkedSignal(() => request()?.loading() ?? null, ...(ngDevMode ? [{ debugName: "loading" }] : []));
470
- const error = linkedSignal(() => request()?.error() ?? null, ...(ngDevMode ? [{ debugName: "error" }] : []));
471
- const latestHttpEvent = linkedSignal(() => request()?.currentEvent() ?? null, ...(ngDevMode ? [{ debugName: "latestHttpEvent" }] : []));
472
- const args = signal(null, ...(ngDevMode ? [{ debugName: "args" }] : []));
473
- const lastTimeExecutedAt = signal(null, ...(ngDevMode ? [{ debugName: "lastTimeExecutedAt" }] : []));
474
- const lastTriggeredBy = signal(null, ...(ngDevMode ? [{ debugName: "lastTriggeredBy" }] : []));
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
- }, ...(ngDevMode ? [{ debugName: "executionState" }] : []));
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, ...(ngDevMode ? [{ debugName: "isAlive" }] : []));
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
- if (!currentResponse && !currentError)
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
- return createQueryObject({ state, execute, deps });
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 (error) {
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, ...(ngDevMode ? [{ debugName: "currentEvent" }] : []));
1542
- const loading = signal(null, ...(ngDevMode ? [{ debugName: "loading" }] : []));
1543
- const error = signal(null, ...(ngDevMode ? [{ debugName: "error" }] : []));
1544
- const response = signal(null, ...(ngDevMode ? [{ debugName: "response" }] : []));
1545
- const lastLoadEventTime = signal(0, ...(ngDevMode ? [{ debugName: "lastLoadEventTime" }] : []));
1546
- const lastLoadEventAmount = signal(0, ...(ngDevMode ? [{ debugName: "lastLoadEventAmount" }] : []));
1547
- const lastExecuteTime = signal(0, ...(ngDevMode ? [{ debugName: "lastExecuteTime" }] : []));
1548
- const expiresIn = signal(null, ...(ngDevMode ? [{ debugName: "expiresIn" }] : []));
1549
- const isStale = computed(() => {
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
- }, ...(ngDevMode ? [{ debugName: "isStale" }] : []));
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 = typeof args?.headers === 'function' ? args.headers() : args?.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
- if (currentStreamSubscription.closed) {
1604
- return false;
1605
- }
1908
+ const wasActive = !currentStreamSubscription.closed;
1606
1909
  currentStreamSubscription.unsubscribe();
1607
- return true;
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
- currentEvent.set({ type: 'error', error: errorRes });
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([], ...(ngDevMode ? [{ debugName: "queries" }] : []));
1700
- const lastQuery = signal(null, ...(ngDevMode ? [{ debugName: "lastQuery" }] : []));
1701
- const firstQuery = computed(() => queries()[0] ?? null, ...(ngDevMode ? [{ debugName: "firstQuery" }] : []));
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()), ...(ngDevMode ? [{ debugName: "anyLoading" }] : []));
1784
- const loadingCount = computed(() => queries().filter((q) => q.loading()).length, ...(ngDevMode ? [{ debugName: "loadingCount" }] : []));
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
- }, ...(ngDevMode ? [{ debugName: "allLoading" }] : []));
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
- }, ...(ngDevMode ? [{ debugName: "loadingProgress" }] : []));
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, ...(ngDevMode ? [{ debugName: "anyError" }] : []));
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), ...(ngDevMode ? [{ debugName: "errors" }] : []));
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, ...(ngDevMode ? [{ debugName: "currentPageArgs" }] : []));
1895
- const initialPage = signal(options.initialPage ?? 1, ...(ngDevMode ? [{ debugName: "initialPage" }] : []));
1896
- const loadedMinPage = signal(initialPage(), ...(ngDevMode ? [{ debugName: "loadedMinPage" }] : []));
1897
- const loadedMaxPage = signal(initialPage(), ...(ngDevMode ? [{ debugName: "loadedMaxPage" }] : []));
1898
- const lastResetTimestamp = signal(Date.now(), ...(ngDevMode ? [{ debugName: "lastResetTimestamp" }] : []));
1899
- const pageDirection = signal('next', ...(ngDevMode ? [{ debugName: "pageDirection" }] : []));
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
- lastResetTimestamp();
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
- }, ...(ngDevMode ? [{ debugName: "maxPagination" }] : []));
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
- }, ...(ngDevMode ? [{ debugName: "minPagination" }] : []));
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
- }, ...(ngDevMode ? [{ debugName: "isFirstPageLoaded" }] : []));
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
- }, ...(ngDevMode ? [{ debugName: "isLastPageLoaded" }] : []));
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
- lastResetTimestamp.set(Date.now());
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
- }, ...(ngDevMode ? [{ debugName: "canFetchPreviousPage" }] : []));
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
- }, ...(ngDevMode ? [{ debugName: "canFetchNextPage" }] : []));
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
- }, ...(ngDevMode ? [{ debugName: "items" }] : []));
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
- }, ...(ngDevMode ? [{ debugName: "isFirstLoad" }] : []));
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, ...(ngDevMode ? [{ debugName: "lastResult" }] : []));
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, ...(ngDevMode ? [{ debugName: "rwSignal" }] : []));
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
- }, ...(ngDevMode ? [{ debugName: "dataSignal" }] : []));
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, ...(ngDevMode ? [{ debugName: "_signal" }] : []));
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
- authAndExec(execArgsWithDefaults);
3097
+ authAndExecWhenTokenReady(execArgsWithDefaults);
2711
3098
  }
2712
3099
  }));
2713
3100
  }))
2714
3101
  .subscribe();
2715
3102
  }
2716
3103
  else if (authQuery.response()) {
2717
- authAndExec(execArgsWithDefaults);
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 = effect(() => {
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.newQuery.subtle.destroyRef.onDestroy(() => this.destroy());
2977
- }
2978
- destroy() {
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(() => this.newQuery.execute({ args: this._arguments, options: { allowCache: options.skipCache !== true } }));
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
- if (this._pollingSubscription) {
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
- const injector = args?.injector ?? inject(Injector);
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
- }, ...(ngDevMode ? [{ debugName: "destroyEffect" }] : []));
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, ...(ngDevMode ? [{ debugName: "_signal" }] : []));
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 = (queryMap, config) => {
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 = (queryMap, config) => {
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, ...(ngDevMode ? [{ debugName: "_signal" }] : []));
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, ...(ngDevMode ? [{ debugName: "enabled" }] : []));
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(dir, ctx) {
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: "21.2.1", ngImport: i0, type: InfinityQueryDirective, deps: [], target: i0.ɵɵFactoryTarget.Directive }); }
4320
- static { this.ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "21.2.1", type: InfinityQueryDirective, isStandalone: true, selector: "[etInfinityQuery]", inputs: { infinityQuery: ["etInfinityQuery", "infinityQuery"] }, providers: [
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: "21.2.1", ngImport: i0, type: InfinityQueryDirective, decorators: [{
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, ...(ngDevMode ? [{ debugName: "scrollContainerSelector" }] : []));
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, ...(ngDevMode ? [{ debugName: "infinityQueryOverride" }] : []));
4350
- this.infinityQuery = computed(() => this.infinityQueryOverride() ?? this._infinityQuery, ...(ngDevMode ? [{ debugName: "infinityQuery" }] : []));
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: "21.2.1", ngImport: i0, type: InfinityQueryTriggerDirective, deps: [], target: i0.ɵɵFactoryTarget.Directive }); }
4383
- static { this.ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "17.1.0", version: "21.2.1", 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 }); }
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: "21.2.1", ngImport: i0, type: InfinityQueryTriggerDirective, decorators: [{
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(dir, ctx) {
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: "21.2.1", ngImport: i0, type: QueryDirective, deps: [], target: i0.ɵɵFactoryTarget.Directive }); }
4472
- static { this.ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "17.1.0", version: "21.2.1", 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 }); }
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: "21.2.1", ngImport: i0, type: QueryDirective, decorators: [{
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: "21.2.1", ngImport: i0, type: QueryShortNamePipe, deps: [], target: i0.ɵɵFactoryTarget.Pipe }); }
4496
- static { this.ɵpipe = i0.ɵɵngDeclarePipe({ minVersion: "14.0.0", version: "21.2.1", ngImport: i0, type: QueryShortNamePipe, isStandalone: true, name: "queryShortName" }); }
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: "21.2.1", ngImport: i0, type: QueryShortNamePipe, decorators: [{
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, ...(ngDevMode ? [{ debugName: "isOpen" }] : []));
4521
- this.isTranslucent = signal(false, ...(ngDevMode ? [{ debugName: "isTranslucent" }] : []));
4522
- this.snapLayout = signal('full', ...(ngDevMode ? [{ debugName: "snapLayout" }] : []));
4523
- this.queryListMode = signal('live', ...(ngDevMode ? [{ debugName: "queryListMode" }] : []));
4524
- this.viewMode = signal('query', ...(ngDevMode ? [{ debugName: "viewMode" }] : []));
4525
- this.showResponse = signal(false, ...(ngDevMode ? [{ debugName: "showResponse" }] : []));
4526
- this.showRawResponse = signal(false, ...(ngDevMode ? [{ debugName: "showRawResponse" }] : []));
4527
- this.showQueryConfig = signal(false, ...(ngDevMode ? [{ debugName: "showQueryConfig" }] : []));
4528
- this.showEntityStoreValue = signal(false, ...(ngDevMode ? [{ debugName: "showEntityStoreValue" }] : []));
4529
- this.showArguments = signal(false, ...(ngDevMode ? [{ debugName: "showArguments" }] : []));
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, ...(ngDevMode ? [{ debugName: "selectedQueryId" }] : []));
5068
+ this.selectedQueryId = signal(null, /* @ts-ignore */
5069
+ ...(ngDevMode ? [{ debugName: "selectedQueryId" }] : /* istanbul ignore next */ []));
4533
5070
  this.selectedClientConfig = computed(() => {
4534
- if (this.queryClientConfigs.length === 1) {
4535
- return this.queryClientConfigs[0];
4536
- }
4537
- const index = this.selectedClientId() ?? 0;
4538
- const cfg = this.queryClientConfigs[index];
4539
- if (!cfg) {
4540
- return this.queryClientConfigs[0];
4541
- }
4542
- return cfg;
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
- }, ...(ngDevMode ? [{ debugName: "queryStore" }] : []));
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
- }, ...(ngDevMode ? [{ debugName: "selectedQuery" }] : []));
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
- }, ...(ngDevMode ? [{ debugName: "selectedQueryEntityStore" }] : []));
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
- }, ...(ngDevMode ? [{ debugName: "stringifiedQueryConfig" }] : []));
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
- }, ...(ngDevMode ? [{ debugName: "authProviderDetails" }] : []));
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: "21.2.1", ngImport: i0, type: QueryDevtoolsComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
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: "21.2.1", ngImport: i0, type: QueryDevtoolsComponent, decorators: [{
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', changeDetection: ChangeDetectionStrategy.OnPush, encapsulation: ViewEncapsulation.ShadowDom, host: {
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
- const generateUuid = () => crypto.randomUUID();
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
- if (!shouldCache && runQueryOptions?.allowCache)
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
- bind(cacheKey, options.consumerDestroyRef, cacheEntry.request, options.isSecure ?? false, true);
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
- cacheEntry.request.destroy();
5018
- cacheEntry.eventSubscription?.unsubscribe();
5019
- cache.delete(key);
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
- for (const consumerDestroyRef of cacheEntry.consumers.keys()) {
5027
- unbind(key, consumerDestroyRef);
5028
- }
5029
- cacheEntry.eventSubscription?.unsubscribe();
5030
- cache.delete(key);
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 bind = (key, consumerDestroyRef, request, isSecure, allowReuse) => {
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
- const eventSubscription = combineLatest([
5044
- toObservable(request.error, { injector: config.dependencies.injector }),
5045
- toObservable(request.response, { injector: config.dependencies.injector }),
5046
- ]).subscribe(([errorValue, responseValue]) => {
5047
- if (errorValue?.raw instanceof HttpErrorResponse) {
5048
- eventsSubject.next({
5049
- type: 'request-error',
5050
- error: errorValue.raw,
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 (responseValue !== null && !errorValue) {
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, ...(ngDevMode ? [{ debugName: "isLeader" }] : []));
5168
- const instanceCount = signal(1, ...(ngDevMode ? [{ debugName: "instanceCount" }] : []));
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 setupBearerQueryRegistry = (builders, injector, latestExecutedQuery, latestNonInternalQuery, setTokens) => {
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, ...(ngDevMode ? [{ debugName: "querySnapshot" }] : []));
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
- latestExecutedQuery.set({ key: builder.key, snapshot });
5576
- if (!snapshot.triggeredBy()) {
5577
- latestNonInternalQuery.set({ key: builder.key, snapshot });
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, ...(ngDevMode ? [{ debugName: "accessToken" }] : []));
5639
- const refreshToken = signal(null, ...(ngDevMode ? [{ debugName: "refreshToken" }] : []));
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
- }, ...(ngDevMode ? [{ debugName: "bearerData" }] : []));
5655
- const isAuthenticated = computed(() => !!accessToken(), ...(ngDevMode ? [{ debugName: "isAuthenticated" }] : []));
5656
- const latestExecutedQuery = signal(null, ...(ngDevMode ? [{ debugName: "latestExecutedQuery" }] : []));
5657
- const latestNonInternalQuery = signal(null, ...(ngDevMode ? [{ debugName: "latestNonInternalQuery" }] : []));
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
- return {
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, ...(ngDevMode ? [{ debugName: "enabled" }] : []));
5857
- const lastActivityTime = signal(Date.now(), ...(ngDevMode ? [{ debugName: "lastActivityTime" }] : []));
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(), ...(ngDevMode ? [{ debugName: "rememberMeSignal" }] : []));
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
- }, ...(ngDevMode ? [{ debugName: "expiresAt" }] : []));
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, ...(ngDevMode ? [{ debugName: "enabled" }] : []));
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 revoke = () => {
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(() => revoke());
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: "21.2.1", ngImport: i0, type: ParseHttpErrorCodeToTitleDePipe, deps: [], target: i0.ɵɵFactoryTarget.Pipe }); }
6386
- static { this.ɵpipe = i0.ɵɵngDeclarePipe({ minVersion: "14.0.0", version: "21.2.1", ngImport: i0, type: ParseHttpErrorCodeToTitleDePipe, isStandalone: true, name: "parseHttpErrorCodeToTitleDe" }); }
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: "21.2.1", ngImport: i0, type: ParseHttpErrorCodeToTitleDePipe, decorators: [{
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: "21.2.1", ngImport: i0, type: ParseHttpErrorCodeToMessageDePipe, deps: [], target: i0.ɵɵFactoryTarget.Pipe }); }
6399
- static { this.ɵpipe = i0.ɵɵngDeclarePipe({ minVersion: "14.0.0", version: "21.2.1", ngImport: i0, type: ParseHttpErrorCodeToMessageDePipe, isStandalone: true, name: "parseHttpErrorCodeToMessageDe" }); }
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: "21.2.1", ngImport: i0, type: ParseHttpErrorCodeToMessageDePipe, decorators: [{
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: "21.2.1", ngImport: i0, type: ParseHttpErrorCodeToTitleEnPipe, deps: [], target: i0.ɵɵFactoryTarget.Pipe }); }
6586
- static { this.ɵpipe = i0.ɵɵngDeclarePipe({ minVersion: "14.0.0", version: "21.2.1", ngImport: i0, type: ParseHttpErrorCodeToTitleEnPipe, isStandalone: true, name: "parseHttpErrorCodeToTitleEn" }); }
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: "21.2.1", ngImport: i0, type: ParseHttpErrorCodeToTitleEnPipe, decorators: [{
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: "21.2.1", ngImport: i0, type: ParseHttpErrorCodeToMessageEnPipe, deps: [], target: i0.ɵɵFactoryTarget.Pipe }); }
6599
- static { this.ɵpipe = i0.ɵɵngDeclarePipe({ minVersion: "14.0.0", version: "21.2.1", ngImport: i0, type: ParseHttpErrorCodeToMessageEnPipe, isStandalone: true, name: "parseHttpErrorCodeToMessageEn" }); }
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: "21.2.1", ngImport: i0, type: ParseHttpErrorCodeToMessageEnPipe, decorators: [{
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
- field.control.setValue(this._getDefaultValue(key), { emitEvent: false });
7026
- didValueChanges = true;
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, ...(ngDevMode ? [{ debugName: "isConnected" }] : []));
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, ...(ngDevMode ? [{ debugName: "roomData" }] : []));
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, ...(ngDevMode ? [{ debugName: "message" }] : []));
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