@ethlete/query 6.0.0-next.2 → 6.0.0-next.20

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
 
@@ -745,6 +1019,7 @@ const queryExecute = (options) => {
745
1019
  state.lastTimeExecutedAt.set(Date.now());
746
1020
  state.lastTriggeredBy.set(runQueryOptions?.triggeredBy ?? null);
747
1021
  state.subtle.request.set(request);
1022
+ state.subtle.bindRequestEvents(request);
748
1023
  };
749
1024
  const circularQueryDependencyChecker = () => {
750
1025
  let lastTriggerTs = 0;
@@ -778,9 +1053,6 @@ const createExecuteFn = (executeOptions) => {
778
1053
  const reset = () => resetExecuteState({ executeState, executeOptions });
779
1054
  const exec = (executeArgs) => {
780
1055
  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
1056
  const { args = executeOptions.state.args(), options } = executeArgs ?? {};
785
1057
  queryExecute({ executeOptions, executeState, args, options });
786
1058
  };
@@ -790,6 +1062,19 @@ const createExecuteFn = (executeOptions) => {
790
1062
  };
791
1063
 
792
1064
  /* eslint-disable @typescript-eslint/no-explicit-any */
1065
+ /**
1066
+ * The empty request-args object, typed to satisfy `RequestArgs<TArgs>` for a query that takes no
1067
+ * arguments — its `QueryArgs` is just `{ response: T }`, so `RequestArgs` resolves to `{}`.
1068
+ *
1069
+ * Only needed in generic code, where `TArgs` is still a type parameter and TS therefore cannot see
1070
+ * that `{}` is the whole of it. Against a concrete query, omit the argument instead: both
1071
+ * `query.execute()` and the auth registry's `execute()` already take none.
1072
+ *
1073
+ * @example
1074
+ * const runAnything = <TArgs extends QueryArgs>(query: Query<TArgs>) =>
1075
+ * query.execute({ args: emptyQueryArgs<TArgs>() });
1076
+ */
1077
+ const emptyQueryArgs = () => ({});
793
1078
  const createQuery = (options) => createBaseQuery({
794
1079
  creator: options.creator,
795
1080
  creatorInternals: options.creatorInternals,
@@ -839,41 +1124,6 @@ const createBaseQueryCreator = (config) => {
839
1124
  return queryCreator;
840
1125
  };
841
1126
 
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
1127
  const buildErrorMessage = (code, message) => `[@ethlete\\query:${code}] ${message}`;
878
1128
  class QueryError extends Error {
879
1129
  constructor(code, message, data) {
@@ -1245,7 +1495,7 @@ const request = (config) => {
1245
1495
  body = text;
1246
1496
  }
1247
1497
  }
1248
- catch (error) {
1498
+ catch {
1249
1499
  // Ignore the error.
1250
1500
  }
1251
1501
  }
@@ -1471,6 +1721,34 @@ const shouldRetryRequest = (options) => {
1471
1721
  return { retry: false };
1472
1722
  };
1473
1723
 
1724
+ const isQueryErrorResponse = (error) => {
1725
+ return typeof error === 'object' && error !== null && 'raw' in error && 'retryState' in error && 'isList' in error;
1726
+ };
1727
+ /**
1728
+ * Every message carried by an error, flattened into a plain list — one entry for a single error,
1729
+ * one per violation for a list (a form response with several field violations), empty for `null`.
1730
+ *
1731
+ * The single/list split exists because that is how APIs answer; UI almost never wants to branch on
1732
+ * it. Reach for this when rendering, and keep {@link QueryErrorResponse} for anything that needs
1733
+ * the status code or the raw response.
1734
+ *
1735
+ * @example
1736
+ * ```html
1737
+ * @for (message of queryErrorMessages(myQuery.error()); track message) {
1738
+ * <p class="error">{{ message }}</p>
1739
+ * }
1740
+ * ```
1741
+ */
1742
+ const queryErrorMessages = (error) => {
1743
+ if (!error)
1744
+ return [];
1745
+ return error.isList ? error.errors.map((item) => item.message) : [error.error.message];
1746
+ };
1747
+ /**
1748
+ * The first message of an error, or `null` when there is none — the single-line counterpart to
1749
+ * {@link queryErrorMessages}, for a toast or a form-field hint.
1750
+ */
1751
+ const queryErrorMessage = (error) => queryErrorMessages(error)[0] ?? null;
1474
1752
  const createQueryErrorResponse = (error) => {
1475
1753
  let err = error instanceof HttpErrorResponse ? error : null;
1476
1754
  if (!err) {
@@ -1538,20 +1816,46 @@ const SPEED_BUFFER_TIME_IN_MS = 2000;
1538
1816
  const createHttpRequest = (options) => {
1539
1817
  let currentStreamSubscription = Subscription.EMPTY;
1540
1818
  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(() => {
1819
+ const currentEvent = signal(null, /* @ts-ignore */
1820
+ ...(ngDevMode ? [{ debugName: "currentEvent" }] : /* istanbul ignore next */ []));
1821
+ const event$ = new Subject();
1822
+ const loading = signal(null, /* @ts-ignore */
1823
+ ...(ngDevMode ? [{ debugName: "loading" }] : /* istanbul ignore next */ []));
1824
+ const error = signal(null, /* @ts-ignore */
1825
+ ...(ngDevMode ? [{ debugName: "error" }] : /* istanbul ignore next */ []));
1826
+ const response = signal(null, /* @ts-ignore */
1827
+ ...(ngDevMode ? [{ debugName: "response" }] : /* istanbul ignore next */ []));
1828
+ const lastLoadEventTime = signal(0, /* @ts-ignore */
1829
+ ...(ngDevMode ? [{ debugName: "lastLoadEventTime" }] : /* istanbul ignore next */ []));
1830
+ const lastLoadEventAmount = signal(0, /* @ts-ignore */
1831
+ ...(ngDevMode ? [{ debugName: "lastLoadEventAmount" }] : /* istanbul ignore next */ []));
1832
+ const lastExecuteTime = signal(0, /* @ts-ignore */
1833
+ ...(ngDevMode ? [{ debugName: "lastExecuteTime" }] : /* istanbul ignore next */ []));
1834
+ const expiresIn = signal(null, /* @ts-ignore */
1835
+ ...(ngDevMode ? [{ debugName: "expiresIn" }] : /* istanbul ignore next */ []));
1836
+ // NOTE: This must be a plain function, not a `computed`. The freshness check compares against
1837
+ // `Date.now()`, which is not reactive, so a memoized computed would only ever recompute when
1838
+ // `expiresIn` changes — once it evaluated to `false` (fresh) it would stay `false` forever, even
1839
+ // after the window elapsed, turning every `allowCache` execute into a permanent cache hit.
1840
+ const isStale = () => {
1550
1841
  const expiresInTs = expiresIn();
1551
1842
  return expiresInTs === null || expiresInTs < Date.now();
1552
- }, ...(ngDevMode ? [{ debugName: "isStale" }] : []));
1843
+ };
1844
+ // Resolved per execution rather than once at creation, so a client whose headers are a function
1845
+ // reading a signal (a preview token, a tenant id) sees the current value on every re-run.
1846
+ const resolveHeaders = () => {
1847
+ const clientHeaders = typeof options.clientHeaders === 'function' ? options.clientHeaders() : options.clientHeaders;
1848
+ const argHeaders = typeof args?.headers === 'function' ? args.headers() : args?.headers;
1849
+ if (!clientHeaders)
1850
+ return argHeaders;
1851
+ if (!argHeaders)
1852
+ return clientHeaders;
1853
+ // `set` replaces every value of that name, so a per-request header fully overrides the
1854
+ // client-level one instead of being appended next to it.
1855
+ return argHeaders.keys().reduce((merged, key) => merged.set(key, argHeaders.getAll(key) ?? []), clientHeaders);
1856
+ };
1553
1857
  const createStream = () => {
1554
- const headers = typeof args?.headers === 'function' ? args.headers() : args?.headers;
1858
+ const headers = resolveHeaders();
1555
1859
  return dependencies.httpClient
1556
1860
  .request(options.method, options.fullPath, {
1557
1861
  observe: 'events',
@@ -1577,7 +1881,7 @@ const createHttpRequest = (options) => {
1577
1881
  }));
1578
1882
  };
1579
1883
  const execute = (options) => {
1580
- if (loading() || (!isStale() && options?.allowCache)) {
1884
+ if (!options?.force && (loading() || (!isStale() && options?.allowCache))) {
1581
1885
  // Do not execute if there is already a request in progress or caching is allowed
1582
1886
  return false;
1583
1887
  }
@@ -1600,14 +1904,12 @@ const createHttpRequest = (options) => {
1600
1904
  return true;
1601
1905
  };
1602
1906
  const destroy = () => {
1603
- if (currentStreamSubscription.closed) {
1604
- return false;
1605
- }
1907
+ const wasActive = !currentStreamSubscription.closed;
1606
1908
  currentStreamSubscription.unsubscribe();
1607
- return true;
1909
+ event$.complete();
1910
+ return wasActive;
1608
1911
  };
1609
1912
  const updateState = (event) => {
1610
- currentEvent.set(event);
1611
1913
  switch (event.type) {
1612
1914
  case HttpEventType.Response:
1613
1915
  {
@@ -1634,12 +1936,16 @@ const createHttpRequest = (options) => {
1634
1936
  }
1635
1937
  break;
1636
1938
  }
1939
+ currentEvent.set(event);
1940
+ event$.next(event);
1637
1941
  };
1638
1942
  const updateErrorState = (errorResponse) => {
1639
1943
  const errorRes = createQueryErrorResponse(errorResponse);
1640
1944
  error.set(errorRes);
1641
1945
  loading.set(null);
1642
- currentEvent.set({ type: 'error', error: errorRes });
1946
+ const errorEvent = { type: 'error', error: errorRes };
1947
+ currentEvent.set(errorEvent);
1948
+ event$.next(errorEvent);
1643
1949
  options.dependencies.ngErrorHandler.handleError(errorRes.raw);
1644
1950
  };
1645
1951
  const updateLoadingState = (event) => {
@@ -1673,13 +1979,17 @@ const createHttpRequest = (options) => {
1673
1979
  loading.set(state);
1674
1980
  };
1675
1981
  const httpRequest = {
1982
+ method: options.method,
1983
+ url: options.fullPath,
1676
1984
  execute,
1677
1985
  destroy,
1678
1986
  loading: loading.asReadonly(),
1679
1987
  error: error.asReadonly(),
1680
1988
  response: response.asReadonly(),
1681
1989
  currentEvent: currentEvent.asReadonly(),
1990
+ events$: event$.asObservable(),
1682
1991
  isStale,
1992
+ expiresAt: expiresIn.asReadonly(),
1683
1993
  };
1684
1994
  return httpRequest;
1685
1995
  };
@@ -1696,9 +2006,12 @@ const createQueryStack = (options) => {
1696
2006
  return { queries, lastQuery };
1697
2007
  }, transform, argsKeyFn, deduplicateArgs, maxQueries = Infinity, removeStrategy = 'oldest', } = options ?? {};
1698
2008
  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" }] : []));
2009
+ const queries = signal([], /* @ts-ignore */
2010
+ ...(ngDevMode ? [{ debugName: "queries" }] : /* istanbul ignore next */ []));
2011
+ const lastQuery = signal(null, /* @ts-ignore */
2012
+ ...(ngDevMode ? [{ debugName: "lastQuery" }] : /* istanbul ignore next */ []));
2013
+ const firstQuery = computed(() => queries()[0] ?? null, /* @ts-ignore */
2014
+ ...(ngDevMode ? [{ debugName: "firstQuery" }] : /* istanbul ignore next */ []));
1702
2015
  const hasWithArgsFeature = features.some((f) => f.type == QueryFeatureType.WITH_ARGS);
1703
2016
  const hasWithOptimisticUpdateFeature = features.some((f) => f.type == QueryFeatureType.WITH_RESPONSE_UPDATE);
1704
2017
  if (hasWithArgsFeature) {
@@ -1780,23 +2093,29 @@ const createQueryStack = (options) => {
1780
2093
  untracked(() => clear());
1781
2094
  });
1782
2095
  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" }] : []));
2096
+ const anyLoading = computed(() => queries().some((q) => q.loading()), /* @ts-ignore */
2097
+ ...(ngDevMode ? [{ debugName: "anyLoading" }] : /* istanbul ignore next */ []));
2098
+ const loadingCount = computed(() => queries().filter((q) => q.loading()).length, /* @ts-ignore */
2099
+ ...(ngDevMode ? [{ debugName: "loadingCount" }] : /* istanbul ignore next */ []));
1785
2100
  const allLoading = computed(() => {
1786
2101
  const qs = queries();
1787
2102
  return qs.length > 0 && qs.every((q) => q.loading());
1788
- }, ...(ngDevMode ? [{ debugName: "allLoading" }] : []));
2103
+ }, /* @ts-ignore */
2104
+ ...(ngDevMode ? [{ debugName: "allLoading" }] : /* istanbul ignore next */ []));
1789
2105
  const loadingProgress = computed(() => {
1790
2106
  const total = queries().length;
1791
2107
  const loaded = queries().filter((q) => !q.loading()).length;
1792
2108
  return { loaded, total };
1793
- }, ...(ngDevMode ? [{ debugName: "loadingProgress" }] : []));
2109
+ }, /* @ts-ignore */
2110
+ ...(ngDevMode ? [{ debugName: "loadingProgress" }] : /* istanbul ignore next */ []));
1794
2111
  const anyError = computed(() => queries()
1795
2112
  .map((q) => q.error())
1796
- .find((e) => e !== null) ?? null, ...(ngDevMode ? [{ debugName: "anyError" }] : []));
2113
+ .find((e) => e !== null) ?? null, /* @ts-ignore */
2114
+ ...(ngDevMode ? [{ debugName: "anyError" }] : /* istanbul ignore next */ []));
1797
2115
  const errors = computed(() => queries()
1798
2116
  .map((q) => q.error())
1799
- .filter((e) => e !== null), ...(ngDevMode ? [{ debugName: "errors" }] : []));
2117
+ .filter((e) => e !== null), /* @ts-ignore */
2118
+ ...(ngDevMode ? [{ debugName: "errors" }] : /* istanbul ignore next */ []));
1800
2119
  const response = computed(() => {
1801
2120
  const responses = queries().map((q) => q.response());
1802
2121
  return transform?.(responses) ?? responses;
@@ -1831,6 +2150,17 @@ const createQueryStack = (options) => {
1831
2150
  runWithArgs,
1832
2151
  },
1833
2152
  };
2153
+ const skipDevtools = consumeSuppressQueryStackDevtools();
2154
+ if (isQueryDevtoolsEnabled() && !skipDevtools) {
2155
+ const unregister = registerQueryDevtoolsEntry({
2156
+ kind: 'query-stack',
2157
+ handle: stack,
2158
+ meta: {
2159
+ featureTypes: features.map((f) => f.type),
2160
+ },
2161
+ });
2162
+ inject(DestroyRef).onDestroy(unregister);
2163
+ }
1834
2164
  return stack;
1835
2165
  };
1836
2166
 
@@ -1891,12 +2221,19 @@ const fakePaginationAdapter = (totalHits = 10) => {
1891
2221
  };
1892
2222
  const createPagedQueryStack = (options) => {
1893
2223
  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" }] : []));
2224
+ const currentPageArgs = signal(null, /* @ts-ignore */
2225
+ ...(ngDevMode ? [{ debugName: "currentPageArgs" }] : /* istanbul ignore next */ []));
2226
+ const initialPage = signal(options.initialPage ?? 1, /* @ts-ignore */
2227
+ ...(ngDevMode ? [{ debugName: "initialPage" }] : /* istanbul ignore next */ []));
2228
+ const loadedMinPage = signal(initialPage(), /* @ts-ignore */
2229
+ ...(ngDevMode ? [{ debugName: "loadedMinPage" }] : /* istanbul ignore next */ []));
2230
+ const loadedMaxPage = signal(initialPage(), /* @ts-ignore */
2231
+ ...(ngDevMode ? [{ debugName: "loadedMaxPage" }] : /* istanbul ignore next */ []));
2232
+ const resetTrigger = signal(0, /* @ts-ignore */
2233
+ ...(ngDevMode ? [{ debugName: "resetTrigger" }] : /* istanbul ignore next */ []));
2234
+ const pageDirection = signal('next', /* @ts-ignore */
2235
+ ...(ngDevMode ? [{ debugName: "pageDirection" }] : /* istanbul ignore next */ []));
2236
+ suppressNextQueryStackDevtools();
1900
2237
  const stack = createQueryStack({
1901
2238
  args: () => null,
1902
2239
  queryCreator,
@@ -1919,7 +2256,7 @@ const createPagedQueryStack = (options) => {
1919
2256
  transform: transformArrayResponse,
1920
2257
  });
1921
2258
  effect(() => {
1922
- lastResetTimestamp();
2259
+ resetTrigger();
1923
2260
  const args = options.args(initialPage(), []);
1924
2261
  untracked(() => {
1925
2262
  stack.clear();
@@ -1935,22 +2272,26 @@ const createPagedQueryStack = (options) => {
1935
2272
  if (!res)
1936
2273
  return null;
1937
2274
  return responseNormalizer(res, all);
1938
- }, ...(ngDevMode ? [{ debugName: "maxPagination" }] : []));
2275
+ }, /* @ts-ignore */
2276
+ ...(ngDevMode ? [{ debugName: "maxPagination" }] : /* istanbul ignore next */ []));
1939
2277
  const minPagination = computed(() => {
1940
2278
  const res = stack.firstQuery()?.response();
1941
2279
  const all = stack.response();
1942
2280
  if (!res)
1943
2281
  return null;
1944
2282
  return responseNormalizer(res, all);
1945
- }, ...(ngDevMode ? [{ debugName: "minPagination" }] : []));
2283
+ }, /* @ts-ignore */
2284
+ ...(ngDevMode ? [{ debugName: "minPagination" }] : /* istanbul ignore next */ []));
1946
2285
  const isFirstPageLoaded = computed(() => {
1947
2286
  const min = minPagination();
1948
2287
  return min ? loadedMinPage() === min.currentPage : false;
1949
- }, ...(ngDevMode ? [{ debugName: "isFirstPageLoaded" }] : []));
2288
+ }, /* @ts-ignore */
2289
+ ...(ngDevMode ? [{ debugName: "isFirstPageLoaded" }] : /* istanbul ignore next */ []));
1950
2290
  const isLastPageLoaded = computed(() => {
1951
2291
  const max = maxPagination();
1952
2292
  return max ? loadedMaxPage() === max.totalPages : false;
1953
- }, ...(ngDevMode ? [{ debugName: "isLastPageLoaded" }] : []));
2293
+ }, /* @ts-ignore */
2294
+ ...(ngDevMode ? [{ debugName: "isLastPageLoaded" }] : /* istanbul ignore next */ []));
1954
2295
  const fetchPreviousPage = () => {
1955
2296
  const page = loadedMinPage() - 1;
1956
2297
  const allResponses = stack.response();
@@ -2019,25 +2360,29 @@ const createPagedQueryStack = (options) => {
2019
2360
  const page = resetOptions?.initialPage ?? initialPage();
2020
2361
  initialPage.set(page);
2021
2362
  pageDirection.set('next');
2022
- lastResetTimestamp.set(Date.now());
2363
+ resetTrigger.update((v) => v + 1);
2023
2364
  };
2024
2365
  const canFetchPreviousPage = computed(() => {
2025
2366
  const currentMinPagination = minPagination();
2026
2367
  if (!currentMinPagination)
2027
2368
  return false;
2028
2369
  return loadedMinPage() > 1;
2029
- }, ...(ngDevMode ? [{ debugName: "canFetchPreviousPage" }] : []));
2370
+ }, /* @ts-ignore */
2371
+ ...(ngDevMode ? [{ debugName: "canFetchPreviousPage" }] : /* istanbul ignore next */ []));
2030
2372
  const canFetchNextPage = computed(() => {
2031
2373
  const currentMaxPagination = maxPagination();
2032
2374
  return currentMaxPagination ? loadedMaxPage() < currentMaxPagination.totalPages : false;
2033
- }, ...(ngDevMode ? [{ debugName: "canFetchNextPage" }] : []));
2375
+ }, /* @ts-ignore */
2376
+ ...(ngDevMode ? [{ debugName: "canFetchNextPage" }] : /* istanbul ignore next */ []));
2034
2377
  const items = computed(() => {
2035
2378
  const res = stack.response();
2036
2379
  return res.map((r) => responseNormalizer(r, res).items).flat();
2037
- }, ...(ngDevMode ? [{ debugName: "items" }] : []));
2380
+ }, /* @ts-ignore */
2381
+ ...(ngDevMode ? [{ debugName: "items" }] : /* istanbul ignore next */ []));
2038
2382
  const isFirstLoad = computed(() => {
2039
2383
  return stack.queries().length === 1 && !!stack.lastQuery()?.loading();
2040
- }, ...(ngDevMode ? [{ debugName: "isFirstLoad" }] : []));
2384
+ }, /* @ts-ignore */
2385
+ ...(ngDevMode ? [{ debugName: "isFirstLoad" }] : /* istanbul ignore next */ []));
2041
2386
  const execute = (options) => {
2042
2387
  const whereFn = options?.where;
2043
2388
  if (whereFn) {
@@ -2096,6 +2441,16 @@ const createPagedQueryStack = (options) => {
2096
2441
  reset,
2097
2442
  execute,
2098
2443
  };
2444
+ if (isQueryDevtoolsEnabled()) {
2445
+ const unregister = registerQueryDevtoolsEntry({
2446
+ kind: 'paged-query-stack',
2447
+ handle: pagedQuery,
2448
+ meta: {
2449
+ featureTypes: features?.map((f) => f.type),
2450
+ },
2451
+ });
2452
+ inject(DestroyRef).onDestroy(unregister);
2453
+ }
2099
2454
  return pagedQuery;
2100
2455
  };
2101
2456
 
@@ -2210,7 +2565,8 @@ function effectComputed(computation, injector) {
2210
2565
  // Ignore errors in the initial computation
2211
2566
  // Angular might throw an error if required inputs are read but not available yet
2212
2567
  }
2213
- const lastResult = signal(initialData, ...(ngDevMode ? [{ debugName: "lastResult" }] : []));
2568
+ const lastResult = signal(initialData, /* @ts-ignore */
2569
+ ...(ngDevMode ? [{ debugName: "lastResult" }] : /* istanbul ignore next */ []));
2214
2570
  effect(() => {
2215
2571
  const data = runInInjectionContext(injector, () => computation());
2216
2572
  untracked(() => {
@@ -2261,7 +2617,8 @@ function queryStateSignal(source, options) {
2261
2617
  })), {
2262
2618
  initialValue: null,
2263
2619
  });
2264
- const rwSignal = linkedSignal(roSignal, ...(ngDevMode ? [{ debugName: "rwSignal" }] : []));
2620
+ const rwSignal = linkedSignal(roSignal, /* @ts-ignore */
2621
+ ...(ngDevMode ? [{ debugName: "rwSignal" }] : /* istanbul ignore next */ []));
2265
2622
  return rwSignal;
2266
2623
  }
2267
2624
  function queryStateResponseSignal(source, options) {
@@ -2269,7 +2626,8 @@ function queryStateResponseSignal(source, options) {
2269
2626
  const dataSignal = computed(() => {
2270
2627
  const state = s();
2271
2628
  return isQueryStateSuccess(state) ? state.response : null;
2272
- }, ...(ngDevMode ? [{ debugName: "dataSignal" }] : []));
2629
+ }, /* @ts-ignore */
2630
+ ...(ngDevMode ? [{ debugName: "dataSignal" }] : /* istanbul ignore next */ []));
2273
2631
  dataSignal['reset'] = () => {
2274
2632
  s.set(null);
2275
2633
  };
@@ -2352,7 +2710,8 @@ class V2QueryCreator {
2352
2710
  };
2353
2711
  this.createSignal = (initialValue, config) => {
2354
2712
  assertInInjectionContext(this.createSignal);
2355
- const _signal = signal(initialValue ?? null, ...(ngDevMode ? [{ debugName: "_signal" }] : []));
2713
+ const _signal = signal(initialValue ?? null, /* @ts-ignore */
2714
+ ...(ngDevMode ? [{ debugName: "_signal" }] : /* istanbul ignore next */ []));
2356
2715
  addQueryContainerHandling(toObservable(_signal), () => _signal(), config);
2357
2716
  return _signal;
2358
2717
  };
@@ -2622,16 +2981,25 @@ const createSecureExecuteFactory = (options) => {
2622
2981
  const circularChecker = circularQueryDependencyChecker();
2623
2982
  let authQuerySubscription = Subscription.EMPTY;
2624
2983
  let tokenRefreshSubscription = Subscription.EMPTY;
2984
+ let tokenWaitSubscription = Subscription.EMPTY;
2625
2985
  const reset = () => {
2626
2986
  authQuerySubscription.unsubscribe();
2627
2987
  authQuerySubscription = Subscription.EMPTY;
2628
2988
  tokenRefreshSubscription.unsubscribe();
2629
2989
  tokenRefreshSubscription = Subscription.EMPTY;
2990
+ tokenWaitSubscription.unsubscribe();
2991
+ tokenWaitSubscription = Subscription.EMPTY;
2630
2992
  resetExecuteState({
2631
2993
  executeState,
2632
2994
  executeOptions: { deps: options.deps, state: options.state },
2633
2995
  });
2634
2996
  };
2997
+ // A logout tears down the secure entries in the repository, but the query object holding one keeps
2998
+ // its own `response`/`error` signals — so without this a component still mounted after logout goes
2999
+ // on rendering the previous user's data until something calls `reset()` by hand.
3000
+ options.deps.client.repository.events$
3001
+ .pipe(filter((event) => event.type === 'unbind-all-secure'), takeUntilDestroyed(options.deps.destroyRef))
3002
+ .subscribe(() => reset());
2635
3003
  const error = (query) => {
2636
3004
  const state = options.state;
2637
3005
  state.error.set(query.error());
@@ -2667,11 +3035,27 @@ const createSecureExecuteFactory = (options) => {
2667
3035
  },
2668
3036
  }, tokens, headers, executeState);
2669
3037
  };
3038
+ // The auth query completing and the access token being populated happen on two
3039
+ // different reactive timelines: the token is set by a separate token-extraction
3040
+ // effect (see `setupBearerQueryRegistry`), and for cross-client / secure auth
3041
+ // queries the completion is delivered through several nested `toObservable` hops.
3042
+ // Calling `authAndExec` purely because the auth query is "done" therefore races
3043
+ // the token and can run while it is still null. Gate on the token signal instead:
3044
+ // execute immediately if it is already available, otherwise wait for it.
3045
+ const authAndExecWhenTokenReady = (executeArgs) => {
3046
+ if (options.authProvider.accessToken()) {
3047
+ authAndExec(executeArgs);
3048
+ return;
3049
+ }
3050
+ tokenWaitSubscription.unsubscribe();
3051
+ tokenWaitSubscription = toObservable(options.authProvider.accessToken, {
3052
+ injector: options.deps.injector,
3053
+ })
3054
+ .pipe(filter(Boolean), take(1), tap(() => authAndExec(executeArgs)))
3055
+ .subscribe();
3056
+ };
2670
3057
  const exec = (executeArgs) => {
2671
3058
  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
3059
  const execArgsWithDefaults = {
2676
3060
  args: executeArgs?.args ?? options.state.args(),
2677
3061
  options: executeArgs?.options,
@@ -2680,6 +3064,8 @@ const createSecureExecuteFactory = (options) => {
2680
3064
  authQuerySubscription = Subscription.EMPTY;
2681
3065
  tokenRefreshSubscription.unsubscribe();
2682
3066
  tokenRefreshSubscription = Subscription.EMPTY;
3067
+ tokenWaitSubscription.unsubscribe();
3068
+ tokenWaitSubscription = Subscription.EMPTY;
2683
3069
  tokenRefreshSubscription = options.authProvider.afterTokenRefresh$
2684
3070
  .pipe(filter(() => {
2685
3071
  const currentError = options.state.error();
@@ -2707,14 +3093,14 @@ const createSecureExecuteFactory = (options) => {
2707
3093
  error(query);
2708
3094
  }
2709
3095
  else if (query.response()) {
2710
- authAndExec(execArgsWithDefaults);
3096
+ authAndExecWhenTokenReady(execArgsWithDefaults);
2711
3097
  }
2712
3098
  }));
2713
3099
  }))
2714
3100
  .subscribe();
2715
3101
  }
2716
3102
  else if (authQuery.response()) {
2717
- authAndExec(execArgsWithDefaults);
3103
+ authAndExecWhenTokenReady(execArgsWithDefaults);
2718
3104
  }
2719
3105
  else if (authQuery.error()) {
2720
3106
  error(authQuery);
@@ -2931,10 +3317,18 @@ class LegacyQuery {
2931
3317
  get _isInMockMode() {
2932
3318
  return false;
2933
3319
  }
2934
- constructor(newQuery, _arguments, entity) {
3320
+ constructor(newQuery, _arguments, entity,
3321
+ /**
3322
+ * Wraps an inert query — see `createInertQuery`. Skips everything that needs a live injector,
3323
+ * so the wrapper is safe to build during teardown.
3324
+ *
3325
+ * @internal
3326
+ */
3327
+ _isInert = false) {
2935
3328
  this.newQuery = newQuery;
2936
3329
  this._arguments = _arguments;
2937
3330
  this.entity = entity;
3331
+ this._isInert = _isInert;
2938
3332
  /**
2939
3333
  * @internal
2940
3334
  */
@@ -2944,22 +3338,11 @@ class LegacyQuery {
2944
3338
  */
2945
3339
  this._currentPollConfig = null;
2946
3340
  /**
3341
+ * `null` for an inert query — there is no live injector to own an effect.
3342
+ *
2947
3343
  * @internal
2948
3344
  */
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" }] : []));
3345
+ this.storeSyncEffect = null;
2963
3346
  /**
2964
3347
  * @internal
2965
3348
  */
@@ -2972,15 +3355,50 @@ class LegacyQuery {
2972
3355
  * @internal
2973
3356
  */
2974
3357
  this._isPollingPaused = false;
3358
+ this._isDestroyed = false;
3359
+ if (this._isInert) {
3360
+ this.state$ = of(transformExecStateToQueryState(null));
3361
+ this._isDestroyed = true;
3362
+ return;
3363
+ }
2975
3364
  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() {
3365
+ this.storeSyncEffect = effect(() => {
3366
+ const res = this.newQuery.response();
3367
+ untracked(() => {
3368
+ if (this.entity?.set) {
3369
+ const id = this.entity.id({ args: this._arguments, response: res });
3370
+ this.entity.set({
3371
+ args: this._arguments,
3372
+ response: res,
3373
+ id,
3374
+ store: this.entity.store,
3375
+ });
3376
+ }
3377
+ });
3378
+ }, /* @ts-ignore */
3379
+ ...(ngDevMode ? [{ debugName: "storeSyncEffect" }] : /* istanbul ignore next */ []));
3380
+ // Tear down only what this wrapper owns. The underlying query is already being destroyed when this fires, so
3381
+ // destroying it again would call `R3Injector.destroy()` on an injector that is mid-teardown, which throws
3382
+ // NG0205 ("Injector has already been destroyed") from inside the view's cleanup phase.
3383
+ this.newQuery.subtle.destroyRef.onDestroy(() => this._teardown());
3384
+ }
3385
+ _teardown() {
3386
+ if (this._isDestroyed) {
3387
+ return;
3388
+ }
3389
+ this._isDestroyed = true;
2979
3390
  this._pollingSubscription?.unsubscribe();
2980
3391
  this._pollingSubscription = null;
2981
3392
  this._dependentsChanged$.complete();
3393
+ this.storeSyncEffect?.destroy();
3394
+ }
3395
+ destroy() {
3396
+ if (this._isDestroyed) {
3397
+ return;
3398
+ }
3399
+ this._teardown();
3400
+ // Re-enters `_teardown` through the query's `destroyRef`, which the `_isDestroyed` guard absorbs.
2982
3401
  this.newQuery.subtle.destroy();
2983
- this.storeSyncEffect.destroy();
2984
3402
  }
2985
3403
  execute(options = {}) {
2986
3404
  untracked(() => this.newQuery.execute({ args: this._arguments, options: { allowCache: options.skipCache !== true } }));
@@ -2994,7 +3412,8 @@ class LegacyQuery {
2994
3412
  return this;
2995
3413
  }
2996
3414
  poll(config) {
2997
- if (this._pollingSubscription) {
3415
+ // An inert or already destroyed query has nothing to poll, and the interval would outlive it.
3416
+ if (this._pollingSubscription || this._isDestroyed) {
2998
3417
  return this;
2999
3418
  }
3000
3419
  this._currentPollConfig = config;
@@ -3079,11 +3498,89 @@ class LegacyQuery {
3079
3498
  }
3080
3499
  }
3081
3500
 
3501
+ /**
3502
+ * A query that never executes and never leaves its initial state.
3503
+ *
3504
+ * `LegacyQueryCreator.prepare()` hands one back when the injector it was given is already destroyed
3505
+ * — the shape v2 call sites still fire into during teardown (a debounced search resolving after the
3506
+ * user navigated away, an RxJS callback on a stream that has not been unsubscribed yet). v2 had no
3507
+ * injector at all there, so the same code used to be harmless; building a real query would now
3508
+ * throw NG0205 from inside the view's cleanup phase, with no hint about which query caused it.
3509
+ *
3510
+ * Everything reads as "prepared but never executed": `execute()` and friends are no-ops, signals
3511
+ * stay `null`, observables complete immediately. Nothing here touches Angular's DI, which is the
3512
+ * point — there is no live injector left to bind to.
3513
+ */
3514
+ const createInertQuery = () => {
3515
+ const inertSignal = (value) => Object.assign(signal(value), { asObservable: () => of(value) });
3516
+ const noop = () => {
3517
+ /* the query is inert — there is nothing to execute, reset or destroy */
3518
+ };
3519
+ const base = {
3520
+ args: inertSignal(null),
3521
+ response: inertSignal(null),
3522
+ latestHttpEvent: inertSignal(null),
3523
+ loading: inertSignal(null),
3524
+ error: inertSignal(null),
3525
+ lastTimeExecutedAt: inertSignal(null),
3526
+ triggeredBy: inertSignal(null),
3527
+ id: inertSignal(null),
3528
+ executionState: inertSignal(null),
3529
+ };
3530
+ const createSnapshot = () => ({ ...base, isAlive: inertSignal(false) });
3531
+ // A DestroyRef that is already destroyed: callbacks registered on it are dropped rather than
3532
+ // stored, so nothing keeps this object (or its registrant) alive.
3533
+ const destroyRef = {
3534
+ destroyed: true,
3535
+ onDestroy: () => noop,
3536
+ };
3537
+ return {
3538
+ ...base,
3539
+ execute: noop,
3540
+ createSnapshot,
3541
+ reset: noop,
3542
+ asReadonly: () => ({ ...base, createSnapshot }),
3543
+ subtle: {
3544
+ destroy: noop,
3545
+ setResponse: noop,
3546
+ setLoading: noop,
3547
+ setError: noop,
3548
+ request: inertSignal(null),
3549
+ destroyRef,
3550
+ injector: Injector.NULL,
3551
+ },
3552
+ };
3553
+ };
3554
+
3555
+ /**
3556
+ * Whether an injector can still be used. A destroyed `R3Injector` throws on `get()` rather than
3557
+ * answering, so the throw itself is the answer; a node injector hands back a `DestroyRef` that
3558
+ * reports its own state.
3559
+ */
3560
+ const isInjectorUsable = (injector) => {
3561
+ try {
3562
+ return injector.get(DestroyRef, null, { optional: true })?.destroyed !== true;
3563
+ }
3564
+ catch {
3565
+ return false;
3566
+ }
3567
+ };
3082
3568
  class LegacyQueryCreator {
3083
3569
  constructor(options) {
3084
3570
  this.options = options;
3085
3571
  this.prepare = (args) => {
3086
- const injector = args?.injector ?? inject(Injector);
3572
+ // v2's `prepare()` needed no injection context, so plenty of call sites are plain callbacks.
3573
+ // Angular's raw NG0203 names neither the query nor the call site, which makes those a bisection
3574
+ // exercise — replace it with an error that does.
3575
+ const injector = args?.injector ??
3576
+ (() => {
3577
+ try {
3578
+ return inject(Injector);
3579
+ }
3580
+ catch {
3581
+ throw legacyPrepareWithoutInjectionContext(this.options.name);
3582
+ }
3583
+ })();
3087
3584
  let headers = new HttpHeaders();
3088
3585
  if (args?.headers) {
3089
3586
  Object.entries(args.headers).forEach(([key, value]) => {
@@ -3098,6 +3595,17 @@ class LegacyQueryCreator {
3098
3595
  ...(args?.queryParams ? { queryParams: args.queryParams } : {}),
3099
3596
  headers,
3100
3597
  };
3598
+ // A captured component injector routinely outlives its component here: a debounced search
3599
+ // resolving after navigation, an RxJS callback on a stream torn down a tick later. That is not
3600
+ // a programming error the way it would be in new code — v2 had no injector to destroy — so it
3601
+ // yields a query that does nothing instead of an NG0205 raised from the view's cleanup phase.
3602
+ if (!isInjectorUsable(injector)) {
3603
+ if (isDevMode()) {
3604
+ console.warn(`${this.options.name ? `"${this.options.name}"` : 'A legacy query'}.prepare() was called with a destroyed injector, so an inert query was returned. ` +
3605
+ `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.`);
3606
+ }
3607
+ return new LegacyQuery(createInertQuery(), queryArgs, this.options.entity, true);
3608
+ }
3101
3609
  return runInInjectionContext(injector, () => {
3102
3610
  return untracked(() => {
3103
3611
  const newQuery = this.options.creator({
@@ -3112,7 +3620,8 @@ class LegacyQueryCreator {
3112
3620
  legacyQuery.destroy();
3113
3621
  destroyEffect.destroy();
3114
3622
  }
3115
- }, ...(ngDevMode ? [{ debugName: "destroyEffect" }] : []));
3623
+ }, /* @ts-ignore */
3624
+ ...(ngDevMode ? [{ debugName: "destroyEffect" }] : /* istanbul ignore next */ []));
3116
3625
  }
3117
3626
  return legacyQuery;
3118
3627
  });
@@ -3124,7 +3633,8 @@ class LegacyQueryCreator {
3124
3633
  return subject;
3125
3634
  };
3126
3635
  this.createSignal = (initialValue, config) => {
3127
- const _signal = signal(initialValue ?? null, ...(ngDevMode ? [{ debugName: "_signal" }] : []));
3636
+ const _signal = signal(initialValue ?? null, /* @ts-ignore */
3637
+ ...(ngDevMode ? [{ debugName: "_signal" }] : /* istanbul ignore next */ []));
3128
3638
  addQueryContainerHandling(toObservable(_signal), () => _signal(), config);
3129
3639
  return _signal;
3130
3640
  };
@@ -3263,15 +3773,20 @@ const isQueryCollection = (query) => {
3263
3773
  const createQueryCollection = (
3264
3774
  // eslint-disable-next-line @typescript-eslint/no-unused-vars
3265
3775
  queryMap) => new BehaviorSubject(null);
3266
- const createQueryCollectionSubject = (queryMap, config) => {
3776
+ const createQueryCollectionSubject = (
3777
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
3778
+ queryMap, config) => {
3267
3779
  assertInInjectionContext(createQueryCollectionSubject);
3268
3780
  const subject = new BehaviorSubject(null);
3269
3781
  addQueryContainerHandling(subject.pipe(map((s) => s?.query ?? null)), () => subject.getValue()?.query ?? null, config);
3270
3782
  return subject;
3271
3783
  };
3272
- const createQueryCollectionSignal = (queryMap, config) => {
3784
+ const createQueryCollectionSignal = (
3785
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
3786
+ queryMap, config) => {
3273
3787
  assertInInjectionContext(createQueryCollectionSignal);
3274
- const _signal = signal(null, ...(ngDevMode ? [{ debugName: "_signal" }] : []));
3788
+ const _signal = signal(null, /* @ts-ignore */
3789
+ ...(ngDevMode ? [{ debugName: "_signal" }] : /* istanbul ignore next */ []));
3275
3790
  addQueryContainerHandling(toObservable(_signal).pipe(map((s) => s?.query ?? null)), () => _signal()?.query ?? null, config);
3276
3791
  return _signal;
3277
3792
  };
@@ -4005,7 +4520,8 @@ class V2BearerAuthProvider {
4005
4520
  }
4006
4521
 
4007
4522
  const [provideInfinityQueryResponseDelay, injectInfinityQueryResponseDelay] = createProvider(() => {
4008
- const enabled = signal(false, ...(ngDevMode ? [{ debugName: "enabled" }] : []));
4523
+ const enabled = signal(false, /* @ts-ignore */
4524
+ ...(ngDevMode ? [{ debugName: "enabled" }] : /* istanbul ignore next */ []));
4009
4525
  const enabled$ = toObservable(enabled);
4010
4526
  return {
4011
4527
  enabled,
@@ -4214,7 +4730,7 @@ class InfinityQueryDirective {
4214
4730
  get data$() {
4215
4731
  return this._data$.asObservable();
4216
4732
  }
4217
- static ngTemplateContextGuard(dir, ctx) {
4733
+ static ngTemplateContextGuard(_dir, _ctx) {
4218
4734
  return true;
4219
4735
  }
4220
4736
  constructor() {
@@ -4316,13 +4832,13 @@ class InfinityQueryDirective {
4316
4832
  this._viewContext.currentQuery = null;
4317
4833
  this._data$.next([]);
4318
4834
  }
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: [
4835
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.7", ngImport: i0, type: InfinityQueryDirective, deps: [], target: i0.ɵɵFactoryTarget.Directive }); }
4836
+ 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
4837
  { provide: INFINITY_QUERY_TOKEN, useExisting: InfinityQueryDirective },
4322
4838
  provideInfinityQueryResponseDelay(),
4323
4839
  ], exportAs: ["etInfinityQuery"], ngImport: i0 }); }
4324
4840
  }
4325
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.1", ngImport: i0, type: InfinityQueryDirective, decorators: [{
4841
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.7", ngImport: i0, type: InfinityQueryDirective, decorators: [{
4326
4842
  type: Directive,
4327
4843
  args: [{
4328
4844
  selector: '[etInfinityQuery]',
@@ -4344,10 +4860,13 @@ class InfinityQueryTriggerDirective {
4344
4860
  this._destroy = new Subject();
4345
4861
  this._observer = null;
4346
4862
  this.click$ = fromEvent(this._elementRef.nativeElement, 'click');
4347
- this.scrollContainerSelector = input(null, ...(ngDevMode ? [{ debugName: "scrollContainerSelector" }] : []));
4863
+ this.scrollContainerSelector = input(null, /* @ts-ignore */
4864
+ ...(ngDevMode ? [{ debugName: "scrollContainerSelector" }] : /* istanbul ignore next */ []));
4348
4865
  // 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" }] : []));
4866
+ this.infinityQueryOverride = input(null, /* @ts-ignore */
4867
+ ...(ngDevMode ? [{ debugName: "infinityQueryOverride" }] : /* istanbul ignore next */ []));
4868
+ this.infinityQuery = computed(() => this.infinityQueryOverride() ?? this._infinityQuery, /* @ts-ignore */
4869
+ ...(ngDevMode ? [{ debugName: "infinityQuery" }] : /* istanbul ignore next */ []));
4351
4870
  }
4352
4871
  ngOnInit() {
4353
4872
  const isInteractive = this._elementRef.nativeElement.tagName === 'BUTTON';
@@ -4379,10 +4898,10 @@ class InfinityQueryTriggerDirective {
4379
4898
  });
4380
4899
  this._observer.observe(this._elementRef.nativeElement);
4381
4900
  }
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 }); }
4901
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.7", ngImport: i0, type: InfinityQueryTriggerDirective, deps: [], target: i0.ɵɵFactoryTarget.Directive }); }
4902
+ 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
4903
  }
4385
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.1", ngImport: i0, type: InfinityQueryTriggerDirective, decorators: [{
4904
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.7", ngImport: i0, type: InfinityQueryTriggerDirective, decorators: [{
4386
4905
  type: Directive,
4387
4906
  args: [{
4388
4907
  selector: '[etInfinityQueryTrigger], et-infinity-query-trigger',
@@ -4390,7 +4909,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.1", ngImpor
4390
4909
  }], propDecorators: { scrollContainerSelector: [{ type: i0.Input, args: [{ isSignal: true, alias: "scrollContainerSelector", required: false }] }], infinityQueryOverride: [{ type: i0.Input, args: [{ isSignal: true, alias: "infinityQueryOverride", required: false }] }] } });
4391
4910
 
4392
4911
  class QueryDirective {
4393
- static ngTemplateContextGuard(dir, ctx) {
4912
+ static ngTemplateContextGuard(_dir, _ctx) {
4394
4913
  return true;
4395
4914
  }
4396
4915
  constructor() {
@@ -4406,8 +4925,8 @@ class QueryDirective {
4406
4925
  scope: null,
4407
4926
  query: null,
4408
4927
  };
4409
- this.query = input.required({ ...(ngDevMode ? { debugName: "query" } : {}), alias: 'etQuery' });
4410
- this.cache = input(false, { ...(ngDevMode ? { debugName: "cache" } : {}), alias: 'etQueryCache' });
4928
+ this.query = input.required({ ...(ngDevMode ? { debugName: "query" } : /* istanbul ignore next */ {}), alias: 'etQuery' });
4929
+ this.cache = input(false, { ...(ngDevMode ? { debugName: "cache" } : /* istanbul ignore next */ {}), alias: 'etQueryCache' });
4411
4930
  this.queryState = queryStateSignal(this.query);
4412
4931
  inject(ViewContainerRef).createEmbeddedView(inject(TemplateRef), this._viewContext);
4413
4932
  effect(() => {
@@ -4468,10 +4987,10 @@ class QueryDirective {
4468
4987
  });
4469
4988
  });
4470
4989
  }
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 }); }
4990
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.7", ngImport: i0, type: QueryDirective, deps: [], target: i0.ɵɵFactoryTarget.Directive }); }
4991
+ 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
4992
  }
4474
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.1", ngImport: i0, type: QueryDirective, decorators: [{
4993
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.7", ngImport: i0, type: QueryDirective, decorators: [{
4475
4994
  type: Directive,
4476
4995
  args: [{
4477
4996
  selector: '[etQuery]',
@@ -4492,10 +5011,10 @@ class QueryShortNamePipe {
4492
5011
  }
4493
5012
  return route;
4494
5013
  }
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" }); }
5014
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.7", ngImport: i0, type: QueryShortNamePipe, deps: [], target: i0.ɵɵFactoryTarget.Pipe }); }
5015
+ static { this.ɵpipe = i0.ɵɵngDeclarePipe({ minVersion: "14.0.0", version: "22.0.7", ngImport: i0, type: QueryShortNamePipe, isStandalone: true, name: "queryShortName" }); }
4497
5016
  }
4498
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.1", ngImport: i0, type: QueryShortNamePipe, decorators: [{
5017
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.7", ngImport: i0, type: QueryShortNamePipe, decorators: [{
4499
5018
  type: Pipe,
4500
5019
  args: [{ name: 'queryShortName' }]
4501
5020
  }] });
@@ -4517,34 +5036,45 @@ class QueryDevtoolsComponent {
4517
5036
  this._cdr = inject(ChangeDetectorRef);
4518
5037
  this._destroy$ = createDestroy();
4519
5038
  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" }] : []));
5039
+ this.isOpen = signal(false, /* @ts-ignore */
5040
+ ...(ngDevMode ? [{ debugName: "isOpen" }] : /* istanbul ignore next */ []));
5041
+ this.isTranslucent = signal(false, /* @ts-ignore */
5042
+ ...(ngDevMode ? [{ debugName: "isTranslucent" }] : /* istanbul ignore next */ []));
5043
+ this.snapLayout = signal('full', /* @ts-ignore */
5044
+ ...(ngDevMode ? [{ debugName: "snapLayout" }] : /* istanbul ignore next */ []));
5045
+ this.queryListMode = signal('live', /* @ts-ignore */
5046
+ ...(ngDevMode ? [{ debugName: "queryListMode" }] : /* istanbul ignore next */ []));
5047
+ this.viewMode = signal('query', /* @ts-ignore */
5048
+ ...(ngDevMode ? [{ debugName: "viewMode" }] : /* istanbul ignore next */ []));
5049
+ this.showResponse = signal(false, /* @ts-ignore */
5050
+ ...(ngDevMode ? [{ debugName: "showResponse" }] : /* istanbul ignore next */ []));
5051
+ this.showRawResponse = signal(false, /* @ts-ignore */
5052
+ ...(ngDevMode ? [{ debugName: "showRawResponse" }] : /* istanbul ignore next */ []));
5053
+ this.showQueryConfig = signal(false, /* @ts-ignore */
5054
+ ...(ngDevMode ? [{ debugName: "showQueryConfig" }] : /* istanbul ignore next */ []));
5055
+ this.showEntityStoreValue = signal(false, /* @ts-ignore */
5056
+ ...(ngDevMode ? [{ debugName: "showEntityStoreValue" }] : /* istanbul ignore next */ []));
5057
+ this.showArguments = signal(false, /* @ts-ignore */
5058
+ ...(ngDevMode ? [{ debugName: "showArguments" }] : /* istanbul ignore next */ []));
4530
5059
  this.selectedClientIdCtrl = new FormControl(0);
4531
5060
  this.selectedClientId = toSignal(this.selectedClientIdCtrl.valueChanges.pipe(startWith(this.selectedClientIdCtrl.value)));
4532
- this.selectedQueryId = signal(null, ...(ngDevMode ? [{ debugName: "selectedQueryId" }] : []));
5061
+ this.selectedQueryId = signal(null, /* @ts-ignore */
5062
+ ...(ngDevMode ? [{ debugName: "selectedQueryId" }] : /* istanbul ignore next */ []));
4533
5063
  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" }] : []));
5064
+ const configs = this.queryClientConfigs;
5065
+ const index = configs.length === 1 ? 0 : (this.selectedClientId() ?? 0);
5066
+ const config = configs[index] ?? configs[0];
5067
+ if (!config) {
5068
+ throw new Error('QueryDevtools: no query client configs registered');
5069
+ }
5070
+ return config;
5071
+ }, /* @ts-ignore */
5072
+ ...(ngDevMode ? [{ debugName: "selectedClientConfig" }] : /* istanbul ignore next */ []));
4544
5073
  this.queryStore = computed(() => {
4545
5074
  const config = this.selectedClientConfig();
4546
5075
  return config.client._store;
4547
- }, ...(ngDevMode ? [{ debugName: "queryStore" }] : []));
5076
+ }, /* @ts-ignore */
5077
+ ...(ngDevMode ? [{ debugName: "queryStore" }] : /* istanbul ignore next */ []));
4548
5078
  this.queryStore$ = toObservable(this.queryStore);
4549
5079
  this._queries$ = new BehaviorSubject([]);
4550
5080
  this.queries = toSignal(this._queries$);
@@ -4558,14 +5088,16 @@ class QueryDevtoolsComponent {
4558
5088
  return this.queries()?.find((q) => q._id === id) ?? null;
4559
5089
  }
4560
5090
  return this.queryHistory$.getValue()?.find((q) => q._id === id) ?? null;
4561
- }, ...(ngDevMode ? [{ debugName: "selectedQuery" }] : []));
5091
+ }, /* @ts-ignore */
5092
+ ...(ngDevMode ? [{ debugName: "selectedQuery" }] : /* istanbul ignore next */ []));
4562
5093
  this.selectedQuery$ = toObservable(this.selectedQuery);
4563
5094
  this.selectedQueryEntityStore = computed(() => {
4564
5095
  const query = this.selectedQuery();
4565
5096
  if (!query)
4566
5097
  return null;
4567
5098
  return query.store;
4568
- }, ...(ngDevMode ? [{ debugName: "selectedQueryEntityStore" }] : []));
5099
+ }, /* @ts-ignore */
5100
+ ...(ngDevMode ? [{ debugName: "selectedQueryEntityStore" }] : /* istanbul ignore next */ []));
4569
5101
  this.selectedQueryEntityStore$ = toObservable(this.selectedQueryEntityStore);
4570
5102
  this.selectedQueryEntityStoreValue$ = this.selectedQueryEntityStore$.pipe(switchMap((s) => s?._change$.pipe(startWith(''), map(() => {
4571
5103
  if (!s._dictionary)
@@ -4603,7 +5135,8 @@ class QueryDevtoolsComponent {
4603
5135
  return {
4604
5136
  ...sharedCfg,
4605
5137
  };
4606
- }, ...(ngDevMode ? [{ debugName: "stringifiedQueryConfig" }] : []));
5138
+ }, /* @ts-ignore */
5139
+ ...(ngDevMode ? [{ debugName: "stringifiedQueryConfig" }] : /* istanbul ignore next */ []));
4607
5140
  this.authProvider = toSignal(toObservable(this.selectedClientConfig).pipe(switchMap((c) => c.client.authProvider$)));
4608
5141
  this.authProviderDetails = computed(() => {
4609
5142
  const provider = this.authProvider();
@@ -4665,7 +5198,8 @@ class QueryDevtoolsComponent {
4665
5198
  config: {},
4666
5199
  };
4667
5200
  }
4668
- }, ...(ngDevMode ? [{ debugName: "authProviderDetails" }] : []));
5201
+ }, /* @ts-ignore */
5202
+ ...(ngDevMode ? [{ debugName: "authProviderDetails" }] : /* istanbul ignore next */ []));
4669
5203
  this.trackByClient = (_, { client }) => client.config.baseRoute;
4670
5204
  this.trackByQuery = (_, { _id }) => _id;
4671
5205
  this.selectedClientIdCtrl.valueChanges
@@ -4786,14 +5320,14 @@ class QueryDevtoolsComponent {
4786
5320
  clearQueryHistory() {
4787
5321
  this.queryHistory$.next([]);
4788
5322
  }
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 }); }
5323
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.7", ngImport: i0, type: QueryDevtoolsComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
5324
+ 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
5325
  }
4792
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.1", ngImport: i0, type: QueryDevtoolsComponent, decorators: [{
5326
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.7", ngImport: i0, type: QueryDevtoolsComponent, decorators: [{
4793
5327
  type: Component,
4794
- args: [{ selector: 'et-query-devtools', changeDetection: ChangeDetectionStrategy.OnPush, encapsulation: ViewEncapsulation.ShadowDom, host: {
5328
+ args: [{ selector: 'et-query-devtools', encapsulation: ViewEncapsulation.ShadowDom, host: {
4795
5329
  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"] }]
5330
+ }, 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
5331
  }], ctorParameters: () => [] });
4798
5332
 
4799
5333
  class EntityStoreError extends Error {
@@ -4950,10 +5484,238 @@ const removeFrom = (store, { where, id }) => {
4950
5484
  .subscribe();
4951
5485
  };
4952
5486
 
4953
- const generateUuid = () => crypto.randomUUID();
5487
+ /** `kind` of a `ValidationError` created from a single server violation. */
5488
+ const SERVER_VIOLATION_ERROR_KIND = 'etServerViolation';
5489
+ /** `kind` of the form-level fallback error created when a failed request carries no violations. */
5490
+ const SERVER_ERROR_KIND = 'etServerError';
5491
+ /**
5492
+ * Extracts the violation list from a failed request in any of the shapes it may reach you:
5493
+ * a `QueryErrorResponse` (from `query.error()` or a settled snapshot), a raw `HttpErrorResponse`,
5494
+ * an already-unwrapped error body, or a plain violation array. Returns `[]` when the error
5495
+ * carries no violations.
5496
+ */
5497
+ const extractFormViolations = (error) => {
5498
+ if (!error)
5499
+ return [];
5500
+ const body = isQueryErrorResponse(error) ? error.raw.error : error instanceof HttpErrorResponse ? error.error : error;
5501
+ if (isSymfonyFormViolationListError(body)) {
5502
+ return body.violations;
5503
+ }
5504
+ if (isSymfonyListError(body)) {
5505
+ return body;
5506
+ }
5507
+ return [];
5508
+ };
5509
+ /**
5510
+ * Maps a failed request's violations onto a signal-forms field tree as validation errors —
5511
+ * the bridge between an API's violation list (`@ethlete/types` `FormViolationView`) and
5512
+ * signal forms.
5513
+ *
5514
+ * Each violation's `propertyPath` (e.g. `items[2].name`) is resolved against `fieldTree`; a
5515
+ * resolved violation becomes a `ServerViolationValidationError` bound to that field, an
5516
+ * unresolved one becomes a form-level error (customizable via `onUnmappedViolation`). A failed
5517
+ * request that carries no violations at all degrades to form-level `ServerValidationError`s
5518
+ * built from the normalized error message — so returning this function's result from a
5519
+ * `submit()` action never silently succeeds on failure.
5520
+ *
5521
+ * ```ts
5522
+ * await submit(this.form, async (field) => {
5523
+ * const snapshot = await executeUntilSettled(this.createPost, { args: { body: field().value() } });
5524
+ * const error = snapshot.error();
5525
+ *
5526
+ * if (!error) return;
5527
+ *
5528
+ * return mapViolationsToFormErrors({ fieldTree: field, error });
5529
+ * });
5530
+ * ```
5531
+ */
5532
+ const mapViolationsToFormErrors = (options) => {
5533
+ const { fieldTree, error, rewritePath, onUnmappedViolation } = options;
5534
+ if (error === null || error === undefined)
5535
+ return [];
5536
+ const violations = extractFormViolations(error);
5537
+ if (!violations.length) {
5538
+ // An empty violation array is an explicit "nothing to map" — only unrecognized error
5539
+ // shapes degrade to the form-level fallback.
5540
+ if (Array.isArray(error) && !error.length)
5541
+ return [];
5542
+ return createFallbackErrors(error);
5543
+ }
5544
+ const errors = [];
5545
+ for (const violation of violations) {
5546
+ const rawPath = violation.propertyPath;
5547
+ const path = rawPath && rewritePath ? rewritePath(rawPath, violation) : rawPath;
5548
+ const resolvedFieldTree = path ? resolveFieldTreePath(fieldTree, path) : null;
5549
+ if (resolvedFieldTree) {
5550
+ const mappedError = {
5551
+ kind: SERVER_VIOLATION_ERROR_KIND,
5552
+ message: violation.message,
5553
+ fieldTree: resolvedFieldTree,
5554
+ violation,
5555
+ };
5556
+ errors.push(mappedError);
5557
+ }
5558
+ else if (onUnmappedViolation) {
5559
+ const customError = onUnmappedViolation(violation);
5560
+ if (customError) {
5561
+ errors.push(customError);
5562
+ }
5563
+ }
5564
+ else {
5565
+ const unmappedError = {
5566
+ kind: SERVER_VIOLATION_ERROR_KIND,
5567
+ message: violation.message,
5568
+ violation,
5569
+ };
5570
+ errors.push(unmappedError);
5571
+ }
5572
+ }
5573
+ return errors;
5574
+ };
5575
+ const createFallbackErrors = (error) => {
5576
+ const queryError = isQueryErrorResponse(error) ? error : createQueryErrorResponse(error);
5577
+ if (queryError.isList) {
5578
+ return queryError.errors.map((item) => ({ kind: SERVER_ERROR_KIND, message: item.message }));
5579
+ }
5580
+ return [{ kind: SERVER_ERROR_KIND, message: queryError.error.message }];
5581
+ };
5582
+ /** Splits a property path like `items[2].name` or `translations['de'].title` into its segments. */
5583
+ const parsePropertyPath = (path) => {
5584
+ return path
5585
+ .replace(/\[(['"]?)([^\]]*?)\1\]/g, '.$2')
5586
+ .split('.')
5587
+ .filter((segment) => segment.length > 0);
5588
+ };
5589
+ const resolveFieldTreePath = (fieldTree, path) => {
5590
+ let current = fieldTree;
5591
+ for (const segment of parsePropertyPath(path)) {
5592
+ if (current === null || (typeof current !== 'object' && typeof current !== 'function')) {
5593
+ return null;
5594
+ }
5595
+ current = current[segment];
5596
+ }
5597
+ // A field tree node is callable (calling it returns the field state) — anything else means the
5598
+ // path left the tree (e.g. an index past the array's current length).
5599
+ return typeof current === 'function' ? current : null;
5600
+ };
5601
+
5602
+ // Shared core for the query-backed async validators (`validateWithQuery` /
5603
+ // `validateWithV2Query`). Everything except how the query is executed — the `factory` that turns
5604
+ // the debounced params into a `Resource` — is identical between the v2 and v3 variants, so it is
5605
+ // factored here and imported by both. This module is intentionally NOT part of the `http` barrel;
5606
+ // it is internal plumbing consumed by direct path (the legacy variant already imports from `http`).
5607
+ /** Default debounce (ms) applied before a validate request runs, mirroring `selectOptionsFrom*Query`. */
5608
+ const DEFAULT_QUERY_VALIDATION_DEBOUNCE = 300;
5609
+ /**
5610
+ * Turns a failed validate request into field errors — the default `onError` mapping shared by both
5611
+ * validators. Reuses the existing `mapViolationsToFormErrors` bridge: a `422` violation list is
5612
+ * resolved onto the child fields by `propertyPath`, while a network / non-violation error degrades
5613
+ * to a non-swallowed form-level error. A caller-supplied `mapViolations` overrides only the
5614
+ * violation → error step (it receives the already-extracted violations).
5615
+ */
5616
+ const mapQueryValidationError = (error, ctx, mapViolations) => {
5617
+ // Angular's `resource()` wraps a thrown non-`Error` (our `QueryErrorResponse` / `HttpErrorResponse`)
5618
+ // into an `Error` and stashes the original on `.cause`, so unwrap it before extraction.
5619
+ const cause = error instanceof Error && error.cause !== undefined ? error.cause : error;
5620
+ if (mapViolations) {
5621
+ return mapViolations(extractFormViolations(cause), ctx);
5622
+ }
5623
+ // `ctx.fieldTree` is the (read-only) tree of the validated field; `propertyPath`s resolve against
5624
+ // it. The cast bridges `ReadonlyFieldTree` → `FieldTree` — `mapViolationsToFormErrors` only walks
5625
+ // and reads the tree, never mutates it.
5626
+ return mapViolationsToFormErrors({ fieldTree: ctx.fieldTree, error: cause });
5627
+ };
5628
+ /**
5629
+ * Binds a query-backed async validator to `path` via `validateAsync`. Success (`204` / any `2xx`
5630
+ * with no violation body) reports no errors; a failed request is mapped by
5631
+ * {@link mapQueryValidationError}. Async validation only runs once the field's synchronous
5632
+ * validators pass and while `when` holds — signal forms feeds the `factory` `undefined` params when
5633
+ * the request shouldn't run, so the resource stays idle.
5634
+ */
5635
+ const applyQueryAsyncValidator = (path, config) => {
5636
+ validateAsync(path, {
5637
+ params: config.params,
5638
+ debounce: config.debounce ?? DEFAULT_QUERY_VALIDATION_DEBOUNCE,
5639
+ factory: config.factory,
5640
+ when: config.when,
5641
+ onSuccess: () => null,
5642
+ onError: (error, ctx) => mapQueryValidationError(error, ctx, config.mapViolations),
5643
+ });
5644
+ };
5645
+
5646
+ /**
5647
+ * Binds a query-backed async validator to a signal-forms field, adapting a **legacy v2**
5648
+ * `@ethlete/query` query into what `validateAsync` consumes — the `V2QueryClient` counterpart of
5649
+ * {@link validateWithQuery}, so apps that haven't migrated yet can still validate against the
5650
+ * server. The query runs through the client (bearer auth, base route, caching, Symfony error
5651
+ * normalization), where a raw `httpResource` would fire unauthenticated and bypass the pipeline.
5652
+ *
5653
+ * The query is prepared and executed (debounced) as the field value changes, only after the
5654
+ * field's synchronous validators pass. A `204` / successful response reports no errors; a
5655
+ * `422 FormViolationListView` maps each violation onto its child field by `propertyPath`; a
5656
+ * network / other error degrades to a non-swallowed form-level error.
5657
+ *
5658
+ * Call it from a schema definition, the same place you'd call `validateAsync`:
5659
+ *
5660
+ * ```ts
5661
+ * const schema = schema<OpportunityEmail>((p) => {
5662
+ * validateWithV2Query(p, {
5663
+ * queryCreator: postOpportunityEmailValidate, // hubApiClient.post({ ... })
5664
+ * args: (ctx) => ({ pathParams: { opportunityId }, body: { ...ctx.value() } }),
5665
+ * });
5666
+ * });
5667
+ * ```
5668
+ */
5669
+ const validateWithV2Query = (path, config) => {
5670
+ applyQueryAsyncValidator(path, {
5671
+ params: config.args,
5672
+ debounce: config.debounce,
5673
+ when: config.when,
5674
+ mapViolations: config.mapViolations,
5675
+ factory: (params) => resource({
5676
+ params: () => params(),
5677
+ loader: ({ params: prepareArgs, abortSignal }) => {
5678
+ // Prepared per request — v2 caches by the prepared args, so a fresh `prepare()` is the
5679
+ // legacy idiom (the sibling `selectOptionsFromV2Query` does the same via `queryComputed`).
5680
+ const query = config.queryCreator.prepare(prepareArgs);
5681
+ abortSignal.addEventListener('abort', () => query.abort());
5682
+ query.execute();
5683
+ // Resolve once the query settles. A failure is thrown so it lands in `onError`, where the
5684
+ // violations are mapped onto the fields; the underlying `HttpErrorResponse` is thrown so
5685
+ // `extractFormViolations` can unwrap the 422 body.
5686
+ return firstValueFrom(query.state$.pipe(filter((state) => isQueryStateSuccess(state) || isQueryStateFailure(state)), take(1))).then((state) => {
5687
+ if (isQueryStateFailure(state)) {
5688
+ throw state.error.httpErrorResponse;
5689
+ }
5690
+ return state.response;
5691
+ });
5692
+ },
5693
+ }),
5694
+ });
5695
+ };
5696
+
5697
+ const generateUuid = () => randomId();
5698
+ /** @see CreateQueryClientConfigOptions.keepUnusedFor */
5699
+ const DEFAULT_KEEP_UNUSED_FOR = 300_000;
5700
+ /**
5701
+ * Hard cap on entries kept without consumers, per client. Retention is opportunistic, so a runaway
5702
+ * count (a search-as-you-type query produces a new cache key per keystroke, and each one goes through
5703
+ * `unbind`) must never grow unbounded — the least recently orphaned entries are dropped first.
5704
+ */
5705
+ const MAX_UNUSED_ENTRIES = 50;
4954
5706
  const createQueryRepository = (config) => {
4955
5707
  const cache = new Map();
4956
5708
  const eventsSubject = new Subject();
5709
+ // Bumped on every cache mutation so the devtools cache view can react. Cheap enough to keep
5710
+ // unconditional — it is a single integer signal with no readers unless the devtools are open.
5711
+ const cacheVersion = signal(0, /* @ts-ignore */
5712
+ ...(ngDevMode ? [{ debugName: "cacheVersion" }] : /* istanbul ignore next */ []));
5713
+ const bumpCacheVersion = () => cacheVersion.update((v) => v + 1);
5714
+ const resolveKeepUnusedFor = (creatorOptions, shouldCache) => {
5715
+ if (config.retentionEnabled === false || !shouldCache)
5716
+ return 0;
5717
+ return Math.max(0, creatorOptions?.keepUnusedFor ?? config.keepUnusedFor ?? DEFAULT_KEEP_UNUSED_FOR);
5718
+ };
4957
5719
  const request = (options) => {
4958
5720
  const { args, creatorOptions, runQueryOptions } = options;
4959
5721
  const shouldCache = creatorOptions?.subtle?.useQueryRepositoryCache === false
@@ -4979,6 +5741,7 @@ const createQueryRepository = (config) => {
4979
5741
  })
4980
5742
  : false;
4981
5743
  const trackingKey = cacheKey || generateUuid();
5744
+ const keepUnusedFor = resolveKeepUnusedFor(creatorOptions, shouldCache);
4982
5745
  const previousKey = options.previousKey;
4983
5746
  if (cacheKey !== previousKey && previousKey) {
4984
5747
  unbind(previousKey, options.consumerDestroyRef);
@@ -4986,7 +5749,10 @@ const createQueryRepository = (config) => {
4986
5749
  if (shouldCache && cacheKey) {
4987
5750
  const cacheEntry = cache.get(cacheKey);
4988
5751
  if (cacheEntry) {
4989
- bind(cacheKey, options.consumerDestroyRef, cacheEntry.request, options.isSecure ?? false, true);
5752
+ // The entry may have been sitting out its `keepUnusedFor` window — a consumer binding again
5753
+ // makes it live, so the pending eviction must go.
5754
+ cancelEviction(cacheEntry);
5755
+ bind(cacheKey, options.consumerDestroyRef, cacheEntry.request, options.isSecure ?? false, true, keepUnusedFor);
4990
5756
  if (!runQueryOptions?.allowCache || cacheEntry.request.isStale()) {
4991
5757
  cacheEntry.request.execute({ allowCache: runQueryOptions?.allowCache });
4992
5758
  }
@@ -4999,13 +5765,43 @@ const createQueryRepository = (config) => {
4999
5765
  method: options.method,
5000
5766
  dependencies: config.dependencies,
5001
5767
  clientOptions: creatorOptions,
5768
+ clientHeaders: config.headers,
5002
5769
  cacheAdapter: config.cacheAdapter,
5003
5770
  retryFn: options.retryFn ?? config.retryFn,
5004
5771
  });
5005
5772
  request.execute();
5006
- bind(trackingKey, options.consumerDestroyRef, request, options.isSecure ?? false, shouldCache);
5773
+ bind(trackingKey, options.consumerDestroyRef, request, options.isSecure ?? false, shouldCache, keepUnusedFor);
5007
5774
  return { key: trackingKey, request };
5008
5775
  };
5776
+ /** Tears an entry down for good, whether it currently has consumers or not. */
5777
+ const destroyEntry = (key, cacheEntry) => {
5778
+ clearTimeout(cacheEntry.evictTimer);
5779
+ cacheEntry.request.destroy();
5780
+ cacheEntry.eventSubscription?.unsubscribe();
5781
+ cache.delete(key);
5782
+ };
5783
+ const cancelEviction = (cacheEntry) => {
5784
+ if (cacheEntry.evictTimer === undefined)
5785
+ return;
5786
+ clearTimeout(cacheEntry.evictTimer);
5787
+ cacheEntry.evictTimer = undefined;
5788
+ cacheEntry.unusedSince = undefined;
5789
+ };
5790
+ /** Drops the least recently orphaned unused entries until the cap is respected again. */
5791
+ const enforceUnusedEntryLimit = () => {
5792
+ const unused = Array.from(cache.entries()).filter(([, entry]) => entry.consumers.size === 0);
5793
+ if (unused.length <= MAX_UNUSED_ENTRIES)
5794
+ return;
5795
+ unused.sort(([, a], [, b]) => (a.unusedSince ?? 0) - (b.unusedSince ?? 0));
5796
+ for (const [key, entry] of unused.slice(0, unused.length - MAX_UNUSED_ENTRIES)) {
5797
+ destroyEntry(key, entry);
5798
+ }
5799
+ };
5800
+ const retain = (key, cacheEntry) => {
5801
+ cacheEntry.unusedSince = Date.now();
5802
+ cacheEntry.evictTimer = setTimeout(() => evict(key), cacheEntry.keepUnusedFor);
5803
+ enforceUnusedEntryLimit();
5804
+ };
5009
5805
  const unbind = (key, consumerDestroyRef) => {
5010
5806
  if (key === null)
5011
5807
  return false;
@@ -5014,24 +5810,41 @@ const createQueryRepository = (config) => {
5014
5810
  return false;
5015
5811
  cacheEntry.consumers.delete(consumerDestroyRef);
5016
5812
  if (cacheEntry.consumers.size === 0) {
5017
- cacheEntry.request.destroy();
5018
- cacheEntry.eventSubscription?.unsubscribe();
5019
- cache.delete(key);
5813
+ // Only data is worth keeping around: an entry that never produced a response has nothing to
5814
+ // hand back to a returning consumer, so it is aborted immediately as it always was.
5815
+ if (cacheEntry.keepUnusedFor > 0 && cacheEntry.request.response() !== null) {
5816
+ retain(key, cacheEntry);
5817
+ }
5818
+ else {
5819
+ destroyEntry(key, cacheEntry);
5820
+ }
5020
5821
  }
5822
+ bumpCacheVersion();
5021
5823
  return true;
5022
5824
  };
5023
5825
  const unbindAllSecure = () => {
5024
5826
  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
- }
5827
+ if (!cacheEntry.isSecure)
5828
+ continue;
5829
+ // Force the teardown instead of routing through `unbind`: a logged out session must not leave a
5830
+ // retained response body behind waiting out its `keepUnusedFor` window.
5831
+ cacheEntry.consumers.clear();
5832
+ destroyEntry(key, cacheEntry);
5833
+ }
5834
+ bumpCacheVersion();
5835
+ eventsSubject.next({ type: 'unbind-all-secure' });
5836
+ };
5837
+ const refreshInUse = () => {
5838
+ for (const cacheEntry of cache.values()) {
5839
+ if (cacheEntry.consumers.size === 0)
5840
+ continue;
5841
+ // Re-firing a mutation would be a side effect nobody asked for, so only reads are refreshed.
5842
+ if (!shouldCacheQuery(cacheEntry.request.method))
5843
+ continue;
5844
+ cacheEntry.request.execute({ force: true });
5032
5845
  }
5033
5846
  };
5034
- const bind = (key, consumerDestroyRef, request, isSecure, allowReuse) => {
5847
+ const bind = (key, consumerDestroyRef, request, isSecure, allowReuse, keepUnusedFor) => {
5035
5848
  const destroyListener = consumerDestroyRef.onDestroy(() => unbind(key, consumerDestroyRef));
5036
5849
  const cacheEntry = cache.get(key);
5037
5850
  if (cacheEntry && allowReuse) {
@@ -5040,26 +5853,17 @@ const createQueryRepository = (config) => {
5040
5853
  else {
5041
5854
  const consumers = new Map([]);
5042
5855
  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
- });
5856
+ // Drive repository events off the request's discrete, terminal event stream rather than
5857
+ // recombining the `error` + `response` signals via combineLatest (which could observe stale
5858
+ // pairings). Each settle emits exactly one terminal event.
5859
+ const eventSubscription = request.events$.subscribe((event) => {
5860
+ if (event.type === 'error') {
5861
+ if (event.error.raw instanceof HttpErrorResponse) {
5862
+ eventsSubject.next({ type: 'request-error', error: event.error.raw, key, isSecure, request });
5863
+ }
5055
5864
  }
5056
- else if (responseValue !== null && !errorValue) {
5057
- eventsSubject.next({
5058
- type: 'request-success',
5059
- key,
5060
- isSecure,
5061
- request,
5062
- });
5865
+ else if (event.type === HttpEventType.Response) {
5866
+ eventsSubject.next({ type: 'request-success', key, isSecure, request });
5063
5867
  }
5064
5868
  });
5065
5869
  cache.set(key, {
@@ -5067,14 +5871,36 @@ const createQueryRepository = (config) => {
5067
5871
  request,
5068
5872
  isSecure,
5069
5873
  eventSubscription,
5874
+ keepUnusedFor,
5070
5875
  });
5071
5876
  }
5877
+ bumpCacheVersion();
5878
+ };
5879
+ const cacheEntries = () => Array.from(cache.entries()).map(([key, entry]) => ({
5880
+ key,
5881
+ isSecure: entry.isSecure,
5882
+ consumerCount: entry.consumers.size,
5883
+ isUnused: entry.consumers.size === 0,
5884
+ request: entry.request,
5885
+ }));
5886
+ const evict = (key) => {
5887
+ const entry = cache.get(key);
5888
+ if (!entry)
5889
+ return;
5890
+ destroyEntry(key, entry);
5891
+ bumpCacheVersion();
5072
5892
  };
5073
5893
  const repository = {
5074
5894
  request,
5075
5895
  unbind,
5076
5896
  unbindAllSecure,
5897
+ refreshInUse,
5077
5898
  events$: eventsSubject.asObservable(),
5899
+ subtle: {
5900
+ cacheEntries,
5901
+ cacheVersion: cacheVersion.asReadonly(),
5902
+ evict,
5903
+ },
5078
5904
  };
5079
5905
  return repository;
5080
5906
  };
@@ -5085,10 +5911,15 @@ const createQueryClient = (options) => createRootProvider(() => {
5085
5911
  const injector = inject(Injector);
5086
5912
  const repository = createQueryRepository({
5087
5913
  ...options,
5914
+ // Retaining unused entries on the server would pin response bodies (and a pending timer) inside
5915
+ // a per-request injector for the whole window, so retention is browser only.
5916
+ retentionEnabled: isPlatformBrowser(inject(PLATFORM_ID)),
5088
5917
  dependencies: { httpClient, ngErrorHandler, injector },
5089
5918
  });
5090
5919
  const client = {
5091
5920
  repository,
5921
+ baseUrl: options.baseUrl,
5922
+ refreshQueriesInUse: () => repository.refreshInUse(),
5092
5923
  };
5093
5924
  return client;
5094
5925
  }, {
@@ -5157,6 +5988,191 @@ const createPatchQuery = createQueryTemplate('PATCH');
5157
5988
  /** A query creator that creates a secure PATCH query (requires authentication) */
5158
5989
  const createSecurePatchQuery = createSecureQueryTemplate('PATCH');
5159
5990
 
5991
+ /**
5992
+ * Executes the query and resolves once that execution has settled (response or error received).
5993
+ * The returned snapshot is frozen to the awaited execution, so a later execution can't swap the
5994
+ * `response()` / `error()` you read from it.
5995
+ *
5996
+ * Designed for imperative flows that need a mutation's outcome in place — e.g. a signal-forms
5997
+ * `submit()` action mapping server violations onto the form via `mapViolationsToFormErrors`.
5998
+ *
5999
+ * The promise stays pending if the execution is cancelled (e.g. `reset()`) and the query is never
6000
+ * executed again — the snapshot settles with whichever execution completes next.
6001
+ */
6002
+ const executeUntilSettled = async (query, executeArgs) => {
6003
+ query.execute(executeArgs);
6004
+ const snapshot = query.createSnapshot();
6005
+ await firstValueFrom(snapshot.isAlive.asObservable().pipe(filter((isAlive) => !isAlive)));
6006
+ return snapshot;
6007
+ };
6008
+
6009
+ /* eslint-disable @typescript-eslint/no-explicit-any */
6010
+ const createSequenceState = () => ({
6011
+ status: signal('idle'),
6012
+ running: signal(false),
6013
+ currentStep: signal(0),
6014
+ error: signal(null),
6015
+ failedAt: signal(null),
6016
+ snapshots: signal([]),
6017
+ stepArgs: signal([]),
6018
+ responses: signal([]),
6019
+ });
6020
+ const buildSequence = (steps, state, devtoolsHandle) => {
6021
+ const run = async () => {
6022
+ if (state.running())
6023
+ throw querySequenceAlreadyRunning();
6024
+ state.status.set('running');
6025
+ state.running.set(true);
6026
+ state.currentStep.set(0);
6027
+ state.error.set(null);
6028
+ state.failedAt.set(null);
6029
+ state.snapshots.set([]);
6030
+ state.stepArgs.set([]);
6031
+ state.responses.set([]);
6032
+ const snapshots = [];
6033
+ const stepArgs = [];
6034
+ const responses = [];
6035
+ let previousResponse = undefined;
6036
+ for (const [i, step] of steps.entries()) {
6037
+ state.currentStep.set(i + 1);
6038
+ const { args } = step.produceArgs(previousResponse, responses);
6039
+ stepArgs.push(args);
6040
+ state.stepArgs.set([...stepArgs]);
6041
+ const snapshot = await executeUntilSettled(step.query, { args });
6042
+ snapshots.push(snapshot);
6043
+ state.snapshots.set([...snapshots]);
6044
+ const error = snapshot.error();
6045
+ if (error) {
6046
+ state.status.set('error');
6047
+ state.error.set(error);
6048
+ state.failedAt.set(i);
6049
+ state.running.set(false);
6050
+ return { ok: false, failedAt: i, error, snapshots };
6051
+ }
6052
+ const response = snapshot.response();
6053
+ responses.push(response);
6054
+ state.responses.set([...responses]);
6055
+ previousResponse = response;
6056
+ }
6057
+ state.status.set('success');
6058
+ state.running.set(false);
6059
+ return { ok: true, responses: responses, snapshots };
6060
+ };
6061
+ const sequence = {
6062
+ then: (query, mapArgs) => buildSequence([
6063
+ ...steps,
6064
+ { query, produceArgs: (previousResponse, responses) => mapArgs(previousResponse, responses) },
6065
+ ], state, devtoolsHandle),
6066
+ status: state.status.asReadonly(),
6067
+ running: state.running.asReadonly(),
6068
+ currentStep: state.currentStep.asReadonly(),
6069
+ total: steps.length,
6070
+ queries: steps.map((s) => s.query),
6071
+ error: state.error.asReadonly(),
6072
+ failedAt: state.failedAt.asReadonly(),
6073
+ snapshots: state.snapshots.asReadonly(),
6074
+ stepArgs: state.stepArgs.asReadonly(),
6075
+ responses: state.responses.asReadonly(),
6076
+ run,
6077
+ };
6078
+ if (devtoolsHandle)
6079
+ devtoolsHandle.current = sequence;
6080
+ return sequence;
6081
+ };
6082
+ /**
6083
+ * Starts an imperative waterfall of dependent queries. Chain the remaining steps with `.then()`
6084
+ * and trigger it with `.run()` — see {@link QuerySequence}.
6085
+ *
6086
+ * The seed step's `args` are produced lazily (at `run()` time), so the sequence can safely live as
6087
+ * a component field while still reading current signal values on each run.
6088
+ *
6089
+ * @example
6090
+ * ```ts
6091
+ * readonly checkout = querySequence(this.createOrder, () => ({ args: { body: this.order() } }))
6092
+ * .then(this.createPayment, (order) => ({ args: { body: { orderId: order.id } } }))
6093
+ * .then(this.confirmOrder, (payment, [order]) => ({
6094
+ * args: { pathParams: { paymentId: payment.id }, body: { orderRef: order.id } },
6095
+ * }));
6096
+ *
6097
+ * async submit() {
6098
+ * const result = await this.checkout.run();
6099
+ * if (!result.ok) return this.handle(result.error);
6100
+ * const [order, payment, confirmation] = result.responses;
6101
+ * }
6102
+ * ```
6103
+ */
6104
+ const querySequence = (query, seedArgs) => {
6105
+ const devtoolsHandle = isQueryDevtoolsEnabled()
6106
+ ? { current: null }
6107
+ : undefined;
6108
+ const seed = buildSequence([{ query, produceArgs: () => seedArgs() }], createSequenceState(), devtoolsHandle);
6109
+ if (devtoolsHandle) {
6110
+ const unregister = registerQueryDevtoolsEntry({
6111
+ kind: 'query-sequence',
6112
+ handle: devtoolsHandle,
6113
+ meta: {},
6114
+ });
6115
+ // Sequences have no destroy hook; tie cleanup to the creating scope when there is one.
6116
+ try {
6117
+ inject(DestroyRef).onDestroy(unregister);
6118
+ }
6119
+ catch {
6120
+ // Created outside an injection context — the entry lives for the app's lifetime.
6121
+ }
6122
+ }
6123
+ return seed;
6124
+ };
6125
+
6126
+ /**
6127
+ * Binds a query-backed async validator to a signal-forms field, adapting an `@ethlete/query` v3
6128
+ * query into what `validateAsync` consumes. The query runs through the query client — so bearer
6129
+ * auth, base route, caching and Symfony error normalization all apply — where a raw `httpResource`
6130
+ * would fire unauthenticated and bypass the pipeline.
6131
+ *
6132
+ * On the current v3 `QueryClient` / `createPostQuery` creators; the `validateWithV2Query` twin
6133
+ * covers the legacy `V2QueryClient`. The query is created once and re-executed (debounced) as the
6134
+ * field value changes, only after the field's synchronous validators pass. A `204` / successful
6135
+ * response reports no errors; a `422 FormViolationListView` maps each violation onto its child
6136
+ * field by `propertyPath`; a network / other error degrades to a non-swallowed form-level error.
6137
+ *
6138
+ * Call it from a schema definition, the same place you'd call `validateAsync`:
6139
+ *
6140
+ * ```ts
6141
+ * const schema = schema<OpportunityEmail>((p) => {
6142
+ * validateWithQuery(p, {
6143
+ * queryCreator: postOpportunityEmailValidate,
6144
+ * args: (ctx) => ({ pathParams: { opportunityId }, body: { ...ctx.value() } }),
6145
+ * });
6146
+ * });
6147
+ * ```
6148
+ */
6149
+ const validateWithQuery = (path, config) => {
6150
+ applyQueryAsyncValidator(path, {
6151
+ params: config.args,
6152
+ debounce: config.debounce,
6153
+ when: config.when,
6154
+ mapViolations: config.mapViolations,
6155
+ factory: (params) => {
6156
+ // Created once, exactly like a query stack. A POST validate route doesn't auto-execute, so
6157
+ // the resource loader drives it — each params change re-executes with the new args.
6158
+ const query = config.queryCreator();
6159
+ return resource({
6160
+ params: () => params(),
6161
+ loader: async ({ params: requestArgs }) => {
6162
+ const snapshot = await executeUntilSettled(query, { args: requestArgs });
6163
+ const error = snapshot.error();
6164
+ // A failed request (e.g. 422) is thrown so it lands in `onError`, where the violations are
6165
+ // mapped onto the fields. A `204` / success settles with no error and no violations.
6166
+ if (error) {
6167
+ throw error;
6168
+ }
6169
+ return snapshot.response() ?? undefined;
6170
+ },
6171
+ });
6172
+ },
6173
+ });
6174
+ };
6175
+
5160
6176
  const setupLeaderElection = () => {
5161
6177
  const destroyRef = inject(DestroyRef);
5162
6178
  const channelName = 'ethlete-auth-leader';
@@ -5164,8 +6180,10 @@ const setupLeaderElection = () => {
5164
6180
  const leaderTimeout = 3000;
5165
6181
  const storageKey = 'ethlete-auth-leader-heartbeat';
5166
6182
  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" }] : []));
6183
+ const isLeader = signal(false, /* @ts-ignore */
6184
+ ...(ngDevMode ? [{ debugName: "isLeader" }] : /* istanbul ignore next */ []));
6185
+ const instanceCount = signal(1, /* @ts-ignore */
6186
+ ...(ngDevMode ? [{ debugName: "instanceCount" }] : /* istanbul ignore next */ []));
5169
6187
  const knownTabs = new Map();
5170
6188
  knownTabs.set(tabId, Date.now());
5171
6189
  let channel = null;
@@ -5539,11 +6557,21 @@ const defaultExtractTokens = (response) => {
5539
6557
  }
5540
6558
  return { accessToken: response['accessToken'], refreshToken: response['refreshToken'] };
5541
6559
  };
5542
- const setupBearerQueryRegistry = (builders, injector, latestExecutedQuery, latestNonInternalQuery, setTokens) => {
6560
+ const deriveExecutionStateType = (builder, triggeredBy) => {
6561
+ if (triggeredBy === 'persistent-auth')
6562
+ return 'autoLogin';
6563
+ if (builder._type === 'tokenRefreshQuery')
6564
+ return 'tokenRefresh';
6565
+ if (triggeredBy === 'token-revocation')
6566
+ return 'revocation';
6567
+ return builder.key;
6568
+ };
6569
+ const setupBearerQueryRegistry = (builders, injector, latestExecutedQuery, latestNonInternalQuery, setTokens, executionState) => {
5543
6570
  const queries = {};
5544
6571
  const querySnapshots = new Map();
5545
6572
  for (const builder of builders) {
5546
- const querySnapshot = signal(null, ...(ngDevMode ? [{ debugName: "querySnapshot" }] : []));
6573
+ const querySnapshot = signal(null, /* @ts-ignore */
6574
+ ...(ngDevMode ? [{ debugName: "querySnapshot" }] : /* istanbul ignore next */ []));
5547
6575
  querySnapshots.set(builder.key, querySnapshot);
5548
6576
  const extractTokens = builder.config.extractTokens ?? defaultExtractTokens;
5549
6577
  effect(() => {
@@ -5572,10 +6600,27 @@ const setupBearerQueryRegistry = (builders, injector, latestExecutedQuery, lates
5572
6600
  });
5573
6601
  query.execute({ args, options });
5574
6602
  const snapshot = query.createSnapshot();
5575
- latestExecutedQuery.set({ key: builder.key, snapshot });
5576
- if (!snapshot.triggeredBy()) {
5577
- latestNonInternalQuery.set({ key: builder.key, snapshot });
6603
+ const stateType = deriveExecutionStateType(builder, options?.triggeredBy);
6604
+ if (stateType !== 'revocation') {
6605
+ latestExecutedQuery.set({ key: builder.key, snapshot });
6606
+ if (!snapshot.triggeredBy()) {
6607
+ latestNonInternalQuery.set({ key: builder.key, snapshot });
6608
+ }
5578
6609
  }
6610
+ executionState.set({ type: stateType, state: 'loading' });
6611
+ effect(() => {
6612
+ const response = snapshot.response();
6613
+ const loading = snapshot.loading();
6614
+ const error = snapshot.error();
6615
+ if (loading)
6616
+ return;
6617
+ if (error) {
6618
+ executionState.set({ type: stateType, state: 'error', error });
6619
+ }
6620
+ else if (response) {
6621
+ executionState.set({ type: stateType, state: 'success', response });
6622
+ }
6623
+ }, { injector });
5579
6624
  querySnapshot.set(snapshot);
5580
6625
  return snapshot;
5581
6626
  };
@@ -5635,8 +6680,10 @@ const createBearerAuthProviderImpl = (config) => {
5635
6680
  const injector = inject(Injector);
5636
6681
  const destroyRef = inject(DestroyRef);
5637
6682
  const queryClient = config.queryClientRef[1]();
5638
- const accessToken = signal(null, ...(ngDevMode ? [{ debugName: "accessToken" }] : []));
5639
- const refreshToken = signal(null, ...(ngDevMode ? [{ debugName: "refreshToken" }] : []));
6683
+ const accessToken = signal(null, /* @ts-ignore */
6684
+ ...(ngDevMode ? [{ debugName: "accessToken" }] : /* istanbul ignore next */ []));
6685
+ const refreshToken = signal(null, /* @ts-ignore */
6686
+ ...(ngDevMode ? [{ debugName: "refreshToken" }] : /* istanbul ignore next */ []));
5640
6687
  const afterTokenRefresh$ = new Subject();
5641
6688
  const bearerData = computed(() => {
5642
6689
  const token = accessToken();
@@ -5651,20 +6698,29 @@ const createBearerAuthProviderImpl = (config) => {
5651
6698
  }
5652
6699
  return null;
5653
6700
  }
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" }] : []));
6701
+ }, /* @ts-ignore */
6702
+ ...(ngDevMode ? [{ debugName: "bearerData" }] : /* istanbul ignore next */ []));
6703
+ const isAuthenticated = computed(() => !!accessToken(), /* @ts-ignore */
6704
+ ...(ngDevMode ? [{ debugName: "isAuthenticated" }] : /* istanbul ignore next */ []));
6705
+ const latestExecutedQuery = signal(null, /* @ts-ignore */
6706
+ ...(ngDevMode ? [{ debugName: "latestExecutedQuery" }] : /* istanbul ignore next */ []));
6707
+ const latestNonInternalQuery = signal(null, /* @ts-ignore */
6708
+ ...(ngDevMode ? [{ debugName: "latestNonInternalQuery" }] : /* istanbul ignore next */ []));
6709
+ const executionState = signal(null, /* @ts-ignore */
6710
+ ...(ngDevMode ? [{ debugName: "executionState" }] : /* istanbul ignore next */ []));
5658
6711
  const setTokens = (access, refresh) => {
5659
6712
  accessToken.set(access);
5660
6713
  refreshToken.set(refresh);
5661
6714
  afterTokenRefresh$.next();
5662
6715
  };
5663
- const { queries } = setupBearerQueryRegistry(config.queries, injector, latestExecutedQuery, latestNonInternalQuery, setTokens);
6716
+ const { queries } = setupBearerQueryRegistry(config.queries, injector, latestExecutedQuery, latestNonInternalQuery, setTokens, executionState);
5664
6717
  const logout = () => {
5665
6718
  accessToken.set(null);
5666
6719
  refreshToken.set(null);
5667
6720
  queryClient.repository.unbindAllSecure();
6721
+ latestExecutedQuery.set(null);
6722
+ latestNonInternalQuery.set(null);
6723
+ executionState.set({ type: 'logout', state: 'success' });
5668
6724
  };
5669
6725
  const isLeader = createLeaderElection(config);
5670
6726
  const querySetupContext = {
@@ -5692,10 +6748,11 @@ const createBearerAuthProviderImpl = (config) => {
5692
6748
  leaderElection: isLeader.leaderElectionContext,
5693
6749
  afterTokenRefresh$,
5694
6750
  queries: queries,
6751
+ executionState,
5695
6752
  };
5696
6753
  const features = setupFeatures(config.features, featureSetupContext);
5697
6754
  setupMultiTabSyncIfEnabled(config, accessToken, refreshToken, queryClient);
5698
- return {
6755
+ const provider = {
5699
6756
  queries,
5700
6757
  features: features,
5701
6758
  accessToken: accessToken.asReadonly(),
@@ -5704,9 +6761,25 @@ const createBearerAuthProviderImpl = (config) => {
5704
6761
  isAuthenticated,
5705
6762
  latestExecutedQuery: latestExecutedQuery.asReadonly(),
5706
6763
  latestNonInternalQuery: latestNonInternalQuery.asReadonly(),
6764
+ executionState: executionState.asReadonly(),
6765
+ setTokens,
5707
6766
  logout,
5708
6767
  afterTokenRefresh$,
5709
6768
  };
6769
+ if (isQueryDevtoolsEnabled()) {
6770
+ const unregister = registerQueryDevtoolsEntry({
6771
+ kind: 'auth-provider',
6772
+ handle: provider,
6773
+ meta: {
6774
+ name: config.name,
6775
+ clientName: getQueryClientName(config.queryClientRef),
6776
+ clientBaseUrl: queryClient.baseUrl,
6777
+ repository: queryClient.repository,
6778
+ },
6779
+ });
6780
+ destroyRef.onDestroy(unregister);
6781
+ }
6782
+ return provider;
5710
6783
  };
5711
6784
  const createBearerAuthProvider = (config) => createRootProvider(() => createBearerAuthProviderImpl(config), { name: `BearerAuthProvider_${config.name}` });
5712
6785
 
@@ -5853,8 +6926,10 @@ const withInactivityLogout = (config = {}) => {
5853
6926
  const activityEvents = config.activityEvents ?? ['mousedown', 'keydown', 'scroll', 'touchstart'];
5854
6927
  const destroyRef = inject(DestroyRef);
5855
6928
  const document = inject(DOCUMENT);
5856
- const enabled = signal(true, ...(ngDevMode ? [{ debugName: "enabled" }] : []));
5857
- const lastActivityTime = signal(Date.now(), ...(ngDevMode ? [{ debugName: "lastActivityTime" }] : []));
6929
+ const enabled = signal(true, /* @ts-ignore */
6930
+ ...(ngDevMode ? [{ debugName: "enabled" }] : /* istanbul ignore next */ []));
6931
+ const lastActivityTime = signal(Date.now(), /* @ts-ignore */
6932
+ ...(ngDevMode ? [{ debugName: "lastActivityTime" }] : /* istanbul ignore next */ []));
5858
6933
  const resetTimer = () => {
5859
6934
  lastActivityTime.set(Date.now());
5860
6935
  };
@@ -5937,7 +7012,8 @@ const createPersistentAuthFeature = (config, context) => {
5937
7012
  }
5938
7013
  return config.defaultRememberMe ?? false;
5939
7014
  };
5940
- const rememberMeSignal = signal(initializeRememberMe(), ...(ngDevMode ? [{ debugName: "rememberMeSignal" }] : []));
7015
+ const rememberMeSignal = signal(initializeRememberMe(), /* @ts-ignore */
7016
+ ...(ngDevMode ? [{ debugName: "rememberMeSignal" }] : /* istanbul ignore next */ []));
5941
7017
  effect(() => {
5942
7018
  const token = refreshToken();
5943
7019
  const rememberMe = rememberMeSignal();
@@ -5998,7 +7074,8 @@ const withTokenExpirationWarning = (config = {}) => {
5998
7074
  if (typeof exp !== 'number')
5999
7075
  return null;
6000
7076
  return new Date(exp * 1000);
6001
- }, ...(ngDevMode ? [{ debugName: "expiresAt" }] : []));
7077
+ }, /* @ts-ignore */
7078
+ ...(ngDevMode ? [{ debugName: "expiresAt" }] : /* istanbul ignore next */ []));
6002
7079
  const expiresIn$ = combineLatest([interval(checkInterval), toObservable(context.bearerData)]).pipe(map(() => {
6003
7080
  const expiry = expiresAt();
6004
7081
  if (!expiry)
@@ -6029,23 +7106,27 @@ const withTokenExpirationWarning = (config = {}) => {
6029
7106
  const withTokenRevocation = (config) => {
6030
7107
  return (context) => {
6031
7108
  const revokeOnLogout = config.revokeOnLogout ?? true;
6032
- const enabled = signal(true, ...(ngDevMode ? [{ debugName: "enabled" }] : []));
7109
+ const enabled = signal(true, /* @ts-ignore */
7110
+ ...(ngDevMode ? [{ debugName: "enabled" }] : /* istanbul ignore next */ []));
6033
7111
  let previousAccessToken = null;
7112
+ let previousRefreshToken = null;
6034
7113
  let currentRevocationSnapshot = null;
6035
- const revoke = () => {
7114
+ const revokeWithTokens = (accessToken, refreshToken) => {
6036
7115
  if (currentRevocationSnapshot?.isAlive())
6037
7116
  return currentRevocationSnapshot;
6038
- const accessToken = context.accessToken();
6039
- const refreshToken = context.refreshToken();
6040
7117
  if (!accessToken && !refreshToken) {
6041
7118
  return null;
6042
7119
  }
6043
7120
  const args = config.buildArgs({ accessToken, refreshToken });
7121
+ context.executionState.set({ type: 'revocation', state: 'loading' });
6044
7122
  currentRevocationSnapshot = context.queries[config.queryKey].execute(args, {
6045
7123
  triggeredBy: 'token-revocation',
6046
7124
  });
6047
7125
  return currentRevocationSnapshot;
6048
7126
  };
7127
+ const revoke = () => {
7128
+ return revokeWithTokens(context.accessToken(), context.refreshToken());
7129
+ };
6049
7130
  const enable = () => {
6050
7131
  enabled.set(true);
6051
7132
  };
@@ -6055,10 +7136,12 @@ const withTokenRevocation = (config) => {
6055
7136
  if (revokeOnLogout) {
6056
7137
  effect(() => {
6057
7138
  const currentToken = context.accessToken();
7139
+ const currentRefreshToken = context.refreshToken();
6058
7140
  if (previousAccessToken && !currentToken && enabled()) {
6059
- untracked(() => revoke());
7141
+ untracked(() => revokeWithTokens(previousAccessToken, previousRefreshToken));
6060
7142
  }
6061
7143
  previousAccessToken = currentToken;
7144
+ previousRefreshToken = currentRefreshToken;
6062
7145
  }, { injector: context.injector });
6063
7146
  }
6064
7147
  const instance = {
@@ -6382,10 +7465,10 @@ class ParseHttpErrorCodeToTitleDePipe {
6382
7465
  constructor() {
6383
7466
  this.transform = parseHttpErrorCodeToTitleDe;
6384
7467
  }
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" }); }
7468
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.7", ngImport: i0, type: ParseHttpErrorCodeToTitleDePipe, deps: [], target: i0.ɵɵFactoryTarget.Pipe }); }
7469
+ static { this.ɵpipe = i0.ɵɵngDeclarePipe({ minVersion: "14.0.0", version: "22.0.7", ngImport: i0, type: ParseHttpErrorCodeToTitleDePipe, isStandalone: true, name: "parseHttpErrorCodeToTitleDe" }); }
6387
7470
  }
6388
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.1", ngImport: i0, type: ParseHttpErrorCodeToTitleDePipe, decorators: [{
7471
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.7", ngImport: i0, type: ParseHttpErrorCodeToTitleDePipe, decorators: [{
6389
7472
  type: Pipe,
6390
7473
  args: [{
6391
7474
  name: 'parseHttpErrorCodeToTitleDe',
@@ -6395,10 +7478,10 @@ class ParseHttpErrorCodeToMessageDePipe {
6395
7478
  constructor() {
6396
7479
  this.transform = parseHttpErrorCodeToMessageDe;
6397
7480
  }
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" }); }
7481
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.7", ngImport: i0, type: ParseHttpErrorCodeToMessageDePipe, deps: [], target: i0.ɵɵFactoryTarget.Pipe }); }
7482
+ static { this.ɵpipe = i0.ɵɵngDeclarePipe({ minVersion: "14.0.0", version: "22.0.7", ngImport: i0, type: ParseHttpErrorCodeToMessageDePipe, isStandalone: true, name: "parseHttpErrorCodeToMessageDe" }); }
6400
7483
  }
6401
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.1", ngImport: i0, type: ParseHttpErrorCodeToMessageDePipe, decorators: [{
7484
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.7", ngImport: i0, type: ParseHttpErrorCodeToMessageDePipe, decorators: [{
6402
7485
  type: Pipe,
6403
7486
  args: [{
6404
7487
  name: 'parseHttpErrorCodeToMessageDe',
@@ -6582,10 +7665,10 @@ class ParseHttpErrorCodeToTitleEnPipe {
6582
7665
  constructor() {
6583
7666
  this.transform = parseHttpErrorCodeToTitleEn;
6584
7667
  }
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" }); }
7668
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.7", ngImport: i0, type: ParseHttpErrorCodeToTitleEnPipe, deps: [], target: i0.ɵɵFactoryTarget.Pipe }); }
7669
+ static { this.ɵpipe = i0.ɵɵngDeclarePipe({ minVersion: "14.0.0", version: "22.0.7", ngImport: i0, type: ParseHttpErrorCodeToTitleEnPipe, isStandalone: true, name: "parseHttpErrorCodeToTitleEn" }); }
6587
7670
  }
6588
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.1", ngImport: i0, type: ParseHttpErrorCodeToTitleEnPipe, decorators: [{
7671
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.7", ngImport: i0, type: ParseHttpErrorCodeToTitleEnPipe, decorators: [{
6589
7672
  type: Pipe,
6590
7673
  args: [{
6591
7674
  name: 'parseHttpErrorCodeToTitleEn',
@@ -6595,10 +7678,10 @@ class ParseHttpErrorCodeToMessageEnPipe {
6595
7678
  constructor() {
6596
7679
  this.transform = parseHttpErrorCodeToMessageEn;
6597
7680
  }
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" }); }
7681
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.7", ngImport: i0, type: ParseHttpErrorCodeToMessageEnPipe, deps: [], target: i0.ɵɵFactoryTarget.Pipe }); }
7682
+ static { this.ɵpipe = i0.ɵɵngDeclarePipe({ minVersion: "14.0.0", version: "22.0.7", ngImport: i0, type: ParseHttpErrorCodeToMessageEnPipe, isStandalone: true, name: "parseHttpErrorCodeToMessageEn" }); }
6600
7683
  }
6601
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.1", ngImport: i0, type: ParseHttpErrorCodeToMessageEnPipe, decorators: [{
7684
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.7", ngImport: i0, type: ParseHttpErrorCodeToMessageEnPipe, decorators: [{
6602
7685
  type: Pipe,
6603
7686
  args: [{
6604
7687
  name: 'parseHttpErrorCodeToMessageEn',
@@ -6806,7 +7889,7 @@ class DateArrayQueryField {
6806
7889
  };
6807
7890
  }
6808
7891
  }
6809
- const IGNORED_FILTER_COUNT_FIELDS = ['page', 'skip', 'take', 'limit', 'sort', 'sortBy', 'sortOrder', 'query', 'search'];
7892
+ const IGNORED_FILTER_COUNT_FIELDS$1 = ['page', 'skip', 'take', 'limit', 'sort', 'sortBy', 'sortOrder', 'query', 'search'];
6810
7893
  // eslint-disable-next-line @typescript-eslint/no-explicit-any
6811
7894
  class QueryForm {
6812
7895
  get _formValue() {
@@ -6881,7 +7964,7 @@ class QueryForm {
6881
7964
  */
6882
7965
  this.activeFilterCount$ = this.changes$.pipe(map(({ currentValue }) => Object.entries(currentValue)
6883
7966
  .map(([key, value]) => {
6884
- if (IGNORED_FILTER_COUNT_FIELDS.includes(key) || this._fields[key]?.data.skipInFilterCount) {
7967
+ if (IGNORED_FILTER_COUNT_FIELDS$1.includes(key) || this._fields[key]?.data.skipInFilterCount) {
6885
7968
  return true;
6886
7969
  }
6887
7970
  return this._isDefaultValue(key, value);
@@ -7022,8 +8105,11 @@ class QueryForm {
7022
8105
  continue;
7023
8106
  const valueGotRemoved = value === ET_PROPERTY_REMOVED;
7024
8107
  if (valueGotRemoved) {
7025
- field.control.setValue(this._getDefaultValue(key), { emitEvent: false });
7026
- didValueChanges = true;
8108
+ const defaultValue = this._getDefaultValue(key);
8109
+ if (!equal(field.control.value, defaultValue)) {
8110
+ field.control.setValue(defaultValue, { emitEvent: false });
8111
+ didValueChanges = true;
8112
+ }
7027
8113
  continue;
7028
8114
  }
7029
8115
  let deserializedValue = value;
@@ -7195,6 +8281,21 @@ class QueryForm {
7195
8281
  });
7196
8282
  }
7197
8283
  _handleFormChange(forceOverwrite = false) {
8284
+ // Normalize values that have a valueToQueryParamTransformFn returning null/undefined to their
8285
+ // field default before capturing the new value. This prevents intermediate "empty object" states
8286
+ // (e.g. Sort emitting { active: '', direction: '' } when cleared) from being
8287
+ // emitted as distinct form values.
8288
+ for (const [key, field] of Object.entries(this._fields)) {
8289
+ if (!field.data.valueToQueryParamTransformFn)
8290
+ continue;
8291
+ const rawValue = this._formValue[key];
8292
+ if (this._isDefaultValue(key, rawValue))
8293
+ continue;
8294
+ const serialized = field.data.valueToQueryParamTransformFn(rawValue);
8295
+ if (serialized !== null && serialized !== undefined)
8296
+ continue;
8297
+ this.form.controls[key]?.setValue(this._getDefaultValue(key), { emitEvent: false });
8298
+ }
7198
8299
  const currentVal = clone(this._currentFormValue$.value);
7199
8300
  const newVal = clone(this._formValue);
7200
8301
  if (equal(currentVal, newVal)) {
@@ -7229,6 +8330,433 @@ class QueryForm {
7229
8330
  }
7230
8331
  }
7231
8332
 
8333
+ /** URL sentinel for an explicit `null` value (a bare empty param would be ambiguous). */
8334
+ const ET_NULL_VALUE = 'ET_NULL__';
8335
+ /**
8336
+ * Fields excluded from `activeFilterCount` by default — pagination, sorting and
8337
+ * search are navigation state, not filters.
8338
+ */
8339
+ const IGNORED_FILTER_COUNT_FIELDS = [
8340
+ 'page',
8341
+ 'skip',
8342
+ 'take',
8343
+ 'limit',
8344
+ 'sort',
8345
+ 'sortBy',
8346
+ 'sortOrder',
8347
+ 'query',
8348
+ 'search',
8349
+ ];
8350
+ const resolveDefault = (def) => {
8351
+ const { defaultValue } = def;
8352
+ return typeof defaultValue === 'function' ? defaultValue() : (defaultValue ?? null);
8353
+ };
8354
+ const buildDefaults = (fields) => {
8355
+ const out = {};
8356
+ for (const [key, def] of Object.entries(fields)) {
8357
+ out[key] = resolveDefault(def);
8358
+ }
8359
+ return out;
8360
+ };
8361
+ const changedKeysBetween = (previous, current) => Object.keys(current).filter((key) => !equal(previous?.[key], current[key]));
8362
+ /** Best-effort URL string → value coercion, mirroring the auto-transform of the legacy QueryForm. */
8363
+ const autoCoerce = (raw, defaultValue) => {
8364
+ if (raw === ET_NULL_VALUE)
8365
+ return null;
8366
+ if (typeof raw !== 'string')
8367
+ return raw;
8368
+ const defaultIsNumber = typeof defaultValue === 'number';
8369
+ const looksNumeric = raw.trim() === raw && !raw.startsWith('0') && !raw.endsWith('.') && !isNaN(Number(raw));
8370
+ if (defaultIsNumber || (looksNumeric && raw !== '')) {
8371
+ return transformToNumber(raw);
8372
+ }
8373
+ if (raw === 'true' || raw === 'false') {
8374
+ return transformToBoolean(raw);
8375
+ }
8376
+ return raw;
8377
+ };
8378
+ const computeFilterCount = (fields, value, defaults) => {
8379
+ let count = 0;
8380
+ for (const [key, def] of Object.entries(fields)) {
8381
+ if (IGNORED_FILTER_COUNT_FIELDS.includes(key) || def.skipInFilterCount)
8382
+ continue;
8383
+ if (!equal(value[key], defaults[key]))
8384
+ count++;
8385
+ }
8386
+ return count;
8387
+ };
8388
+ const createBranch = (fields, initial, injector) => {
8389
+ const defaults = buildDefaults(fields);
8390
+ const model = signal(clone(initial), /* @ts-ignore */
8391
+ ...(ngDevMode ? [{ debugName: "model" }] : /* istanbul ignore next */ []));
8392
+ const tree = form(model, { injector });
8393
+ return {
8394
+ fields: tree,
8395
+ value: model.asReadonly(),
8396
+ activeFilterCount: computed(() => computeFilterCount(fields, model(), defaults)),
8397
+ setValue: (value) => model.set(clone(value)),
8398
+ patchValue: (value) => model.update((cur) => ({ ...cur, ...value })),
8399
+ resetFieldToDefault: (key) => model.update((cur) => ({ ...cur, [key]: defaults[key] })),
8400
+ resetAllFieldsToDefault: () => model.set(clone(defaults)),
8401
+ };
8402
+ };
8403
+ /**
8404
+ * A signals-first query form: binds on-screen controls to typed fields, keeps a
8405
+ * debounced committed value, syncs to the URL query params, and resets dependent
8406
+ * fields via the `isResetBy` graph — all as signals.
8407
+ *
8408
+ * @example
8409
+ * const qf = createQueryForm({
8410
+ * fields: {
8411
+ * search: searchQueryField(),
8412
+ * sort: sortQueryField(),
8413
+ * page: queryField<number>({ defaultValue: 1, isResetBy: ['search', 'sort'] }),
8414
+ * },
8415
+ * }).observe();
8416
+ * // template: <input etInput [formField]="qf.fields.search" />
8417
+ */
8418
+ class QueryFormSignals {
8419
+ constructor(config) {
8420
+ this._router = inject(Router);
8421
+ this._route = inject(ActivatedRoute);
8422
+ this._injector = inject(Injector);
8423
+ this._previous = signal(null, /* @ts-ignore */
8424
+ ...(ngDevMode ? [{ debugName: "_previous" }] : /* istanbul ignore next */ []));
8425
+ this._observing = signal(false, /* @ts-ignore */
8426
+ ...(ngDevMode ? [{ debugName: "_observing" }] : /* istanbul ignore next */ []));
8427
+ this._queryParamChanges = injectQueryParamChanges();
8428
+ this._pendingTimer = null;
8429
+ this._skipNextResets = false;
8430
+ assertInInjectionContext(QueryFormSignals);
8431
+ this._fields = config.fields;
8432
+ this._prefix = config.queryParamPrefix;
8433
+ this._defaults = buildDefaults(config.fields);
8434
+ this.defaultValue = clone(this._defaults);
8435
+ const initial = clone(this._defaults);
8436
+ this._model = signal(initial, /* @ts-ignore */
8437
+ ...(ngDevMode ? [{ debugName: "_model" }] : /* istanbul ignore next */ []));
8438
+ this._committed = signal(clone(initial), /* @ts-ignore */
8439
+ ...(ngDevMode ? [{ debugName: "_committed" }] : /* istanbul ignore next */ []));
8440
+ this.fields = form(this._model);
8441
+ this.value = this._committed.asReadonly();
8442
+ this.previousValue = this._previous.asReadonly();
8443
+ this.changes = computed(() => ({ previousValue: this._previous(), currentValue: this._committed() }), /* @ts-ignore */
8444
+ ...(ngDevMode ? [{ debugName: "changes" }] : /* istanbul ignore next */ []));
8445
+ this.activeFilterCount = computed(() => computeFilterCount(this._fields, this._committed(), this._defaults), /* @ts-ignore */
8446
+ ...(ngDevMode ? [{ debugName: "activeFilterCount" }] : /* istanbul ignore next */ []));
8447
+ // React to live control edits: schedule a debounced commit.
8448
+ effect(() => {
8449
+ const live = this._model();
8450
+ untracked(() => this._onLiveChange(live));
8451
+ });
8452
+ // React to navigation (back/forward, external links): apply URL → form immediately.
8453
+ effect(() => {
8454
+ const changes = this._queryParamChanges();
8455
+ untracked(() => {
8456
+ if (!this._observing() || this._observeOptions?.syncOnNavigation === false)
8457
+ return;
8458
+ this._applyFromUrl(changes);
8459
+ });
8460
+ });
8461
+ inject(DestroyRef).onDestroy(() => this._cleanup());
8462
+ }
8463
+ observe(options) {
8464
+ if (this._observing()) {
8465
+ if (isDevMode()) {
8466
+ console.warn('QueryFormSignals.observe() was called more than once. Ignoring the extra call.');
8467
+ }
8468
+ return this;
8469
+ }
8470
+ this._observeOptions = options;
8471
+ this._observing.set(true);
8472
+ if (options?.syncOnNavigation !== false) {
8473
+ this._applyFromUrl(this._route.snapshot.queryParams);
8474
+ }
8475
+ // Commit whatever the model holds now (URL-restored or programmatic defaults).
8476
+ this._flush();
8477
+ return this;
8478
+ }
8479
+ unobserve() {
8480
+ this._cleanup();
8481
+ }
8482
+ setValue(value, options) {
8483
+ if (options?.skipResets)
8484
+ this._skipNextResets = true;
8485
+ this._model.set(clone(value));
8486
+ }
8487
+ patchValue(value, options) {
8488
+ if (options?.skipResets)
8489
+ this._skipNextResets = true;
8490
+ this._model.update((cur) => ({ ...cur, ...value }));
8491
+ }
8492
+ resetFieldToDefault(key, options) {
8493
+ if (options?.skipResets)
8494
+ this._skipNextResets = true;
8495
+ this._model.update((cur) => ({ ...cur, [key]: this._defaults[key] }));
8496
+ }
8497
+ resetFieldsToDefault(keys, options) {
8498
+ if (options?.skipResets)
8499
+ this._skipNextResets = true;
8500
+ this._model.update((cur) => {
8501
+ const next = { ...cur };
8502
+ for (const key of keys) {
8503
+ next[key] = this._defaults[key];
8504
+ }
8505
+ return next;
8506
+ });
8507
+ }
8508
+ resetAllFieldsToDefault(options) {
8509
+ const skip = new Set((options?.skipFields ?? []).map((key) => key));
8510
+ const keys = Object.keys(this._fields).filter((key) => !skip.has(key));
8511
+ this.resetFieldsToDefault(keys, options);
8512
+ }
8513
+ /** Create a detached editor over the same fields, seeded from the current committed value. */
8514
+ branch() {
8515
+ return createBranch(this._fields, clone(this._committed()), this._injector);
8516
+ }
8517
+ _onLiveChange(live) {
8518
+ if (!this._observing())
8519
+ return;
8520
+ if (equal(live, this._committed()))
8521
+ return;
8522
+ const changedKeys = changedKeysBetween(this._committed(), live);
8523
+ const debounceMs = this._resolveDebounce(changedKeys, live);
8524
+ this._clearTimer();
8525
+ if (debounceMs === null) {
8526
+ this._flush();
8527
+ }
8528
+ else {
8529
+ this._pendingTimer = setTimeout(() => {
8530
+ this._pendingTimer = null;
8531
+ this._flush();
8532
+ }, debounceMs);
8533
+ }
8534
+ }
8535
+ /** Shortest-debounce-wins; `null` means commit immediately. */
8536
+ _resolveDebounce(changedKeys, live) {
8537
+ if (!changedKeys.length)
8538
+ return null;
8539
+ const times = [];
8540
+ for (const key of changedKeys) {
8541
+ const def = this._fields[key];
8542
+ if (!def)
8543
+ return null;
8544
+ if (def.disableDebounceIfFalsy && !live[key])
8545
+ return null;
8546
+ if (def.debounce === undefined)
8547
+ return null;
8548
+ times.push(def.debounce);
8549
+ }
8550
+ return Math.min(...times);
8551
+ }
8552
+ _flush() {
8553
+ this._clearTimer();
8554
+ const live = this._model();
8555
+ const previous = this._committed();
8556
+ if (equal(live, previous)) {
8557
+ this._skipNextResets = false;
8558
+ return;
8559
+ }
8560
+ const changedKeys = changedKeysBetween(previous, live);
8561
+ const next = this._skipNextResets ? { ...live } : this._applyResets(live, changedKeys);
8562
+ this._skipNextResets = false;
8563
+ this._previous.set(clone(previous));
8564
+ this._committed.set(clone(next));
8565
+ // Reflect any reset overrides back into the bound controls. The effect re-runs
8566
+ // but no-ops because the model now equals the committed value.
8567
+ if (!equal(next, live)) {
8568
+ this._model.set(clone(next));
8569
+ }
8570
+ if (this._observing() && this._observeOptions?.writeToQueryParams !== false) {
8571
+ this._writeToUrl(next);
8572
+ }
8573
+ }
8574
+ _applyResets(live, changedKeys) {
8575
+ const next = { ...live };
8576
+ for (const [key, def] of Object.entries(this._fields)) {
8577
+ const resets = def.isResetBy;
8578
+ if (!resets?.length)
8579
+ continue;
8580
+ const triggered = resets.some((resetKey) => {
8581
+ if (!(resetKey in this._fields)) {
8582
+ if (isDevMode()) {
8583
+ console.warn(`QueryFormSignals: isResetBy references unknown field "${resetKey}". Is it a typo?`);
8584
+ }
8585
+ return false;
8586
+ }
8587
+ return changedKeys.includes(resetKey);
8588
+ });
8589
+ if (!triggered)
8590
+ continue;
8591
+ const defaultValue = this._defaults[key];
8592
+ if (!equal(next[key], defaultValue)) {
8593
+ next[key] = defaultValue;
8594
+ }
8595
+ }
8596
+ return next;
8597
+ }
8598
+ _applyFromUrl(params) {
8599
+ const current = { ...this._committed() };
8600
+ let changed = false;
8601
+ for (const [key, def] of Object.entries(this._fields)) {
8602
+ const raw = params[this._paramKey(key)];
8603
+ if (raw === undefined)
8604
+ continue;
8605
+ const value = raw === ET_PROPERTY_REMOVED ? this._defaults[key] : this._deserialize(def, raw);
8606
+ if (!equal(current[key], value)) {
8607
+ current[key] = value;
8608
+ changed = true;
8609
+ }
8610
+ }
8611
+ if (!changed)
8612
+ return;
8613
+ this._commitFromUrl(current);
8614
+ }
8615
+ _commitFromUrl(next) {
8616
+ this._clearTimer();
8617
+ this._previous.set(clone(this._committed()));
8618
+ this._committed.set(clone(next));
8619
+ // Feed the URL value into the live model & bound controls; the effect no-ops (model === committed).
8620
+ this._model.set(clone(next));
8621
+ }
8622
+ _deserialize(def, raw) {
8623
+ if (def.queryParamToValue)
8624
+ return def.queryParamToValue(raw);
8625
+ if (def.skipAutoTransform)
8626
+ return raw;
8627
+ return autoCoerce(raw, resolveDefault(def));
8628
+ }
8629
+ _writeToUrl(value) {
8630
+ const queryParams = {};
8631
+ for (const [key, def] of Object.entries(this._fields)) {
8632
+ const paramKey = this._paramKey(key);
8633
+ const fieldValue = value[key];
8634
+ const isDefault = equal(fieldValue, this._defaults[key]);
8635
+ const writeToUrl = def.appendToUrl !== false;
8636
+ const writeDefault = def.appendDefaultValueToUrl === true;
8637
+ if (!writeToUrl || (isDefault && !writeDefault)) {
8638
+ queryParams[paramKey] = undefined;
8639
+ }
8640
+ else if (def.valueToQueryParam) {
8641
+ queryParams[paramKey] = def.valueToQueryParam(fieldValue);
8642
+ }
8643
+ else {
8644
+ queryParams[paramKey] = fieldValue === null ? ET_NULL_VALUE : fieldValue;
8645
+ }
8646
+ }
8647
+ queueMicrotask(() => {
8648
+ this._router.navigate([], {
8649
+ queryParams,
8650
+ queryParamsHandling: 'merge',
8651
+ replaceUrl: this._observeOptions?.replaceUrl,
8652
+ });
8653
+ });
8654
+ }
8655
+ _paramKey(key) {
8656
+ const prefix = this._prefix;
8657
+ if (!prefix)
8658
+ return key;
8659
+ return `${typeof prefix === 'string' ? prefix : prefix()}-${key}`;
8660
+ }
8661
+ _clearTimer() {
8662
+ if (this._pendingTimer !== null) {
8663
+ clearTimeout(this._pendingTimer);
8664
+ this._pendingTimer = null;
8665
+ }
8666
+ }
8667
+ _cleanup() {
8668
+ if (!this._observing())
8669
+ return;
8670
+ this._observing.set(false);
8671
+ this._clearTimer();
8672
+ if (this._observeOptions?.writeToQueryParams === false)
8673
+ return;
8674
+ const queryParams = {};
8675
+ for (const key of Object.keys(this._fields)) {
8676
+ queryParams[this._paramKey(key)] = undefined;
8677
+ }
8678
+ queueMicrotask(() => {
8679
+ this._router.navigate([], { queryParams, queryParamsHandling: 'merge', replaceUrl: true });
8680
+ });
8681
+ }
8682
+ }
8683
+ /**
8684
+ * Create a {@link QueryFormSignals}. Call `.observe()` to start syncing with the
8685
+ * URL. Must be called in an injection context.
8686
+ */
8687
+ const createQueryForm = (config) => new QueryFormSignals(config);
8688
+
8689
+ const normalizeConfig = (config) => {
8690
+ if (!config)
8691
+ return {};
8692
+ const { isResetBy, ...rest } = config;
8693
+ return {
8694
+ ...rest,
8695
+ isResetBy: isResetBy === undefined ? undefined : Array.isArray(isResetBy) ? isResetBy : [isResetBy],
8696
+ };
8697
+ };
8698
+ /**
8699
+ * A generic query-form field. Defaults to `null` and, unless a transform is
8700
+ * given, relies on the auto-coercion when reading back from the URL.
8701
+ *
8702
+ * @example
8703
+ * queryField<number>({ defaultValue: 1, isResetBy: ['search'] })
8704
+ */
8705
+ const queryField = (config) => ({
8706
+ defaultValue: null,
8707
+ ...normalizeConfig(config),
8708
+ });
8709
+ /**
8710
+ * A debounced free-text search field. Debounces typing by 300ms but applies
8711
+ * clearing immediately (`disableDebounceIfFalsy`).
8712
+ */
8713
+ const searchQueryField = (config) => ({
8714
+ defaultValue: null,
8715
+ debounce: 300,
8716
+ disableDebounceIfFalsy: true,
8717
+ ...normalizeConfig(config),
8718
+ });
8719
+ /**
8720
+ * A sort field serialized as `"active:direction"` (e.g. `"name:asc"`). Matches
8721
+ * the wire format the table system's URL adapter uses, so the two interoperate.
8722
+ */
8723
+ const sortQueryField = (config) => ({
8724
+ defaultValue: null,
8725
+ queryParamToValue: transformToSort,
8726
+ valueToQueryParam: transformToSortQueryParam,
8727
+ ...normalizeConfig(config),
8728
+ });
8729
+ /** A field holding a list of strings. */
8730
+ const stringArrayQueryField = (config) => ({
8731
+ defaultValue: null,
8732
+ queryParamToValue: transformToStringArray,
8733
+ ...normalizeConfig(config),
8734
+ });
8735
+ /** A field holding a list of numbers. */
8736
+ const numberArrayQueryField = (config) => ({
8737
+ defaultValue: null,
8738
+ queryParamToValue: transformToNumberArray,
8739
+ ...normalizeConfig(config),
8740
+ });
8741
+ /** A field holding a list of booleans. */
8742
+ const booleanArrayQueryField = (config) => ({
8743
+ defaultValue: null,
8744
+ queryParamToValue: transformToBooleanArray,
8745
+ ...normalizeConfig(config),
8746
+ });
8747
+ /** A single date field (expects an ISO/`Date`-parseable string in the URL). */
8748
+ const dateQueryField = (config) => ({
8749
+ defaultValue: null,
8750
+ queryParamToValue: transformToDate,
8751
+ ...normalizeConfig(config),
8752
+ });
8753
+ /** A field holding a list of dates. */
8754
+ const dateArrayQueryField = (config) => ({
8755
+ defaultValue: null,
8756
+ queryParamToValue: transformToDateArray,
8757
+ ...normalizeConfig(config),
8758
+ });
8759
+
7232
8760
  // codes 1000-1999
7233
8761
  const WebSocketRuntimeErrorCode = {
7234
8762
  // Web Socket Client
@@ -7242,6 +8770,7 @@ const messageMalformed = () => {
7242
8770
  return new RuntimeError(WebSocketRuntimeErrorCode.MESSAGE_MALFORMED, 'A message has been received but it is malformed (cannot be parsed as JSON).');
7243
8771
  };
7244
8772
 
8773
+ const MAX_DEVTOOLS_MESSAGES = 100;
7245
8774
  const createWebSocketClient = (options) => {
7246
8775
  return createRootProvider(() => {
7247
8776
  const socket = io(options.url, {
@@ -7250,21 +8779,36 @@ const createWebSocketClient = (options) => {
7250
8779
  transports: options.transports,
7251
8780
  });
7252
8781
  const rooms = new Map();
7253
- const isConnected = signal(false, ...(ngDevMode ? [{ debugName: "isConnected" }] : []));
8782
+ const isConnected = signal(false, /* @ts-ignore */
8783
+ ...(ngDevMode ? [{ debugName: "isConnected" }] : /* istanbul ignore next */ []));
8784
+ // Devtools instrumentation (no-op unless provideQueryDevtools() was called).
8785
+ const devtoolsEnabled = isQueryDevtoolsEnabled();
8786
+ const devtoolsRooms = signal([], /* @ts-ignore */
8787
+ ...(ngDevMode ? [{ debugName: "devtoolsRooms" }] : /* istanbul ignore next */ []));
8788
+ const devtoolsMessages = signal([], /* @ts-ignore */
8789
+ ...(ngDevMode ? [{ debugName: "devtoolsMessages" }] : /* istanbul ignore next */ []));
8790
+ let devtoolsMessageId = 0;
8791
+ const syncDevtoolsRooms = () => {
8792
+ if (devtoolsEnabled)
8793
+ devtoolsRooms.set([...rooms.keys()]);
8794
+ };
7254
8795
  const joinRoom = (room) => {
7255
8796
  const roomFn = typeof room === 'function' ? room : () => room;
7256
8797
  const pre = previousSignalValue(computed(() => roomFn()));
7257
- const roomData = signal(null, ...(ngDevMode ? [{ debugName: "roomData" }] : []));
8798
+ const roomData = signal(null, /* @ts-ignore */
8799
+ ...(ngDevMode ? [{ debugName: "roomData" }] : /* istanbul ignore next */ []));
7258
8800
  const join = (name) => {
7259
8801
  socket.emit('join-room', name);
7260
8802
  const existingRoom = rooms.get(name);
7261
8803
  if (existingRoom)
7262
8804
  return existingRoom;
7263
- const message = signal(null, ...(ngDevMode ? [{ debugName: "message" }] : []));
8805
+ const message = signal(null, /* @ts-ignore */
8806
+ ...(ngDevMode ? [{ debugName: "message" }] : /* istanbul ignore next */ []));
7264
8807
  const newRoom = {
7265
8808
  latestMessage: message,
7266
8809
  };
7267
8810
  rooms.set(name, newRoom);
8811
+ syncDevtoolsRooms();
7268
8812
  return newRoom;
7269
8813
  };
7270
8814
  effect(() => {
@@ -7301,6 +8845,7 @@ const createWebSocketClient = (options) => {
7301
8845
  }
7302
8846
  socket.emit('leave-room', room);
7303
8847
  rooms.delete(room);
8848
+ syncDevtoolsRooms();
7304
8849
  };
7305
8850
  const setupWebSocketConnectionListener = () => {
7306
8851
  socket.on('connect', () => {
@@ -7315,6 +8860,16 @@ const createWebSocketClient = (options) => {
7315
8860
  socket.onAny((data) => {
7316
8861
  try {
7317
8862
  const json = JSON.parse(data);
8863
+ if (devtoolsEnabled) {
8864
+ const message = {
8865
+ id: devtoolsMessageId++,
8866
+ timestamp: Date.now(),
8867
+ room: json.room,
8868
+ event: json.event,
8869
+ data: json.data,
8870
+ };
8871
+ devtoolsMessages.update((log) => [message, ...log].slice(0, MAX_DEVTOOLS_MESSAGES));
8872
+ }
7318
8873
  const room = rooms.get(json.room);
7319
8874
  if (room)
7320
8875
  room.latestMessage.set(json);
@@ -7337,6 +8892,19 @@ const createWebSocketClient = (options) => {
7337
8892
  leaveRoom,
7338
8893
  },
7339
8894
  };
8895
+ if (devtoolsEnabled) {
8896
+ const handle = {
8897
+ connected: isConnected.asReadonly(),
8898
+ rooms: devtoolsRooms.asReadonly(),
8899
+ messages: devtoolsMessages.asReadonly(),
8900
+ };
8901
+ const unregister = registerQueryDevtoolsEntry({
8902
+ kind: 'ws-client',
8903
+ handle,
8904
+ meta: { name: options.name, url: options.url },
8905
+ });
8906
+ inject(DestroyRef).onDestroy(unregister);
8907
+ }
7340
8908
  return client;
7341
8909
  }, {
7342
8910
  name: `WebSocketClient_${options.name}`,
@@ -7347,5 +8915,5 @@ const createWebSocketClient = (options) => {
7347
8915
  * Generated bundle index. Do not edit.
7348
8916
  */
7349
8917
 
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 };
8918
+ 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
8919
  //# sourceMappingURL=ethlete-query.mjs.map