@ethlete/query 6.0.0-next.23 → 6.0.0-next.25

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,54 @@
1
1
  import * as i0 from '@angular/core';
2
- import { DestroyRef, InjectionToken, inject, Injector, EnvironmentInjector, ElementRef, ErrorHandler, createEnvironmentInjector, effect, untracked, signal, linkedSignal, computed, runInInjectionContext, isDevMode, PLATFORM_ID, resource, DOCUMENT, makeEnvironmentProviders, assertInInjectionContext, ChangeDetectorRef, ViewContainerRef, TemplateRef, Input, Directive, input, Pipe, ViewEncapsulation, Component, NgZone } from '@angular/core';
2
+ import { DestroyRef, InjectionToken, inject, Injector, EnvironmentInjector, ElementRef, ErrorHandler, createEnvironmentInjector, effect, untracked, signal, linkedSignal, computed, runInInjectionContext, isDevMode, PLATFORM_ID, resource, DOCUMENT, makeEnvironmentProviders, assertInInjectionContext, provideEnvironmentInitializer, ChangeDetectorRef, ViewContainerRef, TemplateRef, Input, Directive, input, Pipe, ViewEncapsulation, Component, NgZone } from '@angular/core';
3
3
  import { defineStaticProvider, RuntimeError, clamp, randomId, defineRootProvider, isObject, injectUnsavedChangesCoordinator, injectRoute, getCookie, getDomain, setCookie, deleteCookie, createDestroy, computedTillTruthy, defineProvider, toProvideFn, toInjectFn, nextFrame, injectQueryParams, injectQueryParamChanges, clone, equal, ET_PROPERTY_REMOVED, previousSignalValue } from '@ethlete/core';
4
- import { filter, Subject, Subscription, tap, retry, throwError, timer, catchError, take, switchMap, of, firstValueFrom, merge, fromEvent, throttleTime, interval, combineLatest, map, Observable, takeUntil, startWith, pairwise, BehaviorSubject, ReplaySubject, shareReplay, skip, takeWhile, EMPTY, withLatestFrom, debounceTime } from 'rxjs';
5
- import { toObservable, takeUntilDestroyed, toSignal } from '@angular/core/rxjs-interop';
4
+ import { filter, Subject, Subscription, defer, throwError, timer, switchMap, tap, retry, catchError, take, of, firstValueFrom, merge, fromEvent, throttleTime, interval, combineLatest, map, Observable, takeUntil, startWith, pairwise, BehaviorSubject, ReplaySubject, shareReplay, skip, takeWhile, EMPTY, withLatestFrom, debounceTime } from 'rxjs';
6
5
  import { HttpClient, HttpEventType, HttpErrorResponse, HttpHeaders, HttpStatusCode } from '@angular/common/http';
6
+ import { toObservable, takeUntilDestroyed, toSignal } from '@angular/core/rxjs-interop';
7
7
  import { getActiveConsumer, setActiveConsumer } from '@angular/core/primitives/signals';
8
8
  import { isPlatformBrowser, NgClass, AsyncPipe, JsonPipe } from '@angular/common';
9
9
  import { validateAsync, form } from '@angular/forms/signals';
10
10
  import * as i1 from '@angular/forms';
11
11
  import { FormControl, ReactiveFormsModule, FormGroup } from '@angular/forms';
12
12
  import { Router, ActivatedRoute } from '@angular/router';
13
- import { io } from 'socket.io-client';
13
+
14
+ /**
15
+ * Turns the features applied at creation into their devtools representation.
16
+ * @internal
17
+ */
18
+ const describeQueryDevtoolsFeatures = (features) => features?.map((feature) => ({ type: feature.type, details: feature.devtools?.() ?? [] })) ?? [];
19
+ /**
20
+ * Renders a millisecond duration the way feature details spell one out - `500ms`, `1.5s`, `15m`.
21
+ * @internal
22
+ */
23
+ const formatQueryDevtoolsDuration = (ms) => {
24
+ if (!Number.isFinite(ms))
25
+ return String(ms);
26
+ if (ms < 1000)
27
+ return `${ms}ms`;
28
+ const round = (value, unit) => `${Number(value.toFixed(1))}${unit}`;
29
+ if (ms < 60_000)
30
+ return round(ms / 1000, 's');
31
+ if (ms < 3_600_000)
32
+ return round(ms / 60_000, 'm');
33
+ return round(ms / 3_600_000, 'h');
34
+ };
35
+ /**
36
+ * The name of a function passed as `options.<key>`, or `null` when it is an inline lambda. An inline
37
+ * `{ handler: (e) => … }` inherits the name `handler` from the key it is assigned to, which tells the
38
+ * reader nothing - a reference to a declared function does.
39
+ */
40
+ const queryDevtoolsFnName = (fn, key) => {
41
+ const name = typeof fn === 'function' ? fn.name.replace(/^bound /, '') : '';
42
+ return !name || name === key ? null : name;
43
+ };
44
+ /**
45
+ * A detail for the function passed as `options.<key>`, omitted when it is an inline lambda.
46
+ * @internal
47
+ */
48
+ const queryDevtoolsFnDetail = (fn, key) => {
49
+ const name = queryDevtoolsFnName(fn, key);
50
+ return name ? [{ label: key, value: name }] : [];
51
+ };
14
52
 
15
53
  const noop$2 = () => undefined;
16
54
  let registrar = null;
@@ -33,6 +71,85 @@ const isQueryDevtoolsEnabled = () => registrar !== null;
33
71
  * @internal
34
72
  */
35
73
  const registerQueryDevtoolsEntry = (entry) => registrar?.(entry) ?? noop$2;
74
+ let statsFactory = null;
75
+ /**
76
+ * Installs the stats recorder factory. Called by `provideQueryDevtools()`; nothing else may call it.
77
+ * @internal
78
+ */
79
+ const setQueryDevtoolsStatsFactory = (fn) => {
80
+ statsFactory = fn;
81
+ };
82
+ /**
83
+ * A stats recorder for one entry, or `null` unless {@link provideQueryDevtools} has been called.
84
+ *
85
+ * Instrumentation sites must go through this instead of importing `createQueryDevtoolsStats`: a static
86
+ * import pins the whole stats module (~1.4 kB) into every bundle that touches a query, and a runtime
87
+ * `isQueryDevtoolsEnabled()` guard at the call site does not undo that. Reached only from the provider,
88
+ * it is dead code in an app without devtools.
89
+ * @internal
90
+ */
91
+ const createQueryDevtoolsStatsRecorder = () => statsFactory?.() ?? null;
92
+ let formLinksFactory = null;
93
+ /**
94
+ * Installs the form-links recorder factory. Called by `provideQueryDevtools()`; nothing else may call it.
95
+ * @internal
96
+ */
97
+ const setQueryDevtoolsFormLinksFactory = (fn) => {
98
+ formLinksFactory = fn;
99
+ };
100
+ /**
101
+ * A form-links recorder for one query, or `null` unless {@link provideQueryDevtools} has been called.
102
+ * Reached only from the provider for the same reason {@link createQueryDevtoolsStatsRecorder} is.
103
+ * @internal
104
+ */
105
+ const createQueryDevtoolsFormLinksRecorder = () => formLinksFactory?.() ?? null;
106
+ /** The ids of the query forms read by the args build currently running, or `null` outside of one. */
107
+ let formLinkSink = null;
108
+ /**
109
+ * Runs `build` and reports which query forms it read. Nested builds are supported - the outer one
110
+ * resumes collecting once the inner one is done.
111
+ * @internal
112
+ */
113
+ const collectQueryFormLinks = (build) => {
114
+ const outer = formLinkSink;
115
+ const sink = new Set();
116
+ formLinkSink = sink;
117
+ try {
118
+ return { value: build(), ids: [...sink] };
119
+ }
120
+ finally {
121
+ formLinkSink = outer;
122
+ }
123
+ };
124
+ /**
125
+ * Records that a query form's committed value was read. The form calls this from its `value` signal, so
126
+ * a read that happens while a query builds its args is what links the two - no naming convention, and
127
+ * nothing to keep in sync when the args change.
128
+ * @internal
129
+ */
130
+ const noteQueryFormRead = (id) => {
131
+ formLinkSink?.add(id);
132
+ };
133
+ let faultResolver = null;
134
+ /**
135
+ * Installs the fault resolver. Called by `provideQueryDevtools()`; nothing else may call it.
136
+ * @internal
137
+ */
138
+ const setQueryDevtoolsFaultResolver = (fn) => {
139
+ faultResolver = fn;
140
+ };
141
+ /**
142
+ * Whether fault injection can do anything at all. Checked before building the wrapper that resolves a
143
+ * fault per attempt, so a request in an app without devtools keeps the pipeline it always had.
144
+ * @internal
145
+ */
146
+ const isQueryDevtoolsFaultInjectionEnabled = () => faultResolver !== null;
147
+ /**
148
+ * Resolves the fault to apply to one upcoming attempt, or `null` when nothing is armed. Consumes a
149
+ * `failNext` budget, so call it exactly once per attempt.
150
+ * @internal
151
+ */
152
+ const resolveQueryDevtoolsFault = (target) => faultResolver?.(target) ?? null;
36
153
  let suppressStackRegistration = false;
37
154
  /**
38
155
  * Suppresses devtools registration for the next {@link createQueryStack} call. Used by
@@ -230,12 +347,15 @@ const invalidStateInsideSecureExecuteFactory = () => {
230
347
  const circularQueryDependency = () => {
231
348
  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.`);
232
349
  };
233
- const legacyPrepareWithoutInjectionContext = (creatorName) => {
350
+ const legacyPrepareWithoutInjectionContext = (creatorName, method = 'prepare') => {
234
351
  const label = creatorName ? `"${creatorName}"` : 'A legacy query creator';
235
- 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` +
352
+ const example = method === 'prepare'
353
+ ? ` search = (term: string) => myQuery.prepare({ queryParams: { term }, injector: this.injector });`
354
+ : ` container = myQuery.${method}(null, { injector: this.injector });`;
355
+ return new RuntimeError(QueryRuntimeErrorCode.LEGACY_PREPARE_WITHOUT_INJECTION_CONTEXT, `${label}.${method}() 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` +
236
356
  `Capture one where a context does exist (field initializer, constructor) and pass it along:\n\n` +
237
357
  ` private injector = inject(Injector);\n` +
238
- ` search = (term: string) => myQuery.prepare({ queryParams: { term }, injector: this.injector });\n\n` +
358
+ `${example}\n\n` +
239
359
  `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.`);
240
360
  };
241
361
  const querySequenceAlreadyRunning = () => {
@@ -322,7 +442,7 @@ const withArgs = (args) => {
322
442
  // auto-refresh) always sees the latest value with no pending-effect window. Sentinels are
323
443
  // resolved here: CLEAR -> null, `null` ("don't change") -> keep the previous value.
324
444
  let previous = null;
325
- context.state.subtle.setArgsSource(() => {
445
+ const resolve = () => {
326
446
  const value = args();
327
447
  if (value === CLEAR_QUERY_ARGS) {
328
448
  previous = null;
@@ -332,7 +452,12 @@ const withArgs = (args) => {
332
452
  return previous;
333
453
  previous = value;
334
454
  return value;
335
- });
455
+ };
456
+ // Only ever non-null while the devtools are installed. Wrapping the source (rather than reading
457
+ // `state.args` from the outside) is what puts the tracking window around the *evaluation* of the
458
+ // args - a cached read of the computed runs nothing and would record no form at all.
459
+ const formLinks = context.state.subtle.devtoolsFormLinks;
460
+ context.state.subtle.setArgsSource(formLinks ? () => formLinks.track(resolve) : resolve);
336
461
  // Trigger (auto-)execution whenever the resolved args change.
337
462
  nestedEffect(() => {
338
463
  const currArgsNow = context.state.args();
@@ -362,6 +487,10 @@ const withArgs = (args) => {
362
487
  const withPolling = (options) => {
363
488
  return createQueryFeature({
364
489
  type: QueryFeatureType.WITH_POLLING,
490
+ devtools: () => [
491
+ { label: 'interval', value: formatQueryDevtoolsDuration(options.interval) },
492
+ { label: 'execute initially', value: options.executeInitially ? 'yes' : 'no' },
493
+ ],
365
494
  fn: (context) => {
366
495
  if (!context.flags.shouldAutoExecuteMethod) {
367
496
  throw withPollingUsedOnUnsupportedHttpMethod(context.flags.method);
@@ -428,6 +557,7 @@ const withPolling = (options) => {
428
557
  const withLogging = (options) => {
429
558
  return createQueryFeature({
430
559
  type: QueryFeatureType.WITH_LOGGING,
560
+ devtools: () => queryDevtoolsFnDetail(options.logFn, 'logFn'),
431
561
  fn: (context) => {
432
562
  context.state.events$.pipe(takeUntilDestroyed(context.deps.destroyRef)).subscribe((event) => {
433
563
  options.logFn(event);
@@ -439,6 +569,7 @@ const withLogging = (options) => {
439
569
  const withErrorHandling = (options) => {
440
570
  return createQueryFeature({
441
571
  type: QueryFeatureType.WITH_ERROR_HANDLING,
572
+ devtools: () => queryDevtoolsFnDetail(options.handler, 'handler'),
442
573
  fn: (context) => {
443
574
  context.state.events$
444
575
  .pipe(filter((event) => event.type === 'error'), takeUntilDestroyed(context.deps.destroyRef))
@@ -452,6 +583,7 @@ const withErrorHandling = (options) => {
452
583
  const withSuccessHandling = (options) => {
453
584
  return createQueryFeature({
454
585
  type: QueryFeatureType.WITH_SUCCESS_HANDLING,
586
+ devtools: () => queryDevtoolsFnDetail(options.handler, 'handler'),
455
587
  fn: (context) => {
456
588
  context.state.events$
457
589
  .pipe(filter((event) => event.type === HttpEventType.Response), takeUntilDestroyed(context.deps.destroyRef))
@@ -469,6 +601,10 @@ const withSuccessHandling = (options) => {
469
601
  const withAutoRefresh = (options) => {
470
602
  return createQueryFeature({
471
603
  type: QueryFeatureType.WITH_AUTO_REFRESH,
604
+ devtools: () => [
605
+ { label: 'signals', value: String(options.onSignalChanges.length) },
606
+ ...(options.ignoreOnlyManualExecution ? [{ label: 'ignores manual only', value: 'yes' }] : []),
607
+ ],
472
608
  fn: (context) => {
473
609
  if (!context.flags.shouldAutoExecuteMethod) {
474
610
  throw withAutoRefreshUsedOnUnsupportedHttpMethod(context.flags.method);
@@ -495,6 +631,7 @@ const withAutoRefresh = (options) => {
495
631
  const withResponseUpdate = (options) => {
496
632
  return createQueryFeature({
497
633
  type: QueryFeatureType.WITH_RESPONSE_UPDATE,
634
+ devtools: () => queryDevtoolsFnDetail(options.updater, 'updater'),
498
635
  fn: (context) => {
499
636
  nestedEffect(() => {
500
637
  const currentResponse = untracked(() => context.state.response());
@@ -545,6 +682,11 @@ const withPageResetOnError = (options) => {
545
682
  const matches = options.when ?? isPageOutOfRangeError;
546
683
  return createQueryFeature({
547
684
  type: QueryFeatureType.WITH_PAGE_RESET_ON_ERROR,
685
+ devtools: () => [
686
+ { label: 'resets', value: options.reset ? 'custom callback' : 'page signal' },
687
+ ...(options.reset ? [] : [{ label: 'reset to', value: String(options.resetTo ?? 1) }]),
688
+ { label: 'when', value: options.when ? 'custom' : 'page out of range' },
689
+ ],
548
690
  fn: (context) => {
549
691
  context.state.events$
550
692
  .pipe(filter((event) => event.type === 'error'), takeUntilDestroyed(context.deps.destroyRef))
@@ -624,7 +766,10 @@ const setupQueryState = (options) => {
624
766
  error: currentError,
625
767
  };
626
768
  }
627
- else if (currentResponse) {
769
+ else if (currentResponse !== null || latestHttpEvent()?.type === HttpEventType.Response) {
770
+ // A response event having arrived is what makes the execution a success, not a truthy body: a 204 (or any
771
+ // other empty payload) sets `response` to `null`, which would otherwise be indistinguishable from a query
772
+ // that never ran - leaving `filterSuccess`, `onSuccess` and the success handlers waiting forever.
628
773
  return {
629
774
  type: 'success',
630
775
  response: currentResponse,
@@ -651,6 +796,8 @@ const setupQueryState = (options) => {
651
796
  rawResponse,
652
797
  bindRequestEvents,
653
798
  setArgsSource,
799
+ devtoolsStats: options.devtoolsStats ?? null,
800
+ devtoolsFormLinks: options.devtoolsFormLinks ?? null,
654
801
  },
655
802
  };
656
803
  return state;
@@ -824,8 +971,12 @@ const createBaseQuery = (options) => {
824
971
  queryConfig: options.queryConfig,
825
972
  });
826
973
  return runInInjectionContext(deps.injector, () => {
974
+ const devtoolsStats = createQueryDevtoolsStatsRecorder();
975
+ const devtoolsFormLinks = createQueryDevtoolsFormLinksRecorder();
827
976
  const state = setupQueryState({
828
977
  transformResponse: options.creator?.transformResponse,
978
+ devtoolsStats,
979
+ devtoolsFormLinks,
829
980
  });
830
981
  const flags = getQueryFeatureUsage(options);
831
982
  const execute = options.executeFactory({
@@ -844,18 +995,40 @@ const createBaseQuery = (options) => {
844
995
  applyQueryFeatures(options.features, featureFnContext);
845
996
  maybeExecute({ execute, flags });
846
997
  const query = createQueryObject({ state, execute, deps });
847
- if (isQueryDevtoolsEnabled()) {
998
+ // Only ever non-null while the devtools are installed, so this whole block - the entry and the
999
+ // per-response payload measuring behind it - is dead code in an app without them.
1000
+ if (devtoolsStats) {
1001
+ const statsSubscription = state.events$.subscribe((event) => {
1002
+ if (event.type === 'error') {
1003
+ devtoolsStats.recordError();
1004
+ }
1005
+ else if (event.type === HttpEventType.Response) {
1006
+ devtoolsStats.recordResponse({ headers: event.headers, body: event.body });
1007
+ }
1008
+ });
1009
+ deps.destroyRef.onDestroy(() => statsSubscription.unsubscribe());
1010
+ // A retry happens inside the request, which is shared by every query hitting the same cache key -
1011
+ // so the recorder reads it off the request instead of being handed down into one. `recordRetry` is
1012
+ // idempotent per attempt, which is what makes reading a signal a safe way to count them.
1013
+ effect(() => {
1014
+ const retry = state.subtle.request()?.subtle.retryState();
1015
+ if (retry)
1016
+ devtoolsStats.recordRetry({ attempt: retry.attempt });
1017
+ });
848
1018
  const unregister = registerQueryDevtoolsEntry({
849
1019
  kind: 'query',
850
1020
  handle: query,
851
1021
  clientRef: client,
1022
+ stats: devtoolsStats,
1023
+ formLinks: devtoolsFormLinks ?? undefined,
852
1024
  route: options.creatorInternals.route ??
853
1025
  options.creator?.route ??
854
1026
  null,
1027
+ authProviderRef: options.creatorInternals.authProvider,
855
1028
  meta: {
856
1029
  clientBaseUrl: deps.client.baseUrl,
857
1030
  method: flags.method,
858
- featureTypes: options.features.map((f) => f.type),
1031
+ features: describeQueryDevtoolsFeatures(options.features),
859
1032
  queryConfig: options.queryConfig,
860
1033
  creator: options.creator,
861
1034
  repository: deps.client.repository,
@@ -890,7 +1063,7 @@ const setupQueryExecuteState = () => {
890
1063
  const queryExecute = (options) => {
891
1064
  const { executeOptions, args, executeState, options: runQueryOptions, isSecure } = options;
892
1065
  const { deps, state, creator, creatorInternals, queryConfig } = executeOptions;
893
- const { key, request } = deps.client.repository.request({
1066
+ const { key, request, executed } = deps.client.repository.request({
894
1067
  route: creatorInternals.route,
895
1068
  method: creatorInternals.method,
896
1069
  args,
@@ -908,6 +1081,7 @@ const queryExecute = (options) => {
908
1081
  state.lastTriggeredBy.set(runQueryOptions?.triggeredBy ?? null);
909
1082
  state.subtle.request.set(request);
910
1083
  state.subtle.bindRequestEvents(request);
1084
+ state.subtle.devtoolsStats?.recordExecution({ didRequest: executed, body: args?.body, url: request.url });
911
1085
  };
912
1086
  const circularQueryDependencyChecker = () => {
913
1087
  let lastTriggerTs = 0;
@@ -1009,6 +1183,10 @@ const createBaseQueryCreator = (config) => {
1009
1183
  options: { ...config.options, ...additionalOptions },
1010
1184
  });
1011
1185
  };
1186
+ queryCreator.subtle = {
1187
+ creatorInternals: config.internals,
1188
+ creatorOptions: config.options,
1189
+ };
1012
1190
  return queryCreator;
1013
1191
  };
1014
1192
 
@@ -1341,6 +1519,12 @@ const createHttpRequest = (options) => {
1341
1519
  ...(ngDevMode ? [{ debugName: "lastExecuteTime" }] : /* istanbul ignore next */ []));
1342
1520
  const expiresIn = signal(null, /* @ts-ignore */
1343
1521
  ...(ngDevMode ? [{ debugName: "expiresIn" }] : /* istanbul ignore next */ []));
1522
+ const attempts = signal(1, /* @ts-ignore */
1523
+ ...(ngDevMode ? [{ debugName: "attempts" }] : /* istanbul ignore next */ []));
1524
+ const retryState = signal(null, /* @ts-ignore */
1525
+ ...(ngDevMode ? [{ debugName: "retryState" }] : /* istanbul ignore next */ []));
1526
+ const lastDurationMs = signal(null, /* @ts-ignore */
1527
+ ...(ngDevMode ? [{ debugName: "lastDurationMs" }] : /* istanbul ignore next */ []));
1344
1528
  // NOTE: This must be a plain function, not a `computed`. The freshness check compares against
1345
1529
  // `Date.now()`, which is not reactive, so a memoized computed would only ever recompute when
1346
1530
  // `expiresIn` changes - once it evaluated to `false` (fresh) it would stay `false` forever, even
@@ -1362,26 +1546,68 @@ const createHttpRequest = (options) => {
1362
1546
  // client-level one instead of being appended next to it.
1363
1547
  return argHeaders.keys().reduce((merged, key) => merged.set(key, argHeaders.getAll(key) ?? []), clientHeaders);
1364
1548
  };
1549
+ const send = (headers) => dependencies.httpClient.request(options.method, options.fullPath, {
1550
+ observe: 'events',
1551
+ body: args?.body,
1552
+ reportProgress: clientOptions?.reportProgress,
1553
+ withCredentials: clientOptions?.withCredentials,
1554
+ transferCache: clientOptions?.transferCache,
1555
+ responseType: clientOptions?.responseType || 'json',
1556
+ headers,
1557
+ });
1558
+ /**
1559
+ * Wraps the request in the fault the devtools have armed for this client, if any.
1560
+ *
1561
+ * Inside a `defer` so a fault is resolved per subscription rather than per execution - which is what
1562
+ * makes `retry` re-roll it, so an armed "fail the next two attempts" lets the third one through like a
1563
+ * flaky server would. Injected latency delays the attempt rather than its response, so the loading
1564
+ * state genuinely lasts that long and a retry waits it out too.
1565
+ *
1566
+ * A faulted attempt always goes through `timer`, even at zero latency: no real request can fail in the
1567
+ * same tick it was started in, and settling synchronously inside `execute()` would let an injected
1568
+ * failure land before the caller has finished wiring the request up.
1569
+ */
1570
+ const sendWithFaults = (headers) => defer(() => {
1571
+ const fault = resolveQueryDevtoolsFault({
1572
+ clientName: options.clientName ?? '',
1573
+ method: options.method,
1574
+ url: options.fullPath,
1575
+ });
1576
+ if (!fault)
1577
+ return send(headers);
1578
+ const { status } = fault;
1579
+ const attempt$ = status === null
1580
+ ? send(headers)
1581
+ : throwError(() => new HttpErrorResponse({
1582
+ status,
1583
+ statusText: 'Injected by the query devtools',
1584
+ url: options.fullPath,
1585
+ error: { message: `Injected ${status} (query devtools)` },
1586
+ }));
1587
+ return timer(fault.latencyMs).pipe(switchMap(() => attempt$));
1588
+ });
1365
1589
  const createStream = () => {
1366
1590
  const headers = resolveHeaders();
1367
- return dependencies.httpClient
1368
- .request(options.method, options.fullPath, {
1369
- observe: 'events',
1370
- body: args?.body,
1371
- reportProgress: clientOptions?.reportProgress,
1372
- withCredentials: clientOptions?.withCredentials,
1373
- transferCache: clientOptions?.transferCache,
1374
- responseType: clientOptions?.responseType || 'json',
1375
- headers,
1376
- })
1377
- .pipe(tap((event) => updateState(event)), retry({
1591
+ const source$ = isQueryDevtoolsFaultInjectionEnabled() ? sendWithFaults(headers) : send(headers);
1592
+ return source$.pipe(tap((event) => updateState(event)), retry({
1378
1593
  delay: (error, retryCount) => {
1379
1594
  const retryOptions = { error, retryCount };
1380
1595
  const retryResult = options.retryFn?.(retryOptions) || runDefaultQueryRetry(retryOptions);
1381
1596
  if (!retryResult.retry) {
1382
1597
  return throwError(() => error);
1383
1598
  }
1384
- return timer(retryResult.delay);
1599
+ // rxjs counts retries from 1, so the attempt this delay leads up to is one further along.
1600
+ const attempt = retryCount + 1;
1601
+ retryState.set({
1602
+ attempt,
1603
+ delayMs: retryResult.delay,
1604
+ startsAt: Date.now() + retryResult.delay,
1605
+ status: error instanceof HttpErrorResponse ? error.status : 0,
1606
+ });
1607
+ return timer(retryResult.delay).pipe(tap(() => {
1608
+ retryState.set(null);
1609
+ attempts.set(attempt);
1610
+ }));
1385
1611
  },
1386
1612
  }), catchError((e) => {
1387
1613
  updateErrorState(e);
@@ -1396,12 +1622,15 @@ const createHttpRequest = (options) => {
1396
1622
  currentStreamSubscription.unsubscribe();
1397
1623
  lastExecuteTime.set(Date.now());
1398
1624
  lastLoadEventTime.set(lastExecuteTime());
1625
+ lastLoadEventAmount.set(0);
1399
1626
  loading.set({
1400
1627
  executeTime: lastExecuteTime(),
1401
1628
  progress: null,
1402
1629
  });
1403
1630
  error.set(null);
1404
1631
  expiresIn.set(null);
1632
+ attempts.set(1);
1633
+ retryState.set(null);
1405
1634
  const stream = createStream();
1406
1635
  currentStreamSubscription = stream.subscribe({
1407
1636
  error: () => {
@@ -1414,6 +1643,9 @@ const createHttpRequest = (options) => {
1414
1643
  const destroy = () => {
1415
1644
  const wasActive = !currentStreamSubscription.closed;
1416
1645
  currentStreamSubscription.unsubscribe();
1646
+ // A request destroyed mid-backoff never runs the attempt it was waiting for, so leaving the state
1647
+ // set would keep a "retrying in …" readout counting down for a request that is gone.
1648
+ retryState.set(null);
1417
1649
  event$.complete();
1418
1650
  return wasActive;
1419
1651
  };
@@ -1423,6 +1655,7 @@ const createHttpRequest = (options) => {
1423
1655
  {
1424
1656
  loading.set(null);
1425
1657
  response.set(event.body);
1658
+ lastDurationMs.set(Date.now() - lastExecuteTime());
1426
1659
  const expiresInSeconds = options.cacheAdapter
1427
1660
  ? options.cacheAdapter(event.headers)
1428
1661
  : extractExpiresInSeconds(event.headers);
@@ -1451,6 +1684,7 @@ const createHttpRequest = (options) => {
1451
1684
  const errorRes = createQueryErrorResponse(errorResponse);
1452
1685
  error.set(errorRes);
1453
1686
  loading.set(null);
1687
+ lastDurationMs.set(Date.now() - lastExecuteTime());
1454
1688
  const errorEvent = { type: 'error', error: errorRes };
1455
1689
  currentEvent.set(errorEvent);
1456
1690
  event$.next(errorEvent);
@@ -1475,15 +1709,21 @@ const createHttpRequest = (options) => {
1475
1709
  const elapsedTimeSinceLastEvent = currentTime - lastLoadEventTime();
1476
1710
  const elapsedTimeSinceLastExecute = currentTime - lastExecuteTime();
1477
1711
  const loadedAmount = event.loaded - lastLoadEventAmount();
1712
+ // An event in the same millisecond as the last one, or one that carries no new bytes, has no rate
1713
+ // to report - dividing by either zero yields Infinity or NaN. Such an event also keeps the
1714
+ // baseline where it is, so the next one measures across the wider window instead of restarting.
1715
+ const isMeasurable = elapsedTimeSinceLastEvent > 0 && loadedAmount > 0;
1478
1716
  // We only want to calculate speed and remaining time after 2 seconds of the execution
1479
1717
  // This is to avoid showing incorrect speed and remaining time when the request is very fast
1480
- if (elapsedTimeSinceLastExecute > SPEED_BUFFER_TIME_IN_MS) {
1481
- const speed = (loadedAmount / elapsedTimeSinceLastEvent) * 1000;
1482
- progress.speed = speed * 1000;
1483
- progress.remainingTime = Math.round((event.total - event.loaded) / speed) * 1000;
1718
+ if (isMeasurable && elapsedTimeSinceLastExecute > SPEED_BUFFER_TIME_IN_MS) {
1719
+ const bytesPerSecond = (loadedAmount / elapsedTimeSinceLastEvent) * 1000;
1720
+ progress.speed = bytesPerSecond;
1721
+ progress.remainingTime = Math.round((event.total - event.loaded) / bytesPerSecond) * 1000;
1722
+ }
1723
+ if (isMeasurable) {
1724
+ lastLoadEventTime.set(currentTime);
1725
+ lastLoadEventAmount.set(event.loaded);
1484
1726
  }
1485
- lastLoadEventTime.set(currentTime);
1486
- lastLoadEventAmount.set(event.loaded);
1487
1727
  loading.set(state);
1488
1728
  };
1489
1729
  const lastExternalResponseAt = signal(null, /* @ts-ignore */
@@ -1504,6 +1744,7 @@ const createHttpRequest = (options) => {
1504
1744
  const httpRequest = {
1505
1745
  method: options.method,
1506
1746
  url: options.fullPath,
1747
+ args: args ?? null,
1507
1748
  execute,
1508
1749
  destroy,
1509
1750
  loading: loading.asReadonly(),
@@ -1518,6 +1759,10 @@ const createHttpRequest = (options) => {
1518
1759
  lastExternalResponseAt: lastExternalResponseAt.asReadonly(),
1519
1760
  applyPersistedResponse,
1520
1761
  lastPersistedResponseAt: lastPersistedResponseAt.asReadonly(),
1762
+ resolveHeaders,
1763
+ attempts: attempts.asReadonly(),
1764
+ retryState: retryState.asReadonly(),
1765
+ lastDurationMs: lastDurationMs.asReadonly(),
1521
1766
  },
1522
1767
  };
1523
1768
  return httpRequest;
@@ -1685,7 +1930,7 @@ const createQueryStack = (options) => {
1685
1930
  kind: 'query-stack',
1686
1931
  handle: stack,
1687
1932
  meta: {
1688
- featureTypes: features.map((f) => f.type),
1933
+ features: describeQueryDevtoolsFeatures(features),
1689
1934
  },
1690
1935
  });
1691
1936
  inject(DestroyRef).onDestroy(unregister);
@@ -1974,7 +2219,7 @@ const createPagedQueryStack = (options) => {
1974
2219
  kind: 'paged-query-stack',
1975
2220
  handle: pagedQuery,
1976
2221
  meta: {
1977
- featureTypes: features?.map((f) => f.type),
2222
+ features: describeQueryDevtoolsFeatures(features),
1978
2223
  },
1979
2224
  });
1980
2225
  inject(DestroyRef).onDestroy(unregister);
@@ -2732,13 +2977,21 @@ const createQuerySyncEngine = (options) => {
2732
2977
  // An invalidation is something the other tab's app code asked for explicitly, so unlike the
2733
2978
  // mutation heuristic it is not something `refreshOnMutation: false` opts out of.
2734
2979
  if (message.type === 'invalidate') {
2735
- repository.refreshInUse(createQueryInvalidationFilter({ url: message.url }));
2980
+ repository.refreshInUse(createQueryInvalidationFilter({ url: message.url }), {
2981
+ type: 'invalidation',
2982
+ url: message.url,
2983
+ otherTab: true,
2984
+ });
2736
2985
  return;
2737
2986
  }
2738
2987
  if (!refreshOnMutation)
2739
2988
  return;
2740
2989
  const mutation = { method: message.method, url: message.url };
2741
- repository.refreshInUse(mutationFilter ? (request) => mutationFilter(mutation, request) : undefined);
2990
+ repository.refreshInUse(mutationFilter ? (request) => mutationFilter(mutation, request) : undefined, {
2991
+ type: 'mutation',
2992
+ url: message.url,
2993
+ otherTab: true,
2994
+ });
2742
2995
  };
2743
2996
  const postInvalidation = (url) => transport.post({ type: 'invalidate', url });
2744
2997
  const eventSubscription = repository.events$.subscribe(broadcast);
@@ -2853,6 +3106,13 @@ const QueryClientFeatureType = {
2853
3106
  DEFAULT_RETRY: 'DEFAULT_RETRY',
2854
3107
  ETHLETE_API_ERRORS: 'ETHLETE_API_ERRORS',
2855
3108
  };
3109
+ const describeRefreshOnMutation = (config) => {
3110
+ if (config === false)
3111
+ return 'no';
3112
+ if (config === true || config === undefined)
3113
+ return 'every query in use';
3114
+ return 'filtered';
3115
+ };
2856
3116
  /**
2857
3117
  * Coordinates a query client with its own instances in the user's **other tabs**, over a
2858
3118
  * `BroadcastChannel` and the Web Locks API. Three things happen once it is on:
@@ -2893,7 +3153,16 @@ const withMultiTabSync = (config) => ({ clientName, repository, destroyRef, isBr
2893
3153
  lockManager: createQueryKeyLockManager(`et-query-poll:${channelName}`),
2894
3154
  });
2895
3155
  destroyRef.onDestroy(sync.destroy);
2896
- return { type: QueryClientFeatureType.MULTI_TAB_SYNC, instance: sync };
3156
+ return {
3157
+ type: QueryClientFeatureType.MULTI_TAB_SYNC,
3158
+ instance: sync,
3159
+ devtools: () => [
3160
+ { label: 'channel', value: channelName },
3161
+ { label: 'share responses', value: syncConfig.syncResponses === false ? 'no' : 'yes' },
3162
+ { label: 'dedupe polling', value: syncConfig.dedupePolling === false ? 'no' : 'yes' },
3163
+ { label: 'refresh on mutation', value: describeRefreshOnMutation(syncConfig.refreshOnMutation) },
3164
+ ],
3165
+ };
2897
3166
  };
2898
3167
  /**
2899
3168
  * Keeps a query client's successful reads on disk (IndexedDB), so a reload renders the last known
@@ -2947,7 +3216,19 @@ const withQueryPersistence = (config) => ({ clientName, repository, destroyRef,
2947
3216
  window.removeEventListener('pagehide', flush);
2948
3217
  engine.destroy();
2949
3218
  });
2950
- return { type: QueryClientFeatureType.PERSISTENCE, instance: engine };
3219
+ return {
3220
+ type: QueryClientFeatureType.PERSISTENCE,
3221
+ instance: engine,
3222
+ devtools: () => [
3223
+ { label: 'store', value: persistenceConfig.storageName ?? `et-query-persistence-${clientName}` },
3224
+ { label: 'version', value: String(persistenceConfig.version ?? 1) },
3225
+ { label: 'max age', value: formatQueryDevtoolsDuration(persistenceConfig.maxAge ?? 86_400_000) },
3226
+ { label: 'max entries', value: String(persistenceConfig.maxEntries ?? 50) },
3227
+ { label: 'write delay', value: formatQueryDevtoolsDuration(persistenceConfig.writeDelay ?? 1000) },
3228
+ ...(persistenceConfig.adapter ? [{ label: 'adapter', value: 'custom' }] : []),
3229
+ ...queryDevtoolsFnDetail(persistenceConfig.filter, 'filter'),
3230
+ ],
3231
+ };
2951
3232
  };
2952
3233
  /**
2953
3234
  * Teaches the error pipeline to read an **HTML error page** - the 502 a proxy answers with, a
@@ -3104,10 +3385,10 @@ const createQueryRepository = (config) => {
3104
3385
  isPersistEnabled,
3105
3386
  keepUnusedFor,
3106
3387
  });
3107
- if (!runQueryOptions?.allowCache || cacheEntry.request.isStale()) {
3108
- cacheEntry.request.execute({ allowCache: runQueryOptions?.allowCache });
3109
- }
3110
- return { key: cacheKey, request: cacheEntry.request };
3388
+ const executed = !runQueryOptions?.allowCache || cacheEntry.request.isStale()
3389
+ ? cacheEntry.request.execute({ allowCache: runQueryOptions?.allowCache })
3390
+ : false;
3391
+ return { key: cacheKey, request: cacheEntry.request, executed };
3111
3392
  }
3112
3393
  }
3113
3394
  const request = createHttpRequest({
@@ -3116,11 +3397,12 @@ const createQueryRepository = (config) => {
3116
3397
  method: options.method,
3117
3398
  dependencies: config.dependencies,
3118
3399
  clientOptions: creatorOptions,
3400
+ clientName: config.name,
3119
3401
  clientHeaders: config.headers,
3120
3402
  cacheAdapter: config.cacheAdapter,
3121
3403
  retryFn: options.retryFn ?? config.retryFn,
3122
3404
  });
3123
- request.execute();
3405
+ const executed = request.execute();
3124
3406
  bind({
3125
3407
  key: trackingKey,
3126
3408
  consumerDestroyRef: options.consumerDestroyRef,
@@ -3135,7 +3417,7 @@ const createQueryRepository = (config) => {
3135
3417
  // on it - hydration from the persisted store - is only ever allowed to fill a gap this request has
3136
3418
  // not filled itself.
3137
3419
  eventsSubject.next({ type: 'entry-created', key: trackingKey, isCached: shouldCache, isPersistEnabled });
3138
- return { key: trackingKey, request };
3420
+ return { key: trackingKey, request, executed };
3139
3421
  };
3140
3422
  /** Tears an entry down for good, whether it currently has consumers or not. */
3141
3423
  const destroyEntry = (key, cacheEntry) => {
@@ -3198,7 +3480,8 @@ const createQueryRepository = (config) => {
3198
3480
  bumpCacheVersion();
3199
3481
  eventsSubject.next({ type: 'unbind-all-secure' });
3200
3482
  };
3201
- const refreshInUse = (filter) => {
3483
+ const refreshInUse = (filter, cause) => {
3484
+ const refreshed = [];
3202
3485
  for (const cacheEntry of cache.values()) {
3203
3486
  if (cacheEntry.consumers.size === 0)
3204
3487
  continue;
@@ -3209,8 +3492,14 @@ const createQueryRepository = (config) => {
3209
3492
  continue;
3210
3493
  if (filter && !filter(cacheEntry.request))
3211
3494
  continue;
3495
+ refreshed.push(cacheEntry.request);
3212
3496
  cacheEntry.request.execute({ force: true });
3213
3497
  }
3498
+ eventsSubject.next({
3499
+ type: 'queries-refreshed',
3500
+ cause: cause ?? { type: 'refresh', url: null, otherTab: false },
3501
+ requests: refreshed,
3502
+ });
3214
3503
  };
3215
3504
  const applyExternalResponse = (options) => {
3216
3505
  const cacheEntry = cache.get(options.key);
@@ -3322,6 +3611,7 @@ const createQueryClient = (options) => defineRootProvider(() => {
3322
3611
  const destroyRef = inject(DestroyRef);
3323
3612
  let sync = null;
3324
3613
  let persistenceEngine = null;
3614
+ const appliedFeatures = [];
3325
3615
  if (options.features?.length) {
3326
3616
  const seen = new Set();
3327
3617
  for (const featureFn of options.features) {
@@ -3329,6 +3619,7 @@ const createQueryClient = (options) => defineRootProvider(() => {
3329
3619
  if (seen.has(feature.type))
3330
3620
  throw queryClientFeatureUsedMultipleTimes(feature.type);
3331
3621
  seen.add(feature.type);
3622
+ appliedFeatures.push(feature);
3332
3623
  if (feature.type === QueryClientFeatureType.MULTI_TAB_SYNC) {
3333
3624
  sync = feature.instance;
3334
3625
  }
@@ -3343,7 +3634,11 @@ const createQueryClient = (options) => defineRootProvider(() => {
3343
3634
  refreshQueriesInUse: () => repository.refreshInUse(),
3344
3635
  invalidateQueries: (invalidation) => {
3345
3636
  const url = invalidation?.url ? resolveInvalidationUrl(options.baseUrl, invalidation.url) : null;
3346
- repository.refreshInUse(createQueryInvalidationFilter({ url, filter: invalidation?.filter }));
3637
+ repository.refreshInUse(createQueryInvalidationFilter({ url, filter: invalidation?.filter }), {
3638
+ type: 'invalidation',
3639
+ url,
3640
+ otherTab: false,
3641
+ });
3347
3642
  // The resolved URL is what travels: the other tabs are the same client, so they would
3348
3643
  // resolve it identically, and a message that needs no interpretation cannot drift.
3349
3644
  if (invalidation?.otherTabs ?? true)
@@ -3351,7 +3646,11 @@ const createQueryClient = (options) => defineRootProvider(() => {
3351
3646
  },
3352
3647
  clearPersistedQueries: () => persistenceEngine?.clear() ?? Promise.resolve(),
3353
3648
  whenPersistenceReady: persistenceEngine?.whenReady ?? Promise.resolve(),
3354
- subtle: { sync, persistence: persistenceEngine },
3649
+ subtle: {
3650
+ sync,
3651
+ persistence: persistenceEngine,
3652
+ devtoolsFeatures: isQueryDevtoolsEnabled() ? describeQueryDevtoolsFeatures(appliedFeatures) : [],
3653
+ },
3355
3654
  };
3356
3655
  return client;
3357
3656
  }, {
@@ -3998,8 +4297,10 @@ const setupBearerQueryRegistry = (builders, injector, latestExecutedQuery, lates
3998
4297
  };
3999
4298
  const setupFeatures = (featureBuilders, context) => {
4000
4299
  const features = {};
4300
+ /** The features as applied, in order - what the devtools describe. */
4301
+ const applied = [];
4001
4302
  if (!featureBuilders?.length) {
4002
- return features;
4303
+ return { features, applied };
4003
4304
  }
4004
4305
  const featureTypes = new Set();
4005
4306
  for (const featureSetup of featureBuilders) {
@@ -4014,9 +4315,25 @@ const setupFeatures = (featureBuilders, context) => {
4014
4315
  .map((word, index) => (index === 0 ? word : word.charAt(0).toUpperCase() + word.slice(1)))
4015
4316
  .join('');
4016
4317
  features[featureName] = feature.instance;
4318
+ applied.push({ type: feature.type, devtools: feature.devtools });
4017
4319
  }
4018
- return features;
4320
+ return { features, applied };
4019
4321
  };
4322
+ /**
4323
+ * Describes the provider's queries for the devtools from what their creators were built with, so a
4324
+ * refresh request can be exported before the refresh query has ever run.
4325
+ */
4326
+ const describeAuthQueries = (builders) => builders.map((builder) => {
4327
+ const internals = builder.config.queryCreator.subtle.creatorInternals;
4328
+ const isRefresh = builder._type === 'tokenRefreshQuery';
4329
+ return {
4330
+ key: builder.key,
4331
+ kind: (isRefresh ? 'token-refresh' : 'auth'),
4332
+ method: internals.method ?? 'POST',
4333
+ route: internals.route,
4334
+ buildArgs: isRefresh ? builder.buildArgs : undefined,
4335
+ };
4336
+ });
4020
4337
  const alwaysLeader = () => true;
4021
4338
  const runEarlyFeatureSetup = (featureBuilders, context) => {
4022
4339
  let isLeaderFn = alwaysLeader;
@@ -4110,7 +4427,7 @@ const createBearerAuthProviderImpl = (config) => {
4110
4427
  queries: queries,
4111
4428
  executionState,
4112
4429
  };
4113
- const features = setupFeatures(config.features, featureSetupContext);
4430
+ const { features, applied: appliedFeatures } = setupFeatures(config.features, featureSetupContext);
4114
4431
  const provider = {
4115
4432
  queries,
4116
4433
  features: features,
@@ -4130,11 +4447,13 @@ const createBearerAuthProviderImpl = (config) => {
4130
4447
  kind: 'auth-provider',
4131
4448
  handle: provider,
4132
4449
  clientRef: config.queryClientRef,
4450
+ authQueries: describeAuthQueries(config.queries),
4133
4451
  meta: {
4134
4452
  name: config.name,
4135
4453
  clientBaseUrl: queryClient.baseUrl,
4136
4454
  repository: queryClient.repository,
4137
4455
  client: queryClient,
4456
+ features: describeQueryDevtoolsFeatures(appliedFeatures),
4138
4457
  },
4139
4458
  });
4140
4459
  destroyRef.onDestroy(unregister);
@@ -4177,6 +4496,7 @@ const withRefreshQuery = (key, config) => {
4177
4496
  const delay = Math.min(exponentialDelay, maxRetryDelayMs);
4178
4497
  return { retry: true, delay };
4179
4498
  };
4499
+ const buildArgs = (refreshToken) => ({ body: { token: refreshToken } });
4180
4500
  const setup = (context) => {
4181
4501
  const expiresInPropertyName = config.expiresInPropertyName ?? 'exp';
4182
4502
  const minRefreshInterval = config.minRefreshInterval ?? 30000; // 30 seconds default
@@ -4192,9 +4512,8 @@ const withRefreshQuery = (key, config) => {
4192
4512
  if (timeSinceLastRefresh < minRefreshInterval)
4193
4513
  return;
4194
4514
  lastRefreshTime = now;
4195
- const refreshArgs = { body: { token: currentRefreshToken } };
4196
4515
  // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
4197
- context.queries[key].execute(refreshArgs, {
4516
+ context.queries[key].execute(buildArgs(currentRefreshToken), {
4198
4517
  triggeredBy: triggeredInternally ? 'token-refresh' : undefined,
4199
4518
  });
4200
4519
  };
@@ -4277,6 +4596,7 @@ const withRefreshQuery = (key, config) => {
4277
4596
  queryCreator: config.queryCreator.clone({ retryFn: refreshRetryFn, subtle: { useQueryRepositoryCache: true } }),
4278
4597
  },
4279
4598
  setup,
4599
+ buildArgs,
4280
4600
  };
4281
4601
  };
4282
4602
 
@@ -4343,6 +4663,11 @@ const withInactivityLogout = (config = {}) => {
4343
4663
  return {
4344
4664
  type: BearerAuthFeatureType.INACTIVITY_LOGOUT,
4345
4665
  instance,
4666
+ devtools: () => [
4667
+ { label: 'timeout', value: formatQueryDevtoolsDuration(inactivityTimeout) },
4668
+ { label: 'activity events', value: activityEvents.join(', ') },
4669
+ ...(config.customActivityCheck ? [{ label: 'custom activity check', value: 'yes' }] : []),
4670
+ ],
4346
4671
  };
4347
4672
  };
4348
4673
  };
@@ -4619,6 +4944,15 @@ const withBearerAuthMultiTabSync = (config = {}) => {
4619
4944
  const setup = (_context) => ({
4620
4945
  type: BearerAuthFeatureType.MULTI_TAB_SYNC,
4621
4946
  instance,
4947
+ devtools: () => [
4948
+ { label: 'channel', value: config.channelName ?? 'ethlete-auth-sync' },
4949
+ { label: 'tokens', value: config.syncTokens === false ? 'tab local' : 'synced' },
4950
+ { label: 'logout', value: config.syncLogout === false ? 'tab local' : 'synced' },
4951
+ {
4952
+ label: 'leader election',
4953
+ value: config.leaderElection === false ? 'every tab refreshes' : 'one tab refreshes',
4954
+ },
4955
+ ],
4622
4956
  });
4623
4957
  // The elected leader gates the auth queries' automatic refresh, which is wired up before the
4624
4958
  // regular feature setup runs - so this half has to happen earlier than `setup`.
@@ -4639,6 +4973,16 @@ const withPersistentAuth = (config) => {
4639
4973
  return {
4640
4974
  type: BearerAuthFeatureType.PERSISTENT_AUTH,
4641
4975
  instance,
4976
+ devtools: () => [
4977
+ { label: 'cookie', value: config.cookie?.name ?? 'etAuth' },
4978
+ { label: 'remembered for', value: `${config.cookie?.expiresInDays ?? 30}d` },
4979
+ { label: 'same site', value: config.cookie?.sameSite ?? 'lax' },
4980
+ { label: 'remember me default', value: config.defaultRememberMe ? 'yes' : 'no' },
4981
+ { label: 'auto login', value: config.autoLogin.queryKey },
4982
+ ...(config.autoLogin.excludeRoutes?.length
4983
+ ? [{ label: 'excluded routes', value: config.autoLogin.excludeRoutes.join(', ') }]
4984
+ : []),
4985
+ ],
4642
4986
  };
4643
4987
  };
4644
4988
  };
@@ -4745,6 +5089,10 @@ const withTokenExpirationWarning = (config = {}) => {
4745
5089
  return {
4746
5090
  type: BearerAuthFeatureType.TOKEN_EXPIRATION_WARNING,
4747
5091
  instance,
5092
+ devtools: () => [
5093
+ { label: 'warn before', value: formatQueryDevtoolsDuration(warningThreshold) },
5094
+ { label: 'check every', value: formatQueryDevtoolsDuration(checkInterval) },
5095
+ ],
4748
5096
  };
4749
5097
  };
4750
5098
  };
@@ -4799,6 +5147,10 @@ const withTokenRevocation = (config) => {
4799
5147
  return {
4800
5148
  type: BearerAuthFeatureType.TOKEN_REVOCATION,
4801
5149
  instance,
5150
+ devtools: () => [
5151
+ { label: 'query', value: config.queryKey },
5152
+ { label: 'on logout', value: revokeOnLogout ? 'yes' : 'no' },
5153
+ ],
4802
5154
  };
4803
5155
  };
4804
5156
  };
@@ -4806,9 +5158,14 @@ const withTokenRevocation = (config) => {
4806
5158
  const withTracking = (config) => {
4807
5159
  return (context) => {
4808
5160
  const instance = createTrackingFeature(context, config);
5161
+ const handledEvents = Object.keys(config?.on ?? {});
4809
5162
  return {
4810
5163
  type: BearerAuthFeatureType.TRACKING,
4811
5164
  instance,
5165
+ devtools: () => [
5166
+ { label: 'internal events', value: config?.trackInternalEvents === false ? 'ignored' : 'tracked' },
5167
+ ...(handledEvents.length ? [{ label: 'handlers', value: handledEvents.join(', ') }] : []),
5168
+ ],
4812
5169
  };
4813
5170
  };
4814
5171
  };
@@ -4945,6 +5302,349 @@ const createTrackingFeature = (context, config) => {
4945
5302
  };
4946
5303
  };
4947
5304
 
5305
+ /** The neutral fault: nothing armed. */
5306
+ const EMPTY_QUERY_DEVTOOLS_FAULT = {
5307
+ latencyMs: 0,
5308
+ failNext: 0,
5309
+ failRate: 0,
5310
+ status: 503,
5311
+ };
5312
+ /**
5313
+ * Statuses worth injecting, and whether the SDK's default retry policy retries them. A picker built from
5314
+ * this cannot arm a 500 and then leave you wondering why no retry fired.
5315
+ * @see runDefaultQueryRetry
5316
+ */
5317
+ const QUERY_DEVTOOLS_FAULT_STATUSES = [
5318
+ { status: 0, label: 'Network error', retryable: true },
5319
+ { status: 400, label: 'Bad Request', retryable: false },
5320
+ { status: 401, label: 'Unauthorized', retryable: false },
5321
+ { status: 403, label: 'Forbidden', retryable: false },
5322
+ { status: 404, label: 'Not Found', retryable: false },
5323
+ { status: 408, label: 'Request Timeout', retryable: true },
5324
+ { status: 429, label: 'Too Many Requests', retryable: true },
5325
+ { status: 500, label: 'Internal Server Error', retryable: false },
5326
+ { status: 502, label: 'Bad Gateway', retryable: true },
5327
+ { status: 503, label: 'Service Unavailable', retryable: true },
5328
+ ];
5329
+ const faults = /* @__PURE__ */ signal({}, /* @ts-ignore */
5330
+ ...(ngDevMode ? [{ debugName: "faults" }] : /* istanbul ignore next */ []));
5331
+ /**
5332
+ * The faults armed per client name, consumed by the `<et-query-devtools>` UI. Clients with nothing armed
5333
+ * are absent rather than present with an {@link EMPTY_QUERY_DEVTOOLS_FAULT}.
5334
+ */
5335
+ const queryDevtoolsFaults = /* @__PURE__ */ faults.asReadonly();
5336
+ /** Whether anything at all is armed on a fault. */
5337
+ const isQueryDevtoolsFaultArmed = (fault) => fault.latencyMs > 0 || fault.failNext > 0 || fault.failRate > 0;
5338
+ /**
5339
+ * Arms (or updates) the fault of one client. Only the given fields change; the rest keep their current
5340
+ * value. A client whose resulting fault has nothing armed is dropped from {@link queryDevtoolsFaults}.
5341
+ *
5342
+ * Part of the devtools contract consumed by `<et-query-devtools>`; a fault is a debugging tool, not
5343
+ * something an application should arm on itself.
5344
+ */
5345
+ const setQueryDevtoolsFault = (options) => {
5346
+ const { clientName, patch } = options;
5347
+ faults.update((current) => {
5348
+ const next = { ...(current[clientName] ?? EMPTY_QUERY_DEVTOOLS_FAULT), ...patch };
5349
+ if (!isQueryDevtoolsFaultArmed(next)) {
5350
+ const { [clientName]: _removed, ...rest } = current;
5351
+ return rest;
5352
+ }
5353
+ return { ...current, [clientName]: next };
5354
+ });
5355
+ };
5356
+ /**
5357
+ * Disarms every fault of one client, or of all clients when no name is given.
5358
+ *
5359
+ * @see setQueryDevtoolsFault
5360
+ */
5361
+ const clearQueryDevtoolsFaults = (clientName) => faults.update((current) => {
5362
+ if (clientName === undefined)
5363
+ return {};
5364
+ const { [clientName]: _removed, ...rest } = current;
5365
+ return rest;
5366
+ });
5367
+ /**
5368
+ * Resolves what to do with one upcoming attempt, consuming a `failNext` budget in the process - so this
5369
+ * must be called once per attempt and never speculatively.
5370
+ *
5371
+ * Installed as the fault resolver by `provideQueryDevtools()`; nothing else may call it.
5372
+ * @internal
5373
+ */
5374
+ const resolveQueryDevtoolsFaultForAttempt = (target) => {
5375
+ const fault = faults()[target.clientName];
5376
+ if (!fault || !isQueryDevtoolsFaultArmed(fault))
5377
+ return null;
5378
+ let shouldFail = false;
5379
+ if (fault.failNext > 0) {
5380
+ shouldFail = true;
5381
+ setQueryDevtoolsFault({ clientName: target.clientName, patch: { failNext: fault.failNext - 1 } });
5382
+ }
5383
+ else if (fault.failRate > 0) {
5384
+ shouldFail = Math.random() * 100 < fault.failRate;
5385
+ }
5386
+ return {
5387
+ latencyMs: fault.latencyMs,
5388
+ status: shouldFail ? fault.status : null,
5389
+ };
5390
+ };
5391
+
5392
+ /**
5393
+ * Records which query forms feed one query's args, so the devtools can answer "which query does this
5394
+ * form drive?" from the reads themselves rather than from a naming convention.
5395
+ * @internal
5396
+ */
5397
+ const createQueryDevtoolsFormLinks = () => {
5398
+ const ids = signal([], /* @ts-ignore */
5399
+ ...(ngDevMode ? [{ debugName: "ids" }] : /* istanbul ignore next */ []));
5400
+ return {
5401
+ ids: ids.asReadonly(),
5402
+ track: (build) => {
5403
+ const collected = collectQueryFormLinks(build);
5404
+ // Args are built inside a reactive computation, which may not write to a signal - `untracked`
5405
+ // lifts that restriction, and skipping the write unless something is new keeps the panel from
5406
+ // being notified on every evaluation.
5407
+ untracked(() => {
5408
+ const known = ids();
5409
+ const added = collected.ids.filter((id) => !known.includes(id));
5410
+ if (added.length)
5411
+ ids.set([...known, ...added]);
5412
+ });
5413
+ return collected.value;
5414
+ },
5415
+ };
5416
+ };
5417
+
5418
+ const EMPTY_STATS = {
5419
+ executions: 0,
5420
+ requests: 0,
5421
+ responses: 0,
5422
+ errors: 0,
5423
+ retries: 0,
5424
+ receivedBytes: 0,
5425
+ sentBytes: 0,
5426
+ hasEstimatedBytes: false,
5427
+ firstExecutedAt: null,
5428
+ lastResponseAt: null,
5429
+ lastDurationMs: null,
5430
+ totalDurationMs: 0,
5431
+ };
5432
+ const textEncoder = typeof TextEncoder === 'function' ? /* @__PURE__ */ new TextEncoder() : null;
5433
+ const byteLengthOf = (text) => textEncoder?.encode(text).length ?? text.length;
5434
+ const estimateBodySize = (body) => {
5435
+ if (body === null || body === undefined)
5436
+ return 0;
5437
+ if (typeof body === 'string')
5438
+ return byteLengthOf(body);
5439
+ if (typeof Blob !== 'undefined' && body instanceof Blob)
5440
+ return body.size;
5441
+ if (body instanceof ArrayBuffer)
5442
+ return body.byteLength;
5443
+ if (ArrayBuffer.isView(body))
5444
+ return body.byteLength;
5445
+ if (typeof FormData !== 'undefined' && body instanceof FormData)
5446
+ return 0;
5447
+ try {
5448
+ return byteLengthOf(JSON.stringify(body) ?? '');
5449
+ }
5450
+ catch {
5451
+ // A body holding a circular reference or a BigInt has no serialized size to report.
5452
+ return 0;
5453
+ }
5454
+ };
5455
+ /** How many runs a query keeps. Enough to see a stampede or a chain without growing unbounded. */
5456
+ const RUN_HISTORY = 25;
5457
+ /**
5458
+ * How many of those runs keep their response body. Bodies dominate what the buffer retains, and a diff
5459
+ * only ever looks a couple of runs back.
5460
+ */
5461
+ const RESPONSE_HISTORY = 5;
5462
+ /** The index of the newest run still in flight, or -1 when none is. */
5463
+ const lastPendingRunIndex = (runs) => {
5464
+ for (let index = runs.length - 1; index >= 0; index--) {
5465
+ if (runs[index]?.status === 'pending')
5466
+ return index;
5467
+ }
5468
+ return -1;
5469
+ };
5470
+ /**
5471
+ * Drops the response body of every run past the newest {@link RESPONSE_HISTORY} that hold one. Mutates
5472
+ * the array it is given, which is always one this module just built.
5473
+ */
5474
+ const trimRetainedResponses = (runs) => {
5475
+ let kept = 0;
5476
+ for (let index = runs.length - 1; index >= 0; index--) {
5477
+ const run = runs[index];
5478
+ if (!run?.hasResponse)
5479
+ continue;
5480
+ if (++kept > RESPONSE_HISTORY)
5481
+ runs[index] = { ...run, response: null, hasResponse: false };
5482
+ }
5483
+ return runs;
5484
+ };
5485
+ /**
5486
+ * The size of a payload in bytes, and whether that size is the one that went over the wire. Only a
5487
+ * `content-length` header is exact; everything else is measured by serializing the body.
5488
+ *
5489
+ * Part of the devtools contract - the panel measures the cache and the event log with it. **Not part of
5490
+ * the general public contract.**
5491
+ */
5492
+ const measureQueryDevtoolsPayload = (payload) => {
5493
+ const contentLength = Number(payload.headers?.get('content-length'));
5494
+ if (Number.isFinite(contentLength) && contentLength > 0)
5495
+ return { bytes: contentLength, isExact: true };
5496
+ return { bytes: estimateBodySize(payload.body), isExact: false };
5497
+ };
5498
+ /**
5499
+ * Accumulates what a query does, for the devtools panel. Only ever created while
5500
+ * {@link provideQueryDevtools} is installed - measuring a body's size serializes it, which no app
5501
+ * without devtools should pay for.
5502
+ * @internal
5503
+ */
5504
+ const createQueryDevtoolsStats = () => {
5505
+ const stats = signal(EMPTY_STATS, /* @ts-ignore */
5506
+ ...(ngDevMode ? [{ debugName: "stats" }] : /* istanbul ignore next */ []));
5507
+ const runs = signal([], /* @ts-ignore */
5508
+ ...(ngDevMode ? [{ debugName: "runs" }] : /* istanbul ignore next */ []));
5509
+ // The baseline the next response's duration is measured against. Deliberately not part of the stats:
5510
+ // a duration is only ever reported once the response it belongs to has arrived.
5511
+ let lastExecutionAt = null;
5512
+ // The URL of the last execution, whether or not it requested - so a response that arrives without a run
5513
+ // of the query's own is still attributed to the URL the query was pointed at.
5514
+ let lastUrl = null;
5515
+ let runCounter = 0;
5516
+ const appendRun = (run, previous) => {
5517
+ const appended = { ...run, index: ++runCounter };
5518
+ runs.set(trimRetainedResponses([...previous, appended].slice(-RUN_HISTORY)));
5519
+ };
5520
+ /**
5521
+ * Ends the newest run still in flight. A response with no run of the query's own waiting for it came
5522
+ * from elsewhere (see {@link QueryDevtoolsRun.didRequest}), so it is recorded as an instant instead.
5523
+ */
5524
+ const endRun = (end) => {
5525
+ const current = runs();
5526
+ const pending = lastPendingRunIndex(current);
5527
+ if (pending === -1) {
5528
+ appendRun({ ...end, startedAt: end.endedAt, didRequest: false, url: lastUrl, sentBytes: 0, attempts: 0 }, current);
5529
+ return;
5530
+ }
5531
+ runs.set(trimRetainedResponses(current.map((run, index) => (index === pending ? { ...run, ...end } : run))));
5532
+ };
5533
+ const recordExecution = ({ didRequest, body, url }) => {
5534
+ const now = Date.now();
5535
+ const sent = didRequest ? measureQueryDevtoolsPayload({ body }) : null;
5536
+ lastExecutionAt = now;
5537
+ lastUrl = url ?? null;
5538
+ stats.update((current) => ({
5539
+ ...current,
5540
+ executions: current.executions + 1,
5541
+ requests: current.requests + (didRequest ? 1 : 0),
5542
+ sentBytes: current.sentBytes + (sent?.bytes ?? 0),
5543
+ hasEstimatedBytes: current.hasEstimatedBytes || (!!sent?.bytes && !sent.isExact),
5544
+ firstExecutedAt: current.firstExecutedAt ?? now,
5545
+ }));
5546
+ // An execution answered from the cache, or one that joined a request already in flight, starts no
5547
+ // run of its own - the response it eventually gets (if any) is what records one.
5548
+ if (!didRequest)
5549
+ return;
5550
+ appendRun({
5551
+ startedAt: now,
5552
+ endedAt: null,
5553
+ status: 'pending',
5554
+ didRequest: true,
5555
+ url: lastUrl,
5556
+ attempts: 1,
5557
+ sentBytes: sent?.bytes ?? 0,
5558
+ receivedBytes: 0,
5559
+ response: null,
5560
+ hasResponse: false,
5561
+ }, runs().map((run) => (run.status === 'pending' ? { ...run, status: 'aborted', endedAt: now } : run)));
5562
+ };
5563
+ const recordResponse = (payload) => {
5564
+ const now = Date.now();
5565
+ const received = measureQueryDevtoolsPayload(payload);
5566
+ const duration = lastExecutionAt === null ? null : now - lastExecutionAt;
5567
+ stats.update((current) => ({
5568
+ ...current,
5569
+ responses: current.responses + 1,
5570
+ receivedBytes: current.receivedBytes + received.bytes,
5571
+ hasEstimatedBytes: current.hasEstimatedBytes || (received.bytes > 0 && !received.isExact),
5572
+ lastResponseAt: now,
5573
+ lastDurationMs: duration,
5574
+ totalDurationMs: current.totalDurationMs + (duration ?? 0),
5575
+ }));
5576
+ endRun({
5577
+ endedAt: now,
5578
+ status: 'success',
5579
+ receivedBytes: received.bytes,
5580
+ response: payload.body,
5581
+ hasResponse: true,
5582
+ });
5583
+ };
5584
+ const recordError = () => {
5585
+ stats.update((current) => ({ ...current, errors: current.errors + 1 }));
5586
+ endRun({ endedAt: Date.now(), status: 'error', receivedBytes: 0, response: null, hasResponse: false });
5587
+ };
5588
+ const recordRetry = ({ attempt }) => {
5589
+ const current = runs();
5590
+ const pending = lastPendingRunIndex(current);
5591
+ const run = current[pending];
5592
+ // Nothing in flight means the retry belongs to a request this query is not the one waiting on, and an
5593
+ // attempt already accounted for is the caller reporting the same one twice.
5594
+ if (!run || attempt <= run.attempts)
5595
+ return;
5596
+ stats.update((state) => ({ ...state, retries: state.retries + (attempt - run.attempts) }));
5597
+ runs.set(current.map((entry, index) => (index === pending ? { ...entry, attempts: attempt } : entry)));
5598
+ };
5599
+ const reset = () => {
5600
+ lastExecutionAt = null;
5601
+ lastUrl = null;
5602
+ runCounter = 0;
5603
+ stats.set(EMPTY_STATS);
5604
+ runs.set([]);
5605
+ };
5606
+ return {
5607
+ current: stats.asReadonly(),
5608
+ runs: runs.asReadonly(),
5609
+ reset,
5610
+ recordExecution,
5611
+ recordResponse,
5612
+ recordError,
5613
+ recordRetry,
5614
+ };
5615
+ };
5616
+ /**
5617
+ * Adds up the stats of several entries, for a stack's or a client's total. `lastDurationMs` is taken from
5618
+ * whichever entry responded most recently rather than summed.
5619
+ */
5620
+ const sumQueryDevtoolsStats = (handles) => {
5621
+ const total = { ...EMPTY_STATS };
5622
+ for (const handle of handles) {
5623
+ if (!handle)
5624
+ continue;
5625
+ const stats = handle.current();
5626
+ total.executions += stats.executions;
5627
+ total.requests += stats.requests;
5628
+ total.responses += stats.responses;
5629
+ total.errors += stats.errors;
5630
+ total.retries += stats.retries;
5631
+ total.receivedBytes += stats.receivedBytes;
5632
+ total.sentBytes += stats.sentBytes;
5633
+ total.totalDurationMs += stats.totalDurationMs;
5634
+ total.hasEstimatedBytes = total.hasEstimatedBytes || stats.hasEstimatedBytes;
5635
+ if (stats.firstExecutedAt !== null &&
5636
+ (total.firstExecutedAt === null || stats.firstExecutedAt < total.firstExecutedAt)) {
5637
+ total.firstExecutedAt = stats.firstExecutedAt;
5638
+ }
5639
+ if (stats.lastResponseAt !== null &&
5640
+ (total.lastResponseAt === null || stats.lastResponseAt > total.lastResponseAt)) {
5641
+ total.lastResponseAt = stats.lastResponseAt;
5642
+ total.lastDurationMs = stats.lastDurationMs;
5643
+ }
5644
+ }
5645
+ return total;
5646
+ };
5647
+
4948
5648
  const entries = /* @__PURE__ */ signal([], /* @ts-ignore */
4949
5649
  ...(ngDevMode ? [{ debugName: "entries" }] : /* istanbul ignore next */ []));
4950
5650
  /**
@@ -4963,26 +5663,49 @@ const descriptorOf = (entry) => {
4963
5663
  return `auth-provider|${meta.name ?? ''}`;
4964
5664
  if (kind === 'ws-client')
4965
5665
  return `ws-client|${meta.name ?? ''}`;
5666
+ if (kind === 'query-form')
5667
+ return `query-form|${meta.name ?? ''}`;
4966
5668
  return kind;
4967
5669
  };
4968
5670
  /**
4969
- * Stringifies a query creator route. Function routes are invoked with an empty args object and any
4970
- * resulting `undefined` path segments are rendered as `:param`, matching the legacy devtools.
5671
+ * Wraps a path param name where a route function interpolated it. A NUL cannot occur in a route, so
5672
+ * a marked-up route stays unambiguously parseable.
5673
+ */
5674
+ const PARAM_MARKER = '\u0000';
5675
+ /**
5676
+ * Stands in for the path params object a route function expects, recording every param it reads as
5677
+ * `\0<name>\0` in the returned route. A route that transforms what it reads (`p.id.slice(2)`) still
5678
+ * yields a usable route - the recorded name is transformed along with it.
5679
+ */
5680
+ const paramRecorder = () => new Proxy({}, {
5681
+ get: (_, prop) => (typeof prop === 'string' ? `${PARAM_MARKER}${prop}${PARAM_MARKER}` : undefined),
5682
+ });
5683
+ /** Splits a recorded route into its literal chunks and the path params interleaved between them. */
5684
+ const parseRecordedRoute = (recorded) => recorded
5685
+ .split(PARAM_MARKER)
5686
+ // Markers are balanced, so every odd chunk is a param name and every even one is literal text.
5687
+ .map((text, index) => ({ text, param: index % 2 === 1 ? text : null }))
5688
+ .filter((part) => part.text !== '');
5689
+ /**
5690
+ * Parses a query creator route into its literal and path-param parts, so the devtools can show which
5691
+ * segments of a route are dynamic and fill them in with the values a query actually used.
4971
5692
  * @internal
4972
5693
  */
4973
- const stringifyQueryRoute = (route) => {
5694
+ const parseQueryRoute = (route) => {
4974
5695
  if (route === undefined || route === null)
4975
- return '';
5696
+ return [];
4976
5697
  try {
4977
5698
  if (typeof route === 'function') {
4978
- return route({}).replace(/undefined/g, ':param');
5699
+ return parseRecordedRoute(route(paramRecorder()));
4979
5700
  }
4980
- return String(route);
5701
+ return [{ text: String(route), param: null }];
4981
5702
  }
4982
5703
  catch {
4983
- return typeof route === 'function' ? '(dynamic route)' : String(route);
5704
+ return [{ text: typeof route === 'function' ? '(dynamic route)' : String(route), param: null }];
4984
5705
  }
4985
5706
  };
5707
+ /** Renders parsed route parts as a template, e.g. `/team/:teamId/players`. */
5708
+ const stringifyQueryRouteParts = (parts) => parts.map((part) => (part.param ? `:${part.param}` : part.text)).join('');
4986
5709
  /**
4987
5710
  * Extracts the human readable client name from a query client tuple. The client's DI token is named
4988
5711
  * `QueryClient_<name>`, so we strip the prefix to recover the original name.
@@ -4993,16 +5716,37 @@ const getQueryClientName = (client) => {
4993
5716
  const desc = token?.toString?.() ?? '';
4994
5717
  return desc.replace('InjectionToken ', '').replace('QueryClient_', '') || 'unknown';
4995
5718
  };
5719
+ /**
5720
+ * The provider name behind a bearer auth provider definition, recovered from its DI token the way
5721
+ * {@link getQueryClientName} recovers a client's.
5722
+ * @internal
5723
+ */
5724
+ const getBearerAuthProviderName = (ref) => {
5725
+ const desc = String(ref.token ?? '');
5726
+ return desc.replace('InjectionToken ', '').replace('BearerAuthProvider_', '') || 'unknown';
5727
+ };
4996
5728
  /**
4997
5729
  * Ids are derived deterministically from a stable descriptor + a per-descriptor sequence number, so
4998
5730
  * they survive a page reload (letting the UI restore the selected entry) instead of being random.
4999
5731
  */
5000
5732
  const registerEntry = (registration) => {
5001
5733
  const meta = { ...registration.meta };
5002
- if (registration.route !== undefined)
5003
- meta.route = stringifyQueryRoute(registration.route);
5734
+ if (registration.route !== undefined) {
5735
+ meta.routeParts = parseQueryRoute(registration.route);
5736
+ meta.route = stringifyQueryRouteParts(meta.routeParts);
5737
+ }
5004
5738
  if (registration.clientRef)
5005
5739
  meta.clientName = getQueryClientName(registration.clientRef);
5740
+ if (registration.authProviderRef) {
5741
+ meta.isSecure = true;
5742
+ meta.authProviderName = getBearerAuthProviderName(registration.authProviderRef);
5743
+ }
5744
+ if (registration.authQueries) {
5745
+ meta.authQueries = registration.authQueries.map((authQuery) => ({
5746
+ ...authQuery,
5747
+ route: stringifyQueryRouteParts(parseQueryRoute(authQuery.route)),
5748
+ }));
5749
+ }
5006
5750
  let id = registration.id;
5007
5751
  if (!id) {
5008
5752
  const descriptor = descriptorOf({ kind: registration.kind, meta });
@@ -5016,6 +5760,8 @@ const registerEntry = (registration) => {
5016
5760
  handle: registration.handle,
5017
5761
  meta,
5018
5762
  createdAt: Date.now(),
5763
+ stats: registration.stats,
5764
+ formLinks: registration.formLinks,
5019
5765
  };
5020
5766
  entries.update((list) => [...list, fullEntry]);
5021
5767
  return () => entries.update((list) => list.filter((e) => e.id !== fullEntry.id));
@@ -5037,6 +5783,9 @@ const registerEntry = (registration) => {
5037
5783
  */
5038
5784
  const provideQueryDevtools = () => {
5039
5785
  setQueryDevtoolsRegistrar(registerEntry);
5786
+ setQueryDevtoolsStatsFactory(createQueryDevtoolsStats);
5787
+ setQueryDevtoolsFormLinksFactory(createQueryDevtoolsFormLinks);
5788
+ setQueryDevtoolsFaultResolver(resolveQueryDevtoolsFaultForAttempt);
5040
5789
  if (!isDevMode()) {
5041
5790
  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.');
5042
5791
  }
@@ -5246,6 +5995,9 @@ const createGqlMutationViaPost = /* @__PURE__ */ createGqlCreatorTemplate('MUTAT
5246
5995
  /** A query creator that creates a secure GQL mutation where the payload is sent via POST (body) */
5247
5996
  const createSecureGqlMutationViaPost = /* @__PURE__ */ createSecureGqlCreatorTemplate('MUTATE', 'POST');
5248
5997
 
5998
+ /**
5999
+ * @deprecated Part of the legacy (v2) query system. Migrate to the current query API - see https://ethlete-sdk-docs.web.app/query/migrating-from-v2, and run `nx g @ethlete/query:migrate-to-query-v3` to rewrite the mechanical parts. Intent to remove in v7.
6000
+ */
5249
6001
  class CustomHeaderAuthProvider {
5250
6002
  constructor(_config) {
5251
6003
  this._config = _config;
@@ -5258,6 +6010,9 @@ class CustomHeaderAuthProvider {
5258
6010
  }
5259
6011
  }
5260
6012
 
6013
+ /**
6014
+ * @deprecated Part of the legacy (v2) query system. Migrate to the current query API - see https://ethlete-sdk-docs.web.app/query/migrating-from-v2, and run `nx g @ethlete/query:migrate-to-query-v3` to rewrite the mechanical parts. Intent to remove in v7.
6015
+ */
5261
6016
  class BasicAuthProvider {
5262
6017
  constructor(_config) {
5263
6018
  this._config = _config;
@@ -5273,7 +6028,13 @@ class BasicAuthProvider {
5273
6028
  }
5274
6029
  }
5275
6030
 
6031
+ /**
6032
+ * @deprecated Part of the legacy (v2) query system. Migrate to the current query API - see https://ethlete-sdk-docs.web.app/query/migrating-from-v2, and run `nx g @ethlete/query:migrate-to-query-v3` to rewrite the mechanical parts. Intent to remove in v7.
6033
+ */
5276
6034
  const isRequestError = (error) => error instanceof Object && 'status' in error && 'statusText' in error && 'url' in error;
6035
+ /**
6036
+ * @deprecated Part of the legacy (v2) query system. Migrate to the current query API - see https://ethlete-sdk-docs.web.app/query/migrating-from-v2, and run `nx g @ethlete/query:migrate-to-query-v3` to rewrite the mechanical parts. Intent to remove in v7.
6037
+ */
5277
6038
  const v2ExtractExpiresInSeconds = (headers) => {
5278
6039
  const cacheControl = headers['cache-control'];
5279
6040
  const age = headers['age'];
@@ -5319,6 +6080,9 @@ const v2ExtractExpiresInSeconds = (headers) => {
5319
6080
  }
5320
6081
  return expiresIn;
5321
6082
  };
6083
+ /**
6084
+ * @deprecated Part of the legacy (v2) query system. Migrate to the current query API - see https://ethlete-sdk-docs.web.app/query/migrating-from-v2, and run `nx g @ethlete/query:migrate-to-query-v3` to rewrite the mechanical parts. Intent to remove in v7.
6085
+ */
5322
6086
  const serializeBody = (body) => {
5323
6087
  if (body === null || body === undefined) {
5324
6088
  return null;
@@ -5336,6 +6100,9 @@ const serializeBody = (body) => {
5336
6100
  // eslint-disable-next-line @typescript-eslint/no-explicit-any
5337
6101
  return body.toString();
5338
6102
  };
6103
+ /**
6104
+ * @deprecated Part of the legacy (v2) query system. Migrate to the current query API - see https://ethlete-sdk-docs.web.app/query/migrating-from-v2, and run `nx g @ethlete/query:migrate-to-query-v3` to rewrite the mechanical parts. Intent to remove in v7.
6105
+ */
5339
6106
  const transformMethod = (config) => {
5340
6107
  if (config.method === 'GQL_QUERY' || config.method === 'GQL_MUTATE') {
5341
6108
  if (!config.transferVia) {
@@ -5345,6 +6112,9 @@ const transformMethod = (config) => {
5345
6112
  }
5346
6113
  return config.method;
5347
6114
  };
6115
+ /**
6116
+ * @deprecated Part of the legacy (v2) query system. Migrate to the current query API - see https://ethlete-sdk-docs.web.app/query/migrating-from-v2, and run `nx g @ethlete/query:migrate-to-query-v3` to rewrite the mechanical parts. Intent to remove in v7.
6117
+ */
5348
6118
  const detectContentTypeHeader = (body) => {
5349
6119
  // An empty body has no content type.
5350
6120
  if (body === null) {
@@ -5375,14 +6145,23 @@ const detectContentTypeHeader = (body) => {
5375
6145
  // No type could be inferred.
5376
6146
  return null;
5377
6147
  };
6148
+ /**
6149
+ * @deprecated Part of the legacy (v2) query system. Migrate to the current query API - see https://ethlete-sdk-docs.web.app/query/migrating-from-v2, and run `nx g @ethlete/query:migrate-to-query-v3` to rewrite the mechanical parts. Intent to remove in v7.
6150
+ */
5378
6151
  const hasHeader = (headers, header) => {
5379
6152
  return Object.keys(headers).some((key) => key.toLowerCase() === header.toLowerCase());
5380
6153
  };
6154
+ /**
6155
+ * @deprecated Part of the legacy (v2) query system. Migrate to the current query API - see https://ethlete-sdk-docs.web.app/query/migrating-from-v2, and run `nx g @ethlete/query:migrate-to-query-v3` to rewrite the mechanical parts. Intent to remove in v7.
6156
+ */
5381
6157
  const forEachHeader = (headers, callback) => {
5382
6158
  Object.entries(headers).forEach(([key, value]) => {
5383
6159
  callback(key, value);
5384
6160
  });
5385
6161
  };
6162
+ /**
6163
+ * @deprecated Part of the legacy (v2) query system. Migrate to the current query API - see https://ethlete-sdk-docs.web.app/query/migrating-from-v2, and run `nx g @ethlete/query:migrate-to-query-v3` to rewrite the mechanical parts. Intent to remove in v7.
6164
+ */
5386
6165
  const parseAllXhrResponseHeaders = (xhr) => {
5387
6166
  const headers = xhr.getAllResponseHeaders();
5388
6167
  const parsedHeaders = {};
@@ -5397,6 +6176,9 @@ const parseAllXhrResponseHeaders = (xhr) => {
5397
6176
  }
5398
6177
  return parsedHeaders;
5399
6178
  };
6179
+ /**
6180
+ * @deprecated Part of the legacy (v2) query system. Migrate to the current query API - see https://ethlete-sdk-docs.web.app/query/migrating-from-v2, and run `nx g @ethlete/query:migrate-to-query-v3` to rewrite the mechanical parts. Intent to remove in v7.
6181
+ */
5400
6182
  const getResponseUrl = (xhr) => {
5401
6183
  if ('responseURL' in xhr && xhr.responseURL) {
5402
6184
  return xhr.responseURL;
@@ -5406,6 +6188,9 @@ const getResponseUrl = (xhr) => {
5406
6188
  }
5407
6189
  return null;
5408
6190
  };
6191
+ /**
6192
+ * @deprecated Part of the legacy (v2) query system. Migrate to the current query API - see https://ethlete-sdk-docs.web.app/query/migrating-from-v2, and run `nx g @ethlete/query:migrate-to-query-v3` to rewrite the mechanical parts. Intent to remove in v7.
6193
+ */
5409
6194
  const v2ShouldRetryRequest = (config) => {
5410
6195
  const defaultRetryDelay = 1000 + 1000 * config.currentRetryCount;
5411
6196
  if (config.currentRetryCount > 3) {
@@ -5448,6 +6233,9 @@ const v2ShouldRetryRequest = (config) => {
5448
6233
  };
5449
6234
 
5450
6235
  const XSSI_PREFIX = /^\)\]\}',?\n/;
6236
+ /**
6237
+ * @deprecated Part of the legacy (v2) query system. Migrate to the current query API - see https://ethlete-sdk-docs.web.app/query/migrating-from-v2, and run `nx g @ethlete/query:migrate-to-query-v3` to rewrite the mechanical parts. Intent to remove in v7.
6238
+ */
5451
6239
  const request = (config) => {
5452
6240
  const headers = config.headers || {};
5453
6241
  const responseType = config.responseType || 'json';
@@ -5653,11 +6441,16 @@ const request = (config) => {
5653
6441
  });
5654
6442
  };
5655
6443
 
6444
+ /**
6445
+ * @deprecated Part of the legacy (v2) query system. Migrate to the current query API - see https://ethlete-sdk-docs.web.app/query/migrating-from-v2, and run `nx g @ethlete/query:migrate-to-query-v3` to rewrite the mechanical parts. Intent to remove in v7.
6446
+ */
5656
6447
  const addQueryContainerHandling = (obs, valueFn, config) => {
5657
- assertInInjectionContext(addQueryContainerHandling);
6448
+ if (!config?.injector) {
6449
+ assertInInjectionContext(addQueryContainerHandling);
6450
+ }
5658
6451
  const { abortPrevious, abortOnDestroy, stopPreviousPolling } = config ?? {};
5659
- const injector = inject(Injector);
5660
- const destroy$ = createDestroy();
6452
+ const injector = config?.injector ?? inject(Injector);
6453
+ const destroy$ = runInInjectionContext(injector, () => createDestroy());
5661
6454
  // eslint-disable-next-line @typescript-eslint/no-explicit-any
5662
6455
  const tNode = injector._tNode;
5663
6456
  const componentId = tNode?.index ?? -1;
@@ -5722,12 +6515,18 @@ const addQueryContainerHandling = (obs, valueFn, config) => {
5722
6515
  }
5723
6516
  });
5724
6517
  };
6518
+ /**
6519
+ * @deprecated Part of the legacy (v2) query system. Migrate to the current query API - see https://ethlete-sdk-docs.web.app/query/migrating-from-v2, and run `nx g @ethlete/query:migrate-to-query-v3` to rewrite the mechanical parts. Intent to remove in v7.
6520
+ */
5725
6521
  function toQuerySignal(source, options) {
5726
6522
  // eslint-disable-next-line @typescript-eslint/no-explicit-any
5727
6523
  const s = toSignal(source, options);
5728
6524
  addQueryContainerHandling(source, () => s(), options);
5729
6525
  return s;
5730
6526
  }
6527
+ /**
6528
+ * @deprecated Part of the legacy (v2) query system. Migrate to the current query API - see https://ethlete-sdk-docs.web.app/query/migrating-from-v2, and run `nx g @ethlete/query:migrate-to-query-v3` to rewrite the mechanical parts. Intent to remove in v7.
6529
+ */
5731
6530
  function effectComputed(computation, injector) {
5732
6531
  let initialData = null;
5733
6532
  try {
@@ -5746,11 +6545,14 @@ function effectComputed(computation, injector) {
5746
6545
  return;
5747
6546
  lastResult.set(data);
5748
6547
  });
5749
- });
6548
+ }, { injector });
5750
6549
  return lastResult.asReadonly();
5751
6550
  }
6551
+ /**
6552
+ * @deprecated Part of the legacy (v2) query system. Migrate to the current query API - see https://ethlete-sdk-docs.web.app/query/migrating-from-v2, and run `nx g @ethlete/query:migrate-to-query-v3` to rewrite the mechanical parts. Intent to remove in v7.
6553
+ */
5752
6554
  function queryComputed(computation, options) {
5753
- const injector = inject(Injector);
6555
+ const injector = options?.injector ?? inject(Injector);
5754
6556
  const c = effectComputed(computation, injector);
5755
6557
  const obs = toObservable(c, options);
5756
6558
  addQueryContainerHandling(obs, () => c(), options);
@@ -5759,26 +6561,37 @@ function queryComputed(computation, options) {
5759
6561
  /**
5760
6562
  * Creates a signal that will only be reactive until the first query is created.
5761
6563
  * All subsequent changes inside the computation will be ignored.
6564
+ *
6565
+ * @deprecated Part of the legacy (v2) query system. Migrate to the current query API - see https://ethlete-sdk-docs.web.app/query/migrating-from-v2, and run `nx g @ethlete/query:migrate-to-query-v3` to rewrite the mechanical parts. Intent to remove in v7.
5762
6566
  */
5763
6567
  function queryComputedTillTruthy(computation, options) {
5764
- const injector = inject(Injector);
6568
+ const injector = options?.injector ?? inject(Injector);
5765
6569
  const c = computedTillTruthy(effectComputed(computation, injector));
5766
6570
  const obs = toObservable(c, options);
5767
6571
  addQueryContainerHandling(obs, () => c(), options);
5768
6572
  return c;
5769
6573
  }
6574
+ /**
6575
+ * @deprecated Part of the legacy (v2) query system. Migrate to the current query API - see https://ethlete-sdk-docs.web.app/query/migrating-from-v2, and run `nx g @ethlete/query:migrate-to-query-v3` to rewrite the mechanical parts. Intent to remove in v7.
6576
+ */
5770
6577
  function queryArrayComputed(computation, options) {
5771
- const injector = inject(Injector);
6578
+ const injector = options?.injector ?? inject(Injector);
5772
6579
  const c = effectComputed(computation, injector);
5773
6580
  const obs = toObservable(c, options);
5774
6581
  addQueryContainerHandling(obs, () => c(), options);
5775
6582
  return c;
5776
6583
  }
6584
+ /**
6585
+ * @deprecated Part of the legacy (v2) query system. Migrate to the current query API - see https://ethlete-sdk-docs.web.app/query/migrating-from-v2, and run `nx g @ethlete/query:migrate-to-query-v3` to rewrite the mechanical parts. Intent to remove in v7.
6586
+ */
5777
6587
  function toQuerySubject(source, options) {
5778
6588
  const obs = toObservable(source, options);
5779
6589
  addQueryContainerHandling(obs, () => source(), options);
5780
6590
  return obs;
5781
6591
  }
6592
+ /**
6593
+ * @deprecated Part of the legacy (v2) query system. Migrate to the current query API - see https://ethlete-sdk-docs.web.app/query/migrating-from-v2, and run `nx g @ethlete/query:migrate-to-query-v3` to rewrite the mechanical parts. Intent to remove in v7.
6594
+ */
5782
6595
  function queryStateSignal(source, options) {
5783
6596
  const { cacheResponse } = options ?? {};
5784
6597
  const roSignal = toSignal(toObservable(source).pipe(switchMap((q) => extractQuery(q)?.state$ ?? of(null)), switchMap((state) => {
@@ -5793,6 +6606,9 @@ function queryStateSignal(source, options) {
5793
6606
  ...(ngDevMode ? [{ debugName: "rwSignal" }] : /* istanbul ignore next */ []));
5794
6607
  return rwSignal;
5795
6608
  }
6609
+ /**
6610
+ * @deprecated Part of the legacy (v2) query system. Migrate to the current query API - see https://ethlete-sdk-docs.web.app/query/migrating-from-v2, and run `nx g @ethlete/query:migrate-to-query-v3` to rewrite the mechanical parts. Intent to remove in v7.
6611
+ */
5796
6612
  function queryStateResponseSignal(source, options) {
5797
6613
  const s = queryStateSignal(source, options);
5798
6614
  const dataSignal = computed(() => {
@@ -5805,6 +6621,9 @@ function queryStateResponseSignal(source, options) {
5805
6621
  };
5806
6622
  return dataSignal;
5807
6623
  }
6624
+ /**
6625
+ * @deprecated Part of the legacy (v2) query system. Migrate to the current query API - see https://ethlete-sdk-docs.web.app/query/migrating-from-v2, and run `nx g @ethlete/query:migrate-to-query-v3` to rewrite the mechanical parts. Intent to remove in v7.
6626
+ */
5808
6627
  function queryStateErrorSignal(source) {
5809
6628
  const s = queryStateSignal(source);
5810
6629
  return computed(() => {
@@ -5812,6 +6631,9 @@ function queryStateErrorSignal(source) {
5812
6631
  return isQueryStateFailure(state) ? state.error : null;
5813
6632
  });
5814
6633
  }
6634
+ /**
6635
+ * @deprecated Part of the legacy (v2) query system. Migrate to the current query API - see https://ethlete-sdk-docs.web.app/query/migrating-from-v2, and run `nx g @ethlete/query:migrate-to-query-v3` to rewrite the mechanical parts. Intent to remove in v7.
6636
+ */
5815
6637
  function queryStateLoadingSignal(source) {
5816
6638
  const s = queryStateSignal(source);
5817
6639
  return computed(() => {
@@ -5820,6 +6642,9 @@ function queryStateLoadingSignal(source) {
5820
6642
  });
5821
6643
  }
5822
6644
 
6645
+ /**
6646
+ * @deprecated Part of the legacy (v2) query system. Migrate to the current query API - see https://ethlete-sdk-docs.web.app/query/migrating-from-v2, and run `nx g @ethlete/query:migrate-to-query-v3` to rewrite the mechanical parts. Intent to remove in v7.
6647
+ */
5823
6648
  const deepFreeze = (object) => {
5824
6649
  // check if object is actually an object or frozen
5825
6650
  if (!object || typeof object !== 'object' || Object.isFrozen(object)) {
@@ -5842,9 +6667,14 @@ const deepFreeze = (object) => {
5842
6667
  * **This function always returns null.**
5843
6668
  * @see https://github.com/microsoft/TypeScript/issues/26242
5844
6669
  * @returns null
6670
+ *
6671
+ * @deprecated Part of the legacy (v2) query system. Migrate to the current query API - see https://ethlete-sdk-docs.web.app/query/migrating-from-v2, and run `nx g @ethlete/query:migrate-to-query-v3` to rewrite the mechanical parts. Intent to remove in v7.
5845
6672
  */
5846
6673
  const def = () => null;
5847
6674
 
6675
+ /**
6676
+ * @deprecated Part of the legacy (v2) query system. Migrate to the current query API - see https://ethlete-sdk-docs.web.app/query/migrating-from-v2, and run `nx g @ethlete/query:migrate-to-query-v3` to rewrite the mechanical parts. Intent to remove in v7.
6677
+ */
5848
6678
  class V2QueryCreator {
5849
6679
  constructor(_queryConfig, _client, _store) {
5850
6680
  this._queryConfig = _queryConfig;
@@ -5891,6 +6721,9 @@ class V2QueryCreator {
5891
6721
  }
5892
6722
  }
5893
6723
 
6724
+ /**
6725
+ * @deprecated Part of the legacy (v2) query system. Migrate to the current query API - see https://ethlete-sdk-docs.web.app/query/migrating-from-v2, and run `nx g @ethlete/query:migrate-to-query-v3` to rewrite the mechanical parts. Intent to remove in v7.
6726
+ */
5894
6727
  class QueryStore {
5895
6728
  constructor(_config) {
5896
6729
  this._config = _config;
@@ -6034,6 +6867,9 @@ class QueryStore {
6034
6867
  }
6035
6868
  }
6036
6869
 
6870
+ /**
6871
+ * @deprecated Part of the legacy (v2) query system. Migrate to the current query API - see https://ethlete-sdk-docs.web.app/query/migrating-from-v2, and run `nx g @ethlete/query:migrate-to-query-v3` to rewrite the mechanical parts. Intent to remove in v7.
6872
+ */
6037
6873
  class V2QueryClient {
6038
6874
  constructor(_clientConfig) {
6039
6875
  this._clientConfig = _clientConfig;
@@ -6111,9 +6947,15 @@ class V2QueryClient {
6111
6947
  }
6112
6948
  }
6113
6949
 
6950
+ /**
6951
+ * @deprecated Part of the legacy (v2) query system. Migrate to the current query API - see https://ethlete-sdk-docs.web.app/query/migrating-from-v2, and run `nx g @ethlete/query:migrate-to-query-v3` to rewrite the mechanical parts. Intent to remove in v7.
6952
+ */
6114
6953
  const v2ShouldCacheQuery = (method) => {
6115
6954
  return method === 'GET' || method === 'OPTIONS' || method === 'HEAD' || method === 'GQL_QUERY';
6116
6955
  };
6956
+ /**
6957
+ * @deprecated Part of the legacy (v2) query system. Migrate to the current query API - see https://ethlete-sdk-docs.web.app/query/migrating-from-v2, and run `nx g @ethlete/query:migrate-to-query-v3` to rewrite the mechanical parts. Intent to remove in v7.
6958
+ */
6117
6959
  const v2BuildQueryCacheKey = (route, args) => {
6118
6960
  const variables = JSON.stringify(args?.variables || {})
6119
6961
  // replace all curly braces with empty string
@@ -6131,6 +6973,64 @@ const v2BuildQueryCacheKey = (route, args) => {
6131
6973
  return hash.toString();
6132
6974
  };
6133
6975
 
6976
+ let fallbackInjector = null;
6977
+ /**
6978
+ * The stashed root injector, or `null` when the fallback was never provided (or was provided on the
6979
+ * server, where stashing is refused).
6980
+ *
6981
+ * @internal
6982
+ */
6983
+ const legacyPrepareFallbackInjector = () => fallbackInjector;
6984
+ /**
6985
+ * Lets `prepare()` fall back to the application's root injector when it is called outside any injection
6986
+ * context, instead of throwing `ET950`.
6987
+ *
6988
+ * Opt-in, and **browser only**. Two things degrade when the fallback is used, both matching v2, which had
6989
+ * no injector to scope to at all:
6990
+ *
6991
+ * - **The query's lifetime becomes the application's.** Nothing tears it down when a component dies, so
6992
+ * cleanup rests entirely on a query container or `config: { destroyOnResponse: true }`.
6993
+ * - **The devtools lose the host element**, so "inspect" cannot jump to the component. Already the case
6994
+ * for any query created from a root or environment injector.
6995
+ *
6996
+ * It refuses to stash anything on the server: a module-global injector would be shared across concurrent
6997
+ * requests, which is data bleed rather than a leak. Server-side renders keep throwing `ET950`, so a call
6998
+ * site that only works in the browser fails loudly where it matters.
6999
+ *
7000
+ * Prefer passing `injector` at the call site - it keeps the query scoped to whatever created it. Reach for
7001
+ * this when a migration has too many call sites to thread by hand.
7002
+ *
7003
+ * @example
7004
+ * ```ts
7005
+ * bootstrapApplication(AppComponent, {
7006
+ * providers: [provideLegacyPrepareFallback()],
7007
+ * });
7008
+ * ```
7009
+ *
7010
+ * @deprecated Part of the legacy (v2) query system. Migrate to the current query API - see https://ethlete-sdk-docs.web.app/query/migrating-from-v2, and run `nx g @ethlete/query:migrate-to-query-v3` to rewrite the mechanical parts. Intent to remove in v7.
7011
+ */
7012
+ const provideLegacyPrepareFallback = () => makeEnvironmentProviders([
7013
+ provideEnvironmentInitializer(() => {
7014
+ if (!isPlatformBrowser(inject(PLATFORM_ID))) {
7015
+ if (isDevMode()) {
7016
+ console.warn('provideLegacyPrepareFallback() does nothing on the server: a module-global injector would be shared ' +
7017
+ 'between concurrent requests. prepare() calls without an injection context keep throwing ET950 there.');
7018
+ }
7019
+ return;
7020
+ }
7021
+ const injector = inject(EnvironmentInjector);
7022
+ fallbackInjector = injector;
7023
+ inject(DestroyRef).onDestroy(() => {
7024
+ if (fallbackInjector === injector) {
7025
+ fallbackInjector = null;
7026
+ }
7027
+ });
7028
+ }),
7029
+ ]);
7030
+
7031
+ /**
7032
+ * @deprecated Part of the legacy (v2) query system. Migrate to the current query API - see https://ethlete-sdk-docs.web.app/query/migrating-from-v2, and run `nx g @ethlete/query:migrate-to-query-v3` to rewrite the mechanical parts. Intent to remove in v7.
7033
+ */
6134
7034
  const transformExecStateToQueryState = (execState) => {
6135
7035
  const meta = { id: -1, triggeredVia: 'program' };
6136
7036
  switch (execState?.type) {
@@ -6182,6 +7082,9 @@ const transformExecStateToQueryState = (execState) => {
6182
7082
  }
6183
7083
  }
6184
7084
  };
7085
+ /**
7086
+ * @deprecated Part of the legacy (v2) query system. Migrate to the current query API - see https://ethlete-sdk-docs.web.app/query/migrating-from-v2, and run `nx g @ethlete/query:migrate-to-query-v3` to rewrite the mechanical parts. Intent to remove in v7.
7087
+ */
6185
7088
  const isLegacyQuery = (query) => {
6186
7089
  if (!query || typeof query !== 'object' || Array.isArray(query)) {
6187
7090
  return false;
@@ -6191,6 +7094,9 @@ const isLegacyQuery = (query) => {
6191
7094
  }
6192
7095
  return true;
6193
7096
  };
7097
+ /**
7098
+ * @deprecated Part of the legacy (v2) query system. Migrate to the current query API - see https://ethlete-sdk-docs.web.app/query/migrating-from-v2, and run `nx g @ethlete/query:migrate-to-query-v3` to rewrite the mechanical parts. Intent to remove in v7.
7099
+ */
6194
7100
  class LegacyQuery {
6195
7101
  constructor(newQuery, _arguments, entity,
6196
7102
  /**
@@ -6199,11 +7105,19 @@ class LegacyQuery {
6199
7105
  *
6200
7106
  * @internal
6201
7107
  */
6202
- _isInert = false) {
7108
+ _isInert = false,
7109
+ /**
7110
+ * Whether the underlying request is cacheable. Resolved by the creator, which is the only place that
7111
+ * knows the method before the first execution.
7112
+ *
7113
+ * @internal
7114
+ */
7115
+ _canBeCached = false) {
6203
7116
  this.newQuery = newQuery;
6204
7117
  this._arguments = _arguments;
6205
7118
  this.entity = entity;
6206
7119
  this._isInert = _isInert;
7120
+ this._canBeCached = _canBeCached;
6207
7121
  /**
6208
7122
  * @internal
6209
7123
  */
@@ -6238,17 +7152,22 @@ class LegacyQuery {
6238
7152
  }
6239
7153
  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 }));
6240
7154
  this.storeSyncEffect = effect(() => {
6241
- const res = this.newQuery.response();
7155
+ // Gated on the execution state, not on the response being non-null: the effect runs once on
7156
+ // creation, and `response()` is null until the first one arrives - `id`/`set` are typed
7157
+ // non-nullable, so an unguarded write feeds `null` through them on every `prepare()`. A plain
7158
+ // null-check would be wrong the other way: a 204 is a success whose body is legitimately null.
7159
+ const execState = this.newQuery.executionState();
6242
7160
  untracked(() => {
6243
- if (this.entity?.set) {
6244
- const id = this.entity.id({ args: this._arguments, response: res });
6245
- this.entity.set({
6246
- args: this._arguments,
6247
- response: res,
6248
- id,
6249
- store: this.entity.store,
6250
- });
6251
- }
7161
+ if (execState?.type !== 'success' || !this.entity?.set)
7162
+ return;
7163
+ const res = execState.response;
7164
+ const id = this.entity.id({ args: this._arguments, response: res });
7165
+ this.entity.set({
7166
+ args: this._arguments,
7167
+ response: res,
7168
+ id,
7169
+ store: this.entity.store,
7170
+ });
6252
7171
  });
6253
7172
  }, /* @ts-ignore */
6254
7173
  ...(ngDevMode ? [{ debugName: "storeSyncEffect" }] : /* istanbul ignore next */ []));
@@ -6292,7 +7211,7 @@ class LegacyQuery {
6292
7211
  return null;
6293
7212
  }
6294
7213
  get canBeCached() {
6295
- return false;
7214
+ return this._canBeCached;
6296
7215
  }
6297
7216
  get _isInMockMode() {
6298
7217
  return false;
@@ -6484,32 +7403,61 @@ const isInjectorUsable = (injector) => {
6484
7403
  return false;
6485
7404
  }
6486
7405
  };
7406
+ /**
7407
+ * Angular's NG0203, thrown by `inject()` outside an injection context. Matched on the code rather than
7408
+ * the message: production builds strip the message, and NG0205 ("Injector has already been destroyed")
7409
+ * comes out of the same `inject()` call for a different reason entirely.
7410
+ */
7411
+ const isMissingInjectionContextError = (error) => typeof error === 'object' && error !== null && Math.abs(Number(error.code)) === 203;
7412
+ /**
7413
+ * What the creator was built with, for diagnostics - `GET /person`. Lets an unnamed wrapper still point
7414
+ * at an endpoint instead of "a legacy query creator". A route function cannot be described this way, but
7415
+ * those wrappers come from the generator, which always emits a `name`.
7416
+ */
7417
+ const describeCreator = (creator) => {
7418
+ const { method, route } = creator.subtle.creatorInternals;
7419
+ if (typeof route !== 'string')
7420
+ return undefined;
7421
+ return typeof method === 'string' ? `${method} ${route}` : route;
7422
+ };
7423
+ /**
7424
+ * Mirrors the cacheability decision the query repository makes per request: cacheable HTTP methods and
7425
+ * GraphQL queries, unless the creator opted in or out explicitly. The legacy container helpers branch on
7426
+ * `canBeCached` for their default cleanup, so a wrapper has to answer before the first execution.
7427
+ */
7428
+ const canCreatorBeCached = (creator) => {
7429
+ const { method } = creator.subtle.creatorInternals;
7430
+ const explicit = creator.subtle.creatorOptions?.subtle
7431
+ ?.useQueryRepositoryCache;
7432
+ if (explicit !== undefined)
7433
+ return explicit;
7434
+ if (method === 'QUERY')
7435
+ return true;
7436
+ if (method === 'MUTATE')
7437
+ return false;
7438
+ return typeof method === 'string' && shouldCacheQuery(method);
7439
+ };
7440
+ /**
7441
+ * @deprecated Part of the legacy (v2) query system. Migrate to the current query API - see https://ethlete-sdk-docs.web.app/query/migrating-from-v2, and run `nx g @ethlete/query:migrate-to-query-v3` to rewrite the mechanical parts. Intent to remove in v7.
7442
+ */
6487
7443
  class LegacyQueryCreator {
6488
7444
  constructor(options) {
6489
7445
  this.options = options;
6490
7446
  this.prepare = (args) => {
6491
- // v2's `prepare()` needed no injection context, so plenty of call sites are plain callbacks.
6492
- // Angular's raw NG0203 names neither the query nor the call site, which makes those a bisection
6493
- // exercise - replace it with an error that does.
6494
- const injector = args?.injector ??
6495
- (() => {
6496
- try {
6497
- return inject(Injector);
6498
- }
6499
- catch {
6500
- throw legacyPrepareWithoutInjectionContext(this.options.name);
6501
- }
6502
- })();
7447
+ const injector = args?.injector ?? this.resolveAmbientInjector('prepare');
6503
7448
  let headers = new HttpHeaders();
6504
7449
  if (args?.headers) {
6505
- Object.entries(args.headers).forEach(([key, value]) => {
6506
- if (value) {
6507
- headers = headers.set(key, value);
6508
- }
6509
- });
7450
+ for (const [key, value] of Object.entries(args.headers)) {
7451
+ // Only an absent value is skipped - an empty string is a header the caller asked to send.
7452
+ if (value === undefined || value === null)
7453
+ continue;
7454
+ headers = headers.set(key, value);
7455
+ }
6510
7456
  }
6511
7457
  const queryArgs = {
6512
- ...(args?.body ? { body: args.body } : {}),
7458
+ // Presence, not truthiness: `0`, `''` and `false` are bodies, and dropping them silently sends a
7459
+ // different request than the call site asked for.
7460
+ ...(args?.body !== undefined ? { body: args.body } : {}),
6513
7461
  ...(args?.pathParams ? { pathParams: args.pathParams } : {}),
6514
7462
  ...(args?.queryParams ? { queryParams: args.queryParams } : {}),
6515
7463
  headers,
@@ -6520,10 +7468,10 @@ class LegacyQueryCreator {
6520
7468
  // yields a query that does nothing instead of an NG0205 raised from the view's cleanup phase.
6521
7469
  if (!isInjectorUsable(injector)) {
6522
7470
  if (isDevMode()) {
6523
- console.warn(`${this.options.name ? `"${this.options.name}"` : 'A legacy query'}.prepare() was called with a destroyed injector, so an inert query was returned. ` +
7471
+ console.warn(`${this.label ? `"${this.label}"` : 'A legacy query'}.prepare() was called with a destroyed injector, so an inert query was returned. ` +
6524
7472
  `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.`);
6525
7473
  }
6526
- return new LegacyQuery(createInertQuery(), queryArgs, this.options.entity, true);
7474
+ return new LegacyQuery(createInertQuery(), queryArgs, this.options.entity, true, this.canBeCached);
6527
7475
  }
6528
7476
  return runInInjectionContext(injector, () => {
6529
7477
  return untracked(() => {
@@ -6534,33 +7482,71 @@ class LegacyQueryCreator {
6534
7482
  // `LegacyQuery.execute()` forwards v2's `skipCache` as `allowCache` for every method, cacheable or not.
6535
7483
  silenceUncacheableAllowCacheError: true,
6536
7484
  });
6537
- const legacyQuery = new LegacyQuery(newQuery, queryArgs, this.options.entity);
7485
+ const legacyQuery = new LegacyQuery(newQuery, queryArgs, this.options.entity, false, this.canBeCached);
6538
7486
  if (args?.config?.destroyOnResponse) {
6539
- const destroyEffect = effect(() => {
6540
- if (newQuery.executionState()?.type === 'success' || newQuery.executionState()?.type === 'failure') {
7487
+ // Owned by the query's own injector, not the caller's. Call sites are told to pass an injector
7488
+ // that outlives them, so they hand over a root or environment one - and an effect that only
7489
+ // tears itself down on a terminal state then outlives an aborted or never-executed query for
7490
+ // the lifetime of the app. Tied to the query, it dies with the thing it exists to destroy.
7491
+ effect(() => {
7492
+ const type = newQuery.executionState()?.type;
7493
+ if (type === 'success' || type === 'failure') {
6541
7494
  legacyQuery.destroy();
6542
- destroyEffect.destroy();
6543
7495
  }
6544
- }, /* @ts-ignore */
6545
- ...(ngDevMode ? [{ debugName: "destroyEffect" }] : /* istanbul ignore next */ []));
7496
+ }, { injector: newQuery.subtle.injector });
6546
7497
  }
6547
7498
  return legacyQuery;
6548
7499
  });
6549
7500
  });
6550
7501
  };
6551
7502
  this.createSubject = (initialValue, config) => {
7503
+ const injector = config?.injector ?? this.resolveAmbientInjector('createSubject');
6552
7504
  const subject = new BehaviorSubject(initialValue ?? null);
6553
- addQueryContainerHandling(subject, () => subject.getValue(), config);
7505
+ addQueryContainerHandling(subject, () => subject.getValue(), { ...config, injector });
6554
7506
  return subject;
6555
7507
  };
6556
7508
  this.createSignal = (initialValue, config) => {
7509
+ const injector = config?.injector ?? this.resolveAmbientInjector('createSignal');
6557
7510
  const _signal = signal(initialValue ?? null, /* @ts-ignore */
6558
7511
  ...(ngDevMode ? [{ debugName: "_signal" }] : /* istanbul ignore next */ []));
6559
- addQueryContainerHandling(toObservable(_signal), () => _signal(), config);
7512
+ addQueryContainerHandling(toObservable(_signal, { injector }), () => _signal(), { ...config, injector });
6560
7513
  return _signal;
6561
7514
  };
6562
7515
  this.behaviorSubject = this.createSubject;
6563
7516
  }
7517
+ /**
7518
+ * Whether the underlying request is cacheable, which is what the legacy container helpers use to decide
7519
+ * their default cleanup. Resolved from the creator, so it answers before the first execution.
7520
+ */
7521
+ get canBeCached() {
7522
+ return canCreatorBeCached(this.options.creator);
7523
+ }
7524
+ get label() {
7525
+ return this.options.name ?? describeCreator(this.options.creator);
7526
+ }
7527
+ /**
7528
+ * v2 needed no injection context, so plenty of call sites are plain callbacks. Angular's raw NG0203
7529
+ * names neither the query nor the call site, which makes those a bisection exercise - replace it with
7530
+ * an error that does, and let every other DI failure (NG0205 from an injector mid-teardown, a missing
7531
+ * provider) through untouched.
7532
+ */
7533
+ resolveAmbientInjector(method) {
7534
+ try {
7535
+ return inject(Injector);
7536
+ }
7537
+ catch (error) {
7538
+ if (!isMissingInjectionContextError(error)) {
7539
+ throw error;
7540
+ }
7541
+ const fallback = legacyPrepareFallbackInjector();
7542
+ // A stale stash (the app injector is gone) is no injector at all - handing it over would build a
7543
+ // query that silently never runs.
7544
+ if (fallback && isInjectorUsable(fallback)) {
7545
+ return fallback;
7546
+ }
7547
+ throw legacyPrepareWithoutInjectionContext(this.label, method);
7548
+ }
7549
+ }
6564
7550
  }
6565
7551
  /**
6566
7552
  * Creates a legacy query creator.
@@ -6568,32 +7554,49 @@ class LegacyQueryCreator {
6568
7554
  * **NOTE**: This is a temporary solution to support legacy queries. It will be removed in the future.
6569
7555
  *
6570
7556
  * **DO NOT TURN NEW QUERY CREATORS INTO LEGACY ONES MANUALLY. THIS IS ONLY NEEDED FOR MIGRATION PURPOSES.**
7557
+ *
7558
+ * @deprecated Part of the legacy (v2) query system. Migrate to the current query API - see https://ethlete-sdk-docs.web.app/query/migrating-from-v2, and run `nx g @ethlete/query:migrate-to-query-v3` to rewrite the mechanical parts. Intent to remove in v7.
6571
7559
  */
6572
7560
  const createLegacyQueryCreator = (options) => {
6573
7561
  return new LegacyQueryCreator(options);
6574
7562
  };
6575
7563
 
6576
7564
  /* eslint-disable @typescript-eslint/no-explicit-any */
7565
+ /**
7566
+ * @deprecated Part of the legacy (v2) query system. Migrate to the current query API - see https://ethlete-sdk-docs.web.app/query/migrating-from-v2, and run `nx g @ethlete/query:migrate-to-query-v3` to rewrite the mechanical parts. Intent to remove in v7.
7567
+ */
6577
7568
  function filterSuccess() {
6578
7569
  return function (source) {
6579
7570
  return source.pipe(filter((value) => isQueryStateSuccess(value)));
6580
7571
  };
6581
7572
  }
7573
+ /**
7574
+ * @deprecated Part of the legacy (v2) query system. Migrate to the current query API - see https://ethlete-sdk-docs.web.app/query/migrating-from-v2, and run `nx g @ethlete/query:migrate-to-query-v3` to rewrite the mechanical parts. Intent to remove in v7.
7575
+ */
6582
7576
  function ignoreAutoRefresh() {
6583
7577
  return function (source) {
6584
7578
  return source.pipe(filter((value) => !isAutoRefresh(value)));
6585
7579
  };
6586
7580
  }
7581
+ /**
7582
+ * @deprecated Part of the legacy (v2) query system. Migrate to the current query API - see https://ethlete-sdk-docs.web.app/query/migrating-from-v2, and run `nx g @ethlete/query:migrate-to-query-v3` to rewrite the mechanical parts. Intent to remove in v7.
7583
+ */
6587
7584
  function filterFailure() {
6588
7585
  return function (source) {
6589
7586
  return source.pipe(filter((value) => isQueryStateFailure(value)));
6590
7587
  };
6591
7588
  }
7589
+ /**
7590
+ * @deprecated Part of the legacy (v2) query system. Migrate to the current query API - see https://ethlete-sdk-docs.web.app/query/migrating-from-v2, and run `nx g @ethlete/query:migrate-to-query-v3` to rewrite the mechanical parts. Intent to remove in v7.
7591
+ */
6592
7592
  function filterQueryStates(allowedStates) {
6593
7593
  return function (source) {
6594
7594
  return source.pipe(filter((value) => value && allowedStates.includes(value.type)));
6595
7595
  };
6596
7596
  }
7597
+ /**
7598
+ * @deprecated Part of the legacy (v2) query system. Migrate to the current query API - see https://ethlete-sdk-docs.web.app/query/migrating-from-v2, and run `nx g @ethlete/query:migrate-to-query-v3` to rewrite the mechanical parts. Intent to remove in v7.
7599
+ */
6597
7600
  function takeUntilResponse(config) {
6598
7601
  return function (source) {
6599
7602
  return source.pipe(takeWhile((value) => {
@@ -6604,21 +7607,33 @@ function takeUntilResponse(config) {
6604
7607
  }, true));
6605
7608
  };
6606
7609
  }
7610
+ /**
7611
+ * @deprecated Part of the legacy (v2) query system. Migrate to the current query API - see https://ethlete-sdk-docs.web.app/query/migrating-from-v2, and run `nx g @ethlete/query:migrate-to-query-v3` to rewrite the mechanical parts. Intent to remove in v7.
7612
+ */
6607
7613
  function filterNull() {
6608
7614
  return function (source) {
6609
7615
  return source.pipe(filter((value) => value !== null));
6610
7616
  };
6611
7617
  }
7618
+ /**
7619
+ * @deprecated Part of the legacy (v2) query system. Migrate to the current query API - see https://ethlete-sdk-docs.web.app/query/migrating-from-v2, and run `nx g @ethlete/query:migrate-to-query-v3` to rewrite the mechanical parts. Intent to remove in v7.
7620
+ */
6612
7621
  function switchQueryState() {
6613
7622
  return function (source) {
6614
7623
  return source.pipe(switchMap((value) => value?.state$ ?? of(null)));
6615
7624
  };
6616
7625
  }
7626
+ /**
7627
+ * @deprecated Part of the legacy (v2) query system. Migrate to the current query API - see https://ethlete-sdk-docs.web.app/query/migrating-from-v2, and run `nx g @ethlete/query:migrate-to-query-v3` to rewrite the mechanical parts. Intent to remove in v7.
7628
+ */
6617
7629
  function switchQueryCollectionState() {
6618
7630
  return function (source) {
6619
7631
  return source.pipe(switchMap((value) => value?.query.state$ ?? of(null)));
6620
7632
  };
6621
7633
  }
7634
+ /**
7635
+ * @deprecated Part of the legacy (v2) query system. Migrate to the current query API - see https://ethlete-sdk-docs.web.app/query/migrating-from-v2, and run `nx g @ethlete/query:migrate-to-query-v3` to rewrite the mechanical parts. Intent to remove in v7.
7636
+ */
6622
7637
  const resetPageOnError = (config) => (source) => {
6623
7638
  const { queryForm, pageControlKey = 'page' } = config;
6624
7639
  if (isDevMode() && !queryForm.form.controls[pageControlKey]) {
@@ -6647,12 +7662,33 @@ const resetPageOnError = (config) => (source) => {
6647
7662
  }), takeUntilResponse());
6648
7663
  }));
6649
7664
  };
7665
+ /**
7666
+ * @deprecated Part of the legacy (v2) query system. Migrate to the current query API - see https://ethlete-sdk-docs.web.app/query/migrating-from-v2, and run `nx g @ethlete/query:migrate-to-query-v3` to rewrite the mechanical parts. Intent to remove in v7.
7667
+ */
6650
7668
  const isQueryStateLoading = (state) => state?.type === "LOADING" /* QueryStateType.Loading */;
7669
+ /**
7670
+ * @deprecated Part of the legacy (v2) query system. Migrate to the current query API - see https://ethlete-sdk-docs.web.app/query/migrating-from-v2, and run `nx g @ethlete/query:migrate-to-query-v3` to rewrite the mechanical parts. Intent to remove in v7.
7671
+ */
6651
7672
  const isQueryStateSuccess = (state) => state?.type === "SUCCESS" /* QueryStateType.Success */;
7673
+ /**
7674
+ * @deprecated Part of the legacy (v2) query system. Migrate to the current query API - see https://ethlete-sdk-docs.web.app/query/migrating-from-v2, and run `nx g @ethlete/query:migrate-to-query-v3` to rewrite the mechanical parts. Intent to remove in v7.
7675
+ */
6652
7676
  const isQueryStateFailure = (state) => state?.type === "FAILURE" /* QueryStateType.Failure */;
7677
+ /**
7678
+ * @deprecated Part of the legacy (v2) query system. Migrate to the current query API - see https://ethlete-sdk-docs.web.app/query/migrating-from-v2, and run `nx g @ethlete/query:migrate-to-query-v3` to rewrite the mechanical parts. Intent to remove in v7.
7679
+ */
6653
7680
  const isQueryStateCancelled = (state) => state?.type === "CANCELLED" /* QueryStateType.Cancelled */;
7681
+ /**
7682
+ * @deprecated Part of the legacy (v2) query system. Migrate to the current query API - see https://ethlete-sdk-docs.web.app/query/migrating-from-v2, and run `nx g @ethlete/query:migrate-to-query-v3` to rewrite the mechanical parts. Intent to remove in v7.
7683
+ */
6654
7684
  const isQueryStatePrepared = (state) => state?.type === "PREPARED" /* QueryStateType.Prepared */;
7685
+ /**
7686
+ * @deprecated Part of the legacy (v2) query system. Migrate to the current query API - see https://ethlete-sdk-docs.web.app/query/migrating-from-v2, and run `nx g @ethlete/query:migrate-to-query-v3` to rewrite the mechanical parts. Intent to remove in v7.
7687
+ */
6655
7688
  const isAutoRefresh = (state) => state?.meta.triggeredVia === 'auto';
7689
+ /**
7690
+ * @deprecated Part of the legacy (v2) query system. Migrate to the current query API - see https://ethlete-sdk-docs.web.app/query/migrating-from-v2, and run `nx g @ethlete/query:migrate-to-query-v3` to rewrite the mechanical parts. Intent to remove in v7.
7691
+ */
6656
7692
  const mergeHeaders = (...headers) => {
6657
7693
  return headers.reduce((acc, headers) => {
6658
7694
  if (!headers) {
@@ -6664,6 +7700,9 @@ const mergeHeaders = (...headers) => {
6664
7700
  };
6665
7701
  }, {});
6666
7702
  };
7703
+ /**
7704
+ * @deprecated Part of the legacy (v2) query system. Migrate to the current query API - see https://ethlete-sdk-docs.web.app/query/migrating-from-v2, and run `nx g @ethlete/query:migrate-to-query-v3` to rewrite the mechanical parts. Intent to remove in v7.
7705
+ */
6667
7706
  const isGqlQueryConfig = (config) => {
6668
7707
  if (!config || typeof config !== 'object' || Array.isArray(config)) {
6669
7708
  return false;
@@ -6673,6 +7712,9 @@ const isGqlQueryConfig = (config) => {
6673
7712
  }
6674
7713
  return true;
6675
7714
  };
7715
+ /**
7716
+ * @deprecated Part of the legacy (v2) query system. Migrate to the current query API - see https://ethlete-sdk-docs.web.app/query/migrating-from-v2, and run `nx g @ethlete/query:migrate-to-query-v3` to rewrite the mechanical parts. Intent to remove in v7.
7717
+ */
6676
7718
  const isQuery = (query) => {
6677
7719
  if (!query || typeof query !== 'object' || Array.isArray(query)) {
6678
7720
  return false;
@@ -6682,6 +7724,9 @@ const isQuery = (query) => {
6682
7724
  }
6683
7725
  return true;
6684
7726
  };
7727
+ /**
7728
+ * @deprecated Part of the legacy (v2) query system. Migrate to the current query API - see https://ethlete-sdk-docs.web.app/query/migrating-from-v2, and run `nx g @ethlete/query:migrate-to-query-v3` to rewrite the mechanical parts. Intent to remove in v7.
7729
+ */
6685
7730
  const isQueryCollection = (query) => {
6686
7731
  if (!query || typeof query !== 'object' || Array.isArray(query)) {
6687
7732
  return false;
@@ -6691,9 +7736,15 @@ const isQueryCollection = (query) => {
6691
7736
  }
6692
7737
  return true;
6693
7738
  };
7739
+ /**
7740
+ * @deprecated Part of the legacy (v2) query system. Migrate to the current query API - see https://ethlete-sdk-docs.web.app/query/migrating-from-v2, and run `nx g @ethlete/query:migrate-to-query-v3` to rewrite the mechanical parts. Intent to remove in v7.
7741
+ */
6694
7742
  const createQueryCollection = (
6695
7743
  // eslint-disable-next-line @typescript-eslint/no-unused-vars
6696
7744
  queryMap) => new BehaviorSubject(null);
7745
+ /**
7746
+ * @deprecated Part of the legacy (v2) query system. Migrate to the current query API - see https://ethlete-sdk-docs.web.app/query/migrating-from-v2, and run `nx g @ethlete/query:migrate-to-query-v3` to rewrite the mechanical parts. Intent to remove in v7.
7747
+ */
6697
7748
  const createQueryCollectionSubject = (
6698
7749
  // eslint-disable-next-line @typescript-eslint/no-unused-vars
6699
7750
  queryMap, config) => {
@@ -6702,6 +7753,9 @@ queryMap, config) => {
6702
7753
  addQueryContainerHandling(subject.pipe(map((s) => s?.query ?? null)), () => subject.getValue()?.query ?? null, config);
6703
7754
  return subject;
6704
7755
  };
7756
+ /**
7757
+ * @deprecated Part of the legacy (v2) query system. Migrate to the current query API - see https://ethlete-sdk-docs.web.app/query/migrating-from-v2, and run `nx g @ethlete/query:migrate-to-query-v3` to rewrite the mechanical parts. Intent to remove in v7.
7758
+ */
6705
7759
  const createQueryCollectionSignal = (
6706
7760
  // eslint-disable-next-line @typescript-eslint/no-unused-vars
6707
7761
  queryMap, config) => {
@@ -6711,7 +7765,13 @@ queryMap, config) => {
6711
7765
  addQueryContainerHandling(toObservable(_signal).pipe(map((s) => s?.query ?? null)), () => _signal()?.query ?? null, config);
6712
7766
  return _signal;
6713
7767
  };
7768
+ /**
7769
+ * @deprecated Part of the legacy (v2) query system. Migrate to the current query API - see https://ethlete-sdk-docs.web.app/query/migrating-from-v2, and run `nx g @ethlete/query:migrate-to-query-v3` to rewrite the mechanical parts. Intent to remove in v7.
7770
+ */
6714
7771
  const extractQuery = (v) => (isQuery(v) ? v : isLegacyQuery(v) ? v : v?.query) ?? null;
7772
+ /**
7773
+ * @deprecated Part of the legacy (v2) query system. Migrate to the current query API - see https://ethlete-sdk-docs.web.app/query/migrating-from-v2, and run `nx g @ethlete/query:migrate-to-query-v3` to rewrite the mechanical parts. Intent to remove in v7.
7774
+ */
6715
7775
  const getDefaultHeaders = (headers, method) => {
6716
7776
  if (!headers) {
6717
7777
  return {};
@@ -6721,6 +7781,9 @@ const getDefaultHeaders = (headers, method) => {
6721
7781
  }
6722
7782
  return headers;
6723
7783
  };
7784
+ /**
7785
+ * @deprecated Part of the legacy (v2) query system. Migrate to the current query API - see https://ethlete-sdk-docs.web.app/query/migrating-from-v2, and run `nx g @ethlete/query:migrate-to-query-v3` to rewrite the mechanical parts. Intent to remove in v7.
7786
+ */
6724
7787
  const castQueryCreatorTypes = (config) => {
6725
7788
  if (config.args?.pathParams) {
6726
7789
  console.error(config);
@@ -6728,6 +7791,9 @@ const castQueryCreatorTypes = (config) => {
6728
7791
  }
6729
7792
  return config.creator;
6730
7793
  };
7794
+ /**
7795
+ * @deprecated Part of the legacy (v2) query system. Migrate to the current query API - see https://ethlete-sdk-docs.web.app/query/migrating-from-v2, and run `nx g @ethlete/query:migrate-to-query-v3` to rewrite the mechanical parts. Intent to remove in v7.
7796
+ */
6731
7797
  const computeQueryMethod = (config) => {
6732
7798
  let method;
6733
7799
  if (isGqlQueryConfig(config.config)) {
@@ -6739,6 +7805,9 @@ const computeQueryMethod = (config) => {
6739
7805
  }
6740
7806
  return method;
6741
7807
  };
7808
+ /**
7809
+ * @deprecated Part of the legacy (v2) query system. Migrate to the current query API - see https://ethlete-sdk-docs.web.app/query/migrating-from-v2, and run `nx g @ethlete/query:migrate-to-query-v3` to rewrite the mechanical parts. Intent to remove in v7.
7810
+ */
6742
7811
  const computeQueryBody = (config) => {
6743
7812
  let body;
6744
7813
  if (config.method === 'GET' ||
@@ -6759,6 +7828,9 @@ const computeQueryBody = (config) => {
6759
7828
  }
6760
7829
  return body;
6761
7830
  };
7831
+ /**
7832
+ * @deprecated Part of the legacy (v2) query system. Migrate to the current query API - see https://ethlete-sdk-docs.web.app/query/migrating-from-v2, and run `nx g @ethlete/query:migrate-to-query-v3` to rewrite the mechanical parts. Intent to remove in v7.
7833
+ */
6762
7834
  const computeQueryAuthHeader = (config) => {
6763
7835
  let authHeader = null;
6764
7836
  if (config.config.secure) {
@@ -6774,11 +7846,17 @@ const computeQueryAuthHeader = (config) => {
6774
7846
  }
6775
7847
  return authHeader;
6776
7848
  };
7849
+ /**
7850
+ * @deprecated Part of the legacy (v2) query system. Migrate to the current query API - see https://ethlete-sdk-docs.web.app/query/migrating-from-v2, and run `nx g @ethlete/query:migrate-to-query-v3` to rewrite the mechanical parts. Intent to remove in v7.
7851
+ */
6777
7852
  const computeQueryHeaders = (config) => {
6778
7853
  const authHeader = computeQueryAuthHeader(config);
6779
7854
  const mergedHeaders = mergeHeaders(getDefaultHeaders(config.client.config.request?.headers, config.config.method), authHeader, config.args?.headers) || undefined;
6780
7855
  return mergedHeaders;
6781
7856
  };
7857
+ /**
7858
+ * @deprecated Part of the legacy (v2) query system. Migrate to the current query API - see https://ethlete-sdk-docs.web.app/query/migrating-from-v2, and run `nx g @ethlete/query:migrate-to-query-v3` to rewrite the mechanical parts. Intent to remove in v7.
7859
+ */
6782
7860
  const computeQueryQueryParams = (config) => {
6783
7861
  if (isGqlQueryConfig(config.config) &&
6784
7862
  (config.config.transferVia === 'GET' ||
@@ -6793,6 +7871,9 @@ const computeQueryQueryParams = (config) => {
6793
7871
  }
6794
7872
  return config.args?.queryParams;
6795
7873
  };
7874
+ /**
7875
+ * @deprecated Part of the legacy (v2) query system. Migrate to the current query API - see https://ethlete-sdk-docs.web.app/query/migrating-from-v2, and run `nx g @ethlete/query:migrate-to-query-v3` to rewrite the mechanical parts. Intent to remove in v7.
7876
+ */
6796
7877
  const paginatedEntityValueUpdater = (findFn) => ({ response, rawResponse, entity }) => {
6797
7878
  const indexOfEntityRaw = rawResponse.items.findIndex((item) => findFn(item, entity));
6798
7879
  const indexOfEntity = response.items.findIndex((item) => findFn(item, entity));
@@ -6818,6 +7899,9 @@ const paginatedEntityValueUpdater = (findFn) => ({ response, rawResponse, entity
6818
7899
  };
6819
7900
 
6820
7901
  let _nextQueryId = 0;
7902
+ /**
7903
+ * @deprecated Part of the legacy (v2) query system. Migrate to the current query API - see https://ethlete-sdk-docs.web.app/query/migrating-from-v2, and run `nx g @ethlete/query:migrate-to-query-v3` to rewrite the mechanical parts. Intent to remove in v7.
7904
+ */
6821
7905
  class V2Query {
6822
7906
  constructor(_client,
6823
7907
  /**
@@ -7249,10 +8333,22 @@ class V2Query {
7249
8333
  }
7250
8334
  }
7251
8335
 
8336
+ /**
8337
+ * @deprecated Part of the legacy (v2) query system. Migrate to the current query API - see https://ethlete-sdk-docs.web.app/query/migrating-from-v2, and run `nx g @ethlete/query:migrate-to-query-v3` to rewrite the mechanical parts. Intent to remove in v7.
8338
+ */
7252
8339
  const isBasicAuthProvider = (authProvider) => authProvider instanceof BasicAuthProvider;
8340
+ /**
8341
+ * @deprecated Part of the legacy (v2) query system. Migrate to the current query API - see https://ethlete-sdk-docs.web.app/query/migrating-from-v2, and run `nx g @ethlete/query:migrate-to-query-v3` to rewrite the mechanical parts. Intent to remove in v7.
8342
+ */
7253
8343
  const isBearerAuthProvider = (authProvider) => authProvider instanceof V2BearerAuthProvider;
8344
+ /**
8345
+ * @deprecated Part of the legacy (v2) query system. Migrate to the current query API - see https://ethlete-sdk-docs.web.app/query/migrating-from-v2, and run `nx g @ethlete/query:migrate-to-query-v3` to rewrite the mechanical parts. Intent to remove in v7.
8346
+ */
7254
8347
  const isCustomHeaderAuthProvider = (authProvider) => authProvider instanceof CustomHeaderAuthProvider;
7255
8348
 
8349
+ /**
8350
+ * @deprecated Part of the legacy (v2) query system. Migrate to the current query API - see https://ethlete-sdk-docs.web.app/query/migrating-from-v2, and run `nx g @ethlete/query:migrate-to-query-v3` to rewrite the mechanical parts. Intent to remove in v7.
8351
+ */
7256
8352
  class V2BearerAuthProvider {
7257
8353
  constructor(_config) {
7258
8354
  this._config = _config;
@@ -7431,9 +8527,18 @@ const INFINITY_QUERY_RESPONSE_DELAY_DEF = /* @__PURE__ */ defineProvider(() => {
7431
8527
  enabled$,
7432
8528
  };
7433
8529
  }, { name: 'Infinity Query Response Delay' });
8530
+ /**
8531
+ * @deprecated Part of the legacy (v2) query system. Migrate to the current query API - see https://ethlete-sdk-docs.web.app/query/migrating-from-v2, and run `nx g @ethlete/query:migrate-to-query-v3` to rewrite the mechanical parts. Intent to remove in v7.
8532
+ */
7434
8533
  const provideInfinityQueryResponseDelay = /* @__PURE__ */ toProvideFn(INFINITY_QUERY_RESPONSE_DELAY_DEF);
8534
+ /**
8535
+ * @deprecated Part of the legacy (v2) query system. Migrate to the current query API - see https://ethlete-sdk-docs.web.app/query/migrating-from-v2, and run `nx g @ethlete/query:migrate-to-query-v3` to rewrite the mechanical parts. Intent to remove in v7.
8536
+ */
7435
8537
  const injectInfinityQueryResponseDelay = /* @__PURE__ */ toInjectFn(INFINITY_QUERY_RESPONSE_DELAY_DEF);
7436
8538
 
8539
+ /**
8540
+ * @deprecated Part of the legacy (v2) query system. Migrate to the current query API - see https://ethlete-sdk-docs.web.app/query/migrating-from-v2, and run `nx g @ethlete/query:migrate-to-query-v3` to rewrite the mechanical parts. Intent to remove in v7.
8541
+ */
7437
8542
  class InfinityQuery {
7438
8543
  constructor(_config, _destroy$) {
7439
8544
  this._config = _config;
@@ -7604,11 +8709,23 @@ class InfinityQuery {
7604
8709
  }
7605
8710
  }
7606
8711
 
8712
+ /**
8713
+ * @deprecated Part of the legacy (v2) query system. Migrate to the current query API - see https://ethlete-sdk-docs.web.app/query/migrating-from-v2, and run `nx g @ethlete/query:migrate-to-query-v3` to rewrite the mechanical parts. Intent to remove in v7.
8714
+ */
7607
8715
  const createInfinityQueryConfig = (config) => config;
8716
+ /**
8717
+ * @deprecated Part of the legacy (v2) query system. Migrate to the current query API - see https://ethlete-sdk-docs.web.app/query/migrating-from-v2, and run `nx g @ethlete/query:migrate-to-query-v3` to rewrite the mechanical parts. Intent to remove in v7.
8718
+ */
7608
8719
  const skipPaginationPageParamCalculator = ({ page, itemsPerPage }) => itemsPerPage * (page - 1);
7609
8720
 
7610
8721
  /* eslint-disable @typescript-eslint/no-explicit-any */
8722
+ /**
8723
+ * @deprecated Part of the legacy (v2) query system. Migrate to the current query API - see https://ethlete-sdk-docs.web.app/query/migrating-from-v2, and run `nx g @ethlete/query:migrate-to-query-v3` to rewrite the mechanical parts. Intent to remove in v7.
8724
+ */
7611
8725
  const INFINITY_QUERY_TOKEN = new InjectionToken('INFINITY_QUERY_TOKEN');
8726
+ /**
8727
+ * @deprecated Part of the legacy (v2) query system. Migrate to the current query API - see https://ethlete-sdk-docs.web.app/query/migrating-from-v2, and run `nx g @ethlete/query:migrate-to-query-v3` to rewrite the mechanical parts. Intent to remove in v7.
8728
+ */
7612
8729
  class InfinityQueryDirective {
7613
8730
  // TODO: Skipped for migration because:
7614
8731
  // Accessor inputs cannot be migrated as they are too complex.
@@ -7757,6 +8874,9 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.7", ngImpor
7757
8874
  args: ['etInfinityQuery']
7758
8875
  }] } });
7759
8876
 
8877
+ /**
8878
+ * @deprecated Part of the legacy (v2) query system. Migrate to the current query API - see https://ethlete-sdk-docs.web.app/query/migrating-from-v2, and run `nx g @ethlete/query:migrate-to-query-v3` to rewrite the mechanical parts. Intent to remove in v7.
8879
+ */
7760
8880
  class InfinityQueryTriggerDirective {
7761
8881
  constructor() {
7762
8882
  this.elementRef = inject(ElementRef);
@@ -7812,6 +8932,9 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.7", ngImpor
7812
8932
  }]
7813
8933
  }], propDecorators: { scrollContainerSelector: [{ type: i0.Input, args: [{ isSignal: true, alias: "scrollContainerSelector", required: false }] }], infinityQueryOverride: [{ type: i0.Input, args: [{ isSignal: true, alias: "infinityQueryOverride", required: false }] }] } });
7814
8934
 
8935
+ /**
8936
+ * @deprecated Part of the legacy (v2) query system. Migrate to the current query API - see https://ethlete-sdk-docs.web.app/query/migrating-from-v2, and run `nx g @ethlete/query:migrate-to-query-v3` to rewrite the mechanical parts. Intent to remove in v7.
8937
+ */
7815
8938
  class QueryDirective {
7816
8939
  static ngTemplateContextGuard(_dir, _ctx) {
7817
8940
  return true;
@@ -7901,6 +9024,9 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.7", ngImpor
7901
9024
  }]
7902
9025
  }], ctorParameters: () => [], propDecorators: { query: [{ type: i0.Input, args: [{ isSignal: true, alias: "etQuery", required: true }] }], cache: [{ type: i0.Input, args: [{ isSignal: true, alias: "etQueryCache", required: false }] }] } });
7903
9026
 
9027
+ /**
9028
+ * @deprecated Part of the legacy (v2) query system. Migrate to the current query API - see https://ethlete-sdk-docs.web.app/query/migrating-from-v2, and run `nx g @ethlete/query:migrate-to-query-v3` to rewrite the mechanical parts. Intent to remove in v7.
9029
+ */
7904
9030
  class QueryShortNamePipe {
7905
9031
  transform(value) {
7906
9032
  const route = value._routeWithParams;
@@ -7923,7 +9049,13 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.7", ngImpor
7923
9049
  args: [{ name: 'queryShortName' }]
7924
9050
  }] });
7925
9051
 
9052
+ /**
9053
+ * @deprecated Part of the legacy (v2) query system. Migrate to the current query API - see https://ethlete-sdk-docs.web.app/query/migrating-from-v2, and run `nx g @ethlete/query:migrate-to-query-v3` to rewrite the mechanical parts. Intent to remove in v7.
9054
+ */
7926
9055
  const QUERY_CLIENT_DEVTOOLS_TOKEN = new InjectionToken('QUERY_CLIENT_DEVTOOLS_TOKEN');
9056
+ /**
9057
+ * @deprecated Part of the legacy (v2) query system. Migrate to the current query API - see https://ethlete-sdk-docs.web.app/query/migrating-from-v2, and run `nx g @ethlete/query:migrate-to-query-v3` to rewrite the mechanical parts. Intent to remove in v7.
9058
+ */
7927
9059
  const provideQueryClientForDevtools = (queryClient) => {
7928
9060
  if (!isDevMode()) {
7929
9061
  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.');
@@ -7935,6 +9067,9 @@ const provideQueryClientForDevtools = (queryClient) => {
7935
9067
  };
7936
9068
  };
7937
9069
 
9070
+ /**
9071
+ * @deprecated Part of the legacy (v2) query system. Migrate to the current query API - see https://ethlete-sdk-docs.web.app/query/migrating-from-v2, and run `nx g @ethlete/query:migrate-to-query-v3` to rewrite the mechanical parts. Intent to remove in v7.
9072
+ */
7938
9073
  class QueryDevtoolsComponent {
7939
9074
  constructor() {
7940
9075
  this.cdr = inject(ChangeDetectorRef);
@@ -8244,6 +9379,9 @@ class EntityStoreError extends Error {
8244
9379
  function flatten(arr) {
8245
9380
  return arr.reduce((acc, val) => acc.concat(val), []);
8246
9381
  }
9382
+ /**
9383
+ * @deprecated Part of the legacy (v2) query system. Migrate to the current query API - see https://ethlete-sdk-docs.web.app/query/migrating-from-v2, and run `nx g @ethlete/query:migrate-to-query-v3` to rewrite the mechanical parts. Intent to remove in v7.
9384
+ */
8247
9385
  class EntityStore {
8248
9386
  constructor(_config) {
8249
9387
  this._config = _config;
@@ -8360,12 +9498,17 @@ class EntityStore {
8360
9498
  }
8361
9499
  }
8362
9500
 
9501
+ /**
9502
+ * @deprecated Part of the legacy (v2) query system. Migrate to the current query API - see https://ethlete-sdk-docs.web.app/query/migrating-from-v2, and run `nx g @ethlete/query:migrate-to-query-v3` to rewrite the mechanical parts. Intent to remove in v7.
9503
+ */
8363
9504
  function mapToPaginated(response) {
8364
9505
  return map((items) => ({ ...response, items }));
8365
9506
  }
8366
9507
  // FIXME: This breaks if SubStoreType is an array
8367
9508
  /**
8368
9509
  * Use an other store to represent a property of the current entity.
9510
+ *
9511
+ * @deprecated Part of the legacy (v2) query system. Migrate to the current query API - see https://ethlete-sdk-docs.web.app/query/migrating-from-v2, and run `nx g @ethlete/query:migrate-to-query-v3` to rewrite the mechanical parts. Intent to remove in v7.
8369
9512
  */
8370
9513
  const insertFrom = (store, { for: key, id }) => switchMap((value) => {
8371
9514
  if (!value)
@@ -8380,6 +9523,8 @@ const insertFrom = (store, { for: key, id }) => switchMap((value) => {
8380
9523
  });
8381
9524
  /**
8382
9525
  * Remove entities from a store based on a where function.
9526
+ *
9527
+ * @deprecated Part of the legacy (v2) query system. Migrate to the current query API - see https://ethlete-sdk-docs.web.app/query/migrating-from-v2, and run `nx g @ethlete/query:migrate-to-query-v3` to rewrite the mechanical parts. Intent to remove in v7.
8383
9528
  */
8384
9529
  const removeFrom = (store, { where, id }) => {
8385
9530
  store
@@ -8388,9 +9533,21 @@ const removeFrom = (store, { where, id }) => {
8388
9533
  .subscribe();
8389
9534
  };
8390
9535
 
9536
+ /**
9537
+ * @deprecated Part of the legacy (v2) query system. Migrate to the current query API - see https://ethlete-sdk-docs.web.app/query/migrating-from-v2, and run `nx g @ethlete/query:migrate-to-query-v3` to rewrite the mechanical parts. Intent to remove in v7.
9538
+ */
8391
9539
  const queryStateAlreadyHasKeyError = (data) => new QueryError('004', 'The query state already contains the provided key', data);
9540
+ /**
9541
+ * @deprecated Part of the legacy (v2) query system. Migrate to the current query API - see https://ethlete-sdk-docs.web.app/query/migrating-from-v2, and run `nx g @ethlete/query:migrate-to-query-v3` to rewrite the mechanical parts. Intent to remove in v7.
9542
+ */
8392
9543
  const queryStateDoesNotContainKeyError = (data) => new QueryError('005', 'The query state does not contain the provided key', data);
9544
+ /**
9545
+ * @deprecated Part of the legacy (v2) query system. Migrate to the current query API - see https://ethlete-sdk-docs.web.app/query/migrating-from-v2, and run `nx g @ethlete/query:migrate-to-query-v3` to rewrite the mechanical parts. Intent to remove in v7.
9546
+ */
8393
9547
  const queryStateCannotTransformError = (data) => new QueryError('006', 'The query state cannot be transformed because it is not loading', data);
9548
+ /**
9549
+ * @deprecated Part of the legacy (v2) query system. Migrate to the current query API - see https://ethlete-sdk-docs.web.app/query/migrating-from-v2, and run `nx g @ethlete/query:migrate-to-query-v3` to rewrite the mechanical parts. Intent to remove in v7.
9550
+ */
8394
9551
  const invalidBodyError = (data) => new QueryError('007', 'The body is not valid or upsupported', data);
8395
9552
 
8396
9553
  /**
@@ -8415,6 +9572,8 @@ const invalidBodyError = (data) => new QueryError('007', 'The body is not valid
8415
9572
  * });
8416
9573
  * });
8417
9574
  * ```
9575
+ *
9576
+ * @deprecated Part of the legacy (v2) query system. Migrate to the current query API - see https://ethlete-sdk-docs.web.app/query/migrating-from-v2, and run `nx g @ethlete/query:migrate-to-query-v3` to rewrite the mechanical parts. Intent to remove in v7.
8418
9577
  */
8419
9578
  const validateWithV2Query = (path, config) => {
8420
9579
  applyQueryAsyncValidator(path, {
@@ -9556,6 +10715,16 @@ const createBranch = (fields, initial, injector) => {
9556
10715
  resetAllFieldsToDefault: () => model.set(clone(defaults)),
9557
10716
  };
9558
10717
  };
10718
+ /**
10719
+ * Per-name sequence behind the devtools id of a form, mirroring the registry's own scheme so the
10720
+ * panel can restore the selected form after a reload.
10721
+ */
10722
+ const devtoolsIdCounters = /* @__PURE__ */ new Map();
10723
+ const nextQueryFormDevtoolsId = (name) => {
10724
+ const seq = devtoolsIdCounters.get(name) ?? 0;
10725
+ devtoolsIdCounters.set(name, seq + 1);
10726
+ return `query-form|${name}#${seq}`;
10727
+ };
9559
10728
  /**
9560
10729
  * A signals-first query form: binds on-screen controls to typed fields, keeps a
9561
10730
  * debounced committed value, syncs to the URL query params, and resets dependent
@@ -9582,6 +10751,8 @@ class QueryFormSignals {
9582
10751
  this.observing = signal(false, /* @ts-ignore */
9583
10752
  ...(ngDevMode ? [{ debugName: "observing" }] : /* istanbul ignore next */ []));
9584
10753
  this.pendingTimer = null;
10754
+ this.commitPending = signal(false, /* @ts-ignore */
10755
+ ...(ngDevMode ? [{ debugName: "commitPending" }] : /* istanbul ignore next */ []));
9585
10756
  this.skipNextResets = false;
9586
10757
  assertInInjectionContext(QueryFormSignals);
9587
10758
  this._fields = config.fields;
@@ -9594,7 +10765,16 @@ class QueryFormSignals {
9594
10765
  this.committed = signal(clone(initial), /* @ts-ignore */
9595
10766
  ...(ngDevMode ? [{ debugName: "committed" }] : /* istanbul ignore next */ []));
9596
10767
  this.fields = form(this.model);
9597
- this.value = this.committed.asReadonly();
10768
+ const devtoolsName = config.name ?? (typeof this.prefix === 'string' ? this.prefix : 'form');
10769
+ const devtoolsId = isQueryDevtoolsEnabled() ? nextQueryFormDevtoolsId(devtoolsName) : null;
10770
+ // Reading `value` is how a query's args pick the form up, so the devtools learn which query a form
10771
+ // drives by noting the read - see `QueryDevtoolsFormLinksHandle`.
10772
+ this.value = devtoolsId
10773
+ ? computed(() => {
10774
+ noteQueryFormRead(devtoolsId);
10775
+ return this.committed();
10776
+ })
10777
+ : this.committed.asReadonly();
9598
10778
  this.previousValue = this.previous.asReadonly();
9599
10779
  this.changes = computed(() => ({ previousValue: this.previous(), currentValue: this.committed() }), /* @ts-ignore */
9600
10780
  ...(ngDevMode ? [{ debugName: "changes" }] : /* istanbul ignore next */ []));
@@ -9614,7 +10794,16 @@ class QueryFormSignals {
9614
10794
  this.applyFromUrl(changes);
9615
10795
  });
9616
10796
  });
9617
- inject(DestroyRef).onDestroy(() => this.cleanup());
10797
+ const destroyRef = inject(DestroyRef);
10798
+ destroyRef.onDestroy(() => this.cleanup());
10799
+ if (devtoolsId) {
10800
+ destroyRef.onDestroy(registerQueryDevtoolsEntry({
10801
+ id: devtoolsId,
10802
+ kind: 'query-form',
10803
+ handle: this.devtoolsHandle(),
10804
+ meta: { name: devtoolsName },
10805
+ }));
10806
+ }
9618
10807
  }
9619
10808
  observe(options) {
9620
10809
  if (this.observing()) {
@@ -9670,6 +10859,46 @@ class QueryFormSignals {
9670
10859
  branch() {
9671
10860
  return createBranch(this._fields, clone(this.committed()), this.injector);
9672
10861
  }
10862
+ /**
10863
+ * The live view `<et-query-devtools>` renders. Only built while the devtools are installed - it walks
10864
+ * every field on every change, which an app without them should not pay for.
10865
+ */
10866
+ devtoolsHandle() {
10867
+ const fields = computed(() => {
10868
+ const committed = this.committed();
10869
+ const live = this.model();
10870
+ return Object.entries(this._fields).map(([key, def]) => {
10871
+ const value = committed[key];
10872
+ return {
10873
+ key,
10874
+ paramKey: this.paramKey(key),
10875
+ value,
10876
+ liveValue: live[key],
10877
+ defaultValue: this.defaults[key],
10878
+ isDefault: equal(value, this.defaults[key]),
10879
+ queryParam: this.queryParamFor(key, def, value),
10880
+ debounceMs: def.debounce ?? null,
10881
+ isResetBy: def.isResetBy ?? [],
10882
+ countsAsFilter: !IGNORED_FILTER_COUNT_FIELDS.includes(key) && !def.skipInFilterCount,
10883
+ };
10884
+ });
10885
+ }, /* @ts-ignore */
10886
+ ...(ngDevMode ? [{ debugName: "fields" }] : /* istanbul ignore next */ []));
10887
+ return {
10888
+ fields,
10889
+ // Not `this.value` - reading that from the panel would link the form to whichever query happens
10890
+ // to be building its args at the time.
10891
+ value: computed(() => this.committed()),
10892
+ previousValue: computed(() => this.previous() ?? null),
10893
+ defaultValue: this.defaults,
10894
+ activeFilterCount: this.activeFilterCount,
10895
+ isAtDefaults: computed(() => equal(this.committed(), this.defaults)),
10896
+ isCommitPending: this.commitPending.asReadonly(),
10897
+ isObserving: this.observing.asReadonly(),
10898
+ resetField: (key) => this.resetFieldToDefault(key),
10899
+ resetAll: () => this.resetAllFieldsToDefault(),
10900
+ };
10901
+ }
9673
10902
  onLiveChange(live) {
9674
10903
  if (!this.observing())
9675
10904
  return;
@@ -9682,6 +10911,7 @@ class QueryFormSignals {
9682
10911
  this.flush();
9683
10912
  }
9684
10913
  else {
10914
+ this.commitPending.set(true);
9685
10915
  this.pendingTimer = setTimeout(() => {
9686
10916
  this.pendingTimer = null;
9687
10917
  this.flush();
@@ -9782,23 +11012,21 @@ class QueryFormSignals {
9782
11012
  return raw;
9783
11013
  return autoCoerce(raw, resolveDefault(def));
9784
11014
  }
11015
+ /** What a field puts in the URL for a given value, or `undefined` when it writes nothing. */
11016
+ queryParamFor(key, def, value) {
11017
+ const isDefault = equal(value, this.defaults[key]);
11018
+ const writeToUrl = def.appendToUrl !== false;
11019
+ const writeDefault = def.appendDefaultValueToUrl === true;
11020
+ if (!writeToUrl || (isDefault && !writeDefault))
11021
+ return undefined;
11022
+ if (def.valueToQueryParam)
11023
+ return def.valueToQueryParam(value);
11024
+ return value === null ? ET_NULL_VALUE : value;
11025
+ }
9785
11026
  writeToUrl(value) {
9786
11027
  const queryParams = {};
9787
11028
  for (const [key, def] of Object.entries(this._fields)) {
9788
- const paramKey = this.paramKey(key);
9789
- const fieldValue = value[key];
9790
- const isDefault = equal(fieldValue, this.defaults[key]);
9791
- const writeToUrl = def.appendToUrl !== false;
9792
- const writeDefault = def.appendDefaultValueToUrl === true;
9793
- if (!writeToUrl || (isDefault && !writeDefault)) {
9794
- queryParams[paramKey] = undefined;
9795
- }
9796
- else if (def.valueToQueryParam) {
9797
- queryParams[paramKey] = def.valueToQueryParam(fieldValue);
9798
- }
9799
- else {
9800
- queryParams[paramKey] = fieldValue === null ? ET_NULL_VALUE : fieldValue;
9801
- }
11029
+ queryParams[this.paramKey(key)] = this.queryParamFor(key, def, value[key]);
9802
11030
  }
9803
11031
  queueMicrotask(() => {
9804
11032
  this.router.navigate([], {
@@ -9819,6 +11047,7 @@ class QueryFormSignals {
9819
11047
  clearTimeout(this.pendingTimer);
9820
11048
  this.pendingTimer = null;
9821
11049
  }
11050
+ this.commitPending.set(false);
9822
11051
  }
9823
11052
  cleanup() {
9824
11053
  if (!this.observing())
@@ -9929,7 +11158,7 @@ const messageMalformed = () => {
9929
11158
  const MAX_DEVTOOLS_MESSAGES = 100;
9930
11159
  const createWebSocketClient = (options) => {
9931
11160
  return defineRootProvider(() => {
9932
- const socket = io(options.url, {
11161
+ const socket = options.io(options.url, {
9933
11162
  withCredentials: true,
9934
11163
  autoConnect: false,
9935
11164
  transports: options.transports,
@@ -9948,13 +11177,28 @@ const createWebSocketClient = (options) => {
9948
11177
  if (devtoolsEnabled)
9949
11178
  devtoolsRooms.set([...rooms.keys()]);
9950
11179
  };
11180
+ const recordDevtoolsMessage = (message) => {
11181
+ if (!devtoolsEnabled)
11182
+ return;
11183
+ devtoolsMessages.update((log) => [{ ...message, id: devtoolsMessageId++, timestamp: Date.now() }, ...log].slice(0, MAX_DEVTOOLS_MESSAGES));
11184
+ };
11185
+ /** Every outgoing message goes through here, so the devtools log covers both directions. */
11186
+ const emit = (message) => {
11187
+ socket.emit(message.event, message.data);
11188
+ recordDevtoolsMessage({
11189
+ room: message.room ?? '',
11190
+ event: message.event,
11191
+ data: message.data,
11192
+ direction: 'out',
11193
+ });
11194
+ };
9951
11195
  const joinRoom = (room) => {
9952
11196
  const roomFn = typeof room === 'function' ? room : () => room;
9953
11197
  const pre = previousSignalValue(computed(() => roomFn()));
9954
11198
  const roomData = signal(null, /* @ts-ignore */
9955
11199
  ...(ngDevMode ? [{ debugName: "roomData" }] : /* istanbul ignore next */ []));
9956
11200
  const join = (name) => {
9957
- socket.emit('join-room', name);
11201
+ emit({ event: 'join-room', data: name, room: name });
9958
11202
  const existingRoom = rooms.get(name);
9959
11203
  if (existingRoom)
9960
11204
  return existingRoom;
@@ -9999,7 +11243,7 @@ const createWebSocketClient = (options) => {
9999
11243
  if (isDevMode())
10000
11244
  throw roomNotJoined(room);
10001
11245
  }
10002
- socket.emit('leave-room', room);
11246
+ emit({ event: 'leave-room', data: room, room });
10003
11247
  rooms.delete(room);
10004
11248
  syncDevtoolsRooms();
10005
11249
  };
@@ -10007,7 +11251,7 @@ const createWebSocketClient = (options) => {
10007
11251
  socket.on('connect', () => {
10008
11252
  isConnected.set(true);
10009
11253
  for (const room of rooms.keys()) {
10010
- socket.emit('join-room', room);
11254
+ emit({ event: 'join-room', data: room, room });
10011
11255
  }
10012
11256
  });
10013
11257
  socket.on('disconnect', () => isConnected.set(false));
@@ -10016,16 +11260,7 @@ const createWebSocketClient = (options) => {
10016
11260
  socket.onAny((data) => {
10017
11261
  try {
10018
11262
  const json = JSON.parse(data);
10019
- if (devtoolsEnabled) {
10020
- const message = {
10021
- id: devtoolsMessageId++,
10022
- timestamp: Date.now(),
10023
- room: json.room,
10024
- event: json.event,
10025
- data: json.data,
10026
- };
10027
- devtoolsMessages.update((log) => [message, ...log].slice(0, MAX_DEVTOOLS_MESSAGES));
10028
- }
11263
+ recordDevtoolsMessage({ room: json.room, event: json.event, data: json.data, direction: 'in' });
10029
11264
  const room = rooms.get(json.room);
10030
11265
  if (room)
10031
11266
  room.latestMessage.set(json);
@@ -10053,6 +11288,7 @@ const createWebSocketClient = (options) => {
10053
11288
  connected: isConnected.asReadonly(),
10054
11289
  rooms: devtoolsRooms.asReadonly(),
10055
11290
  messages: devtoolsMessages.asReadonly(),
11291
+ emit,
10056
11292
  };
10057
11293
  const unregister = registerQueryDevtoolsEntry({
10058
11294
  kind: 'ws-client',
@@ -10071,5 +11307,5 @@ const createWebSocketClient = (options) => {
10071
11307
  * Generated bundle index. Do not edit.
10072
11308
  */
10073
11309
 
10074
- export { BasicAuthProvider, BearerAuthFeatureType, BooleanArrayQueryField, CLEAR_QUERY_ARGS, CustomHeaderAuthProvider, DEFAULT_KEEP_UNUSED_FOR, DEFAULT_QUERY_PERSISTENCE_MAX_AGE, DEFAULT_QUERY_PERSISTENCE_MAX_ENTRIES, DEFAULT_QUERY_PERSISTENCE_VERSION, DEFAULT_QUERY_PERSISTENCE_WRITE_DELAY, 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, QUERY_PERSISTENCE_STORE_VERSION, QUERY_SYNC_PROTOCOL_VERSION, QueryClientFeatureType, 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, createIndexedDbQueryPersistenceAdapter, createInfinityQueryConfig, createLegacyQueryCreator, createNoopQueryPersistenceAdapter, createPagedQueryStack, createPatchQuery, createPersistentAuthFeature, createPostQuery, createPutQuery, createQuery, createQueryClient, createQueryCollection, createQueryCollectionSignal, createQueryCollectionSubject, createQueryContext, createQueryCreator, createQueryErrorResponse, createQueryFeature, createQueryForm, createQueryInvalidationFilter, createQueryKeyLockManager, createQueryObject, createQueryPersistenceEngine, createQueryRepository, createQuerySnapshotFn, createQueryStack, createQuerySyncEngine, createQuerySyncTransport, 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, extractHtmlErrorMessage, extractQuery, fakePaginationAdapter, filterFailure, filterNull, filterQueryStates, filterSuccess, forEachHeader, getDefaultHeaders, getQueryClientName, getQueryFeatureUsage, getResponseUrl, ggLikePaginationAdapter, gql, gqlDataPropertyMissingInResponse, hasHeader, htmlErrorPayload, htmlQueryErrorParser, ignoreAutoRefresh, injectInfinityQueryResponseDelay, injectQueryContext, insertFrom, invalidBaseRouteError, invalidBodyError, invalidRouteError, invalidStateInsideSecureExecuteFactory, isAutoRefresh, isBasicAuthProvider, isBearerAuthProvider, isClassValidatorError, isCreateGqlQueryOptions, isCustomHeaderAuthProvider, isEmptyString, isGqlQueryConfig, isHtmlErrorPayload, isLegacyQuery, isNaN$1 as isNaN, isPageOutOfRangeError, isQuery, isQueryCollection, isQueryDevtoolsEnabled, isQueryErrorResponse, isQueryStateCancelled, isQueryStateFailure, isQueryStateLoading, isQueryStatePrepared, isQueryStateSuccess, isRequestError, isSymfonyError, isSymfonyFormViolationListError, isSymfonyListError, isSymfonyPagerfantaOutOfRangeError, isUnderInvalidatedUrl, legacyPrepareWithoutInjectionContext, mapToPaginated, mapViolationsToFormErrors, maybeExecute, mergeHeaders, nestedEffect, numberArrayQueryField, pagedQueryStackNextPageCalledWithoutPreviousPage, pagedQueryStackPageBiggerThanTotalPages, pagedQueryStackPreviousPageCalledButAlreadyAtFirstPage, paginatedEntityValueUpdater, parseAllXhrResponseHeaders, parseHttpErrorCodeToMessageDe, parseHttpErrorCodeToMessageEn, parseHttpErrorCodeToTitleDe, parseHttpErrorCodeToTitleEn, pathParamsMissingInRouteFunctionError, provideInfinityQueryResponseDelay, provideQueryClientForDevtools, provideQueryDevtools, queryArrayComputed, queryClientFeatureUsedMultipleTimes, queryComputed, queryComputedTillTruthy, queryDevtoolsEntries, queryErrorMessage, queryErrorMessages, queryExecute, queryFeatureUsedMultipleTimes, queryField, querySequence, querySequenceAlreadyRunning, queryStackTotalQueriesAndExpectedQueriesMismatch, queryStackWithArgsUsed, queryStackWithResponseUpdateUsed, queryStateAlreadyHasKeyError, queryStateCannotTransformError, queryStateDoesNotContainKeyError, queryStateErrorSignal, queryStateLoadingSignal, queryStateResponseSignal, queryStateSignal, registerQueryDevtoolsEntry, registerQueryErrorParser, removeFrom, request, resetExecuteState, resetPageOnError, resolveInvalidationUrl, runDefaultQueryRetry, runQueryErrorParsers, searchQueryField, serializeBody, setDefaultQueryRetryFn, setQueryDevtoolsRegistrar, setupQueryDependencies, setupQueryExecuteState, setupQueryState, shouldAutoExecuteGqlQuery, shouldAutoExecuteQuery, shouldCacheQuery, shouldRetryRequest, silenceMissingWithArgsFeatureErrorUsedButWithArgsPresent, skipPaginationPageParamCalculator, sortQueryField, splitQueryConfig, stringArrayQueryField, stringifyQueryRoute, suppressNextQueryStackDevtools, switchQueryCollectionState, switchQueryState, symfonyQueryErrorParser, takeUntilResponse, toQuerySignal, toQuerySubject, tokensNotAvailableInsideAuthAndExec, transformArrayResponse, transformExecStateToQueryState, transformGql, transformMethod, transformPaginatedResponse, transformToBoolean, transformToBooleanArray, transformToDate, transformToDateArray, transformToNumber, transformToNumberArray, transformToSort, transformToSortQueryParam, transformToString, transformToStringArray, uncacheableRequestHasAllowCacheParam, uncacheableRequestHasCacheKeyParam, unwrapQuerySyncMessage, v2BuildQueryCacheKey, v2ExtractExpiresInSeconds, v2ShouldCacheQuery, v2ShouldRetryRequest, validateWithQuery, validateWithV2Query, withArgs, withArgsQueryFeatureMissingButRouteIsFunction, withAuthenticationQuery, withAutoRefresh, withAutoRefreshUsedInManualQuery, withAutoRefreshUsedOnUnsupportedHttpMethod, withBearerAuthMultiTabSync, withDefaultRetry, withErrorHandling, withEthleteApiErrors, withHtmlErrorParsing, withInactivityLogout, withLogging, withMultiTabSync, withPageResetOnError, withPersistentAuth, withPolling, withPollingUsedOnUnsupportedHttpMethod, withQueryPersistence, withRefreshQuery, withResponseUpdate, withSuccessHandling, withSymfonyErrors, withTokenExpirationWarning, withTokenRevocation, withTracking, wrapAsObservableSignal, wrapQuerySyncMessage };
11310
+ export { BasicAuthProvider, BearerAuthFeatureType, BooleanArrayQueryField, CLEAR_QUERY_ARGS, CustomHeaderAuthProvider, DEFAULT_KEEP_UNUSED_FOR, DEFAULT_QUERY_PERSISTENCE_MAX_AGE, DEFAULT_QUERY_PERSISTENCE_MAX_ENTRIES, DEFAULT_QUERY_PERSISTENCE_VERSION, DEFAULT_QUERY_PERSISTENCE_WRITE_DELAY, DateArrayQueryField, DateQueryField, EMPTY_QUERY_DEVTOOLS_FAULT, 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, QUERY_DEVTOOLS_FAULT_STATUSES, QUERY_PERSISTENCE_STORE_VERSION, QUERY_SYNC_PROTOCOL_VERSION, QueryClientFeatureType, 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, clearQueryDevtoolsFaults, collectQueryFormLinks, computeQueryAuthHeader, computeQueryBody, computeQueryHeaders, computeQueryMethod, computeQueryQueryParams, consumeSuppressQueryStackDevtools, contentfulGqlLikePaginationAdapter, createBaseQuery, createBaseQueryCreator, createBearerAuthProvider, createDeleteQuery, createExecuteFn, createGetQuery, createGqlExecuteFn, createGqlMutationViaGet, createGqlMutationViaPost, createGqlQuery, createGqlQueryCreator, createGqlQueryViaGet, createGqlQueryViaPost, createHttpRequest, createIndexedDbQueryPersistenceAdapter, createInfinityQueryConfig, createLegacyQueryCreator, createNoopQueryPersistenceAdapter, createPagedQueryStack, createPatchQuery, createPersistentAuthFeature, createPostQuery, createPutQuery, createQuery, createQueryClient, createQueryCollection, createQueryCollectionSignal, createQueryCollectionSubject, createQueryContext, createQueryCreator, createQueryDevtoolsFormLinks, createQueryDevtoolsFormLinksRecorder, createQueryDevtoolsStats, createQueryDevtoolsStatsRecorder, createQueryErrorResponse, createQueryFeature, createQueryForm, createQueryInvalidationFilter, createQueryKeyLockManager, createQueryObject, createQueryPersistenceEngine, createQueryRepository, createQuerySnapshotFn, createQueryStack, createQuerySyncEngine, createQuerySyncTransport, createSecureDeleteQuery, createSecureExecuteFactory, createSecureExecuteFn, createSecureGetQuery, createSecureGqlExecuteFn, createSecureGqlMutationViaGet, createSecureGqlMutationViaPost, createSecureGqlQuery, createSecureGqlQueryCreator, createSecureGqlQueryViaGet, createSecureGqlQueryViaPost, createSecurePatchQuery, createSecurePostQuery, createSecurePutQuery, createSecureQuery, createSecureQueryCreator, createTrackingFeature, createWebSocketClient, dateArrayQueryField, dateQueryField, decryptBearer, deepFreeze, def, describeQueryDevtoolsFeatures, detectContentTypeHeader, dynLikePaginationAdapter, effectComputed, emptyQueryArgs, ethletePaginationAdapter, executeUntilSettled, extractExpiresInSeconds, extractFormViolations, extractHtmlErrorMessage, extractQuery, fakePaginationAdapter, filterFailure, filterNull, filterQueryStates, filterSuccess, forEachHeader, formatQueryDevtoolsDuration, getBearerAuthProviderName, getDefaultHeaders, getQueryClientName, getQueryFeatureUsage, getResponseUrl, ggLikePaginationAdapter, gql, gqlDataPropertyMissingInResponse, hasHeader, htmlErrorPayload, htmlQueryErrorParser, ignoreAutoRefresh, injectInfinityQueryResponseDelay, injectQueryContext, insertFrom, invalidBaseRouteError, invalidBodyError, invalidRouteError, invalidStateInsideSecureExecuteFactory, isAutoRefresh, isBasicAuthProvider, isBearerAuthProvider, isClassValidatorError, isCreateGqlQueryOptions, isCustomHeaderAuthProvider, isEmptyString, isGqlQueryConfig, isHtmlErrorPayload, isLegacyQuery, isNaN$1 as isNaN, isPageOutOfRangeError, isQuery, isQueryCollection, isQueryDevtoolsEnabled, isQueryDevtoolsFaultArmed, isQueryDevtoolsFaultInjectionEnabled, isQueryErrorResponse, isQueryStateCancelled, isQueryStateFailure, isQueryStateLoading, isQueryStatePrepared, isQueryStateSuccess, isRequestError, isSymfonyError, isSymfonyFormViolationListError, isSymfonyListError, isSymfonyPagerfantaOutOfRangeError, isUnderInvalidatedUrl, legacyPrepareFallbackInjector, legacyPrepareWithoutInjectionContext, mapToPaginated, mapViolationsToFormErrors, maybeExecute, measureQueryDevtoolsPayload, mergeHeaders, nestedEffect, noteQueryFormRead, numberArrayQueryField, pagedQueryStackNextPageCalledWithoutPreviousPage, pagedQueryStackPageBiggerThanTotalPages, pagedQueryStackPreviousPageCalledButAlreadyAtFirstPage, paginatedEntityValueUpdater, parseAllXhrResponseHeaders, parseHttpErrorCodeToMessageDe, parseHttpErrorCodeToMessageEn, parseHttpErrorCodeToTitleDe, parseHttpErrorCodeToTitleEn, parseQueryRoute, pathParamsMissingInRouteFunctionError, provideInfinityQueryResponseDelay, provideLegacyPrepareFallback, provideQueryClientForDevtools, provideQueryDevtools, queryArrayComputed, queryClientFeatureUsedMultipleTimes, queryComputed, queryComputedTillTruthy, queryDevtoolsEntries, queryDevtoolsFaults, queryDevtoolsFnDetail, queryErrorMessage, queryErrorMessages, queryExecute, queryFeatureUsedMultipleTimes, queryField, querySequence, querySequenceAlreadyRunning, queryStackTotalQueriesAndExpectedQueriesMismatch, queryStackWithArgsUsed, queryStackWithResponseUpdateUsed, queryStateAlreadyHasKeyError, queryStateCannotTransformError, queryStateDoesNotContainKeyError, queryStateErrorSignal, queryStateLoadingSignal, queryStateResponseSignal, queryStateSignal, registerQueryDevtoolsEntry, registerQueryErrorParser, removeFrom, request, resetExecuteState, resetPageOnError, resolveInvalidationUrl, resolveQueryDevtoolsFault, resolveQueryDevtoolsFaultForAttempt, runDefaultQueryRetry, runQueryErrorParsers, searchQueryField, serializeBody, setDefaultQueryRetryFn, setQueryDevtoolsFault, setQueryDevtoolsFaultResolver, setQueryDevtoolsFormLinksFactory, setQueryDevtoolsRegistrar, setQueryDevtoolsStatsFactory, setupQueryDependencies, setupQueryExecuteState, setupQueryState, shouldAutoExecuteGqlQuery, shouldAutoExecuteQuery, shouldCacheQuery, shouldRetryRequest, silenceMissingWithArgsFeatureErrorUsedButWithArgsPresent, skipPaginationPageParamCalculator, sortQueryField, splitQueryConfig, stringArrayQueryField, stringifyQueryRouteParts, sumQueryDevtoolsStats, suppressNextQueryStackDevtools, switchQueryCollectionState, switchQueryState, symfonyQueryErrorParser, takeUntilResponse, toQuerySignal, toQuerySubject, tokensNotAvailableInsideAuthAndExec, transformArrayResponse, transformExecStateToQueryState, transformGql, transformMethod, transformPaginatedResponse, transformToBoolean, transformToBooleanArray, transformToDate, transformToDateArray, transformToNumber, transformToNumberArray, transformToSort, transformToSortQueryParam, transformToString, transformToStringArray, uncacheableRequestHasAllowCacheParam, uncacheableRequestHasCacheKeyParam, unwrapQuerySyncMessage, v2BuildQueryCacheKey, v2ExtractExpiresInSeconds, v2ShouldCacheQuery, v2ShouldRetryRequest, validateWithQuery, validateWithV2Query, withArgs, withArgsQueryFeatureMissingButRouteIsFunction, withAuthenticationQuery, withAutoRefresh, withAutoRefreshUsedInManualQuery, withAutoRefreshUsedOnUnsupportedHttpMethod, withBearerAuthMultiTabSync, withDefaultRetry, withErrorHandling, withEthleteApiErrors, withHtmlErrorParsing, withInactivityLogout, withLogging, withMultiTabSync, withPageResetOnError, withPersistentAuth, withPolling, withPollingUsedOnUnsupportedHttpMethod, withQueryPersistence, withRefreshQuery, withResponseUpdate, withSuccessHandling, withSymfonyErrors, withTokenExpirationWarning, withTokenRevocation, withTracking, wrapAsObservableSignal, wrapQuerySyncMessage };
10075
11311
  //# sourceMappingURL=ethlete-query.mjs.map