@ethlete/query 6.0.0-next.39 → 6.0.0-next.40

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,10 +1,10 @@
1
1
  import * as i0 from '@angular/core';
2
- import { inject, computed, DestroyRef, InjectionToken, Injector, EnvironmentInjector, ElementRef, ErrorHandler, createEnvironmentInjector, effect, untracked, signal, linkedSignal, runInInjectionContext, isDevMode, PLATFORM_ID, resource, DOCUMENT, VERSION, makeEnvironmentProviders, assertInInjectionContext, provideEnvironmentInitializer, ChangeDetectorRef, ViewContainerRef, TemplateRef, Input, Directive, input, Pipe, ViewEncapsulation, Component, NgZone } from '@angular/core';
3
- import { toObservable, takeUntilDestroyed, toSignal } from '@angular/core/rxjs-interop';
2
+ import { inject, Injector, effect, DestroyRef, InjectionToken, EnvironmentInjector, ElementRef, ErrorHandler, createEnvironmentInjector, untracked, signal, linkedSignal, computed, runInInjectionContext, isDevMode, PLATFORM_ID, resource, DOCUMENT, VERSION, makeEnvironmentProviders, assertInInjectionContext, provideEnvironmentInitializer, ChangeDetectorRef, ViewContainerRef, TemplateRef, Input, Directive, input, Pipe, ViewEncapsulation, Component, NgZone } from '@angular/core';
4
3
  import { UrlTree, Router, RedirectCommand, ActivatedRoute } from '@angular/router';
5
- import { filter, take, map, defer, from, Subject, Subscription, throwError, of, timer, switchMap, tap, retry, catchError, EMPTY, takeUntil, finalize, mergeMap, toArray, merge, takeWhile, firstValueFrom, race, concatMap, fromEvent, throttleTime, interval, combineLatest, Observable, startWith, pairwise, BehaviorSubject, ReplaySubject, shareReplay, skip, withLatestFrom, debounceTime } from 'rxjs';
6
- import { defineStaticProvider, RuntimeError, clamp, randomId, defineRootProvider, isObject, injectUnsavedChangesCoordinator, injectRoute, getCookie, getDomain, deleteCookie, setCookie, CORE_VERSION, createDestroy, computedTillTruthy, defineProvider, toProvideFn, toInjectFn, nextFrame, injectQueryParams, injectQueryParamChanges, clone, equal, ET_PROPERTY_REMOVED, previousSignalValue } from '@ethlete/core';
4
+ import { Observable, defer, from, filter, Subject, Subscription, throwError, of, timer, switchMap, tap, retry, catchError, EMPTY, take, takeUntil, finalize, mergeMap, toArray, map, merge, takeWhile, firstValueFrom, race, concatMap, fromEvent, throttleTime, interval, startWith, pairwise, BehaviorSubject, combineLatest, ReplaySubject, shareReplay, skip, withLatestFrom, debounceTime } from 'rxjs';
5
+ import { defineStaticProvider, RuntimeError, clamp, randomId, defineRootProvider, isObject, injectUnsavedChangesCoordinator, injectRoute, getCookie, deleteCookie, setCookie, CORE_VERSION, createDestroy, computedTillTruthy, defineProvider, toProvideFn, toInjectFn, nextFrame, injectQueryParams, injectQueryParamChanges, clone, equal, ET_PROPERTY_REMOVED, previousSignalValue } from '@ethlete/core';
7
6
  import { HttpClient, HttpEventType, HttpErrorResponse, HttpResponse, HttpHeaders, HttpStatusCode } from '@angular/common/http';
7
+ import { toObservable, takeUntilDestroyed, toSignal } from '@angular/core/rxjs-interop';
8
8
  import { getActiveConsumer, setActiveConsumer } from '@angular/core/primitives/signals';
9
9
  import { isPlatformBrowser, NgClass, AsyncPipe, JsonPipe } from '@angular/common';
10
10
  import { validateAsync, form } from '@angular/forms/signals';
@@ -53,15 +53,15 @@ const createAuthGuard = (providerRef, config) => {
53
53
  const behavior = config.navigationBehaviorOptions ?? { replaceUrl: true };
54
54
  const guardFor = (requiresSession) => () => {
55
55
  const router = inject(Router);
56
+ const injector = inject(Injector);
56
57
  const provider = providerRef.inject();
57
58
  // Captured before the wait below: once the session settles the router has moved on, and the URL
58
59
  // the visitor actually asked for is no longer reachable from it.
59
60
  const attemptedUrl = router.currentNavigation()?.extractedUrl.toString() ?? router.url;
60
- const settled = computed(() => {
61
+ const settled = () => {
61
62
  const status = provider.sessionStatus();
62
63
  return status !== 'unknown' && status !== 'restoring';
63
- }, /* @ts-ignore */
64
- ...(ngDevMode ? [{ debugName: "settled" }] : /* istanbul ignore next */ []));
64
+ };
65
65
  const decide = () => {
66
66
  const authenticated = provider.sessionStatus() === 'authenticated';
67
67
  if (authenticated === requiresSession)
@@ -75,7 +75,15 @@ const createAuthGuard = (providerRef, config) => {
75
75
  };
76
76
  if (settled())
77
77
  return decide();
78
- return toObservable(settled).pipe(filter(Boolean), take(1), map(decide));
78
+ return new Observable((subscriber) => {
79
+ const watcher = effect(() => {
80
+ if (!settled())
81
+ return;
82
+ subscriber.next(decide());
83
+ subscriber.complete();
84
+ }, { ...(ngDevMode ? { debugName: "watcher" } : /* istanbul ignore next */ {}), injector });
85
+ return () => watcher.destroy();
86
+ });
79
87
  };
80
88
  const requireSession = guardFor(true);
81
89
  const requireAnonymous = guardFor(false);
@@ -331,13 +339,12 @@ const consumeSuppressQueryStackDevtools = () => {
331
339
  const isCreateGqlQueryOptions = (options) => 'transport' in options.creatorInternals;
332
340
 
333
341
  const wrapAsObservableSignal = (source, defaultInjector) => {
342
+ const default$ = toObservable(source, { injector: defaultInjector });
334
343
  const asObservable = (options) => {
335
- const effectiveInjector = options?.injector ?? defaultInjector;
336
- const base$ = toObservable(source, { injector: effectiveInjector });
337
344
  if (options?.injector) {
338
- return base$.pipe(takeUntilDestroyed(defaultInjector.get(DestroyRef)));
345
+ return toObservable(source, { injector: options.injector }).pipe(takeUntilDestroyed(defaultInjector.get(DestroyRef)));
339
346
  }
340
- return base$;
347
+ return default$;
341
348
  };
342
349
  return Object.assign(source, { asObservable });
343
350
  };
@@ -693,7 +700,7 @@ const withPolling = (options) => {
693
700
  // It should have args because a withArgs feature is present.
694
701
  if (args === null && context.flags.hasWithArgsFeature)
695
702
  return;
696
- if (options.executeInitially) {
703
+ if (options.executeInitially && (!hold || hold.isHolder())) {
697
704
  context.execute({ args });
698
705
  }
699
706
  intervalId = window.setInterval(() => {
@@ -1329,6 +1336,7 @@ const resetExecuteState = (options) => {
1329
1336
  const { executeState, executeOptions: opts } = options;
1330
1337
  const { state } = opts;
1331
1338
  opts.deps.client.repository.unbind(executeState.previousKey(), opts.deps.destroyRef);
1339
+ executeState.previousKey.set(null);
1332
1340
  state.args.set(null);
1333
1341
  state.error.set(null);
1334
1342
  state.latestHttpEvent.set(null);
@@ -1513,21 +1521,24 @@ const buildQueryString = (params, config) => {
1513
1521
  }
1514
1522
  else if (Array.isArray(value)) {
1515
1523
  let currentFilteredIndex = 0;
1524
+ let didAddAnyValue = false;
1516
1525
  for (const arrayValue of value) {
1517
1526
  const nestedKey = writeArrayIndexes ? `${key}[${currentFilteredIndex}]` : `${key}[]`;
1518
1527
  const didAddValue = processValue(nestedKey, arrayValue);
1519
1528
  if (didAddValue) {
1520
1529
  currentFilteredIndex++;
1530
+ didAddAnyValue = true;
1521
1531
  }
1522
1532
  }
1523
- return null;
1533
+ return didAddAnyValue;
1524
1534
  }
1525
1535
  else if (typeof value === 'object' && value !== null) {
1536
+ let didAddAnyValue = false;
1526
1537
  for (const [objKey, val] of Object.entries(value)) {
1527
1538
  const nestedKey = objectNotation === 'dot' ? `${key}.${objKey}` : `${key}[${objKey}]`;
1528
- processValue(nestedKey, val);
1539
+ didAddAnyValue = processValue(nestedKey, val) || didAddAnyValue;
1529
1540
  }
1530
- return null;
1541
+ return didAddAnyValue;
1531
1542
  }
1532
1543
  else {
1533
1544
  if (ignoredValues.includes(value)) {
@@ -1593,7 +1604,7 @@ const decryptBearer = (token) => {
1593
1604
  return JSON.parse(jsonPayload);
1594
1605
  }
1595
1606
  catch (error) {
1596
- console.error(`Invalid bearer token: ${token}`, error);
1607
+ console.error('Invalid bearer token', error);
1597
1608
  return null;
1598
1609
  }
1599
1610
  };
@@ -1605,26 +1616,18 @@ const extractExpiresInSeconds = (headers) => {
1605
1616
  // In seconds
1606
1617
  let expiresIn = null;
1607
1618
  let maxAge = null;
1608
- if (cacheControl?.includes('no-cache')) {
1619
+ if (cacheControl && /(?:^|,)\s*no-(?:cache|store)(?:\s|,|$)/i.test(cacheControl)) {
1609
1620
  return null;
1610
1621
  }
1611
- if (cacheControl?.includes('max-age')) {
1612
- const m = cacheControl.split('max-age=')[1];
1613
- if (m) {
1614
- maxAge = parseInt(m);
1615
- }
1622
+ const maxAgeMatch = cacheControl?.match(/(?:^|,)\s*(?:s-maxage|max-age)=(\d+)/i);
1623
+ if (maxAgeMatch?.[1]) {
1624
+ maxAge = parseInt(maxAgeMatch[1]);
1616
1625
  }
1617
- else if (cacheControl?.includes('s-maxage')) {
1618
- const m = cacheControl.split('s-maxage=')[1];
1619
- if (m) {
1620
- maxAge = parseInt(m);
1621
- }
1622
- }
1623
- if (maxAge && age) {
1626
+ if (maxAge !== null && age) {
1624
1627
  const ageSeconds = parseInt(age);
1625
- expiresIn = maxAge - ageSeconds;
1628
+ expiresIn = Math.max(maxAge - ageSeconds, 0);
1626
1629
  }
1627
- else if (maxAge) {
1630
+ else if (maxAge !== null) {
1628
1631
  expiresIn = maxAge / 2; // We assume the response is half way to its expiration
1629
1632
  }
1630
1633
  else if (expires) {
@@ -1647,21 +1650,27 @@ const shouldCacheQuery = (method) => {
1647
1650
  return method === 'GET' || method === 'OPTIONS' || method === 'HEAD';
1648
1651
  };
1649
1652
  const buildQueryCacheKey = (route, args) => {
1653
+ const headers = typeof args?.headers === 'function' ? args.headers() : args?.headers;
1654
+ const serializedHeaders = headers
1655
+ ?.keys()
1656
+ .filter((name) => name.toLowerCase() !== 'authorization')
1657
+ .sort()
1658
+ .map((name) => [name.toLowerCase(), headers.getAll(name)]);
1650
1659
  // We need to hash the body in case it's a gql query and the query get's transported in the body
1651
1660
  const body = JSON.stringify(args?.body || {})
1652
1661
  // replace all curly braces with empty string
1653
1662
  .replace(/{|}/g, '')
1654
1663
  // replace new lines and whitespaces with empty string
1655
1664
  .replace(/\s/g, '');
1656
- const seed = `${route}_${body}`;
1665
+ const headerInput = serializedHeaders?.length ? `_${JSON.stringify(serializedHeaders)}` : '';
1666
+ const seed = `${route}_${body}${headerInput}`;
1657
1667
  let hash = 0;
1668
+ let secondaryHash = 5381;
1658
1669
  for (const char of seed) {
1659
1670
  hash = (Math.imul(31, hash) + char.charCodeAt(0)) << 0;
1671
+ secondaryHash = Math.imul(33, secondaryHash) ^ char.charCodeAt(0);
1660
1672
  }
1661
- // Force positive number hash.
1662
- // 2147483647 = equivalent of Integer.MAX_VALUE.
1663
- hash += 2147483647 + 1;
1664
- return hash.toString();
1673
+ return `${(hash >>> 0).toString().padStart(10, '0')}${(secondaryHash >>> 0).toString().padStart(10, '0')}`;
1665
1674
  };
1666
1675
 
1667
1676
  const parsers = [];
@@ -1725,7 +1734,7 @@ const queryErrorMessages = (error) => {
1725
1734
  * {@link queryErrorMessages}, for a toast or a form-field hint.
1726
1735
  */
1727
1736
  const queryErrorMessage = (error) => queryErrorMessages(error)[0] ?? null;
1728
- const createQueryErrorResponse = (error) => {
1737
+ const createQueryErrorResponse = (error, retry) => {
1729
1738
  let err = error instanceof HttpErrorResponse ? error : null;
1730
1739
  if (!err) {
1731
1740
  err = new HttpErrorResponse({
@@ -1734,7 +1743,8 @@ const createQueryErrorResponse = (error) => {
1734
1743
  statusText: 'Unknown Error',
1735
1744
  });
1736
1745
  }
1737
- const retryState = runDefaultQueryRetry({ retryCount: 0, error: err });
1746
+ const retryOptions = { retryCount: retry?.retryCount ?? 0, error: err };
1747
+ const retryState = typeof retry?.retryFn === 'function' ? retry.retryFn(retryOptions) : runDefaultQueryRetry(retryOptions);
1738
1748
  const detail = err.error;
1739
1749
  const errorList = [];
1740
1750
  const parsed = runQueryErrorParsers(detail, err);
@@ -2010,7 +2020,10 @@ const createHttpRequest = (options) => {
2010
2020
  event$.next(event);
2011
2021
  };
2012
2022
  const updateErrorState = (errorResponse) => {
2013
- const errorRes = createQueryErrorResponse(errorResponse);
2023
+ const errorRes = createQueryErrorResponse(errorResponse, {
2024
+ retryCount: attempts(),
2025
+ retryFn: options.retryFn,
2026
+ });
2014
2027
  error.set(errorRes);
2015
2028
  loading.set(null);
2016
2029
  lastDurationMs.set(Date.now() - lastExecuteTime());
@@ -2047,7 +2060,7 @@ const createHttpRequest = (options) => {
2047
2060
  if (isMeasurable && elapsedTimeSinceLastExecute > SPEED_BUFFER_TIME_IN_MS) {
2048
2061
  const bytesPerSecond = (loadedAmount / elapsedTimeSinceLastEvent) * 1000;
2049
2062
  progress.speed = bytesPerSecond;
2050
- progress.remainingTime = Math.round((event.total - event.loaded) / bytesPerSecond) * 1000;
2063
+ progress.remainingTime = Math.round(((event.total - event.loaded) / bytesPerSecond) * 1000);
2051
2064
  }
2052
2065
  if (isMeasurable) {
2053
2066
  lastLoadEventTime.set(currentTime);
@@ -2357,7 +2370,8 @@ const createPagedQueryStack = (options) => {
2357
2370
  return { queries: oldQueries, lastQuery };
2358
2371
  }
2359
2372
  else if (dir === 'previous') {
2360
- return { queries: [newQuery, ...oldQueries], lastQuery: newQuery };
2373
+ const queries = [newQuery, ...oldQueries];
2374
+ return { queries, lastQuery: queries[queries.length - 1] ?? null };
2361
2375
  }
2362
2376
  else {
2363
2377
  return { queries: [...oldQueries, newQuery], lastQuery: newQuery };
@@ -2414,10 +2428,14 @@ const createPagedQueryStack = (options) => {
2414
2428
  }
2415
2429
  return null;
2416
2430
  }
2417
- currentPageArgs.set(options.args(page, allResponses));
2418
- loadedMinPage.set(page);
2431
+ const args = options.args(page, allResponses);
2419
2432
  pageDirection.set('previous');
2420
- return stack.subtle.runWithArgs(currentPageArgs());
2433
+ const query = stack.subtle.runWithArgs(args);
2434
+ if (query) {
2435
+ currentPageArgs.set(args);
2436
+ loadedMinPage.set(page);
2437
+ }
2438
+ return query;
2421
2439
  };
2422
2440
  const fetchNextPage = () => {
2423
2441
  const page = loadedMaxPage() + 1;
@@ -2438,10 +2456,14 @@ const createPagedQueryStack = (options) => {
2438
2456
  }
2439
2457
  return null;
2440
2458
  }
2441
- currentPageArgs.set(options.args(page, allResponses));
2442
- loadedMaxPage.set(page);
2459
+ const args = options.args(page, allResponses);
2443
2460
  pageDirection.set('next');
2444
- return stack.subtle.runWithArgs(currentPageArgs());
2461
+ const query = stack.subtle.runWithArgs(args);
2462
+ if (query) {
2463
+ currentPageArgs.set(args);
2464
+ loadedMaxPage.set(page);
2465
+ }
2466
+ return query;
2445
2467
  };
2446
2468
  const canFetchNewPage = (pageToLoad) => {
2447
2469
  const currentMaxPagination = maxPagination();
@@ -3157,7 +3179,11 @@ const createQueryBatch = (options) => {
3157
3179
  completedBeforeRun.set(completed());
3158
3180
  runStartedAt.set(Date.now());
3159
3181
  lastSettleAt.set(null);
3160
- return from(entries).pipe(mergeMap((entry) => runEntry(entry), concurrency), toArray(), map(() => settleRun(entries)));
3182
+ return from(entries).pipe(mergeMap((entry) => runEntry(entry), concurrency), toArray(), map(() => settleRun(entries)), finalize(() => {
3183
+ if (status() === 'running')
3184
+ status.set('idle');
3185
+ running.set(false);
3186
+ }));
3161
3187
  });
3162
3188
  const run = (items) => runEntries(() => {
3163
3189
  settled = new Array(items.length);
@@ -4065,6 +4091,10 @@ const createQueryRepository = (config) => {
4065
4091
  const destroyEntry = (key, cacheEntry, cause) => {
4066
4092
  const { method, url } = cacheEntry.request;
4067
4093
  clearTimeout(cacheEntry.evictTimer);
4094
+ for (const unregister of cacheEntry.consumers.values()) {
4095
+ unregister();
4096
+ }
4097
+ cacheEntry.consumers.clear();
4068
4098
  cacheEntry.request.destroy();
4069
4099
  cacheEntry.eventSubscription?.unsubscribe();
4070
4100
  cache.delete(key);
@@ -4088,6 +4118,7 @@ const createQueryRepository = (config) => {
4088
4118
  }
4089
4119
  };
4090
4120
  const retain = (key, cacheEntry) => {
4121
+ clearTimeout(cacheEntry.evictTimer);
4091
4122
  cacheEntry.unusedSince = Date.now();
4092
4123
  cacheEntry.evictTimer = setTimeout(() => evict(key, 'expired'), cacheEntry.keepUnusedFor);
4093
4124
  enforceUnusedEntryLimit();
@@ -4098,6 +4129,10 @@ const createQueryRepository = (config) => {
4098
4129
  const cacheEntry = cache.get(key);
4099
4130
  if (!cacheEntry)
4100
4131
  return false;
4132
+ const unregister = cacheEntry.consumers.get(consumerDestroyRef);
4133
+ if (!unregister)
4134
+ return false;
4135
+ unregister();
4101
4136
  cacheEntry.consumers.delete(consumerDestroyRef);
4102
4137
  if (cacheEntry.consumers.size === 0) {
4103
4138
  // Only data is worth keeping around: an entry that never produced a response has nothing to
@@ -4172,32 +4207,48 @@ const createQueryRepository = (config) => {
4172
4207
  };
4173
4208
  const bind = (options) => {
4174
4209
  const { key, consumerDestroyRef, request, isSecure, isCached, isRefreshable, isMultiTabSyncEnabled, isPersistEnabled, keepUnusedFor, } = options;
4175
- const destroyListener = consumerDestroyRef.onDestroy(() => unbind(key, consumerDestroyRef));
4176
4210
  const cacheEntry = cache.get(key);
4177
4211
  if (cacheEntry && isCached) {
4178
- cacheEntry.consumers.set(consumerDestroyRef, destroyListener);
4212
+ cacheEntry.isSecure ||= isSecure;
4213
+ cacheEntry.isRefreshable ||= isRefreshable;
4214
+ cacheEntry.isMultiTabSyncEnabled &&= isMultiTabSyncEnabled;
4215
+ cacheEntry.isPersistEnabled &&= isPersistEnabled;
4216
+ cacheEntry.keepUnusedFor = Math.min(cacheEntry.keepUnusedFor, keepUnusedFor);
4217
+ if (!cacheEntry.consumers.has(consumerDestroyRef)) {
4218
+ cacheEntry.consumers.set(consumerDestroyRef, consumerDestroyRef.onDestroy(() => unbind(key, consumerDestroyRef)));
4219
+ }
4179
4220
  }
4180
4221
  else {
4181
4222
  const consumers = new Map([]);
4182
- consumers.set(consumerDestroyRef, destroyListener);
4223
+ consumers.set(consumerDestroyRef, consumerDestroyRef.onDestroy(() => unbind(key, consumerDestroyRef)));
4183
4224
  // Drive repository events off the request's discrete, terminal event stream rather than
4184
4225
  // recombining the `error` + `response` signals via combineLatest (which could observe stale
4185
4226
  // pairings). Each settle emits exactly one terminal event.
4186
4227
  const eventSubscription = request.events$.subscribe((event) => {
4228
+ const currentEntry = cache.get(key);
4229
+ const currentIsSecure = currentEntry?.isSecure ?? isSecure;
4230
+ const currentIsMultiTabSyncEnabled = currentEntry?.isMultiTabSyncEnabled ?? isMultiTabSyncEnabled;
4231
+ const currentIsPersistEnabled = currentEntry?.isPersistEnabled ?? isPersistEnabled;
4187
4232
  if (event.type === 'error') {
4188
4233
  if (event.error.raw instanceof HttpErrorResponse) {
4189
- eventsSubject.next({ type: 'request-error', error: event.error.raw, key, isSecure, request });
4234
+ eventsSubject.next({
4235
+ type: 'request-error',
4236
+ error: event.error.raw,
4237
+ key,
4238
+ isSecure: currentIsSecure,
4239
+ request,
4240
+ });
4190
4241
  }
4191
4242
  }
4192
4243
  else if (event.type === HttpEventType.Response) {
4193
4244
  eventsSubject.next({
4194
4245
  type: 'request-success',
4195
4246
  key,
4196
- isSecure,
4247
+ isSecure: currentIsSecure,
4197
4248
  request,
4198
4249
  isCached,
4199
- isMultiTabSyncEnabled,
4200
- isPersistEnabled,
4250
+ isMultiTabSyncEnabled: currentIsMultiTabSyncEnabled,
4251
+ isPersistEnabled: currentIsPersistEnabled,
4201
4252
  });
4202
4253
  }
4203
4254
  });
@@ -4440,7 +4491,7 @@ const createSecureExecuteFactory = (options) => {
4440
4491
  .pipe(filter(() => {
4441
4492
  const currentError = options.state.error();
4442
4493
  return currentError?.code === 401 && options.authProvider.accessToken() !== lastRequestedWithToken;
4443
- }), take(1), tap(() => exec(execArgsWithDefaults)))
4494
+ }), take(1), tap(() => exec(execArgsWithDefaults)), takeUntilDestroyed(options.deps.destroyRef))
4444
4495
  .subscribe();
4445
4496
  // `setTokens()` seeds an auth session from outside the query registry (an SSO/OIDC callback, a
4446
4497
  // native shell) - no query ever runs for `latestExecutedQuery` to report, so a successful token
@@ -4606,30 +4657,35 @@ const buildSequence = (steps, state, devtoolsHandle) => {
4606
4657
  const stepArgs = [];
4607
4658
  const responses = [];
4608
4659
  let previousResponse = undefined;
4609
- for (const [i, step] of steps.entries()) {
4610
- state.currentStep.set(i + 1);
4611
- const { args } = step.produceArgs(previousResponse, responses);
4612
- stepArgs.push(args);
4613
- state.stepArgs.set([...stepArgs]);
4614
- const snapshot = await executeUntilSettled(step.query, { args });
4615
- snapshots.push(snapshot);
4616
- state.snapshots.set([...snapshots]);
4617
- const error = snapshot.error();
4618
- if (error) {
4619
- state.status.set('error');
4620
- state.error.set(error);
4621
- state.failedAt.set(i);
4622
- state.running.set(false);
4623
- return { ok: false, failedAt: i, error, snapshots };
4660
+ try {
4661
+ for (const [i, step] of steps.entries()) {
4662
+ state.currentStep.set(i + 1);
4663
+ const { args } = step.produceArgs(previousResponse, responses);
4664
+ stepArgs.push(args);
4665
+ state.stepArgs.set([...stepArgs]);
4666
+ const snapshot = await executeUntilSettled(step.query, { args });
4667
+ snapshots.push(snapshot);
4668
+ state.snapshots.set([...snapshots]);
4669
+ const error = snapshot.error();
4670
+ if (error) {
4671
+ state.status.set('error');
4672
+ state.error.set(error);
4673
+ state.failedAt.set(i);
4674
+ return { ok: false, failedAt: i, error, snapshots };
4675
+ }
4676
+ const response = snapshot.response();
4677
+ responses.push(response);
4678
+ state.responses.set([...responses]);
4679
+ previousResponse = response;
4624
4680
  }
4625
- const response = snapshot.response();
4626
- responses.push(response);
4627
- state.responses.set([...responses]);
4628
- previousResponse = response;
4681
+ state.status.set('success');
4682
+ return { ok: true, responses: responses, snapshots };
4683
+ }
4684
+ finally {
4685
+ if (state.status() === 'running')
4686
+ state.status.set('idle');
4687
+ state.running.set(false);
4629
4688
  }
4630
- state.status.set('success');
4631
- state.running.set(false);
4632
- return { ok: true, responses: responses, snapshots };
4633
4689
  };
4634
4690
  const completed = computed(() => state.snapshots().length, /* @ts-ignore */
4635
4691
  ...(ngDevMode ? [{ debugName: "completed" }] : /* istanbul ignore next */ []));
@@ -5002,39 +5058,10 @@ const setupBearerQueryRegistry = (builders, context) => {
5002
5058
  ...(ngDevMode ? [{ debugName: "querySnapshot" }] : /* istanbul ignore next */ []));
5003
5059
  querySnapshots.set(builder.key, querySnapshot);
5004
5060
  const extractTokens = builder.config.extractTokens ?? defaultExtractTokens;
5005
- // A 2xx whose body carries no usable tokens is a failed execution, not a successful one - the
5006
- // state effect below reads this so it cannot report `success` for an attempt that left the tab
5007
- // unauthenticated.
5008
- const extractionError = signal(null, /* @ts-ignore */
5009
- ...(ngDevMode ? [{ debugName: "extractionError" }] : /* istanbul ignore next */ []));
5010
5061
  const currentExecution = signal(null, /* @ts-ignore */
5011
5062
  ...(ngDevMode ? [{ debugName: "currentExecution" }] : /* istanbul ignore next */ []));
5012
5063
  currentExecutions.push(currentExecution);
5013
5064
  const isSuperseded = (execution) => execution.id !== null && execution.id !== latestExecutionId;
5014
- // Runs before the state effect below, which reads the extraction error this one writes. Angular
5015
- // runs effects in creation order, so the two must stay in this order.
5016
- effect(() => {
5017
- const execution = currentExecution();
5018
- if (!execution)
5019
- return;
5020
- const { snapshot } = execution;
5021
- const response = snapshot.response();
5022
- const loading = snapshot.loading();
5023
- const error = snapshot.error();
5024
- if (response && !loading && !error && !isSuperseded(execution)) {
5025
- try {
5026
- const tokens = extractTokens(response);
5027
- extractionError.set(null);
5028
- applyTokens(tokens.accessToken, tokens.refreshToken);
5029
- }
5030
- catch (extractError) {
5031
- extractionError.set(createQueryErrorResponse(extractError));
5032
- if (isDevMode()) {
5033
- console.error(`Failed to extract tokens from ${builder.key} response:`, extractError);
5034
- }
5035
- }
5036
- }
5037
- });
5038
5065
  effect(() => {
5039
5066
  const execution = currentExecution();
5040
5067
  if (!execution)
@@ -5049,13 +5076,17 @@ const setupBearerQueryRegistry = (builders, context) => {
5049
5076
  executionState.set({ type, state: 'error', error });
5050
5077
  }
5051
5078
  else if (response) {
5052
- const failedExtraction = extractionError();
5053
- if (failedExtraction) {
5054
- executionState.set({ type, state: 'error', error: failedExtraction });
5055
- }
5056
- else {
5079
+ try {
5080
+ const tokens = extractTokens(response);
5081
+ applyTokens(tokens.accessToken, tokens.refreshToken);
5057
5082
  executionState.set({ type, state: 'success', response });
5058
5083
  }
5084
+ catch (extractError) {
5085
+ executionState.set({ type, state: 'error', error: createQueryErrorResponse(extractError) });
5086
+ if (isDevMode()) {
5087
+ console.error(`Failed to extract tokens from ${builder.key} response:`, extractError);
5088
+ }
5089
+ }
5059
5090
  }
5060
5091
  // Keyed on `isAlive` rather than on the branches above: a cancelled restore produces neither a
5061
5092
  // response nor an error, and would otherwise leave `sessionStatus` at `restoring` forever.
@@ -5069,7 +5100,6 @@ const setupBearerQueryRegistry = (builders, context) => {
5069
5100
  let query = null;
5070
5101
  const execute = (args, options) => {
5071
5102
  query ??= builder.config.queryCreator({ onlyManualExecution: true, injector });
5072
- extractionError.set(null);
5073
5103
  query.execute({ args, options });
5074
5104
  const snapshot = query.createSnapshot();
5075
5105
  const stateType = deriveExecutionStateType(builder, options?.triggeredBy);
@@ -5098,7 +5128,10 @@ const setupBearerQueryRegistry = (builders, context) => {
5098
5128
  return !!current && current.id !== null && current.snapshot.isAlive();
5099
5129
  }), /* @ts-ignore */
5100
5130
  ...(ngDevMode ? [{ debugName: "hasTokenIssuingExecutionInFlight" }] : /* istanbul ignore next */ []));
5101
- return { queries, querySnapshots, hasTokenIssuingExecutionInFlight };
5131
+ const invalidateTokenIssuingExecutions = () => {
5132
+ latestExecutionId++;
5133
+ };
5134
+ return { queries, querySnapshots, hasTokenIssuingExecutionInFlight, invalidateTokenIssuingExecutions };
5102
5135
  };
5103
5136
  const setupFeatures = (featureBuilders, context) => {
5104
5137
  const features = {};
@@ -5219,7 +5252,7 @@ const createBearerAuthProviderImpl = (config) => {
5219
5252
  applyTokens(access, refresh);
5220
5253
  executionState.set({ type: 'tokenSeed', state: 'success' });
5221
5254
  };
5222
- const { queries, hasTokenIssuingExecutionInFlight } = setupBearerQueryRegistry(config.queries, {
5255
+ const { queries, hasTokenIssuingExecutionInFlight, invalidateTokenIssuingExecutions } = setupBearerQueryRegistry(config.queries, {
5223
5256
  injector,
5224
5257
  latestExecutedQuery,
5225
5258
  latestNonInternalQuery,
@@ -5229,6 +5262,7 @@ const createBearerAuthProviderImpl = (config) => {
5229
5262
  isAuthenticated,
5230
5263
  });
5231
5264
  const logout = (cause = 'user') => {
5265
+ invalidateTokenIssuingExecutions();
5232
5266
  accessToken.set(null);
5233
5267
  refreshToken.set(null);
5234
5268
  queryClient.repository.unbindAllSecure();
@@ -5264,12 +5298,14 @@ const createBearerAuthProviderImpl = (config) => {
5264
5298
  logout,
5265
5299
  afterTokenRefresh$,
5266
5300
  queries: queries,
5301
+ executionState: executionState.asReadonly(),
5267
5302
  hasTokenIssuingExecutionInFlight,
5268
5303
  };
5269
5304
  for (const builder of config.queries) {
5270
5305
  builder.setup?.(querySetupContext);
5271
5306
  }
5272
5307
  const featureSetupContext = {
5308
+ name: config.name,
5273
5309
  refreshToken,
5274
5310
  accessToken,
5275
5311
  bearerData,
@@ -5283,6 +5319,7 @@ const createBearerAuthProviderImpl = (config) => {
5283
5319
  activityCoordination: isLeader.activityCoordination,
5284
5320
  afterTokenRefresh$,
5285
5321
  queries: queries,
5322
+ latestExecutedQuery: latestExecutedQuery.asReadonly(),
5286
5323
  executionState,
5287
5324
  sessionStatus: sessionStatus.asReadonly(),
5288
5325
  sessionEndCause: sessionEndCause.asReadonly(),
@@ -5396,7 +5433,11 @@ const withAuthenticationQuery = (key, config, setup) => ({
5396
5433
  ...config,
5397
5434
  // A spent credential entry must not sit out a retention window: the request body holds the
5398
5435
  // username and password, the response the tokens it was exchanged for.
5399
- queryCreator: config.queryCreator.clone({ keepUnusedFor: 0, subtle: { useQueryRepositoryCache: true } }),
5436
+ queryCreator: config.queryCreator.clone({
5437
+ ...(config.retryFn ? { retryFn: config.retryFn } : {}),
5438
+ keepUnusedFor: 0,
5439
+ subtle: { useQueryRepositoryCache: true },
5440
+ }),
5400
5441
  },
5401
5442
  setup,
5402
5443
  });
@@ -5593,7 +5634,9 @@ const withRefreshQuery = (key, config) => {
5593
5634
  };
5594
5635
  const calculateRefreshBuffer = (tokenLifetimeMs) => {
5595
5636
  if (typeof config.refreshStrategy === 'number') {
5596
- return config.refreshStrategy;
5637
+ return config.refreshStrategy >= 0 && config.refreshStrategy <= 1
5638
+ ? tokenLifetimeMs * (1 - config.refreshStrategy)
5639
+ : config.refreshStrategy;
5597
5640
  }
5598
5641
  const percentage = config.refreshStrategy?.percentage ?? 0.75;
5599
5642
  const minBufferMs = config.refreshStrategy?.minBufferMs ?? 60000; // 1 minute
@@ -5659,7 +5702,7 @@ const withRefreshQuery = (key, config) => {
5659
5702
  * Without the re-arm a single declined attempt strands the session on a token nothing renews until
5660
5703
  * a request fails with a 401 - by which time the refresh token may be gone too.
5661
5704
  */
5662
- const scheduledRefresh = (dueInMs, attemptsLeft) => (dueInMs <= 0 ? of(0) : timer(dueInMs)).pipe(concatMap(() => {
5705
+ const scheduledRefresh = (dueInMs, attemptsLeft) => (dueInMs <= 0 ? of(0) : timer(Math.min(dueInMs, maxTimerDelayMs))).pipe(concatMap(() => {
5663
5706
  const attempt = executeRefresh('scheduled');
5664
5707
  const retryInMs = retryScheduledIn(attempt);
5665
5708
  if (retryInMs === null)
@@ -5711,13 +5754,10 @@ const withRefreshQuery = (key, config) => {
5711
5754
  logout();
5712
5755
  });
5713
5756
  effect(() => {
5714
- const snapshot = refreshQuery().snapshot();
5715
- if (!snapshot || snapshot.loading())
5757
+ const state = context.executionState();
5758
+ if (state?.type !== 'tokenRefresh' || state.state !== 'error')
5716
5759
  return;
5717
- const error = snapshot.error();
5718
- if (!error)
5719
- return;
5720
- onRefreshFailure({ error, logout: () => context.logout('expired') });
5760
+ onRefreshFailure({ error: state.error, logout: () => context.logout('expired') });
5721
5761
  });
5722
5762
  // Auto-retry on 401: Listen to repository events and trigger refresh on 401 errors
5723
5763
  const autoRetryOn401 = config.autoRetryOn401 ?? true;
@@ -5750,7 +5790,7 @@ const withRefreshQuery = (key, config) => {
5750
5790
  config: {
5751
5791
  ...config,
5752
5792
  queryCreator: config.queryCreator.clone({
5753
- retryFn: refreshRetryFn,
5793
+ retryFn: config.retryFn ?? refreshRetryFn,
5754
5794
  keepUnusedFor: 0,
5755
5795
  subtle: { useQueryRepositoryCache: true },
5756
5796
  }),
@@ -6185,31 +6225,38 @@ const setupLeaderElection = (options) => {
6185
6225
 
6186
6226
  const ENCRYPTION_KEY_STORAGE = '__eth_ek';
6187
6227
  const generateEncryptionKey = () => {
6188
- const navigatorInfo = typeof navigator !== 'undefined'
6189
- ? `${navigator.userAgent}${navigator.language}${screen.width}${screen.height}${screen.colorDepth}`
6190
- : 'server';
6228
+ const navigatorInfo = typeof navigator !== 'undefined' ? `${navigator.userAgent}${navigator.language}` : 'server';
6229
+ const screenInfo = typeof screen !== 'undefined' ? `${screen.width}${screen.height}${screen.colorDepth}` : 'no-screen';
6191
6230
  const random = Math.random().toString(36).substring(2, 15);
6192
- return btoa(`${navigatorInfo}${random}`);
6231
+ return btoa(`${navigatorInfo}${screenInfo}${random}`);
6193
6232
  };
6194
6233
  let cachedKey = null;
6195
6234
  const getEncryptionKey = () => {
6196
6235
  if (cachedKey)
6197
6236
  return cachedKey;
6198
6237
  if (typeof localStorage !== 'undefined') {
6199
- const stored = localStorage.getItem(ENCRYPTION_KEY_STORAGE);
6200
- if (stored) {
6201
- cachedKey = stored;
6202
- return stored;
6238
+ try {
6239
+ const stored = localStorage.getItem(ENCRYPTION_KEY_STORAGE);
6240
+ if (stored) {
6241
+ cachedKey = stored;
6242
+ return stored;
6243
+ }
6244
+ }
6245
+ catch {
6246
+ cachedKey = null;
6203
6247
  }
6204
- const newKey = generateEncryptionKey();
6205
- localStorage.setItem(ENCRYPTION_KEY_STORAGE, newKey);
6206
- cachedKey = newKey;
6207
- return newKey;
6208
6248
  }
6209
- if (!cachedKey) {
6210
- cachedKey = generateEncryptionKey();
6249
+ const newKey = generateEncryptionKey();
6250
+ cachedKey = newKey;
6251
+ if (typeof localStorage !== 'undefined') {
6252
+ try {
6253
+ localStorage.setItem(ENCRYPTION_KEY_STORAGE, newKey);
6254
+ }
6255
+ catch {
6256
+ return newKey;
6257
+ }
6211
6258
  }
6212
- return cachedKey;
6259
+ return newKey;
6213
6260
  };
6214
6261
  const xorCipher = (text, key) => {
6215
6262
  let result = '';
@@ -6227,7 +6274,7 @@ const encryptToken = (token) => {
6227
6274
  return btoa(encrypted);
6228
6275
  }
6229
6276
  catch {
6230
- return token;
6277
+ return '';
6231
6278
  }
6232
6279
  };
6233
6280
  const decryptToken = (encryptedToken) => {
@@ -6331,6 +6378,8 @@ const setupMultiTabSync = (config, context) => {
6331
6378
  return;
6332
6379
  }
6333
6380
  if (message.type === 'logout' && syncLogout) {
6381
+ if (!context.accessToken() && !context.refreshToken())
6382
+ return;
6334
6383
  lastSyncedState = LOGGED_OUT;
6335
6384
  hadTokens = false;
6336
6385
  context.logout(incomingCause(message.cause));
@@ -6343,7 +6392,10 @@ const setupMultiTabSync = (config, context) => {
6343
6392
  if (!access || !refresh)
6344
6393
  return;
6345
6394
  const isNewSession = !context.accessToken();
6346
- lastSyncedState = tokenState(access, refresh);
6395
+ const incomingState = tokenState(access, refresh);
6396
+ if (incomingState === lastSyncedState)
6397
+ return;
6398
+ lastSyncedState = incomingState;
6347
6399
  hadTokens = true;
6348
6400
  if (isNewSession) {
6349
6401
  context.setTokens(access, refresh);
@@ -6481,25 +6533,30 @@ const SINGLE_TAB = /* @__PURE__ */ (() => {
6481
6533
  * // provider.features.multiTabSync.isLeader()
6482
6534
  */
6483
6535
  const withBearerAuthMultiTabSync = (config = {}) => {
6484
- let instance = SINGLE_TAB;
6485
- let channelName = config.channelName ?? '';
6486
- const setup = (_context) => ({
6487
- type: BearerAuthFeatureType.MULTI_TAB_SYNC,
6488
- instance,
6489
- devtools: () => [
6490
- { label: 'channel', value: channelName },
6491
- { label: 'tokens', value: config.syncTokens === false ? 'tab local' : 'synced' },
6492
- { label: 'logout', value: config.syncLogout === false ? 'tab local' : 'synced' },
6493
- {
6494
- label: 'leader election',
6495
- value: config.leaderElection === false ? 'every tab refreshes' : 'one tab refreshes',
6496
- },
6497
- ],
6498
- });
6536
+ const pendingSetups = [];
6537
+ const setup = (context) => {
6538
+ const state = pendingSetups.shift() ?? {
6539
+ instance: SINGLE_TAB,
6540
+ channelName: config.channelName ?? defaultSyncChannelName(context.name),
6541
+ };
6542
+ return {
6543
+ type: BearerAuthFeatureType.MULTI_TAB_SYNC,
6544
+ instance: state.instance,
6545
+ devtools: () => [
6546
+ { label: 'channel', value: state.channelName },
6547
+ { label: 'tokens', value: config.syncTokens === false ? 'tab local' : 'synced' },
6548
+ { label: 'logout', value: config.syncLogout === false ? 'tab local' : 'synced' },
6549
+ {
6550
+ label: 'leader election',
6551
+ value: config.leaderElection === false ? 'every tab refreshes' : 'one tab refreshes',
6552
+ },
6553
+ ],
6554
+ };
6555
+ };
6499
6556
  // The elected leader gates the auth queries' automatic refresh, which is wired up before the
6500
6557
  // regular feature setup runs - so this half has to happen earlier than `setup`.
6501
6558
  const earlySetup = (context) => {
6502
- channelName = config.channelName ?? defaultSyncChannelName(context.name);
6559
+ const channelName = config.channelName ?? defaultSyncChannelName(context.name);
6503
6560
  // Filled in below once the election exists. Until then this tab answers as its own leader, which
6504
6561
  // is what it is: the sync is set up first because the auth queries read `isLeader` while being
6505
6562
  // wired, and a `state-request` cannot arrive before this function returns anyway.
@@ -6515,15 +6572,17 @@ const withBearerAuthMultiTabSync = (config = {}) => {
6515
6572
  logout: context.logout,
6516
6573
  });
6517
6574
  if (config.leaderElection === false) {
6575
+ pendingSetups.push({ instance: SINGLE_TAB, channelName });
6518
6576
  return { sessionAdoption: sync.sessionAdoption, activityCoordination: sync.activityCoordination };
6519
6577
  }
6520
6578
  const election = setupLeaderElection({ name: context.name });
6521
6579
  isLeaderRef = () => election.isLeader();
6522
- instance = {
6580
+ const instance = {
6523
6581
  isLeader: election.isLeader,
6524
6582
  instanceCount: election.instanceCount,
6525
6583
  leadership: election.isSupported ? 'election' : 'unsupported',
6526
6584
  };
6585
+ pendingSetups.push({ instance, channelName });
6527
6586
  return {
6528
6587
  isLeader: () => election.isLeader(),
6529
6588
  leaderElection: instance,
@@ -6578,7 +6637,7 @@ const createPersistentAuthFeature = (config, context) => {
6578
6637
  };
6579
6638
  const rememberMeSignal = signal(initializeRememberMe(), /* @ts-ignore */
6580
6639
  ...(ngDevMode ? [{ debugName: "rememberMeSignal" }] : /* istanbul ignore next */ []));
6581
- const cookieDomain = () => config.cookie?.domain ?? getDomain() ?? 'localhost';
6640
+ const cookieDomain = () => config.cookie?.domain ?? null;
6582
6641
  const cookiePath = () => config.cookie?.path ?? '/';
6583
6642
  const removeCookie = () => deleteCookie(cookieName, cookiePath(), cookieDomain());
6584
6643
  effect(() => {
@@ -6593,14 +6652,15 @@ const createPersistentAuthFeature = (config, context) => {
6593
6652
  const expiresInDays = rememberMe ? (config.cookie?.expiresInDays ?? 30) : null;
6594
6653
  setCookie(cookieName, encryptToken(token), expiresInDays, cookieDomain(), cookiePath(), config.cookie?.sameSite ?? 'lax');
6595
6654
  });
6655
+ effect(() => {
6656
+ const endCause = context.sessionEndCause();
6657
+ if (endCause)
6658
+ removeCookie();
6659
+ });
6596
6660
  effect(() => {
6597
6661
  const state = executionState();
6598
6662
  if (!state)
6599
6663
  return;
6600
- if (state.type === 'logout') {
6601
- removeCookie();
6602
- return;
6603
- }
6604
6664
  // A token the server itself rejected is worth nothing on the next visit; anything else (offline, a
6605
6665
  // 500, an aborted startup) leaves it in place so a reload can try again.
6606
6666
  const wasRejected = state.state === 'error' &&
@@ -6674,14 +6734,15 @@ const withTokenExpirationWarning = (config = {}) => {
6674
6734
  return new Date(exp * 1000);
6675
6735
  }, /* @ts-ignore */
6676
6736
  ...(ngDevMode ? [{ debugName: "expiresAt" }] : /* istanbul ignore next */ []));
6677
- const expiresIn$ = combineLatest([interval(checkInterval), toObservable(context.bearerData)]).pipe(map(() => {
6678
- const expiry = expiresAt();
6679
- if (!expiry)
6680
- return null;
6681
- const now = Date.now();
6682
- const msUntilExpiry = expiry.getTime() - now;
6683
- return msUntilExpiry > 0 ? msUntilExpiry : null;
6684
- }));
6737
+ const expiresIn$ = toObservable(context.bearerData).pipe(switchMap((bearerData) => bearerData
6738
+ ? timer(0, checkInterval).pipe(map(() => {
6739
+ const expiry = expiresAt();
6740
+ if (!expiry)
6741
+ return null;
6742
+ const msUntilExpiry = expiry.getTime() - Date.now();
6743
+ return msUntilExpiry > 0 ? msUntilExpiry : null;
6744
+ }))
6745
+ : of(null)));
6685
6746
  const isExpiringSoon$ = expiresIn$.pipe(map((msUntilExpiry) => {
6686
6747
  if (msUntilExpiry === null)
6687
6748
  return false;
@@ -6811,8 +6872,9 @@ const createTrackingFeature = (context, config) => {
6811
6872
  });
6812
6873
  }
6813
6874
  };
6875
+ const pendingForwardedMessages = [];
6814
6876
  const forwardingChannel = context.leaderElection && typeof BroadcastChannel !== 'undefined'
6815
- ? new BroadcastChannel('ethlete-auth-tracking')
6877
+ ? new BroadcastChannel(`ethlete-auth-tracking:${context.name}`)
6816
6878
  : null;
6817
6879
  if (forwardingChannel) {
6818
6880
  forwardingChannel.onmessage = (messageEvent) => {
@@ -6822,21 +6884,44 @@ const createTrackingFeature = (context, config) => {
6822
6884
  };
6823
6885
  context.destroyRef.onDestroy(() => forwardingChannel.close());
6824
6886
  }
6887
+ const forward = (message) => {
6888
+ if (!forwardingChannel)
6889
+ return false;
6890
+ try {
6891
+ forwardingChannel.postMessage(message);
6892
+ return true;
6893
+ }
6894
+ catch (error) {
6895
+ if (isDevMode()) {
6896
+ console.warn(`[@ethlete/query] Could not forward the "${message.event}" tracking event to the leader tab.`, error);
6897
+ }
6898
+ return false;
6899
+ }
6900
+ };
6901
+ if (context.leaderElection) {
6902
+ effect(() => {
6903
+ const isLeader = context.leaderElection?.isLeader() ?? false;
6904
+ const instanceCount = context.leaderElection?.instanceCount() ?? 1;
6905
+ if (!pendingForwardedMessages.length || (!isLeader && instanceCount <= 1))
6906
+ return;
6907
+ untracked(() => {
6908
+ const pending = pendingForwardedMessages.splice(0);
6909
+ for (const message of pending) {
6910
+ if (isLeader || !forward(message))
6911
+ fireHandlers(message.event, message.data);
6912
+ }
6913
+ });
6914
+ }, { injector: context.injector });
6915
+ }
6825
6916
  const emit = (event, data) => {
6826
6917
  if (forwardingChannel && !context.isLeader()) {
6827
- try {
6828
- forwardingChannel.postMessage({ event, data });
6918
+ const message = { event, data };
6919
+ if ((context.leaderElection?.instanceCount() ?? 1) <= 1) {
6920
+ pendingForwardedMessages.push(message);
6829
6921
  return;
6830
6922
  }
6831
- catch (error) {
6832
- // Some payloads cannot leave the tab they were made in: a query snapshot is a bundle of
6833
- // signals, and the structured clone algorithm throws on functions. Firing the handlers here
6834
- // is the lesser evil - the event reaches a handler in the wrong tab rather than taking the
6835
- // effect it was emitted from down with it.
6836
- if (isDevMode()) {
6837
- console.warn(`[@ethlete/query] Could not forward the "${event}" tracking event to the leader tab.`, error);
6838
- }
6839
- }
6923
+ if (forward(message))
6924
+ return;
6840
6925
  }
6841
6926
  fireHandlers(event, data);
6842
6927
  };
@@ -6857,17 +6942,18 @@ const createTrackingFeature = (context, config) => {
6857
6942
  const error = snapshot.error();
6858
6943
  const response = snapshot.response();
6859
6944
  const prevState = trackedQueries.get(key);
6945
+ if (prevState?.snapshot !== snapshot) {
6946
+ trackedQueries.set(key, { snapshot, lastResult: null });
6947
+ emit(`${key}Execute`, { queryKey: key, args: snapshot.args() });
6948
+ }
6860
6949
  if (response && !loading && !error && prevState?.lastResult !== 'success') {
6861
- trackedQueries.set(key, { loading: false, lastResult: 'success' });
6950
+ trackedQueries.set(key, { snapshot, lastResult: 'success' });
6862
6951
  emit(`${key}Success`, { snapshot });
6863
6952
  }
6864
6953
  if (error && !loading && prevState?.lastResult !== 'error') {
6865
- trackedQueries.set(key, { loading: false, lastResult: 'error' });
6954
+ trackedQueries.set(key, { snapshot, lastResult: 'error' });
6866
6955
  emit(`${key}Failure`, { error });
6867
6956
  }
6868
- if (loading) {
6869
- trackedQueries.set(key, { loading: true, lastResult: null });
6870
- }
6871
6957
  });
6872
6958
  });
6873
6959
  let hadTokens = false;
@@ -6880,10 +6966,14 @@ const createTrackingFeature = (context, config) => {
6880
6966
  }
6881
6967
  hadTokens = hasTokens;
6882
6968
  });
6883
- context.afterTokenRefresh$.subscribe(() => {
6969
+ let lastTrackedRefreshState = null;
6970
+ effect(() => {
6971
+ const state = context.executionState();
6972
+ if (state === lastTrackedRefreshState || state?.type !== 'tokenRefresh' || state.state !== 'success')
6973
+ return;
6974
+ lastTrackedRefreshState = state;
6884
6975
  emit('tokenRefreshSuccess', {
6885
- queryKey: 'refresh',
6886
- snapshot: undefined,
6976
+ automatic: !!untracked(context.latestExecutedQuery)?.snapshot.triggeredBy(),
6887
6977
  });
6888
6978
  });
6889
6979
  if (config?.on) {
@@ -8178,12 +8268,13 @@ const armQueryDevtoolsOverrideTransfer = (recorder, ops) => {
8178
8268
 
8179
8269
  // Generated from package.json by tools/scripts/generate-version.js - do not edit.
8180
8270
  /** The version of `@ethlete/query` this build was cut from. */
8181
- const QUERY_VERSION = '6.0.0-next.39';
8271
+ const QUERY_VERSION = '6.0.0-next.40';
8182
8272
 
8183
8273
  /** How deep a schema is expanded before a nested branch is given up on. */
8184
8274
  const MAX_DEPTH = 12;
8185
8275
  /** How many elements are generated for an array, whatever its `minItems` says. */
8186
8276
  const MAX_ITEMS = 3;
8277
+ const MAX_STRING_LENGTH = 10_000;
8187
8278
  /**
8188
8279
  * How many array elements are generated on their own rather than copied from the first. Nested arrays
8189
8280
  * multiply, so without a ceiling a body of arrays-in-arrays costs `MAX_ITEMS ^ MAX_DEPTH` walks.
@@ -8376,12 +8467,12 @@ const finiteNumber = (value) => (typeof value === 'number' && Number.isFinite(va
8376
8467
  const pickStressStringPreset = () => STRESS_STRING_PRESETS[Math.floor(Math.random() * STRESS_STRING_PRESETS.length)];
8377
8468
  /** Keeps a generated string inside `minLength`/`maxLength`, since a seed is exported as a real body. */
8378
8469
  const fitLength = (value, schema) => {
8379
- const min = finiteNumber(schema['minLength']);
8380
- const max = finiteNumber(schema['maxLength']);
8470
+ const min = Math.min(finiteNumber(schema['minLength']) ?? 0, MAX_STRING_LENGTH);
8471
+ const max = Math.min(finiteNumber(schema['maxLength']) ?? MAX_STRING_LENGTH, MAX_STRING_LENGTH);
8381
8472
  let out = value;
8382
- while (min !== null && out.length < min)
8473
+ while (out.length < min)
8383
8474
  out += value || 'x';
8384
- return max === null ? out : out.slice(0, max);
8475
+ return out.slice(0, max);
8385
8476
  };
8386
8477
  /**
8387
8478
  * The value of a string field. A declared format wins in every style - a `date-time` full of lorem is
@@ -9212,6 +9303,7 @@ const snapshotQueryDevtoolsHandle = (handle) => {
9212
9303
  attempts: read(() => frozen(live.subtle.attempts()), frozen(1)),
9213
9304
  lastDurationMs: read(() => frozen(live.subtle.lastDurationMs()), frozen(null)),
9214
9305
  resolveHeaders: read(() => frozen(live.subtle.resolveHeaders()), frozen(undefined)),
9306
+ lastSentHeaders: read(() => frozen(live.subtle.lastSentHeaders()), frozen(undefined)),
9215
9307
  lastPersistedResponseAt: read(() => frozen(live.subtle.lastPersistedResponseAt()), frozen(null)),
9216
9308
  lastExternalResponseAt: read(() => frozen(live.subtle.lastExternalResponseAt()), frozen(null)),
9217
9309
  },
@@ -9438,6 +9530,7 @@ const clearQueryDevtoolsTombstones = (ids) => {
9438
9530
  const doomed = ids ? new Set(ids) : null;
9439
9531
  entries.update((list) => list.filter((e) => !e.destroyedAt || (doomed ? !doomed.has(e.id) : false)));
9440
9532
  };
9533
+ let queryDevtoolsInitialized = false;
9441
9534
  /**
9442
9535
  * Enables the `@ethlete/query` devtools. Add this to your application providers (e.g. in
9443
9536
  * `bootstrapApplication`) to make query clients, queries, stacks, sequences and auth providers
@@ -9454,6 +9547,9 @@ const clearQueryDevtoolsTombstones = (ids) => {
9454
9547
  * ```
9455
9548
  */
9456
9549
  const provideQueryDevtools = (options) => {
9550
+ if (queryDevtoolsInitialized)
9551
+ return makeEnvironmentProviders([]);
9552
+ queryDevtoolsInitialized = true;
9457
9553
  // First: the stored settings say where the override store lives and whether the panel has raised the
9458
9554
  // retention this app asked for, and both are read by the calls below.
9459
9555
  initQueryDevtoolsSettings();
@@ -9478,7 +9574,7 @@ const provideQueryDevtools = (options) => {
9478
9574
  return makeEnvironmentProviders([]);
9479
9575
  };
9480
9576
 
9481
- const getOpName = /(query|mutation) ?([\w\d-_]+)? ?\(.*?\)? \{/;
9577
+ const getOpName = /\b(?:query|mutation)\s+([\w-]+)(?:\s*\([^)]*\))?\s*\{/;
9482
9578
  const transformGql = (str) => {
9483
9579
  const normalizedStr = Array.isArray(str) ? str.join('') : str;
9484
9580
  const name = getOpName.exec(normalizedStr);
@@ -9488,7 +9584,7 @@ const transformGql = (str) => {
9488
9584
  data['variables'] = JSON.stringify(variables);
9489
9585
  }
9490
9586
  if (name && name.length) {
9491
- const operationName = name[2];
9587
+ const operationName = name[1];
9492
9588
  if (operationName) {
9493
9589
  data['operationName'] = operationName;
9494
9590
  }
@@ -9519,7 +9615,7 @@ const createGqlExecuteFn = (executeOptions) => {
9519
9615
  if (args?.queryParams && executeOptions.creatorInternals.transport === 'GET') {
9520
9616
  gqlParams = { ...gqlParams, ...args.queryParams };
9521
9617
  }
9522
- const computedArgs = args ?? {};
9618
+ const computedArgs = { ...(args ?? {}) };
9523
9619
  if (executeOptions.creatorInternals.transport === 'GET') {
9524
9620
  computedArgs.queryParams = gqlParams;
9525
9621
  }
@@ -9607,7 +9703,7 @@ const createSecureGqlExecuteFn = (executeOptions) => {
9607
9703
  ...executeOptions,
9608
9704
  creatorInternals: {
9609
9705
  client: executeOptions.creatorInternals.client,
9610
- method: executeOptions.creatorInternals.method === 'QUERY' ? 'GET' : 'POST',
9706
+ method: executeOptions.creatorInternals.transport,
9611
9707
  route: (executeOptions.creator?.route ?? ''),
9612
9708
  },
9613
9709
  };
@@ -9639,7 +9735,7 @@ const createSecureGqlQueryCreator = (options, internals) => createBaseQueryCreat
9639
9735
  if (rawResponse && typeof rawResponse === 'object' && 'data' in rawResponse) {
9640
9736
  return rawResponse.data;
9641
9737
  }
9642
- return rawResponse;
9738
+ throw gqlDataPropertyMissingInResponse();
9643
9739
  }),
9644
9740
  },
9645
9741
  internals,
@@ -10285,7 +10381,7 @@ function queryStateSignal(source, options) {
10285
10381
  const { cacheResponse } = options ?? {};
10286
10382
  const roSignal = toSignal(toObservable(source).pipe(switchMap((q) => extractQuery(q)?.state$ ?? of(null)), switchMap((state) => {
10287
10383
  if (cacheResponse) {
10288
- return of(state).pipe(filterQueryStates(["SUCCESS" /* QueryStateType.Success */, "FAILURE" /* QueryStateType.Failure */]));
10384
+ return of(state).pipe(filterQueryStates([QueryStateType.Success, QueryStateType.Failure]));
10289
10385
  }
10290
10386
  return of(state);
10291
10387
  })), {
@@ -10468,6 +10564,8 @@ class QueryStore {
10468
10564
  this._queryCreated$.next(query);
10469
10565
  }
10470
10566
  initSmartQueryHandling() {
10567
+ if (typeof window === 'undefined')
10568
+ return;
10471
10569
  const windowBlur$ = fromEvent(window, 'blur');
10472
10570
  const windowFocus$ = fromEvent(window, 'focus');
10473
10571
  windowBlur$.subscribe(() => {
@@ -10522,6 +10620,8 @@ class QueryStore {
10522
10620
  console.log(stateAsJson);
10523
10621
  }
10524
10622
  initGarbageCollector() {
10623
+ if (typeof window === 'undefined')
10624
+ return;
10525
10625
  if (this.garbageCollector !== null) {
10526
10626
  return;
10527
10627
  }
@@ -10531,6 +10631,8 @@ class QueryStore {
10531
10631
  }, 15000);
10532
10632
  }
10533
10633
  stopGarbageCollector() {
10634
+ if (typeof window === 'undefined')
10635
+ return;
10534
10636
  if (this.garbageCollector !== null) {
10535
10637
  window.clearInterval(this.garbageCollector);
10536
10638
  this.garbageCollector = null;
@@ -10632,7 +10734,10 @@ class V2QueryClient {
10632
10734
  return this._authProvider$.getValue() ?? this._clientConfig.parent?._authProvider$.getValue() ?? null;
10633
10735
  }
10634
10736
  get authProvider$() {
10635
- return this._authProvider$.asObservable() ?? this._clientConfig.parent?._authProvider$.asObservable() ?? null;
10737
+ const parent = this._clientConfig.parent?._authProvider$;
10738
+ return parent
10739
+ ? combineLatest([this._authProvider$, parent]).pipe(map(([ownProvider, parentProvider]) => ownProvider ?? parentProvider))
10740
+ : this._authProvider$.asObservable();
10636
10741
  }
10637
10742
  }
10638
10743
 
@@ -10662,6 +10767,17 @@ const v2BuildQueryCacheKey = (route, args) => {
10662
10767
  return hash.toString();
10663
10768
  };
10664
10769
 
10770
+ /**
10771
+ * @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.
10772
+ */
10773
+ const QueryStateType = {
10774
+ Prepared: 'PREPARED',
10775
+ Loading: 'LOADING',
10776
+ Success: 'SUCCESS',
10777
+ Failure: 'FAILURE',
10778
+ Cancelled: 'CANCELLED',
10779
+ };
10780
+
10665
10781
  let fallbackInjector = null;
10666
10782
  /**
10667
10783
  * The stashed root injector, or `null` when the fallback was never provided (or was provided on the
@@ -10726,7 +10842,7 @@ const transformExecStateToQueryState = (execState) => {
10726
10842
  case 'loading': {
10727
10843
  const loading = {
10728
10844
  meta,
10729
- type: "LOADING" /* QueryStateType.Loading */,
10845
+ type: QueryStateType.Loading,
10730
10846
  ...(execState.loading.progress
10731
10847
  ? {
10732
10848
  progress: {
@@ -10744,7 +10860,7 @@ const transformExecStateToQueryState = (execState) => {
10744
10860
  headers: {},
10745
10861
  meta,
10746
10862
  response: execState.response,
10747
- type: "SUCCESS" /* QueryStateType.Success */,
10863
+ type: QueryStateType.Success,
10748
10864
  };
10749
10865
  return success;
10750
10866
  }
@@ -10758,14 +10874,14 @@ const transformExecStateToQueryState = (execState) => {
10758
10874
  url: execState.error.raw.url || '',
10759
10875
  },
10760
10876
  meta,
10761
- type: "FAILURE" /* QueryStateType.Failure */,
10877
+ type: QueryStateType.Failure,
10762
10878
  };
10763
10879
  return error;
10764
10880
  }
10765
10881
  default: {
10766
10882
  const prepared = {
10767
10883
  meta,
10768
- type: "PREPARED" /* QueryStateType.Prepared */,
10884
+ type: QueryStateType.Prepared,
10769
10885
  };
10770
10886
  return prepared;
10771
10887
  }
@@ -11354,23 +11470,23 @@ const resetPageOnError = (config) => (source) => {
11354
11470
  /**
11355
11471
  * @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.
11356
11472
  */
11357
- const isQueryStateLoading = (state) => state?.type === "LOADING" /* QueryStateType.Loading */;
11473
+ const isQueryStateLoading = (state) => state?.type === QueryStateType.Loading;
11358
11474
  /**
11359
11475
  * @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.
11360
11476
  */
11361
- const isQueryStateSuccess = (state) => state?.type === "SUCCESS" /* QueryStateType.Success */;
11477
+ const isQueryStateSuccess = (state) => state?.type === QueryStateType.Success;
11362
11478
  /**
11363
11479
  * @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.
11364
11480
  */
11365
- const isQueryStateFailure = (state) => state?.type === "FAILURE" /* QueryStateType.Failure */;
11481
+ const isQueryStateFailure = (state) => state?.type === QueryStateType.Failure;
11366
11482
  /**
11367
11483
  * @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.
11368
11484
  */
11369
- const isQueryStateCancelled = (state) => state?.type === "CANCELLED" /* QueryStateType.Cancelled */;
11485
+ const isQueryStateCancelled = (state) => state?.type === QueryStateType.Cancelled;
11370
11486
  /**
11371
11487
  * @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.
11372
11488
  */
11373
- const isQueryStatePrepared = (state) => state?.type === "PREPARED" /* QueryStateType.Prepared */;
11489
+ const isQueryStatePrepared = (state) => state?.type === QueryStateType.Prepared;
11374
11490
  /**
11375
11491
  * @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.
11376
11492
  */
@@ -11522,17 +11638,12 @@ const computeQueryBody = (config) => {
11522
11638
  */
11523
11639
  const computeQueryAuthHeader = (config) => {
11524
11640
  let authHeader = null;
11525
- if (config.config.secure) {
11641
+ if (config.config.secure === true) {
11526
11642
  const header = config.client.authProvider?.header;
11527
11643
  if (header) {
11528
11644
  authHeader = header;
11529
11645
  }
11530
11646
  }
11531
- else if (config.client.authProvider?.header) {
11532
- if (config.config.secure === undefined || config.config.secure) {
11533
- authHeader = config.client.authProvider.header;
11534
- }
11535
- }
11536
11647
  return authHeader;
11537
11648
  };
11538
11649
  /**
@@ -11637,7 +11748,7 @@ class V2Query {
11637
11748
  this._isPollingPaused = false;
11638
11749
  this.memoizedState$ = null;
11639
11750
  this._state$ = new BehaviorSubject({
11640
- type: "PREPARED" /* QueryStateType.Prepared */,
11751
+ type: QueryStateType.Prepared,
11641
11752
  meta: { id: this.currentLocalId, triggeredVia: 'program' },
11642
11753
  });
11643
11754
  }
@@ -11732,7 +11843,7 @@ class V2Query {
11732
11843
  return this;
11733
11844
  }
11734
11845
  }
11735
- this.updateState({ type: "LOADING" /* QueryStateType.Loading */, meta });
11846
+ this.updateState({ type: QueryStateType.Loading, meta });
11736
11847
  if (this._isInMockMode) {
11737
11848
  this.mockRequest({}, meta, options);
11738
11849
  }
@@ -11789,7 +11900,7 @@ class V2Query {
11789
11900
  }
11790
11901
  this.onAbort$.next();
11791
11902
  this.updateState({
11792
- type: "CANCELLED" /* QueryStateType.Cancelled */,
11903
+ type: QueryStateType.Cancelled,
11793
11904
  meta: { id: this.rawState.meta.id, triggeredVia: this.rawState.meta.triggeredVia },
11794
11905
  });
11795
11906
  return this;
@@ -11883,7 +11994,7 @@ class V2Query {
11883
11994
  const { type, retryDelay, retryNumber } = requestEvent;
11884
11995
  const newMeta = { ...meta, isWaitingForRetry: type === 'delay-retry', retryDelay, retryNumber };
11885
11996
  this.updateState({
11886
- type: "LOADING" /* QueryStateType.Loading */,
11997
+ type: QueryStateType.Loading,
11887
11998
  meta: newMeta,
11888
11999
  });
11889
12000
  break;
@@ -11891,7 +12002,7 @@ class V2Query {
11891
12002
  case 'upload-progress':
11892
12003
  case 'download-progress': {
11893
12004
  this.updateState({
11894
- type: "LOADING" /* QueryStateType.Loading */,
12005
+ type: QueryStateType.Loading,
11895
12006
  progress: requestEvent.progress,
11896
12007
  partialText: 'partialText' in requestEvent ? requestEvent.partialText : undefined,
11897
12008
  meta,
@@ -11914,7 +12025,7 @@ class V2Query {
11914
12025
  });
11915
12026
  }
11916
12027
  this.updateState({
11917
- type: "SUCCESS" /* QueryStateType.Success */,
12028
+ type: QueryStateType.Success,
11918
12029
  response: responseData,
11919
12030
  headers: headers,
11920
12031
  meta: { ...meta, expiresAt: expiresInTimestamp },
@@ -11923,7 +12034,7 @@ class V2Query {
11923
12034
  }
11924
12035
  case 'failure': {
11925
12036
  const { error } = requestEvent;
11926
- const failure = () => this.updateState({ type: "FAILURE" /* QueryStateType.Failure */, error, meta });
12037
+ const failure = () => this.updateState({ type: QueryStateType.Failure, error, meta });
11927
12038
  const bearerAuthProvider = this.getBearerAuthProvider();
11928
12039
  if (!bearerAuthProvider ||
11929
12040
  options?._isUnauthorizedRetry ||
@@ -11935,13 +12046,15 @@ class V2Query {
11935
12046
  if (!query)
11936
12047
  return failure();
11937
12048
  query.state$
11938
- .pipe(takeUntilResponse(), takeUntil(this.onAbort$), tap((state) => isQueryStateSuccess(state) ? this.execute({ ...options, _isUnauthorizedRetry: true }) : failure()))
12049
+ .pipe(takeUntilResponse(), takeUntil(this.onAbort$), tap((state) => isQueryStateSuccess(state)
12050
+ ? this.execute({ ...options, _isUnauthorizedRetry: true, cancelPrevious: true })
12051
+ : failure()))
11939
12052
  .subscribe();
11940
12053
  break;
11941
12054
  }
11942
12055
  case 'cancel': {
11943
12056
  this.updateState({
11944
- type: "CANCELLED" /* QueryStateType.Cancelled */,
12057
+ type: QueryStateType.Cancelled,
11945
12058
  meta,
11946
12059
  });
11947
12060
  break;
@@ -12022,6 +12135,17 @@ class V2Query {
12022
12135
  }
12023
12136
  }
12024
12137
 
12138
+ /**
12139
+ * @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.
12140
+ */
12141
+ const AuthBearerRefreshStrategy = {
12142
+ /**
12143
+ * Automatically refresh the token 5 minutes before expiration.
12144
+ * The time can be configured with the `refreshBuffer` property.
12145
+ */
12146
+ BeforeExpiration: 'beforeExpiration',
12147
+ };
12148
+
12025
12149
  /**
12026
12150
  * @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.
12027
12151
  */
@@ -12070,7 +12194,8 @@ class V2BearerAuthProvider {
12070
12194
  }
12071
12195
  }
12072
12196
  get header() {
12073
- return { Authorization: `Bearer ${this._tokens$.getValue().token}` };
12197
+ const token = this._tokens$.getValue().token;
12198
+ return token ? { Authorization: `Bearer ${token}` } : {};
12074
12199
  }
12075
12200
  get tokens$() {
12076
12201
  return this._tokens$.asObservable();
@@ -12090,8 +12215,9 @@ class V2BearerAuthProvider {
12090
12215
  this._config.refreshConfig.refreshOnUnauthorizedResponse));
12091
12216
  }
12092
12217
  cleanUp() {
12093
- this.destroy$.next(true);
12094
- this.destroy$.unsubscribe();
12218
+ this.destroy$.next();
12219
+ this.destroy$.complete();
12220
+ this.destroy$ = new Subject();
12095
12221
  this._currentRefreshQuery$.next(null);
12096
12222
  if (this._config.refreshConfig?.cookieName) {
12097
12223
  this.deleteCookie();
@@ -12173,12 +12299,12 @@ class V2BearerAuthProvider {
12173
12299
  setCookie() {
12174
12300
  if (!this._config.refreshConfig?.cookieName || !this.tokens.refreshToken)
12175
12301
  return;
12176
- setCookie(this._config.refreshConfig.cookieName, this.tokens.refreshToken, this._config.refreshConfig.cookieExpiresInDays, this._config.refreshConfig.cookieDomain, this._config.refreshConfig.cookiePath, this._config.refreshConfig.cookieSameSite);
12302
+ setCookie(this._config.refreshConfig.cookieName, this.tokens.refreshToken, this._config.refreshConfig.cookieExpiresInDays, this._config.refreshConfig.cookieDomain ?? null, this._config.refreshConfig.cookiePath, this._config.refreshConfig.cookieSameSite);
12177
12303
  }
12178
12304
  deleteCookie() {
12179
12305
  if (!this._config.refreshConfig?.cookieName)
12180
12306
  return;
12181
- deleteCookie(this._config.refreshConfig.cookieName, this._config.refreshConfig.cookiePath, this._config.refreshConfig.cookieDomain);
12307
+ deleteCookie(this._config.refreshConfig.cookieName, this._config.refreshConfig.cookiePath, this._config.refreshConfig.cookieDomain ?? null);
12182
12308
  }
12183
12309
  prepareForRefresh() {
12184
12310
  if (!this.tokens.token || !this._config.refreshConfig) {
@@ -12192,13 +12318,12 @@ class V2BearerAuthProvider {
12192
12318
  const expiresIn = bearer[expiresInPropertyName];
12193
12319
  const fiveMinutes = 5 * 60 * 1000;
12194
12320
  const refreshBuffer = this._config.refreshConfig.refreshBuffer ?? fiveMinutes;
12195
- if (expiresIn === undefined) {
12196
- throw new Error(`Bearer token does not contain an '${expiresInPropertyName}' property`);
12197
- }
12321
+ if (typeof expiresIn !== 'number')
12322
+ return;
12198
12323
  const remainingTime = new Date(expiresIn * 1000).getTime() - refreshBuffer - new Date().getTime();
12199
- const strategy = this._config.refreshConfig.strategy ?? "beforeExpiration" /* AuthBearerRefreshStrategy.BeforeExpiration */;
12324
+ const strategy = this._config.refreshConfig.strategy ?? AuthBearerRefreshStrategy.BeforeExpiration;
12200
12325
  switch (strategy) {
12201
- case "beforeExpiration" /* AuthBearerRefreshStrategy.BeforeExpiration */:
12326
+ case AuthBearerRefreshStrategy.BeforeExpiration:
12202
12327
  timer(remainingTime)
12203
12328
  .pipe(takeUntil(this.destroy$))
12204
12329
  .subscribe(() => this._refreshQuery());
@@ -12336,6 +12461,7 @@ class InfinityQuery {
12336
12461
  }
12337
12462
  reset(newConfig) {
12338
12463
  this._config = { ...this._config, ...(newConfig ?? {}) };
12464
+ this.destroyQueries();
12339
12465
  this._currentPage$.next(null);
12340
12466
  this._currentCalculatedPage$.next(null);
12341
12467
  this._totalPages$.next(null);
@@ -12343,6 +12469,18 @@ class InfinityQuery {
12343
12469
  this.queries$.next([]);
12344
12470
  this.nextPage();
12345
12471
  }
12472
+ destroy() {
12473
+ this.destroyQueries();
12474
+ this.queries$.next([]);
12475
+ }
12476
+ destroyQueries() {
12477
+ for (const query of this.queries$.value) {
12478
+ query.stopPolling();
12479
+ query.abort();
12480
+ if ('destroy' in query && typeof query.destroy === 'function')
12481
+ query.destroy();
12482
+ }
12483
+ }
12346
12484
  prepareArgs(config, page) {
12347
12485
  const pageParamLocation = this.getPageParamLocation(config?.pageParam?.location);
12348
12486
  const pageArgs = {
@@ -12416,8 +12554,6 @@ const INFINITY_QUERY_TOKEN = new InjectionToken('INFINITY_QUERY_TOKEN');
12416
12554
  * @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.
12417
12555
  */
12418
12556
  class InfinityQueryDirective {
12419
- // TODO: Skipped for migration because:
12420
- // Accessor inputs cannot be migrated as they are too complex.
12421
12557
  get infinityQuery() {
12422
12558
  return this._infinityQuery;
12423
12559
  }
@@ -12530,6 +12666,8 @@ class InfinityQueryDirective {
12530
12666
  }
12531
12667
  cleanup() {
12532
12668
  this.queryConfigChanged$.next(true);
12669
+ this.infinityQueryInstance?.destroy();
12670
+ this.infinityQueryInstance = null;
12533
12671
  this.viewContext.loading = false;
12534
12672
  this.viewContext.error = null;
12535
12673
  this.viewContext.etInfinityQuery = null;
@@ -12909,7 +13047,7 @@ class QueryDevtoolsComponent {
12909
13047
  cookiePath: refreshCfg?.cookiePath ?? '/ (default)',
12910
13048
  cookieEnabled: refreshCfg?.cookieEnabled ?? 'true (default)',
12911
13049
  cookieSameSite: refreshCfg?.cookieSameSite ?? 'lax (default)',
12912
- refreshBuffer: refreshCfg?.refreshBuffer ?? '30000 (default)',
13050
+ refreshBuffer: refreshCfg?.refreshBuffer ?? '300000 (default)',
12913
13051
  expiresInPropertyName: refreshCfg?.expiresInPropertyName ?? 'exp (default)',
12914
13052
  strategy: refreshCfg?.strategy ?? 'AuthBearerRefreshStrategy.BeforeExpiration (default)',
12915
13053
  requestArgsAdapter: refreshCfg?.requestArgsAdapter ? 'custom' : 'default',
@@ -13193,7 +13331,6 @@ class EntityStore {
13193
13331
  function mapToPaginated(response) {
13194
13332
  return map((items) => ({ ...response, items }));
13195
13333
  }
13196
- // FIXME: This breaks if SubStoreType is an array
13197
13334
  /**
13198
13335
  * Use an other store to represent a property of the current entity.
13199
13336
  *
@@ -13265,6 +13402,7 @@ const invalidBodyError = (data) => new RuntimeError(QueryRuntimeErrorCode.LEGACY
13265
13402
  * @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.
13266
13403
  */
13267
13404
  const validateWithV2Query = (path, config) => {
13405
+ const injector = inject(Injector);
13268
13406
  applyQueryAsyncValidator(path, {
13269
13407
  params: config.args,
13270
13408
  debounce: config.debounce,
@@ -13275,18 +13413,30 @@ const validateWithV2Query = (path, config) => {
13275
13413
  loader: ({ params: prepareArgs, abortSignal }) => {
13276
13414
  // Prepared per request - v2 caches by the prepared args, so a fresh `prepare()` is the
13277
13415
  // legacy idiom (the sibling `selectOptionsFromV2Query` does the same via `queryComputed`).
13278
- const query = config.queryCreator.prepare(prepareArgs);
13279
- abortSignal.addEventListener('abort', () => query.abort());
13416
+ const query = runInInjectionContext(injector, () => config.queryCreator.prepare(prepareArgs));
13417
+ let destroyed = false;
13418
+ const destroy = () => {
13419
+ if (destroyed)
13420
+ return;
13421
+ destroyed = true;
13422
+ query.destroy?.();
13423
+ };
13424
+ abortSignal.addEventListener('abort', () => {
13425
+ query.abort();
13426
+ destroy();
13427
+ }, { once: true });
13280
13428
  query.execute();
13281
13429
  // Resolve once the query settles. A failure is thrown so it lands in `onError`, where the
13282
13430
  // violations are mapped onto the fields; the underlying `HttpErrorResponse` is thrown so
13283
13431
  // `extractFormViolations` can unwrap the 422 body.
13284
- return firstValueFrom(query.state$.pipe(filter((state) => isQueryStateSuccess(state) || isQueryStateFailure(state)), take(1))).then((state) => {
13432
+ return firstValueFrom(query.state$.pipe(filter((state) => isQueryStateSuccess(state) || isQueryStateFailure(state)), take(1)))
13433
+ .then((state) => {
13285
13434
  if (isQueryStateFailure(state)) {
13286
13435
  throw state.error.httpErrorResponse;
13287
13436
  }
13288
13437
  return state.response;
13289
- });
13438
+ })
13439
+ .finally(destroy);
13290
13440
  },
13291
13441
  }),
13292
13442
  });
@@ -13703,7 +13853,7 @@ const transformToStringArray = (value) => {
13703
13853
  return value.map(transformToString).filter(Boolean);
13704
13854
  }
13705
13855
  else if (typeof value === 'string') {
13706
- return [transformToString(value)];
13856
+ return value ? [value] : [];
13707
13857
  }
13708
13858
  return null;
13709
13859
  };
@@ -13712,7 +13862,10 @@ const transformToNumber = (value) => {
13712
13862
  return value;
13713
13863
  }
13714
13864
  if (typeof value === 'string') {
13715
- return Number(value);
13865
+ if (!value.trim())
13866
+ return null;
13867
+ const number = Number(value);
13868
+ return Number.isNaN(number) ? null : number;
13716
13869
  }
13717
13870
  return null;
13718
13871
  };
@@ -13720,8 +13873,9 @@ const transformToNumberArray = (value) => {
13720
13873
  if (Array.isArray(value)) {
13721
13874
  return value.map(transformToNumber).filter((item) => item !== null);
13722
13875
  }
13723
- else if (typeof value === 'number') {
13724
- return [value];
13876
+ else if (typeof value === 'string' || typeof value === 'number') {
13877
+ const number = transformToNumber(value);
13878
+ return number === null ? [] : [number];
13725
13879
  }
13726
13880
  return null;
13727
13881
  };
@@ -13739,7 +13893,8 @@ const transformToBooleanArray = (value) => {
13739
13893
  return value.map(transformToBoolean).filter((item) => item !== null);
13740
13894
  }
13741
13895
  else if (typeof value === 'string' || typeof value === 'boolean') {
13742
- return [transformToBoolean(value)];
13896
+ const boolean = transformToBoolean(value);
13897
+ return boolean === null ? [] : [boolean];
13743
13898
  }
13744
13899
  return null;
13745
13900
  };
@@ -13761,7 +13916,8 @@ const transformToDateArray = (value) => {
13761
13916
  return value.map(transformToDate).filter((item) => item !== null);
13762
13917
  }
13763
13918
  else if (typeof value === 'string' || value instanceof Date) {
13764
- return [transformToDate(value)];
13919
+ const date = transformToDate(value);
13920
+ return date === null ? [] : [date];
13765
13921
  }
13766
13922
  return null;
13767
13923
  };
@@ -13773,7 +13929,7 @@ const transformToSort = (value) => {
13773
13929
  }
13774
13930
  return {
13775
13931
  active,
13776
- direction: direction,
13932
+ direction: direction === 'asc' || direction === 'desc' ? direction : '',
13777
13933
  };
13778
13934
  }
13779
13935
  return null;
@@ -13791,7 +13947,9 @@ const transformToSortQueryParam = (value) => {
13791
13947
 
13792
13948
  const ET_ARR_PREFIX = 'ET_ARR__';
13793
13949
  const ET_OBJ_PREFIX = 'ET_OBJ__';
13950
+ const ET_DATE_PREFIX = 'ET_DATE__';
13794
13951
  const ET_PROP_NULL_VALUE = 'ET_NULL__';
13952
+ /** @deprecated Use `queryField` with `defineQueryForm`. */
13795
13953
  class QueryField {
13796
13954
  constructor(data) {
13797
13955
  this.data = data;
@@ -13800,6 +13958,7 @@ class QueryField {
13800
13958
  return this.data.control;
13801
13959
  }
13802
13960
  }
13961
+ /** @deprecated Use `searchQueryField` with `defineQueryForm`. */
13803
13962
  class SearchQueryField {
13804
13963
  constructor(_data) {
13805
13964
  this._data = _data;
@@ -13814,6 +13973,7 @@ class SearchQueryField {
13814
13973
  return this.data.control;
13815
13974
  }
13816
13975
  }
13976
+ /** @deprecated Use `sortQueryField` with `defineQueryForm`. */
13817
13977
  class SortQueryField {
13818
13978
  constructor(_data) {
13819
13979
  this._data = _data;
@@ -13828,6 +13988,7 @@ class SortQueryField {
13828
13988
  return this.data.control;
13829
13989
  }
13830
13990
  }
13991
+ /** @deprecated Use `stringArrayQueryField` with `defineQueryForm`. */
13831
13992
  class StringArrayQueryField {
13832
13993
  constructor(_data) {
13833
13994
  this._data = _data;
@@ -13841,6 +14002,7 @@ class StringArrayQueryField {
13841
14002
  return this.data.control;
13842
14003
  }
13843
14004
  }
14005
+ /** @deprecated Use `booleanArrayQueryField` with `defineQueryForm`. */
13844
14006
  class BooleanArrayQueryField {
13845
14007
  constructor(_data) {
13846
14008
  this._data = _data;
@@ -13854,6 +14016,7 @@ class BooleanArrayQueryField {
13854
14016
  return this.data.control;
13855
14017
  }
13856
14018
  }
14019
+ /** @deprecated Use `numberArrayQueryField` with `defineQueryForm`. */
13857
14020
  class NumberArrayQueryField {
13858
14021
  constructor(_data) {
13859
14022
  this._data = _data;
@@ -13867,6 +14030,7 @@ class NumberArrayQueryField {
13867
14030
  return this.data.control;
13868
14031
  }
13869
14032
  }
14033
+ /** @deprecated Use `dateQueryField` with `defineQueryForm`. */
13870
14034
  class DateQueryField {
13871
14035
  constructor(_data) {
13872
14036
  this._data = _data;
@@ -13880,6 +14044,7 @@ class DateQueryField {
13880
14044
  return this.data.control;
13881
14045
  }
13882
14046
  }
14047
+ /** @deprecated Use `dateArrayQueryField` with `defineQueryForm`. */
13883
14048
  class DateArrayQueryField {
13884
14049
  constructor(_data) {
13885
14050
  this._data = _data;
@@ -13894,6 +14059,7 @@ class DateArrayQueryField {
13894
14059
  }
13895
14060
  }
13896
14061
  const IGNORED_FILTER_COUNT_FIELDS$1 = ['page', 'skip', 'take', 'limit', 'sort', 'sortBy', 'sortOrder', 'query', 'search'];
14062
+ /** @deprecated Use `defineQueryForm`. */
13897
14063
  // eslint-disable-next-line @typescript-eslint/no-explicit-any
13898
14064
  class QueryForm {
13899
14065
  // with prefix eg. page should become ${prefix}-page
@@ -13901,7 +14067,6 @@ class QueryForm {
13901
14067
  this._fields = _fields;
13902
14068
  this._options = _options;
13903
14069
  this.router = inject(Router);
13904
- this.activatedRoute = inject(ActivatedRoute);
13905
14070
  this.zone = inject(NgZone);
13906
14071
  this.queryParams = injectQueryParams();
13907
14072
  this.destroy$ = createDestroy();
@@ -13909,7 +14074,10 @@ class QueryForm {
13909
14074
  this.defaultValues = this.extractDefaultValues();
13910
14075
  this.didValueChanges$ = new Subject();
13911
14076
  this.isObserving = false;
14077
+ this.removeQueryParamsOnCleanup = true;
13912
14078
  this.skipNextResets = false;
14079
+ this.urlWriteVersion = 0;
14080
+ this.urlNavigationMarker = {};
13913
14081
  this.queryParamChanges$ = toObservable(injectQueryParamChanges());
13914
14082
  /**
13915
14083
  * The angular form group that contains all the fields.
@@ -13957,7 +14125,7 @@ class QueryForm {
13957
14125
  })
13958
14126
  .filter((v) => v === false).length));
13959
14127
  assertInInjectionContext(QueryForm);
13960
- inject(DestroyRef).onDestroy(() => this.cleanup());
14128
+ inject(DestroyRef).onDestroy(() => this.cleanup(false));
13961
14129
  }
13962
14130
  get controls() {
13963
14131
  return this.form.controls;
@@ -13979,6 +14147,7 @@ class QueryForm {
13979
14147
  return this;
13980
14148
  }
13981
14149
  this.isObserving = true;
14150
+ this.removeQueryParamsOnCleanup = options?.writeToQueryParams !== false;
13982
14151
  if (options?.syncOnNavigation !== false) {
13983
14152
  const didChanges = this.setFormValueFromUrlQueryParams({
13984
14153
  queryParams: this.queryParams(),
@@ -14048,6 +14217,9 @@ class QueryForm {
14048
14217
  if (options?.syncOnNavigation !== false) {
14049
14218
  this.queryParamChanges$
14050
14219
  .pipe(takeUntil(this.destroy$), takeUntil(this.unobserveTrigger$), tap((changes) => {
14220
+ const info = this.router.lastSuccessfulNavigation()?.extras.info;
14221
+ if (info?.queryForm === this.urlNavigationMarker && (info.version ?? 0) < this.urlWriteVersion)
14222
+ return;
14051
14223
  const didValueChanges = this.setFormValueFromUrlQueryParams({ queryParams: changes });
14052
14224
  if (didValueChanges) {
14053
14225
  this.didValueChanges$.next(true);
@@ -14087,7 +14259,7 @@ class QueryForm {
14087
14259
  const valueContainsWhitespace = typeof value === 'string' && value.trim() !== value;
14088
14260
  const valueHasLeadingZero = typeof value === 'string' && value.startsWith('0');
14089
14261
  const valueEndsWithDot = typeof value === 'string' && value.endsWith('.');
14090
- const defaultIsBool = this.getDefaultValue(key) === 'boolean';
14262
+ const defaultIsBool = typeof this.getDefaultValue(key) === 'boolean';
14091
14263
  const valueIsBool = value === 'true' || value === 'false';
14092
14264
  if (value === ET_PROP_NULL_VALUE) {
14093
14265
  deserializedValue = null;
@@ -14109,16 +14281,20 @@ class QueryForm {
14109
14281
  return didValueChanges;
14110
14282
  }
14111
14283
  setValue(value, options) {
14112
- this._form._setValue(value, options);
14113
14284
  if (options?.skipResets) {
14114
14285
  this.skipNextResets = true;
14115
14286
  }
14287
+ this._form._setValue(value, options);
14288
+ if (!this.isObserving)
14289
+ this.handleFormChange(true);
14116
14290
  }
14117
14291
  patchValue(value, options) {
14118
- this._form._patchValue(value, options);
14119
14292
  if (options?.skipResets) {
14120
14293
  this.skipNextResets = true;
14121
14294
  }
14295
+ this._form._patchValue(value, options);
14296
+ if (!this.isObserving)
14297
+ this.handleFormChange(true);
14122
14298
  }
14123
14299
  resetFieldToDefault(key, options) {
14124
14300
  const defaultValue = this.getDefaultValue(key);
@@ -14196,6 +14372,9 @@ class QueryForm {
14196
14372
  else if (typeof val === 'string' && val.startsWith(ET_OBJ_PREFIX)) {
14197
14373
  return JSON.parse(val.slice(ET_OBJ_PREFIX.length));
14198
14374
  }
14375
+ else if (typeof val === 'string' && val.startsWith(ET_DATE_PREFIX)) {
14376
+ return new Date(val.slice(ET_DATE_PREFIX.length));
14377
+ }
14199
14378
  else if (typeof val === 'function') {
14200
14379
  return val();
14201
14380
  }
@@ -14215,11 +14394,13 @@ class QueryForm {
14215
14394
  isDefaultValue(key, value) {
14216
14395
  const normalizedValue = Array.isArray(value)
14217
14396
  ? `${ET_ARR_PREFIX}${JSON.stringify(value)}`
14218
- : typeof value === 'object' && value !== null
14219
- ? `${ET_OBJ_PREFIX}${JSON.stringify(value)}`
14220
- : value === null
14221
- ? ET_PROP_NULL_VALUE
14222
- : value;
14397
+ : value instanceof Date
14398
+ ? `${ET_DATE_PREFIX}${value.toISOString()}`
14399
+ : typeof value === 'object' && value !== null
14400
+ ? `${ET_OBJ_PREFIX}${JSON.stringify(value)}`
14401
+ : value === null
14402
+ ? ET_PROP_NULL_VALUE
14403
+ : value;
14223
14404
  return this.defaultValues[key] === normalizedValue;
14224
14405
  }
14225
14406
  setupFormGroup() {
@@ -14240,6 +14421,9 @@ class QueryForm {
14240
14421
  if (Array.isArray(value)) {
14241
14422
  defaultValues[key] = `${ET_ARR_PREFIX}${JSON.stringify(value)}`;
14242
14423
  }
14424
+ else if (value instanceof Date) {
14425
+ defaultValues[key] = `${ET_DATE_PREFIX}${value.toISOString()}`;
14426
+ }
14243
14427
  else if (typeof value === 'object' && value !== null) {
14244
14428
  defaultValues[key] = `${ET_OBJ_PREFIX}${JSON.stringify(value)}`;
14245
14429
  }
@@ -14253,7 +14437,8 @@ class QueryForm {
14253
14437
  return defaultValues;
14254
14438
  }
14255
14439
  _syncViaUrlQueryParams(values, replaceUrl) {
14256
- const queryParams = { ...clone(this.activatedRoute.snapshot.queryParams) };
14440
+ const queryParams = {};
14441
+ const version = ++this.urlWriteVersion;
14257
14442
  for (const [key, value] of Object.entries(values)) {
14258
14443
  const queryParamKey = this.transformKeyToQueryParam(key);
14259
14444
  const field = this._fields[key];
@@ -14280,6 +14465,7 @@ class QueryForm {
14280
14465
  queryParams,
14281
14466
  replaceUrl,
14282
14467
  queryParamsHandling: 'merge',
14468
+ info: { queryForm: this.urlNavigationMarker, version },
14283
14469
  });
14284
14470
  });
14285
14471
  });
@@ -14303,6 +14489,7 @@ class QueryForm {
14303
14489
  const currentVal = clone(this.currentFormValue$.value);
14304
14490
  const newVal = clone(this.formValue);
14305
14491
  if (equal(currentVal, newVal)) {
14492
+ this.skipNextResets = false;
14306
14493
  return;
14307
14494
  }
14308
14495
  if (forceOverwrite) {
@@ -14313,10 +14500,12 @@ class QueryForm {
14313
14500
  }
14314
14501
  this.currentFormValue$.next(newVal);
14315
14502
  }
14316
- cleanup() {
14503
+ cleanup(removeQueryParams = this.removeQueryParamsOnCleanup) {
14317
14504
  if (!this.isObserving)
14318
14505
  return;
14319
14506
  this.isObserving = false;
14507
+ if (!removeQueryParams)
14508
+ return;
14320
14509
  const queryParamKeys = Object.keys(this._fields);
14321
14510
  const queryParams = queryParamKeys.reduce((acc, key) => {
14322
14511
  acc[this.transformKeyToQueryParam(key)] = undefined;
@@ -14395,14 +14584,23 @@ const createBranch = (fields, initial, injector) => {
14395
14584
  const model = signal(clone(initial), /* @ts-ignore */
14396
14585
  ...(ngDevMode ? [{ debugName: "model" }] : /* istanbul ignore next */ []));
14397
14586
  const tree = form(model, { injector });
14587
+ const defaultFor = (key) => {
14588
+ const value = resolveDefault(fields[key]);
14589
+ defaults[key] = value;
14590
+ return value;
14591
+ };
14398
14592
  return {
14399
14593
  fields: tree,
14400
14594
  value: model.asReadonly(),
14401
14595
  activeFilterCount: computed(() => computeFilterCount(fields, model(), defaults)),
14402
14596
  setValue: (value) => model.set(clone(value)),
14403
14597
  patchValue: (value) => model.update((cur) => ({ ...cur, ...value })),
14404
- resetFieldToDefault: (key) => model.update((cur) => ({ ...cur, [key]: defaults[key] })),
14405
- resetAllFieldsToDefault: () => model.set(clone(defaults)),
14598
+ resetFieldToDefault: (key) => model.update((cur) => ({ ...cur, [key]: defaultFor(key) })),
14599
+ resetAllFieldsToDefault: () => {
14600
+ for (const key of Object.keys(fields))
14601
+ defaultFor(key);
14602
+ model.set(clone(defaults));
14603
+ },
14406
14604
  };
14407
14605
  };
14408
14606
  /**
@@ -14440,6 +14638,12 @@ const defineQueryForm = (config) => {
14440
14638
  const prefix = config.queryParamPrefix;
14441
14639
  const defaults = buildDefaults(fieldDefs);
14442
14640
  const defaultValue = clone(defaults);
14641
+ const defaultFor = (key) => {
14642
+ const value = resolveDefault(fieldDefs[key]);
14643
+ defaults[key] = value;
14644
+ defaultValue[key] = clone(value);
14645
+ return value;
14646
+ };
14443
14647
  /** Live field values (updated immediately by bound controls). */
14444
14648
  const model = signal(clone(defaults), /* @ts-ignore */
14445
14649
  ...(ngDevMode ? [{ debugName: "model" }] : /* istanbul ignore next */ []));
@@ -14455,6 +14659,8 @@ const defineQueryForm = (config) => {
14455
14659
  let observeOptions;
14456
14660
  let pendingTimer = null;
14457
14661
  let skipNextResets = false;
14662
+ let urlWriteVersion = 0;
14663
+ const urlNavigationMarker = {};
14458
14664
  const fields = form(model);
14459
14665
  const activeFilterCount = computed(() => computeFilterCount(fieldDefs, committed(), defaults), /* @ts-ignore */
14460
14666
  ...(ngDevMode ? [{ debugName: "activeFilterCount" }] : /* istanbul ignore next */ []));
@@ -14472,6 +14678,8 @@ const defineQueryForm = (config) => {
14472
14678
  return undefined;
14473
14679
  if (def.valueToQueryParam)
14474
14680
  return def.valueToQueryParam(value);
14681
+ if (value === '' && defaults[key] === null)
14682
+ return undefined;
14475
14683
  return value === null ? ET_NULL_VALUE : value;
14476
14684
  };
14477
14685
  const deserialize = (def, raw) => {
@@ -14490,6 +14698,7 @@ const defineQueryForm = (config) => {
14490
14698
  };
14491
14699
  const writeToUrl = (value) => {
14492
14700
  const queryParams = {};
14701
+ const version = ++urlWriteVersion;
14493
14702
  for (const [key, def] of Object.entries(fieldDefs)) {
14494
14703
  queryParams[paramKey(key)] = queryParamFor(key, def, value[key]);
14495
14704
  }
@@ -14498,10 +14707,11 @@ const defineQueryForm = (config) => {
14498
14707
  queryParams,
14499
14708
  queryParamsHandling: 'merge',
14500
14709
  replaceUrl: observeOptions?.replaceUrl,
14710
+ info: { queryForm: urlNavigationMarker, version },
14501
14711
  });
14502
14712
  });
14503
14713
  };
14504
- const applyResets = (live, changedKeys) => {
14714
+ const applyResets = (live, changedKeys, resetDefaults) => {
14505
14715
  const next = { ...live };
14506
14716
  for (const [key, def] of Object.entries(fieldDefs)) {
14507
14717
  const resets = def.isResetBy;
@@ -14518,7 +14728,11 @@ const defineQueryForm = (config) => {
14518
14728
  });
14519
14729
  if (!triggered)
14520
14730
  continue;
14521
- const fieldDefault = defaults[key];
14731
+ let fieldDefault = resetDefaults.get(key);
14732
+ if (!resetDefaults.has(key)) {
14733
+ fieldDefault = defaultFor(key);
14734
+ resetDefaults.set(key, fieldDefault);
14735
+ }
14522
14736
  if (!equal(next[key], fieldDefault)) {
14523
14737
  next[key] = fieldDefault;
14524
14738
  }
@@ -14534,8 +14748,9 @@ const defineQueryForm = (config) => {
14534
14748
  */
14535
14749
  const resolveResets = (prev, live) => {
14536
14750
  let next = live;
14751
+ const resetDefaults = new Map();
14537
14752
  for (let pass = 0; pass < MAX_RESET_PASSES; pass++) {
14538
- const applied = applyResets(next, changedKeysBetween(prev, next));
14753
+ const applied = applyResets(next, changedKeysBetween(prev, next), resetDefaults);
14539
14754
  if (equal(applied, next))
14540
14755
  return applied;
14541
14756
  next = applied;
@@ -14547,10 +14762,24 @@ const defineQueryForm = (config) => {
14547
14762
  };
14548
14763
  const flush = () => {
14549
14764
  clearTimer();
14550
- const live = model();
14765
+ const rawLive = model();
14766
+ const live = { ...rawLive };
14767
+ for (const [key, def] of Object.entries(fieldDefs)) {
14768
+ if (equal(live[key], defaults[key]))
14769
+ continue;
14770
+ const serialized = def.valueToQueryParam?.(live[key]);
14771
+ const serializedToNothing = !!def.valueToQueryParam && (serialized === null || serialized === undefined);
14772
+ const clearedNullableText = live[key] === '' && defaults[key] === null;
14773
+ if (serializedToNothing || clearedNullableText) {
14774
+ live[key] = defaults[key];
14775
+ }
14776
+ }
14551
14777
  const prev = committed();
14552
14778
  if (equal(live, prev)) {
14553
14779
  skipNextResets = false;
14780
+ if (!equal(live, rawLive)) {
14781
+ model.set(clone(live));
14782
+ }
14554
14783
  return;
14555
14784
  }
14556
14785
  const next = skipNextResets ? { ...live } : resolveResets(prev, live);
@@ -14559,7 +14788,7 @@ const defineQueryForm = (config) => {
14559
14788
  committed.set(clone(next));
14560
14789
  // Reflect any reset overrides back into the bound controls. The effect re-runs
14561
14790
  // but no-ops because the model now equals the committed value.
14562
- if (!equal(next, live)) {
14791
+ if (!equal(next, rawLive)) {
14563
14792
  model.set(clone(next));
14564
14793
  }
14565
14794
  if (observing() && observeOptions?.writeToQueryParams !== false) {
@@ -14626,12 +14855,12 @@ const defineQueryForm = (config) => {
14626
14855
  return;
14627
14856
  commitFromUrl(current);
14628
14857
  };
14629
- const cleanup = () => {
14858
+ const cleanup = (removeQueryParams = true) => {
14630
14859
  if (!observing())
14631
14860
  return;
14632
14861
  observing.set(false);
14633
14862
  clearTimer();
14634
- if (observeOptions?.writeToQueryParams === false)
14863
+ if (!removeQueryParams || observeOptions?.writeToQueryParams === false)
14635
14864
  return;
14636
14865
  const queryParams = {};
14637
14866
  for (const key of Object.keys(fieldDefs)) {
@@ -14657,7 +14886,7 @@ const defineQueryForm = (config) => {
14657
14886
  model.update((cur) => {
14658
14887
  const next = { ...cur };
14659
14888
  for (const key of keys) {
14660
- next[key] = defaults[key];
14889
+ next[key] = defaultFor(key);
14661
14890
  }
14662
14891
  return next;
14663
14892
  });
@@ -14694,6 +14923,10 @@ const defineQueryForm = (config) => {
14694
14923
  }
14695
14924
  // Commit whatever the model holds now (URL-restored or programmatic defaults).
14696
14925
  flush();
14926
+ if (options?.writeToQueryParams !== false &&
14927
+ Object.values(fieldDefs).some((field) => field.appendDefaultValueToUrl === true)) {
14928
+ writeToUrl(committed());
14929
+ }
14697
14930
  return queryForm;
14698
14931
  },
14699
14932
  unobserve: () => cleanup(),
@@ -14706,7 +14939,7 @@ const defineQueryForm = (config) => {
14706
14939
  const keys = Object.keys(fieldDefs).filter((key) => !skip.has(key));
14707
14940
  resetFieldsToDefault(keys, options);
14708
14941
  },
14709
- branch: () => createBranch(fieldDefs, clone(committed()), injector),
14942
+ branch: (branchInjector = injector) => createBranch(fieldDefs, clone(committed()), branchInjector),
14710
14943
  };
14711
14944
  // React to live control edits: schedule a debounced commit.
14712
14945
  effect(() => {
@@ -14719,10 +14952,13 @@ const defineQueryForm = (config) => {
14719
14952
  untracked(() => {
14720
14953
  if (!observing() || observeOptions?.syncOnNavigation === false)
14721
14954
  return;
14955
+ const info = router.lastSuccessfulNavigation()?.extras.info;
14956
+ if (info?.queryForm === urlNavigationMarker && (info.version ?? 0) < urlWriteVersion)
14957
+ return;
14722
14958
  applyFromUrl(changes);
14723
14959
  });
14724
14960
  });
14725
- destroyRef.onDestroy(() => cleanup());
14961
+ destroyRef.onDestroy(() => cleanup(false));
14726
14962
  if (devtoolsId) {
14727
14963
  /**
14728
14964
  * The live view `<et-query-devtools>` renders. Only built while the devtools are installed - it walks
@@ -14896,13 +15132,14 @@ const createWebSocketClient = (options) => {
14896
15132
  const pre = previousSignalValue(computed(() => roomFn()));
14897
15133
  const roomData = signal(null, /* @ts-ignore */
14898
15134
  ...(ngDevMode ? [{ debugName: "roomData" }] : /* istanbul ignore next */ []));
15135
+ let joinedRoomName = null;
14899
15136
  const join = (name) => {
14900
- emit({ event: 'join-room', data: name, room: name });
14901
15137
  const existingRoom = rooms.get(name);
14902
15138
  if (existingRoom) {
14903
15139
  existingRoom.joinCount++;
14904
15140
  return existingRoom;
14905
15141
  }
15142
+ emit({ event: 'join-room', data: name, room: name });
14906
15143
  const message = signal(null, /* @ts-ignore */
14907
15144
  ...(ngDevMode ? [{ debugName: "message" }] : /* istanbul ignore next */ []));
14908
15145
  const newRoom = {
@@ -14919,12 +15156,16 @@ const createWebSocketClient = (options) => {
14919
15156
  const previous = pre();
14920
15157
  if (previous === current)
14921
15158
  return;
14922
- if (previous)
15159
+ if (previous) {
14923
15160
  leaveRoom(previous);
15161
+ joinedRoomName = null;
15162
+ }
14924
15163
  if (current) {
14925
15164
  const joinedRoom = join(current);
14926
- if (joinedRoom)
15165
+ if (joinedRoom) {
15166
+ joinedRoomName = current;
14927
15167
  roomData.set(joinedRoom);
15168
+ }
14928
15169
  }
14929
15170
  else {
14930
15171
  roomData.set(null);
@@ -14932,9 +15173,9 @@ const createWebSocketClient = (options) => {
14932
15173
  });
14933
15174
  });
14934
15175
  inject(DestroyRef).onDestroy(() => {
14935
- const current = roomFn();
14936
- if (current) {
14937
- leaveRoom(current);
15176
+ if (joinedRoomName) {
15177
+ leaveRoom(joinedRoomName);
15178
+ joinedRoomName = null;
14938
15179
  roomData.set(null);
14939
15180
  }
14940
15181
  });
@@ -14964,8 +15205,11 @@ const createWebSocketClient = (options) => {
14964
15205
  socket.on('disconnect', () => isConnected.set(false));
14965
15206
  };
14966
15207
  const setupWebSocketListener = () => {
14967
- socket.onAny((data) => {
15208
+ socket.onAny((_eventName, ...args) => {
14968
15209
  try {
15210
+ const data = args[0];
15211
+ if (typeof data !== 'string')
15212
+ throw messageMalformed();
14969
15213
  const json = JSON.parse(data);
14970
15214
  recordDevtoolsMessage({ room: json.room, event: json.event, data: json.data, direction: 'in' });
14971
15215
  const room = rooms.get(json.room);
@@ -15014,5 +15258,5 @@ const createWebSocketClient = (options) => {
15014
15258
  * Generated bundle index. Do not edit.
15015
15259
  */
15016
15260
 
15017
- export { BasicAuthProvider, BearerAuthFeatureType, BooleanArrayQueryField, 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_QUERY_BATCH_TOMBSTONES, MAX_QUERY_DEVTOOLS_TOMBSTONES, MAX_UNUSED_ENTRIES, NumberArrayQueryField, ParseHttpErrorCodeToMessageDePipe, ParseHttpErrorCodeToMessageEnPipe, ParseHttpErrorCodeToTitleDePipe, ParseHttpErrorCodeToTitleEnPipe, QUERY_CLIENT_DEVTOOLS_TOKEN, QUERY_DEVTOOLS_FAULT_STATUSES, QUERY_DEVTOOLS_TOKEN_TTL_LIMIT, QUERY_PERSISTENCE_STORE_VERSION, QUERY_SYNC_PROTOCOL_VERSION, QUERY_VERSION, QueryClientFeatureType, QueryDevtoolsComponent, QueryDirective, QueryFeatureType, QueryField, QueryForm, QueryRuntimeErrorCode, QueryShortNamePipe, QueryStore, SERVER_ERROR_KIND, SERVER_VIOLATION_ERROR_KIND, SPEED_BUFFER_TIME_IN_MS, SearchQueryField, SortQueryField, StringArrayQueryField, V2BearerAuthProvider, V2Query, V2QueryClient, V2QueryCreator, addQueryContainerHandling, applyQueryDevtoolsOverrides, applyQueryDevtoolsTokenTtl, applyQueryFeatures, armAllQueryDevtoolsMocks, armQueryDevtoolsMock, armQueryDevtoolsOverrideTransfer, authExtractTokensResponseMissingAccessToken, authExtractTokensResponseMissingRefreshToken, authExtractTokensResponseNotObject, authProviderFeatureUsedMultipleTimes, booleanArrayQueryField, buildQueryCacheKey, buildQueryString, buildRoute, buildTimestampFromSeconds, canOverrideQueryDevtoolsTokenTtl, castQueryCreatorTypes, circularQueryDependency, circularQueryDependencyChecker, clearQueryDevtoolsArmedMocks, clearQueryDevtoolsFaults, clearQueryDevtoolsMockStore, clearQueryDevtoolsOverrideStore, clearQueryDevtoolsStore, clearQueryDevtoolsTokenTtl, clearQueryDevtoolsTombstones, clearRestoredQueryDevtoolsOverrides, collectLeafPaths, collectQueryDevtoolsSchemaComponents, collectQueryFormLinks, computeQueryAuthHeader, computeQueryBody, computeQueryHeaders, computeQueryMethod, computeQueryQueryParams, consumeSuppressQueryStackDevtools, contentfulGqlLikePaginationAdapter, countUnresolvedQueryDevtoolsOverrides, createAuthGuard, createBaseQuery, createBaseQueryCreator, createBearerAuthProvider, createDefaultRetryFn, createDeleteQuery, createExecuteFn, createGetQuery, createGqlExecuteFn, createGqlMutationViaGet, createGqlMutationViaPost, createGqlQuery, createGqlQueryCreator, createGqlQueryViaGet, createGqlQueryViaPost, createHttpRequest, createIndexedDbQueryPersistenceAdapter, createInfinityQueryConfig, createLegacyQueryCreator, createNoopQueryPersistenceAdapter, createPagedQueryStack, createPatchQuery, createPersistentAuthFeature, createPostQuery, createPutQuery, createQuery, createQueryBatch, createQueryClient, createQueryCollection, createQueryCollectionSignal, createQueryCollectionSubject, createQueryContext, createQueryCreator, createQueryDevtoolsFormLinks, createQueryDevtoolsFormLinksRecorder, createQueryDevtoolsOverrides, createQueryDevtoolsOverridesRecorder, createQueryDevtoolsStats, createQueryDevtoolsStatsRecorder, createQueryErrorResponse, createQueryFeature, createQueryInvalidationFilter, createQueryKeyLockManager, createQueryObject, createQueryPersistenceEngine, createQueryRepository, createQuerySnapshotFn, createQueryStack, createQuerySubmission, createQuerySyncEngine, createQuerySyncTransport, createSecureDeleteQuery, createSecureExecuteFactory, createSecureExecuteFn, createSecureGetQuery, createSecureGqlExecuteFn, createSecureGqlMutationViaGet, createSecureGqlMutationViaPost, createSecureGqlQuery, createSecureGqlQueryCreator, createSecureGqlQueryViaGet, createSecureGqlQueryViaPost, createSecurePatchQuery, createSecurePostQuery, createSecurePutQuery, createSecureQuery, createSecureQueryCreator, createTrackingFeature, createWebSocketClient, currentQueryDevtoolsBatch, dateArrayQueryField, dateQueryField, decryptBearer, deepFreeze, def, defineQueryForm, deleteQueryDevtoolsMock, describeQueryDevtoolsFeatures, detectContentTypeHeader, detectPaginationShape, dynLikePaginationAdapter, effectComputed, emptyQueryArgs, ethletePaginationAdapter, executeUntilSettled, extractExpiresInSeconds, extractFormViolations, extractHtmlErrorMessage, extractQuery, fakePaginationAdapter, filterFailure, filterNull, filterQueryStates, filterSuccess, forEachHeader, formatQueryDevtoolsDuration, generateQueryDevtoolsNumberPreset, generateQueryDevtoolsSampleNumber, generateQueryDevtoolsStringPreset, getBearerAuthProviderName, getDefaultHeaders, getQueryClientName, getQueryFeatureUsage, getResponseUrl, ggLikePaginationAdapter, gql, gqlDataPropertyMissingInResponse, hasHeader, hasQueryDevtoolsOverridesAtPath, htmlErrorPayload, htmlQueryErrorParser, ignoreAutoRefresh, initQueryDevtoolsFaults, initQueryDevtoolsMocks, initQueryDevtoolsOverridePersistence, initQueryDevtoolsSettings, injectInfinityQueryResponseDelay, injectQueryContext, insertFrom, invalidBaseRouteError, invalidBodyError, invalidRouteError, invalidStateInsideSecureExecuteFactory, isAutoRefresh, isBasicAuthProvider, isBearerAuthProvider, isClassValidatorError, isCreateGqlQueryOptions, isCustomHeaderAuthProvider, isDateShapedLeaf, isEmptyString, isGqlQueryConfig, isHtmlErrorPayload, isLegacyQuery, isNaN$1 as isNaN, isPageOutOfRangeError, isQuery, isQueryCollection, isQueryDevtoolsEnabled, isQueryDevtoolsFaultArmed, isQueryDevtoolsRequestInterceptionEnabled, isQueryErrorResponse, isQueryStateCancelled, isQueryStateFailure, isQueryStateLoading, isQueryStatePrepared, isQueryStateSuccess, isRequestError, isSymfonyError, isSymfonyFormViolationListError, isSymfonyListError, isSymfonyPagerfantaOutOfRangeError, isUnderInvalidatedUrl, legacyPrepareFallbackInjector, legacyPrepareWithoutInjectionContext, loadQueryDevtoolsSchema, mapToPaginated, mapViolationsToFormErrors, matchesQueryDevtoolsMockPattern, matchesQueryDevtoolsMockQuery, maybeExecute, measureQueryDevtoolsPayload, mergeHeaders, nestedEffect, noteQueryFormRead, numberArrayQueryField, pagedQueryStackNextPageCalledWithoutPreviousPage, pagedQueryStackPageBiggerThanTotalPages, pagedQueryStackPreviousPageCalledButAlreadyAtFirstPage, paginatedEntityValueUpdater, parseAllXhrResponseHeaders, parseHttpErrorCodeToMessageDe, parseHttpErrorCodeToMessageEn, parseHttpErrorCodeToTitleDe, parseHttpErrorCodeToTitleEn, parseQueryDevtoolsOverrideTransfer, parseQueryRoute, patchQueryDevtoolsTokenPayload, pathParamsMissingInRouteFunctionError, provideInfinityQueryResponseDelay, provideLegacyPrepareFallback, provideQueryClientForDevtools, provideQueryDevtools, queryArrayComputed, queryBatchAlreadyRunning, queryBatchWithArgsUsed, queryClientFeatureUsedMultipleTimes, queryComputed, queryComputedTillTruthy, queryDevtoolsAbout, queryDevtoolsArmedMocks, queryDevtoolsArmedMocksRestored, queryDevtoolsEntries, queryDevtoolsFaults, queryDevtoolsFaultsRestored, queryDevtoolsFnDetail, queryDevtoolsMockId, queryDevtoolsMocks, queryDevtoolsOverridePersistence, queryDevtoolsRequestPath, queryDevtoolsResponseHistory, queryDevtoolsRestoredOverridesScope, queryDevtoolsSchemaNames, queryDevtoolsSchemaRoutes, queryDevtoolsSchemaState, queryDevtoolsSettings, queryDevtoolsStorage, queryDevtoolsTokenTtls, queryErrorMessage, queryErrorMessages, queryExecute, queryFeatureUsedMultipleTimes, queryField, querySequence, querySequenceAlreadyRunning, queryStackTotalQueriesAndExpectedQueriesMismatch, queryStackWithArgsUsed, queryStackWithResponseUpdateUsed, queryStateAlreadyHasKeyError, queryStateCannotTransformError, queryStateDoesNotContainKeyError, queryStateErrorSignal, queryStateLoadingSignal, queryStateResponseSignal, queryStateSignal, readQueryDevtoolsStore, registerEthleteVersion, registerQueryDevtoolsEntry, registerQueryErrorParser, removeFrom, request, resetExecuteState, resetPageOnError, resolveInvalidationUrl, resolveQueryDevtoolsFault, resolveQueryDevtoolsFaultForAttempt, resolveQueryDevtoolsMock, resolveQueryDevtoolsMockForAttempt, restoredQueryDevtoolsOverrides, runDefaultQueryRetry, runInQueryDevtoolsBatch, runQueryErrorParsers, saveQueryDevtoolsMock, searchQueryField, seedQueryDevtoolsSchemaBody, seedQueryDevtoolsSchemaRoute, serializeBody, serializeQueryDevtoolsOverrideTransfer, setDefaultQueryRetryFn, setQueryDevtoolsAppInfo, setQueryDevtoolsArmedMocksScope, setQueryDevtoolsFault, setQueryDevtoolsFaultResolver, setQueryDevtoolsFaultsScope, setQueryDevtoolsFormLinksFactory, setQueryDevtoolsMockResolver, setQueryDevtoolsOverridePersistence, setQueryDevtoolsOverridesFactory, setQueryDevtoolsOverridesScope, setQueryDevtoolsRegistrar, setQueryDevtoolsResponseHistory, setQueryDevtoolsSchemaLoader, setQueryDevtoolsSettings, setQueryDevtoolsStatsFactory, setQueryDevtoolsTokenPayloadPatcher, setQueryDevtoolsTokenTtl, setupQueryDependencies, setupQueryExecuteState, setupQueryState, shouldAutoExecuteGqlQuery, shouldAutoExecuteQuery, shouldCacheQuery, shouldRetryRequest, silenceMissingWithArgsFeatureErrorUsedButWithArgsPresent, skipPaginationPageParamCalculator, smartDuplicateArrayItem, snapshotQueryDevtoolsHandle, sortQueryField, splitQueryConfig, stringArrayQueryField, stringifyQueryRouteParts, sumQueryDevtoolsStats, suppressNextQueryStackDevtools, switchQueryCollectionState, switchQueryState, symfonyQueryErrorParser, takeUntilResponse, toQuerySignal, toQuerySubject, tokensNotAvailableInsideAuthAndExec, tombstoneOf, 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, withLongPolling, withLongPollingUsedOnUnsupportedHttpMethod, withLongPollingUsedWithPolling, withMultiTabSync, withPageResetOnError, withPersistentAuth, withPolling, withPollingUsedOnUnsupportedHttpMethod, withQueryDevtoolsOverridePersistence, withQueryPersistence, withRefreshQuery, withResponseUpdate, withSuccessHandling, withSymfonyErrors, withTokenExpirationWarning, withTokenRevocation, withTracking, wrapAsObservableSignal, wrapQuerySyncMessage, writeQueryDevtoolsStore };
15261
+ export { AuthBearerRefreshStrategy, BasicAuthProvider, BearerAuthFeatureType, BooleanArrayQueryField, 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_QUERY_BATCH_TOMBSTONES, MAX_QUERY_DEVTOOLS_TOMBSTONES, MAX_UNUSED_ENTRIES, NumberArrayQueryField, ParseHttpErrorCodeToMessageDePipe, ParseHttpErrorCodeToMessageEnPipe, ParseHttpErrorCodeToTitleDePipe, ParseHttpErrorCodeToTitleEnPipe, QUERY_CLIENT_DEVTOOLS_TOKEN, QUERY_DEVTOOLS_FAULT_STATUSES, QUERY_DEVTOOLS_TOKEN_TTL_LIMIT, QUERY_PERSISTENCE_STORE_VERSION, QUERY_SYNC_PROTOCOL_VERSION, QUERY_VERSION, QueryClientFeatureType, QueryDevtoolsComponent, QueryDirective, QueryFeatureType, QueryField, QueryForm, QueryRuntimeErrorCode, QueryShortNamePipe, QueryStateType, QueryStore, SERVER_ERROR_KIND, SERVER_VIOLATION_ERROR_KIND, SPEED_BUFFER_TIME_IN_MS, SearchQueryField, SortQueryField, StringArrayQueryField, V2BearerAuthProvider, V2Query, V2QueryClient, V2QueryCreator, addQueryContainerHandling, applyQueryDevtoolsOverrides, applyQueryDevtoolsTokenTtl, applyQueryFeatures, armAllQueryDevtoolsMocks, armQueryDevtoolsMock, armQueryDevtoolsOverrideTransfer, authExtractTokensResponseMissingAccessToken, authExtractTokensResponseMissingRefreshToken, authExtractTokensResponseNotObject, authProviderFeatureUsedMultipleTimes, booleanArrayQueryField, buildQueryCacheKey, buildQueryString, buildRoute, buildTimestampFromSeconds, canOverrideQueryDevtoolsTokenTtl, castQueryCreatorTypes, circularQueryDependency, circularQueryDependencyChecker, clearQueryDevtoolsArmedMocks, clearQueryDevtoolsFaults, clearQueryDevtoolsMockStore, clearQueryDevtoolsOverrideStore, clearQueryDevtoolsStore, clearQueryDevtoolsTokenTtl, clearQueryDevtoolsTombstones, clearRestoredQueryDevtoolsOverrides, collectLeafPaths, collectQueryDevtoolsSchemaComponents, collectQueryFormLinks, computeQueryAuthHeader, computeQueryBody, computeQueryHeaders, computeQueryMethod, computeQueryQueryParams, consumeSuppressQueryStackDevtools, contentfulGqlLikePaginationAdapter, countUnresolvedQueryDevtoolsOverrides, createAuthGuard, createBaseQuery, createBaseQueryCreator, createBearerAuthProvider, createDefaultRetryFn, createDeleteQuery, createExecuteFn, createGetQuery, createGqlExecuteFn, createGqlMutationViaGet, createGqlMutationViaPost, createGqlQuery, createGqlQueryCreator, createGqlQueryViaGet, createGqlQueryViaPost, createHttpRequest, createIndexedDbQueryPersistenceAdapter, createInfinityQueryConfig, createLegacyQueryCreator, createNoopQueryPersistenceAdapter, createPagedQueryStack, createPatchQuery, createPersistentAuthFeature, createPostQuery, createPutQuery, createQuery, createQueryBatch, createQueryClient, createQueryCollection, createQueryCollectionSignal, createQueryCollectionSubject, createQueryContext, createQueryCreator, createQueryDevtoolsFormLinks, createQueryDevtoolsFormLinksRecorder, createQueryDevtoolsOverrides, createQueryDevtoolsOverridesRecorder, createQueryDevtoolsStats, createQueryDevtoolsStatsRecorder, createQueryErrorResponse, createQueryFeature, createQueryInvalidationFilter, createQueryKeyLockManager, createQueryObject, createQueryPersistenceEngine, createQueryRepository, createQuerySnapshotFn, createQueryStack, createQuerySubmission, createQuerySyncEngine, createQuerySyncTransport, createSecureDeleteQuery, createSecureExecuteFactory, createSecureExecuteFn, createSecureGetQuery, createSecureGqlExecuteFn, createSecureGqlMutationViaGet, createSecureGqlMutationViaPost, createSecureGqlQuery, createSecureGqlQueryCreator, createSecureGqlQueryViaGet, createSecureGqlQueryViaPost, createSecurePatchQuery, createSecurePostQuery, createSecurePutQuery, createSecureQuery, createSecureQueryCreator, createTrackingFeature, createWebSocketClient, currentQueryDevtoolsBatch, dateArrayQueryField, dateQueryField, decryptBearer, deepFreeze, def, defineQueryForm, deleteQueryDevtoolsMock, describeQueryDevtoolsFeatures, detectContentTypeHeader, detectPaginationShape, dynLikePaginationAdapter, effectComputed, emptyQueryArgs, ethletePaginationAdapter, executeUntilSettled, extractExpiresInSeconds, extractFormViolations, extractHtmlErrorMessage, extractQuery, fakePaginationAdapter, filterFailure, filterNull, filterQueryStates, filterSuccess, forEachHeader, formatQueryDevtoolsDuration, generateQueryDevtoolsNumberPreset, generateQueryDevtoolsSampleNumber, generateQueryDevtoolsStringPreset, getBearerAuthProviderName, getDefaultHeaders, getQueryClientName, getQueryFeatureUsage, getResponseUrl, ggLikePaginationAdapter, gql, gqlDataPropertyMissingInResponse, hasHeader, hasQueryDevtoolsOverridesAtPath, htmlErrorPayload, htmlQueryErrorParser, ignoreAutoRefresh, initQueryDevtoolsFaults, initQueryDevtoolsMocks, initQueryDevtoolsOverridePersistence, initQueryDevtoolsSettings, injectInfinityQueryResponseDelay, injectQueryContext, insertFrom, invalidBaseRouteError, invalidBodyError, invalidRouteError, invalidStateInsideSecureExecuteFactory, isAutoRefresh, isBasicAuthProvider, isBearerAuthProvider, isClassValidatorError, isCreateGqlQueryOptions, isCustomHeaderAuthProvider, isDateShapedLeaf, isEmptyString, isGqlQueryConfig, isHtmlErrorPayload, isLegacyQuery, isNaN$1 as isNaN, isPageOutOfRangeError, isQuery, isQueryCollection, isQueryDevtoolsEnabled, isQueryDevtoolsFaultArmed, isQueryDevtoolsRequestInterceptionEnabled, isQueryErrorResponse, isQueryStateCancelled, isQueryStateFailure, isQueryStateLoading, isQueryStatePrepared, isQueryStateSuccess, isRequestError, isSymfonyError, isSymfonyFormViolationListError, isSymfonyListError, isSymfonyPagerfantaOutOfRangeError, isUnderInvalidatedUrl, legacyPrepareFallbackInjector, legacyPrepareWithoutInjectionContext, loadQueryDevtoolsSchema, mapToPaginated, mapViolationsToFormErrors, matchesQueryDevtoolsMockPattern, matchesQueryDevtoolsMockQuery, maybeExecute, measureQueryDevtoolsPayload, mergeHeaders, nestedEffect, noteQueryFormRead, numberArrayQueryField, pagedQueryStackNextPageCalledWithoutPreviousPage, pagedQueryStackPageBiggerThanTotalPages, pagedQueryStackPreviousPageCalledButAlreadyAtFirstPage, paginatedEntityValueUpdater, parseAllXhrResponseHeaders, parseHttpErrorCodeToMessageDe, parseHttpErrorCodeToMessageEn, parseHttpErrorCodeToTitleDe, parseHttpErrorCodeToTitleEn, parseQueryDevtoolsOverrideTransfer, parseQueryRoute, patchQueryDevtoolsTokenPayload, pathParamsMissingInRouteFunctionError, provideInfinityQueryResponseDelay, provideLegacyPrepareFallback, provideQueryClientForDevtools, provideQueryDevtools, queryArrayComputed, queryBatchAlreadyRunning, queryBatchWithArgsUsed, queryClientFeatureUsedMultipleTimes, queryComputed, queryComputedTillTruthy, queryDevtoolsAbout, queryDevtoolsArmedMocks, queryDevtoolsArmedMocksRestored, queryDevtoolsEntries, queryDevtoolsFaults, queryDevtoolsFaultsRestored, queryDevtoolsFnDetail, queryDevtoolsMockId, queryDevtoolsMocks, queryDevtoolsOverridePersistence, queryDevtoolsRequestPath, queryDevtoolsResponseHistory, queryDevtoolsRestoredOverridesScope, queryDevtoolsSchemaNames, queryDevtoolsSchemaRoutes, queryDevtoolsSchemaState, queryDevtoolsSettings, queryDevtoolsStorage, queryDevtoolsTokenTtls, queryErrorMessage, queryErrorMessages, queryExecute, queryFeatureUsedMultipleTimes, queryField, querySequence, querySequenceAlreadyRunning, queryStackTotalQueriesAndExpectedQueriesMismatch, queryStackWithArgsUsed, queryStackWithResponseUpdateUsed, queryStateAlreadyHasKeyError, queryStateCannotTransformError, queryStateDoesNotContainKeyError, queryStateErrorSignal, queryStateLoadingSignal, queryStateResponseSignal, queryStateSignal, readQueryDevtoolsStore, registerEthleteVersion, registerQueryDevtoolsEntry, registerQueryErrorParser, removeFrom, request, resetExecuteState, resetPageOnError, resolveInvalidationUrl, resolveQueryDevtoolsFault, resolveQueryDevtoolsFaultForAttempt, resolveQueryDevtoolsMock, resolveQueryDevtoolsMockForAttempt, restoredQueryDevtoolsOverrides, runDefaultQueryRetry, runInQueryDevtoolsBatch, runQueryErrorParsers, saveQueryDevtoolsMock, searchQueryField, seedQueryDevtoolsSchemaBody, seedQueryDevtoolsSchemaRoute, serializeBody, serializeQueryDevtoolsOverrideTransfer, setDefaultQueryRetryFn, setQueryDevtoolsAppInfo, setQueryDevtoolsArmedMocksScope, setQueryDevtoolsFault, setQueryDevtoolsFaultResolver, setQueryDevtoolsFaultsScope, setQueryDevtoolsFormLinksFactory, setQueryDevtoolsMockResolver, setQueryDevtoolsOverridePersistence, setQueryDevtoolsOverridesFactory, setQueryDevtoolsOverridesScope, setQueryDevtoolsRegistrar, setQueryDevtoolsResponseHistory, setQueryDevtoolsSchemaLoader, setQueryDevtoolsSettings, setQueryDevtoolsStatsFactory, setQueryDevtoolsTokenPayloadPatcher, setQueryDevtoolsTokenTtl, setupQueryDependencies, setupQueryExecuteState, setupQueryState, shouldAutoExecuteGqlQuery, shouldAutoExecuteQuery, shouldCacheQuery, shouldRetryRequest, silenceMissingWithArgsFeatureErrorUsedButWithArgsPresent, skipPaginationPageParamCalculator, smartDuplicateArrayItem, snapshotQueryDevtoolsHandle, sortQueryField, splitQueryConfig, stringArrayQueryField, stringifyQueryRouteParts, sumQueryDevtoolsStats, suppressNextQueryStackDevtools, switchQueryCollectionState, switchQueryState, symfonyQueryErrorParser, takeUntilResponse, toQuerySignal, toQuerySubject, tokensNotAvailableInsideAuthAndExec, tombstoneOf, 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, withLongPolling, withLongPollingUsedOnUnsupportedHttpMethod, withLongPollingUsedWithPolling, withMultiTabSync, withPageResetOnError, withPersistentAuth, withPolling, withPollingUsedOnUnsupportedHttpMethod, withQueryDevtoolsOverridePersistence, withQueryPersistence, withRefreshQuery, withResponseUpdate, withSuccessHandling, withSymfonyErrors, withTokenExpirationWarning, withTokenRevocation, withTracking, wrapAsObservableSignal, wrapQuerySyncMessage, writeQueryDevtoolsStore };
15018
15262
  //# sourceMappingURL=ethlete-query.mjs.map