@ethlete/query 6.0.0-next.32 → 6.0.0-next.33
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +30 -0
- package/fesm2022/ethlete-query.mjs +1286 -79
- package/fesm2022/ethlete-query.mjs.map +1 -1
- package/package.json +1 -1
- package/types/ethlete-query.d.ts +468 -10
|
@@ -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, makeEnvironmentProviders, assertInInjectionContext, provideEnvironmentInitializer, ChangeDetectorRef, ViewContainerRef, TemplateRef, Input, Directive, input, Pipe, ViewEncapsulation, Component, NgZone } 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
3
|
import { toObservable, takeUntilDestroyed, toSignal } from '@angular/core/rxjs-interop';
|
|
4
4
|
import { UrlTree, Router, RedirectCommand, ActivatedRoute } from '@angular/router';
|
|
5
|
-
import { filter, take, map, defer, from, Subject, Subscription, throwError, timer, switchMap, tap, retry, catchError,
|
|
6
|
-
import { defineStaticProvider, RuntimeError, clamp, randomId, defineRootProvider, isObject, injectUnsavedChangesCoordinator, injectRoute, getCookie, getDomain, deleteCookie, setCookie, createDestroy, computedTillTruthy, defineProvider, toProvideFn, toInjectFn, nextFrame, injectQueryParams, injectQueryParamChanges, clone, equal, ET_PROPERTY_REMOVED, previousSignalValue } from '@ethlete/core';
|
|
7
|
-
import { HttpClient, HttpEventType, HttpErrorResponse, HttpHeaders, HttpStatusCode } from '@angular/common/http';
|
|
5
|
+
import { filter, take, map, defer, from, Subject, Subscription, throwError, of, timer, switchMap, tap, retry, catchError, EMPTY, takeWhile, merge, firstValueFrom, concatMap, fromEvent, throttleTime, interval, combineLatest, Observable, takeUntil, 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';
|
|
7
|
+
import { HttpClient, HttpEventType, HttpErrorResponse, HttpResponse, HttpHeaders, HttpStatusCode } from '@angular/common/http';
|
|
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';
|
|
@@ -232,6 +232,7 @@ const noteQueryFormRead = (id) => {
|
|
|
232
232
|
formLinkSink?.add(id);
|
|
233
233
|
};
|
|
234
234
|
let faultResolver = null;
|
|
235
|
+
let mockResolver = null;
|
|
235
236
|
/**
|
|
236
237
|
* Installs the fault resolver. Called by `provideQueryDevtools()`; nothing else may call it.
|
|
237
238
|
* @internal
|
|
@@ -240,11 +241,24 @@ const setQueryDevtoolsFaultResolver = (fn) => {
|
|
|
240
241
|
faultResolver = fn;
|
|
241
242
|
};
|
|
242
243
|
/**
|
|
243
|
-
*
|
|
244
|
-
* fault per attempt, so a request in an app without devtools keeps the pipeline it always had.
|
|
244
|
+
* Installs the mock resolver. Called by `provideQueryDevtools()`; nothing else may call it.
|
|
245
245
|
* @internal
|
|
246
246
|
*/
|
|
247
|
-
const
|
|
247
|
+
const setQueryDevtoolsMockResolver = (fn) => {
|
|
248
|
+
mockResolver = fn;
|
|
249
|
+
};
|
|
250
|
+
/**
|
|
251
|
+
* Whether the devtools can do anything to a request at all - inject a fault, or serve a mock instead of
|
|
252
|
+
* sending it. Checked before building the wrapper that resolves both per attempt, so a request in an app
|
|
253
|
+
* without devtools keeps the pipeline it always had.
|
|
254
|
+
* @internal
|
|
255
|
+
*/
|
|
256
|
+
const isQueryDevtoolsRequestInterceptionEnabled = () => faultResolver !== null || mockResolver !== null;
|
|
257
|
+
/**
|
|
258
|
+
* The mock to serve one upcoming attempt instead of sending it, or `null` to let it through.
|
|
259
|
+
* @internal
|
|
260
|
+
*/
|
|
261
|
+
const resolveQueryDevtoolsMock = (target) => mockResolver?.(target) ?? null;
|
|
248
262
|
/**
|
|
249
263
|
* Resolves the fault to apply to one upcoming attempt, or `null` when nothing is armed. Consumes a
|
|
250
264
|
* `failNext` budget, so call it exactly once per attempt.
|
|
@@ -1609,6 +1623,11 @@ const createQueryErrorResponse = (error) => {
|
|
|
1609
1623
|
};
|
|
1610
1624
|
|
|
1611
1625
|
const SPEED_BUFFER_TIME_IN_MS = 2000;
|
|
1626
|
+
/**
|
|
1627
|
+
* The `statusText` of a response the devtools served instead of sending the request. It is what a log, a
|
|
1628
|
+
* network-less error and a copied report all read, so it has to say where the response came from.
|
|
1629
|
+
*/
|
|
1630
|
+
const MOCKED_STATUS_TEXT = 'Served by the query devtools';
|
|
1612
1631
|
const createHttpRequest = (options) => {
|
|
1613
1632
|
let currentStreamSubscription = Subscription.EMPTY;
|
|
1614
1633
|
const { args, clientOptions, dependencies } = options;
|
|
@@ -1680,6 +1699,36 @@ const createHttpRequest = (options) => {
|
|
|
1680
1699
|
* same tick it was started in, and settling synchronously inside `execute()` would let an injected
|
|
1681
1700
|
* failure land before the caller has finished wiring the request up.
|
|
1682
1701
|
*/
|
|
1702
|
+
/**
|
|
1703
|
+
* The request, or the response a devtools mock has armed for this route instead of it. A mock bypasses
|
|
1704
|
+
* the interceptor chain by construction - nothing is sent - so a mocked secure route never exercises
|
|
1705
|
+
* the token flow, which the panel says on the row.
|
|
1706
|
+
*/
|
|
1707
|
+
const sendOrMock = (headers) => {
|
|
1708
|
+
const mock = resolveQueryDevtoolsMock({
|
|
1709
|
+
clientName: options.clientName ?? '',
|
|
1710
|
+
method: options.method,
|
|
1711
|
+
url: options.fullPath,
|
|
1712
|
+
});
|
|
1713
|
+
if (!mock)
|
|
1714
|
+
return send(headers);
|
|
1715
|
+
const settle$ = mock.status >= 400
|
|
1716
|
+
? throwError(() => new HttpErrorResponse({
|
|
1717
|
+
status: mock.status,
|
|
1718
|
+
statusText: MOCKED_STATUS_TEXT,
|
|
1719
|
+
url: options.fullPath,
|
|
1720
|
+
error: mock.body,
|
|
1721
|
+
}))
|
|
1722
|
+
: of(new HttpResponse({
|
|
1723
|
+
body: mock.body,
|
|
1724
|
+
status: mock.status,
|
|
1725
|
+
statusText: MOCKED_STATUS_TEXT,
|
|
1726
|
+
url: options.fullPath,
|
|
1727
|
+
}));
|
|
1728
|
+
// Through `timer` even at zero latency, for the same reason a faulted attempt is: settling
|
|
1729
|
+
// synchronously inside `execute()` would land the response before the caller has wired the request up.
|
|
1730
|
+
return timer(mock.latencyMs).pipe(switchMap(() => settle$));
|
|
1731
|
+
};
|
|
1683
1732
|
const sendWithFaults = (headers) => defer(() => {
|
|
1684
1733
|
const fault = resolveQueryDevtoolsFault({
|
|
1685
1734
|
clientName: options.clientName ?? '',
|
|
@@ -1688,10 +1737,10 @@ const createHttpRequest = (options) => {
|
|
|
1688
1737
|
});
|
|
1689
1738
|
lastAttemptFaulted = !!fault && fault.status !== null;
|
|
1690
1739
|
if (!fault)
|
|
1691
|
-
return
|
|
1740
|
+
return sendOrMock(headers);
|
|
1692
1741
|
const { status } = fault;
|
|
1693
1742
|
const attempt$ = status === null
|
|
1694
|
-
?
|
|
1743
|
+
? sendOrMock(headers)
|
|
1695
1744
|
: throwError(() => new HttpErrorResponse({
|
|
1696
1745
|
status,
|
|
1697
1746
|
statusText: 'Injected by the query devtools',
|
|
@@ -1702,7 +1751,7 @@ const createHttpRequest = (options) => {
|
|
|
1702
1751
|
});
|
|
1703
1752
|
const createStream = () => {
|
|
1704
1753
|
const headers = resolveHeaders();
|
|
1705
|
-
const source$ =
|
|
1754
|
+
const source$ = isQueryDevtoolsRequestInterceptionEnabled() ? sendWithFaults(headers) : send(headers);
|
|
1706
1755
|
return source$.pipe(tap((event) => updateState(event)), retry({
|
|
1707
1756
|
delay: (error, retryCount) => {
|
|
1708
1757
|
const retryOptions = { error, retryCount };
|
|
@@ -3812,6 +3861,8 @@ const createSecureExecuteFactory = (options) => {
|
|
|
3812
3861
|
let sessionRestartSubscription = Subscription.EMPTY;
|
|
3813
3862
|
let hasExecuted = false;
|
|
3814
3863
|
let lastExecuteArgs;
|
|
3864
|
+
/** The access token the last request went out with - what the 401 retry below compares against. */
|
|
3865
|
+
let lastRequestedWithToken = null;
|
|
3815
3866
|
const reset = () => {
|
|
3816
3867
|
authQuerySubscription.unsubscribe();
|
|
3817
3868
|
authQuerySubscription = Subscription.EMPTY;
|
|
@@ -3874,15 +3925,14 @@ const createSecureExecuteFactory = (options) => {
|
|
|
3874
3925
|
if (!accessToken) {
|
|
3875
3926
|
throw tokensNotAvailableInsideAuthAndExec();
|
|
3876
3927
|
}
|
|
3877
|
-
|
|
3878
|
-
const headers = headerProvider();
|
|
3928
|
+
lastRequestedWithToken = accessToken;
|
|
3879
3929
|
options.transformAuthAndExec({
|
|
3880
3930
|
...executeArgs,
|
|
3881
3931
|
args: {
|
|
3882
3932
|
...(args ?? {}),
|
|
3883
|
-
headers: headerProvider,
|
|
3933
|
+
headers: headerProvider,
|
|
3884
3934
|
},
|
|
3885
|
-
},
|
|
3935
|
+
}, executeState);
|
|
3886
3936
|
};
|
|
3887
3937
|
// The auth query completing and the access token being populated happen on two
|
|
3888
3938
|
// different reactive timelines: the token is set by a separate token-extraction
|
|
@@ -3919,10 +3969,14 @@ const createSecureExecuteFactory = (options) => {
|
|
|
3919
3969
|
tokenWaitSubscription = Subscription.EMPTY;
|
|
3920
3970
|
sessionRestartSubscription.unsubscribe();
|
|
3921
3971
|
sessionRestartSubscription = Subscription.EMPTY;
|
|
3972
|
+
// Retrying with the token that just produced the 401 would 401 again, and that 401 asks for
|
|
3973
|
+
// another refresh - an endless refresh/retry loop for as long as the server keeps handing out
|
|
3974
|
+
// tokens it rejects. A refresh that did not change the access token is therefore not a reason to
|
|
3975
|
+
// retry; the subscription stays open, so a later one that does change it still is.
|
|
3922
3976
|
tokenRefreshSubscription = options.authProvider.afterTokenRefresh$
|
|
3923
3977
|
.pipe(filter(() => {
|
|
3924
3978
|
const currentError = options.state.error();
|
|
3925
|
-
return currentError?.code === 401;
|
|
3979
|
+
return currentError?.code === 401 && options.authProvider.accessToken() !== lastRequestedWithToken;
|
|
3926
3980
|
}), take(1), tap(() => exec(execArgsWithDefaults)))
|
|
3927
3981
|
.subscribe();
|
|
3928
3982
|
// `setTokens()` seeds an auth session from outside the query registry (an SSO/OIDC callback, a
|
|
@@ -3943,19 +3997,23 @@ const createSecureExecuteFactory = (options) => {
|
|
|
3943
3997
|
injector: options.deps.injector,
|
|
3944
3998
|
}).pipe(switchMap((latestQuery) => {
|
|
3945
3999
|
if (!latestQuery)
|
|
3946
|
-
return
|
|
4000
|
+
return EMPTY;
|
|
3947
4001
|
const query = latestQuery.snapshot;
|
|
3948
|
-
return toObservable(query.isAlive, { injector: options.deps.injector }).pipe(
|
|
3949
|
-
|
|
3950
|
-
|
|
3951
|
-
|
|
3952
|
-
|
|
3953
|
-
|
|
3954
|
-
|
|
3955
|
-
|
|
3956
|
-
|
|
3957
|
-
|
|
3958
|
-
|
|
4002
|
+
return toObservable(query.isAlive, { injector: options.deps.injector }).pipe(filter((isAlive) => !isAlive), filter(() => !!query.error() || !!query.response()), map(() => query));
|
|
4003
|
+
}), tap((query) => {
|
|
4004
|
+
if (query.error()) {
|
|
4005
|
+
error(query);
|
|
4006
|
+
}
|
|
4007
|
+
else {
|
|
4008
|
+
authAndExecWhenTokenReady(execArgsWithDefaults);
|
|
4009
|
+
}
|
|
4010
|
+
}),
|
|
4011
|
+
// Stop at the first auth query that hands a session over. Every later one is a token refresh,
|
|
4012
|
+
// which settles before its tokens are applied - re-running here would send the token that just
|
|
4013
|
+
// expired; `afterTokenRefresh$` above is what re-runs a query after a refresh. A *failed* auth
|
|
4014
|
+
// query keeps the wait open, so a query that mounted during a failed auto-login still runs
|
|
4015
|
+
// once the user logs in for real.
|
|
4016
|
+
takeWhile((query) => !!query.error()));
|
|
3959
4017
|
// A `setTokens()` call that lands while this is already waiting (e.g. the SSO redirect
|
|
3960
4018
|
// resolves after this secure query mounted) never touches `latestExecutedQuery` either -
|
|
3961
4019
|
// wake up on `executionState` becoming a token seed too.
|
|
@@ -3986,13 +4044,12 @@ const createSecureExecuteFn = (executeOptions) => {
|
|
|
3986
4044
|
deps: executeOptions.deps,
|
|
3987
4045
|
state: executeOptions.state,
|
|
3988
4046
|
autoExecutes: shouldAutoExecuteQuery(executeOptions.creatorInternals.method) && !executeOptions.queryConfig.onlyManualExecution,
|
|
3989
|
-
transformAuthAndExec: (executeArgs,
|
|
4047
|
+
transformAuthAndExec: (executeArgs, executeState) => {
|
|
3990
4048
|
const { options: runOptions } = executeArgs ?? {};
|
|
3991
|
-
const updatedArgs = { ...(executeArgs?.args ?? {}), headers };
|
|
3992
4049
|
queryExecute({
|
|
3993
4050
|
executeOptions: executeOptions,
|
|
3994
4051
|
executeState,
|
|
3995
|
-
args:
|
|
4052
|
+
args: executeArgs?.args ?? {},
|
|
3996
4053
|
options: runOptions,
|
|
3997
4054
|
isSecure: true,
|
|
3998
4055
|
});
|
|
@@ -4668,6 +4725,7 @@ const createBearerAuthProviderImpl = (config) => {
|
|
|
4668
4725
|
name: config.name,
|
|
4669
4726
|
applyTokens,
|
|
4670
4727
|
logout,
|
|
4728
|
+
sessionEndCause: sessionEndCause.asReadonly(),
|
|
4671
4729
|
});
|
|
4672
4730
|
const querySetupContext = {
|
|
4673
4731
|
accessToken,
|
|
@@ -4743,6 +4801,13 @@ const createBearerAuthProviderImpl = (config) => {
|
|
|
4743
4801
|
};
|
|
4744
4802
|
const createBearerAuthProvider = (config) => defineRootProvider(() => createBearerAuthProviderImpl(config), { name: `BearerAuthProvider_${config.name}` });
|
|
4745
4803
|
|
|
4804
|
+
/** How long a scheduled refresh waits before retrying an attempt that could not run yet. */
|
|
4805
|
+
const rescheduleDelayMs = 5000;
|
|
4806
|
+
/**
|
|
4807
|
+
* How often a scheduled refresh may re-arm before it waits for a new token pair. A tab that keeps
|
|
4808
|
+
* declining is either not the leader or busy issuing tokens; either way a 401 still forces a refresh.
|
|
4809
|
+
*/
|
|
4810
|
+
const maxRescheduleAttempts = 5;
|
|
4746
4811
|
const withAuthenticationQuery = (key, config, setup) => ({
|
|
4747
4812
|
_type: 'authQuery',
|
|
4748
4813
|
key,
|
|
@@ -4790,25 +4855,44 @@ const withRefreshQuery = (key, config) => {
|
|
|
4790
4855
|
const executeRefresh = (reason) => {
|
|
4791
4856
|
const currentRefreshToken = context.refreshToken();
|
|
4792
4857
|
if (!currentRefreshToken)
|
|
4793
|
-
return;
|
|
4858
|
+
return 'noToken';
|
|
4794
4859
|
if (!context.isLeader()) {
|
|
4795
4860
|
// Only the leader may spend a single-use refresh token, but the event is still real - hand it
|
|
4796
4861
|
// over instead of dropping it, or this tab waits out the leader's timer with a dead token.
|
|
4797
4862
|
context.refreshCoordination?.request();
|
|
4798
|
-
return;
|
|
4863
|
+
return 'delegated';
|
|
4799
4864
|
}
|
|
4800
4865
|
// Any token-issuing execution, not just another refresh: a login already in flight is about to
|
|
4801
4866
|
// issue a token pair of its own, and the refresh token this would spend belongs to the session
|
|
4802
4867
|
// that login is replacing.
|
|
4803
4868
|
if (context.hasTokenIssuingExecutionInFlight())
|
|
4804
|
-
return;
|
|
4869
|
+
return 'busy';
|
|
4805
4870
|
if (reason === 'scheduled') {
|
|
4806
4871
|
const now = Date.now();
|
|
4807
4872
|
if (now - lastRefreshTime < minRefreshInterval)
|
|
4808
|
-
return;
|
|
4873
|
+
return 'throttled';
|
|
4809
4874
|
lastRefreshTime = now;
|
|
4810
4875
|
}
|
|
4811
4876
|
refreshQuery().execute(buildArgs(currentRefreshToken), { triggeredBy: 'token-refresh' });
|
|
4877
|
+
return 'executed';
|
|
4878
|
+
};
|
|
4879
|
+
/**
|
|
4880
|
+
* How long a scheduled refresh that did not spend the refresh token waits before trying again.
|
|
4881
|
+
* `null` re-arms nothing, because only a new token pair can change the outcome - and that restarts
|
|
4882
|
+
* the schedule anyway.
|
|
4883
|
+
*/
|
|
4884
|
+
const retryScheduledIn = (attempt) => {
|
|
4885
|
+
switch (attempt) {
|
|
4886
|
+
case 'executed':
|
|
4887
|
+
case 'noToken':
|
|
4888
|
+
return null;
|
|
4889
|
+
case 'throttled':
|
|
4890
|
+
return Math.max(minRefreshInterval - (Date.now() - lastRefreshTime), rescheduleDelayMs);
|
|
4891
|
+
case 'busy':
|
|
4892
|
+
return rescheduleDelayMs;
|
|
4893
|
+
case 'delegated':
|
|
4894
|
+
return minRefreshInterval;
|
|
4895
|
+
}
|
|
4812
4896
|
};
|
|
4813
4897
|
const calculateRefreshBuffer = (tokenLifetimeMs) => {
|
|
4814
4898
|
if (typeof config.refreshStrategy === 'number') {
|
|
@@ -4820,11 +4904,8 @@ const withRefreshQuery = (key, config) => {
|
|
|
4820
4904
|
const calculatedBuffer = tokenLifetimeMs * (1 - percentage);
|
|
4821
4905
|
return Math.max(minBufferMs, Math.min(maxBufferMs, calculatedBuffer));
|
|
4822
4906
|
};
|
|
4823
|
-
|
|
4824
|
-
|
|
4825
|
-
.pipe(switchMap((token) => {
|
|
4826
|
-
if (!token)
|
|
4827
|
-
return of(null);
|
|
4907
|
+
/** When this access token is due to be refreshed, or `null` if it cannot be scheduled at all. */
|
|
4908
|
+
const scheduledRefreshDelay = (token) => {
|
|
4828
4909
|
try {
|
|
4829
4910
|
const bearerDataValue = context.bearerDecryptFn ? context.bearerDecryptFn(token) : decryptBearer(token);
|
|
4830
4911
|
const expiresIn = bearerDataValue?.[expiresInPropertyName];
|
|
@@ -4832,36 +4913,40 @@ const withRefreshQuery = (key, config) => {
|
|
|
4832
4913
|
if (isDevMode()) {
|
|
4833
4914
|
console.warn(`Token does not contain valid ${expiresInPropertyName} property for auto-refresh`);
|
|
4834
4915
|
}
|
|
4835
|
-
return
|
|
4916
|
+
return null;
|
|
4836
4917
|
}
|
|
4837
|
-
const
|
|
4838
|
-
const now = Date.now();
|
|
4839
|
-
const tokenLifetimeMs = expiresAtMs - now;
|
|
4918
|
+
const tokenLifetimeMs = expiresIn * 1000 - Date.now();
|
|
4840
4919
|
if (tokenLifetimeMs <= 0) {
|
|
4841
|
-
if (refreshIfExpired)
|
|
4842
|
-
|
|
4843
|
-
|
|
4844
|
-
|
|
4845
|
-
return of(true);
|
|
4920
|
+
if (!refreshIfExpired)
|
|
4921
|
+
return null;
|
|
4922
|
+
if (isDevMode()) {
|
|
4923
|
+
console.warn('Token is already expired, triggering immediate refresh');
|
|
4846
4924
|
}
|
|
4847
|
-
return
|
|
4925
|
+
return 0;
|
|
4848
4926
|
}
|
|
4849
|
-
|
|
4850
|
-
const timeUntilRefresh = tokenLifetimeMs - refreshBufferMs;
|
|
4851
|
-
if (timeUntilRefresh <= 0) {
|
|
4852
|
-
return of(true);
|
|
4853
|
-
}
|
|
4854
|
-
return timer(timeUntilRefresh).pipe(tap(() => true));
|
|
4927
|
+
return Math.max(tokenLifetimeMs - calculateRefreshBuffer(tokenLifetimeMs), 0);
|
|
4855
4928
|
}
|
|
4856
4929
|
catch {
|
|
4857
|
-
return
|
|
4930
|
+
return null;
|
|
4858
4931
|
}
|
|
4932
|
+
};
|
|
4933
|
+
/**
|
|
4934
|
+
* The scheduled refresh, re-armed for as long as it keeps declining to spend the refresh token.
|
|
4935
|
+
* Without the re-arm a single declined attempt strands the session on a token nothing renews until
|
|
4936
|
+
* a request fails with a 401 - by which time the refresh token may be gone too.
|
|
4937
|
+
*/
|
|
4938
|
+
const scheduledRefresh = (dueInMs, attemptsLeft) => (dueInMs <= 0 ? of(0) : timer(dueInMs)).pipe(concatMap(() => {
|
|
4939
|
+
const retryInMs = retryScheduledIn(executeRefresh('scheduled'));
|
|
4940
|
+
return retryInMs === null || attemptsLeft <= 0 ? EMPTY : scheduledRefresh(retryInMs, attemptsLeft - 1);
|
|
4941
|
+
}));
|
|
4942
|
+
toObservable(context.accessToken)
|
|
4943
|
+
.pipe(switchMap((token) => {
|
|
4944
|
+
if (!token)
|
|
4945
|
+
return EMPTY;
|
|
4946
|
+
const dueInMs = scheduledRefreshDelay(token);
|
|
4947
|
+
return dueInMs === null ? EMPTY : scheduledRefresh(dueInMs, maxRescheduleAttempts);
|
|
4859
4948
|
}), takeUntilDestroyed())
|
|
4860
|
-
.subscribe(
|
|
4861
|
-
if (shouldRefresh) {
|
|
4862
|
-
executeRefresh('scheduled');
|
|
4863
|
-
}
|
|
4864
|
-
});
|
|
4949
|
+
.subscribe();
|
|
4865
4950
|
context.refreshCoordination?.requests$.pipe(takeUntilDestroyed()).subscribe(() => executeRefresh('unauthorized'));
|
|
4866
4951
|
const onRefreshFailure = config.onRefreshFailure ??
|
|
4867
4952
|
(({ error, logout }) => {
|
|
@@ -5176,6 +5261,12 @@ const resetEncryptionKey = () => {
|
|
|
5176
5261
|
}
|
|
5177
5262
|
};
|
|
5178
5263
|
|
|
5264
|
+
/**
|
|
5265
|
+
* How an incoming logout reads in the receiving tab: a deliberate logout elsewhere is this tab's
|
|
5266
|
+
* `otherTab`, while a session that ended on its own ended for every tab, so that cause is carried
|
|
5267
|
+
* through. A message without one comes from a tab running an older version.
|
|
5268
|
+
*/
|
|
5269
|
+
const incomingCause = (cause) => !cause || cause === 'user' ? 'otherTab' : cause;
|
|
5179
5270
|
/**
|
|
5180
5271
|
* The channel a provider syncs on unless the consumer names one. Carries the provider's name, so two
|
|
5181
5272
|
* providers reachable from the same origin do not push each other's tokens around.
|
|
@@ -5210,7 +5301,7 @@ const setupMultiTabSync = (config, context) => {
|
|
|
5210
5301
|
if (message.type === 'logout' && syncLogout) {
|
|
5211
5302
|
lastSyncedState = LOGGED_OUT;
|
|
5212
5303
|
hadTokens = false;
|
|
5213
|
-
context.logout(
|
|
5304
|
+
context.logout(incomingCause(message.cause));
|
|
5214
5305
|
return;
|
|
5215
5306
|
}
|
|
5216
5307
|
if (message.type !== 'tokens-updated' || !syncTokens)
|
|
@@ -5258,7 +5349,7 @@ const setupMultiTabSync = (config, context) => {
|
|
|
5258
5349
|
if (lastSyncedState === LOGGED_OUT)
|
|
5259
5350
|
return;
|
|
5260
5351
|
lastSyncedState = LOGGED_OUT;
|
|
5261
|
-
const message = { type: 'logout' };
|
|
5352
|
+
const message = { type: 'logout', cause: untracked(context.sessionEndCause) ?? undefined };
|
|
5262
5353
|
channel.postMessage(message);
|
|
5263
5354
|
});
|
|
5264
5355
|
}
|
|
@@ -5314,6 +5405,7 @@ const withBearerAuthMultiTabSync = (config = {}) => {
|
|
|
5314
5405
|
setupMultiTabSync({ channelName, syncTokens: config.syncTokens, syncLogout: config.syncLogout }, {
|
|
5315
5406
|
accessToken: context.accessToken,
|
|
5316
5407
|
refreshToken: context.refreshToken,
|
|
5408
|
+
sessionEndCause: context.sessionEndCause,
|
|
5317
5409
|
name: context.name,
|
|
5318
5410
|
applyTokens: context.applyTokens,
|
|
5319
5411
|
logout: context.logout,
|
|
@@ -5350,6 +5442,7 @@ const withPersistentAuth = (config) => {
|
|
|
5350
5442
|
...(config.autoLogin.excludeRoutes?.length
|
|
5351
5443
|
? [{ label: 'excluded routes', value: config.autoLogin.excludeRoutes.join(', ') }]
|
|
5352
5444
|
: []),
|
|
5445
|
+
...(config.autoLogin.shouldAutoLogin ? [{ label: 'auto login predicate', value: 'custom' }] : []),
|
|
5353
5446
|
],
|
|
5354
5447
|
};
|
|
5355
5448
|
};
|
|
@@ -5410,6 +5503,9 @@ const createPersistentAuthFeature = (config, context) => {
|
|
|
5410
5503
|
if (shouldExclude) {
|
|
5411
5504
|
return;
|
|
5412
5505
|
}
|
|
5506
|
+
if (config.autoLogin.shouldAutoLogin && !config.autoLogin.shouldAutoLogin(currentRoute)) {
|
|
5507
|
+
return;
|
|
5508
|
+
}
|
|
5413
5509
|
const storedToken = getCookie(cookieName);
|
|
5414
5510
|
if (storedToken) {
|
|
5415
5511
|
const decryptedToken = decryptToken(storedToken);
|
|
@@ -5681,6 +5777,40 @@ const createTrackingFeature = (context, config) => {
|
|
|
5681
5777
|
};
|
|
5682
5778
|
};
|
|
5683
5779
|
|
|
5780
|
+
const ethlete = {};
|
|
5781
|
+
let app = null;
|
|
5782
|
+
/** What `window.ethlete` exposes - the same object {@link queryDevtoolsAbout} returns. */
|
|
5783
|
+
const publish = () => {
|
|
5784
|
+
if (typeof window === 'undefined')
|
|
5785
|
+
return;
|
|
5786
|
+
window['ethlete'] = queryDevtoolsAbout();
|
|
5787
|
+
};
|
|
5788
|
+
/**
|
|
5789
|
+
* Records that an `@ethlete/*` package is loaded, and at which version. Each lib passes its own
|
|
5790
|
+
* generated constant; a package that is never imported never reports one, which is what makes the
|
|
5791
|
+
* list "what is actually loaded" rather than "what is declared as a peer dependency".
|
|
5792
|
+
*/
|
|
5793
|
+
const registerEthleteVersion = (name, version) => {
|
|
5794
|
+
ethlete[name] = version;
|
|
5795
|
+
publish();
|
|
5796
|
+
};
|
|
5797
|
+
const setQueryDevtoolsAppInfo = (info) => {
|
|
5798
|
+
app = info ?? null;
|
|
5799
|
+
publish();
|
|
5800
|
+
};
|
|
5801
|
+
/**
|
|
5802
|
+
* Everything the devtools know about what is running: the loaded `@ethlete/*` versions, the Angular
|
|
5803
|
+
* version, and whatever the application handed to `provideQueryDevtools({ about })`.
|
|
5804
|
+
*
|
|
5805
|
+
* Also mirrored onto `window.ethlete` - the same idea as Angular's `window.ng`, so the versions behind
|
|
5806
|
+
* a bug report can be read straight from the console without opening the panel.
|
|
5807
|
+
*/
|
|
5808
|
+
const queryDevtoolsAbout = () => ({
|
|
5809
|
+
ethlete: { ...ethlete },
|
|
5810
|
+
angular: VERSION.full,
|
|
5811
|
+
app,
|
|
5812
|
+
});
|
|
5813
|
+
|
|
5684
5814
|
/** The neutral fault: nothing armed. */
|
|
5685
5815
|
const EMPTY_QUERY_DEVTOOLS_FAULT = {
|
|
5686
5816
|
latencyMs: 0,
|
|
@@ -5794,6 +5924,482 @@ const createQueryDevtoolsFormLinks = () => {
|
|
|
5794
5924
|
};
|
|
5795
5925
|
};
|
|
5796
5926
|
|
|
5927
|
+
const DEFAULTS = {
|
|
5928
|
+
viewState: 'session',
|
|
5929
|
+
pins: 'local',
|
|
5930
|
+
overrides: 'none',
|
|
5931
|
+
mocks: 'local',
|
|
5932
|
+
maxEvents: 100,
|
|
5933
|
+
maxDroppedCacheEntries: 20,
|
|
5934
|
+
responseHistory: null,
|
|
5935
|
+
};
|
|
5936
|
+
/**
|
|
5937
|
+
* `localStorage`, whatever the scopes below say - a scope of `none` still has to be remembered, or the
|
|
5938
|
+
* choice would erase itself on the first reload it applies to.
|
|
5939
|
+
*/
|
|
5940
|
+
const SETTINGS_KEY = 'ethlete:query:devtools:settings:v1';
|
|
5941
|
+
const LIMITS = {
|
|
5942
|
+
maxEvents: { min: 10, max: 1000 },
|
|
5943
|
+
maxDroppedCacheEntries: { min: 0, max: 200 },
|
|
5944
|
+
responseHistory: { min: 1, max: 50 },
|
|
5945
|
+
};
|
|
5946
|
+
const settings = /* @__PURE__ */ signal(DEFAULTS, /* @ts-ignore */
|
|
5947
|
+
...(ngDevMode ? [{ debugName: "settings" }] : /* istanbul ignore next */ []));
|
|
5948
|
+
/**
|
|
5949
|
+
* How the devtools are configured. Every consumer reads the live value rather than a copy, so a change
|
|
5950
|
+
* in the panel's Settings takes effect without a reload.
|
|
5951
|
+
*
|
|
5952
|
+
* Part of the devtools contract. **Not part of the general public contract.**
|
|
5953
|
+
*/
|
|
5954
|
+
const queryDevtoolsSettings = /* @__PURE__ */ settings.asReadonly();
|
|
5955
|
+
/**
|
|
5956
|
+
* The `Storage` a scope names, or `null` for `none` and for a browser that refuses one (private mode,
|
|
5957
|
+
* storage disabled, server-side rendering). Callers treat "no storage" and "not persisted" alike.
|
|
5958
|
+
*
|
|
5959
|
+
* Part of the devtools contract. **Not part of the general public contract.**
|
|
5960
|
+
*/
|
|
5961
|
+
const queryDevtoolsStorage = (scope) => {
|
|
5962
|
+
if (scope === 'none' || typeof window === 'undefined')
|
|
5963
|
+
return null;
|
|
5964
|
+
try {
|
|
5965
|
+
return scope === 'session' ? window.sessionStorage : window.localStorage;
|
|
5966
|
+
}
|
|
5967
|
+
catch {
|
|
5968
|
+
return null;
|
|
5969
|
+
}
|
|
5970
|
+
};
|
|
5971
|
+
/**
|
|
5972
|
+
* Reads one JSON-encoded devtools key from the store a scope names, or `null`.
|
|
5973
|
+
*
|
|
5974
|
+
* Part of the devtools contract. **Not part of the general public contract.**
|
|
5975
|
+
*/
|
|
5976
|
+
const readQueryDevtoolsStore = (scope, key) => {
|
|
5977
|
+
try {
|
|
5978
|
+
const raw = queryDevtoolsStorage(scope)?.getItem(key);
|
|
5979
|
+
return raw ? JSON.parse(raw) : null;
|
|
5980
|
+
}
|
|
5981
|
+
catch {
|
|
5982
|
+
return null;
|
|
5983
|
+
}
|
|
5984
|
+
};
|
|
5985
|
+
/**
|
|
5986
|
+
* Writes one devtools key into the store a scope names. A scope of `none` writes nothing.
|
|
5987
|
+
*
|
|
5988
|
+
* Part of the devtools contract. **Not part of the general public contract.**
|
|
5989
|
+
*/
|
|
5990
|
+
const writeQueryDevtoolsStore = (scope, key, value) => {
|
|
5991
|
+
try {
|
|
5992
|
+
queryDevtoolsStorage(scope)?.setItem(key, JSON.stringify(value));
|
|
5993
|
+
}
|
|
5994
|
+
catch {
|
|
5995
|
+
// ignore (private mode / disabled storage / quota)
|
|
5996
|
+
}
|
|
5997
|
+
};
|
|
5998
|
+
/**
|
|
5999
|
+
* Removes one devtools key from **both** stores, whatever the scope currently is - so a scope that
|
|
6000
|
+
* changed cannot leave a stale copy behind for the next load to read.
|
|
6001
|
+
*
|
|
6002
|
+
* Part of the devtools contract. **Not part of the general public contract.**
|
|
6003
|
+
*/
|
|
6004
|
+
const clearQueryDevtoolsStore = (key) => {
|
|
6005
|
+
for (const scope of ['session', 'local']) {
|
|
6006
|
+
try {
|
|
6007
|
+
queryDevtoolsStorage(scope)?.removeItem(key);
|
|
6008
|
+
}
|
|
6009
|
+
catch {
|
|
6010
|
+
// ignore (private mode / disabled storage)
|
|
6011
|
+
}
|
|
6012
|
+
}
|
|
6013
|
+
};
|
|
6014
|
+
const asScope = (value, fallback) => value === 'none' || value === 'session' || value === 'local' ? value : fallback;
|
|
6015
|
+
const asCount = (value, limits, fallback) => {
|
|
6016
|
+
const count = Math.floor(Number(value));
|
|
6017
|
+
if (!Number.isFinite(count))
|
|
6018
|
+
return fallback;
|
|
6019
|
+
return Math.min(Math.max(count, limits.min), limits.max);
|
|
6020
|
+
};
|
|
6021
|
+
/** A count that may be absent, where absent means "leave the application's own value alone". */
|
|
6022
|
+
const asOptionalCount = (value, limits) => {
|
|
6023
|
+
if (value === null || value === undefined)
|
|
6024
|
+
return null;
|
|
6025
|
+
const count = Math.floor(Number(value));
|
|
6026
|
+
return Number.isFinite(count) ? Math.min(Math.max(count, limits.min), limits.max) : null;
|
|
6027
|
+
};
|
|
6028
|
+
/** Both a hand-edited store and a number typed into the panel come through here. */
|
|
6029
|
+
const sanitize = (value) => ({
|
|
6030
|
+
viewState: asScope(value?.viewState, DEFAULTS.viewState),
|
|
6031
|
+
pins: asScope(value?.pins, DEFAULTS.pins),
|
|
6032
|
+
overrides: asScope(value?.overrides, DEFAULTS.overrides),
|
|
6033
|
+
mocks: asScope(value?.mocks, DEFAULTS.mocks),
|
|
6034
|
+
maxEvents: asCount(value?.maxEvents, LIMITS.maxEvents, DEFAULTS.maxEvents),
|
|
6035
|
+
maxDroppedCacheEntries: asCount(value?.maxDroppedCacheEntries, LIMITS.maxDroppedCacheEntries, DEFAULTS.maxDroppedCacheEntries),
|
|
6036
|
+
responseHistory: asOptionalCount(value?.responseHistory, LIMITS.responseHistory),
|
|
6037
|
+
});
|
|
6038
|
+
/**
|
|
6039
|
+
* Reads the stored settings. Called by `provideQueryDevtools()` **before** the override store is read
|
|
6040
|
+
* and the response-history default is set - both take their configuration from here - and by nothing
|
|
6041
|
+
* else.
|
|
6042
|
+
* @internal
|
|
6043
|
+
*/
|
|
6044
|
+
const initQueryDevtoolsSettings = () => {
|
|
6045
|
+
settings.set(sanitize(readQueryDevtoolsStore('local', SETTINGS_KEY)));
|
|
6046
|
+
};
|
|
6047
|
+
/**
|
|
6048
|
+
* Changes part of the devtools configuration and stores it. Values outside the range a field allows are
|
|
6049
|
+
* clamped rather than rejected.
|
|
6050
|
+
*
|
|
6051
|
+
* Overrides are the one field not to set here: their scope moves an existing store, so it goes through
|
|
6052
|
+
* `setQueryDevtoolsOverridesScope()` instead.
|
|
6053
|
+
*
|
|
6054
|
+
* Part of the devtools contract. **Not part of the general public contract.**
|
|
6055
|
+
*/
|
|
6056
|
+
const setQueryDevtoolsSettings = (patch) => {
|
|
6057
|
+
const next = sanitize({ ...settings(), ...patch });
|
|
6058
|
+
settings.set(next);
|
|
6059
|
+
writeQueryDevtoolsStore('local', SETTINGS_KEY, next);
|
|
6060
|
+
};
|
|
6061
|
+
|
|
6062
|
+
/**
|
|
6063
|
+
* The designed library. Whether a mock is **armed** is deliberately not part of it - see
|
|
6064
|
+
* {@link queryDevtoolsArmedMocks}.
|
|
6065
|
+
*/
|
|
6066
|
+
const STORAGE_KEY$1 = 'ethlete:query:devtools:mocks:v1';
|
|
6067
|
+
const mocks = /* @__PURE__ */ signal([], /* @ts-ignore */
|
|
6068
|
+
...(ngDevMode ? [{ debugName: "mocks" }] : /* istanbul ignore next */ []));
|
|
6069
|
+
const armed = /* @__PURE__ */ signal(/* @__PURE__ */ new Set(), /* @ts-ignore */
|
|
6070
|
+
...(ngDevMode ? [{ debugName: "armed" }] : /* istanbul ignore next */ []));
|
|
6071
|
+
const scope$1 = () => queryDevtoolsSettings().mocks;
|
|
6072
|
+
/** The designed mocks, armed or not. Persisted, because losing an hour of authoring is unacceptable. */
|
|
6073
|
+
const queryDevtoolsMocks = /* @__PURE__ */ mocks.asReadonly();
|
|
6074
|
+
/**
|
|
6075
|
+
* The ids currently being served. **Never persisted, at any scope**: an app that silently serves designed
|
|
6076
|
+
* data tomorrow morning is worse than losing which mocks were armed, so arming is per page load.
|
|
6077
|
+
*/
|
|
6078
|
+
const queryDevtoolsArmedMocks = /* @__PURE__ */ armed.asReadonly();
|
|
6079
|
+
/**
|
|
6080
|
+
* The identity of a designed mock: one per client, method, route **and** declared query. The query is part
|
|
6081
|
+
* of it so `/posts?page=1` and `/posts?page=2` can hold different bodies.
|
|
6082
|
+
*/
|
|
6083
|
+
const queryDevtoolsMockId = (parts) => `${parts.clientName}|${parts.method}|${parts.pattern}${parts.query ? `?${parts.query}` : ''}`;
|
|
6084
|
+
const write = () => {
|
|
6085
|
+
if (scope$1() === 'none') {
|
|
6086
|
+
clearQueryDevtoolsStore(STORAGE_KEY$1);
|
|
6087
|
+
return;
|
|
6088
|
+
}
|
|
6089
|
+
writeQueryDevtoolsStore(scope$1(), STORAGE_KEY$1, mocks());
|
|
6090
|
+
};
|
|
6091
|
+
const isMock = (value) => {
|
|
6092
|
+
const mock = value;
|
|
6093
|
+
return (!!mock &&
|
|
6094
|
+
typeof mock.id === 'string' &&
|
|
6095
|
+
typeof mock.clientName === 'string' &&
|
|
6096
|
+
typeof mock.method === 'string' &&
|
|
6097
|
+
typeof mock.pattern === 'string' &&
|
|
6098
|
+
typeof mock.status === 'number');
|
|
6099
|
+
};
|
|
6100
|
+
/**
|
|
6101
|
+
* Reads the designed library, so a mock can be armed without authoring it again. Called by
|
|
6102
|
+
* `provideQueryDevtools()` after the settings it takes its scope from; nothing else may call it.
|
|
6103
|
+
* @internal
|
|
6104
|
+
*/
|
|
6105
|
+
const initQueryDevtoolsMocks = () => {
|
|
6106
|
+
const stored = readQueryDevtoolsStore(scope$1(), STORAGE_KEY$1);
|
|
6107
|
+
// `query` was added after the first version of the store, so an entry written before it still loads.
|
|
6108
|
+
mocks.set(Array.isArray(stored) ? stored.filter(isMock).map((mock) => ({ ...mock, query: mock.query ?? '' })) : []);
|
|
6109
|
+
armed.set(new Set());
|
|
6110
|
+
};
|
|
6111
|
+
/** The path of a request URL, without its origin or query string - what a pattern is matched against. */
|
|
6112
|
+
const queryDevtoolsRequestPath = (url) => {
|
|
6113
|
+
const withoutQuery = url.split('?')[0] ?? '';
|
|
6114
|
+
const schemeEnd = withoutQuery.indexOf('://');
|
|
6115
|
+
if (schemeEnd === -1)
|
|
6116
|
+
return withoutQuery;
|
|
6117
|
+
const pathStart = withoutQuery.indexOf('/', schemeEnd + 3);
|
|
6118
|
+
return pathStart === -1 ? '/' : withoutQuery.slice(pathStart);
|
|
6119
|
+
};
|
|
6120
|
+
const segmentsOf$1 = (path) => path.split('/').filter(Boolean);
|
|
6121
|
+
/** The query string of a request URL, without the `?`. */
|
|
6122
|
+
const queryOf = (url) => url.split('?')[1] ?? '';
|
|
6123
|
+
/**
|
|
6124
|
+
* Whether a request carries every query parameter a mock declares. Extra parameters are ignored - a mock
|
|
6125
|
+
* says what the request must ask for, not everything it may.
|
|
6126
|
+
*/
|
|
6127
|
+
const matchesQueryDevtoolsMockQuery = (declared, url) => {
|
|
6128
|
+
if (!declared)
|
|
6129
|
+
return true;
|
|
6130
|
+
const asked = new URLSearchParams(queryOf(url));
|
|
6131
|
+
return [...new URLSearchParams(declared)].every(([key, value]) => asked.get(key) === value);
|
|
6132
|
+
};
|
|
6133
|
+
/**
|
|
6134
|
+
* Whether a request path is the route a pattern describes. A `:param` segment matches any single
|
|
6135
|
+
* non-empty segment, which is the whole point: a mock is armed for a route, not for the one URL a query
|
|
6136
|
+
* happened to build.
|
|
6137
|
+
*/
|
|
6138
|
+
const matchesQueryDevtoolsMockPattern = (pattern, path) => {
|
|
6139
|
+
const patternSegments = segmentsOf$1(pattern);
|
|
6140
|
+
const pathSegments = segmentsOf$1(path);
|
|
6141
|
+
if (patternSegments.length !== pathSegments.length)
|
|
6142
|
+
return false;
|
|
6143
|
+
return patternSegments.every((segment, index) => segment.startsWith(':') || segment === pathSegments[index]);
|
|
6144
|
+
};
|
|
6145
|
+
/**
|
|
6146
|
+
* Adds a designed mock, or replaces the one already under its id. Arming is untouched: re-designing a
|
|
6147
|
+
* body while it is being served changes what the next request gets.
|
|
6148
|
+
*/
|
|
6149
|
+
const saveQueryDevtoolsMock = (mock) => {
|
|
6150
|
+
mocks.update((current) => {
|
|
6151
|
+
const index = current.findIndex((entry) => entry.id === mock.id);
|
|
6152
|
+
if (index === -1)
|
|
6153
|
+
return [...current, mock];
|
|
6154
|
+
return current.map((entry) => (entry.id === mock.id ? mock : entry));
|
|
6155
|
+
});
|
|
6156
|
+
write();
|
|
6157
|
+
};
|
|
6158
|
+
/** Removes a designed mock, disarming it on the way out. */
|
|
6159
|
+
const deleteQueryDevtoolsMock = (id) => {
|
|
6160
|
+
mocks.update((current) => current.filter((entry) => entry.id !== id));
|
|
6161
|
+
armQueryDevtoolsMock(id, false);
|
|
6162
|
+
write();
|
|
6163
|
+
};
|
|
6164
|
+
/** Starts or stops serving one designed mock. Arming a mock that is not in the library does nothing. */
|
|
6165
|
+
const armQueryDevtoolsMock = (id, isArmed) => {
|
|
6166
|
+
armed.update((current) => {
|
|
6167
|
+
if (isArmed && !mocks().some((mock) => mock.id === id))
|
|
6168
|
+
return current;
|
|
6169
|
+
const next = new Set(current);
|
|
6170
|
+
if (isArmed)
|
|
6171
|
+
next.add(id);
|
|
6172
|
+
else
|
|
6173
|
+
next.delete(id);
|
|
6174
|
+
return next;
|
|
6175
|
+
});
|
|
6176
|
+
};
|
|
6177
|
+
/** Stops serving every mock, leaving the library alone - the armed bar's one-click way out. */
|
|
6178
|
+
const clearQueryDevtoolsArmedMocks = () => armed.set(new Set());
|
|
6179
|
+
/** Empties the designed library and disarms everything - the panel's "Reset devtools". */
|
|
6180
|
+
const clearQueryDevtoolsMockStore = () => {
|
|
6181
|
+
mocks.set([]);
|
|
6182
|
+
armed.set(new Set());
|
|
6183
|
+
clearQueryDevtoolsStore(STORAGE_KEY$1);
|
|
6184
|
+
};
|
|
6185
|
+
const declaredParamCount = (mock) => (mock.query ? [...new URLSearchParams(mock.query)].length : 0);
|
|
6186
|
+
/**
|
|
6187
|
+
* The mock to serve one upcoming attempt, or `null` to let it reach the network. The armed mock declaring
|
|
6188
|
+
* the most query parameters wins, so a special case armed on top of a general one is what answers.
|
|
6189
|
+
*
|
|
6190
|
+
* Installed as the mock resolver by `provideQueryDevtools()`; nothing else may call it.
|
|
6191
|
+
* @internal
|
|
6192
|
+
*/
|
|
6193
|
+
const resolveQueryDevtoolsMockForAttempt = (target) => {
|
|
6194
|
+
const ids = armed();
|
|
6195
|
+
if (!ids.size)
|
|
6196
|
+
return null;
|
|
6197
|
+
const path = queryDevtoolsRequestPath(target.url);
|
|
6198
|
+
const mock = mocks()
|
|
6199
|
+
.filter((candidate) => ids.has(candidate.id) &&
|
|
6200
|
+
candidate.clientName === target.clientName &&
|
|
6201
|
+
candidate.method === target.method &&
|
|
6202
|
+
matchesQueryDevtoolsMockPattern(candidate.pattern, path) &&
|
|
6203
|
+
matchesQueryDevtoolsMockQuery(candidate.query, target.url))
|
|
6204
|
+
// The most specific armed mock answers: one that names `page=2` beats one that takes any query, so
|
|
6205
|
+
// arming a general mock does not shadow the special case someone armed on top of it.
|
|
6206
|
+
.sort((a, b) => declaredParamCount(b) - declaredParamCount(a))[0];
|
|
6207
|
+
if (!mock)
|
|
6208
|
+
return null;
|
|
6209
|
+
return { status: mock.status, body: mock.body, latencyMs: mock.latencyMs };
|
|
6210
|
+
};
|
|
6211
|
+
|
|
6212
|
+
/**
|
|
6213
|
+
* Not persisted at all unless asked for, and then in `sessionStorage`: "survives a reload" and "survives
|
|
6214
|
+
* until I notice" are different promises, and an app that stays tampered with across days is hours of
|
|
6215
|
+
* debugging the wrong thing. `local` is offered in Settings for the dev who has a reason, and the
|
|
6216
|
+
* restored-overrides bar names the scope whatever it is.
|
|
6217
|
+
*/
|
|
6218
|
+
const STORAGE_KEY = 'ethlete:query:devtools:overrides:v1';
|
|
6219
|
+
const restored = /* @__PURE__ */ signal([], /* @ts-ignore */
|
|
6220
|
+
...(ngDevMode ? [{ debugName: "restored" }] : /* istanbul ignore next */ []));
|
|
6221
|
+
const restoredScope = /* @__PURE__ */ signal(null, /* @ts-ignore */
|
|
6222
|
+
...(ngDevMode ? [{ debugName: "restoredScope" }] : /* istanbul ignore next */ []));
|
|
6223
|
+
const scope = () => queryDevtoolsSettings().overrides;
|
|
6224
|
+
/** Where "Keep across reloads" puts them back when it is switched on again. */
|
|
6225
|
+
let lastPersistentScope = 'session';
|
|
6226
|
+
/** Every recorder that currently holds at least one op, so switching persistence on can capture them. */
|
|
6227
|
+
const armedRecorders = /* @__PURE__ */ new Map();
|
|
6228
|
+
/** What the store held when this page loaded, per id - the banner's subject, not the live registry. */
|
|
6229
|
+
const carriedOver = /* @__PURE__ */ new Map();
|
|
6230
|
+
/** Of those, the ids a registering query actually took. */
|
|
6231
|
+
const replayed = /* @__PURE__ */ new Set();
|
|
6232
|
+
let ops = {};
|
|
6233
|
+
const readStore = () => {
|
|
6234
|
+
const parsed = readQueryDevtoolsStore(scope(), STORAGE_KEY);
|
|
6235
|
+
const stored = parsed?.ops;
|
|
6236
|
+
const usable = !!stored && typeof stored === 'object' && !Array.isArray(stored);
|
|
6237
|
+
return { enabled: parsed?.enabled === true, ops: usable ? stored : {} };
|
|
6238
|
+
};
|
|
6239
|
+
const writeStore = () => {
|
|
6240
|
+
if (scope() === 'none') {
|
|
6241
|
+
clearQueryDevtoolsStore(STORAGE_KEY);
|
|
6242
|
+
return;
|
|
6243
|
+
}
|
|
6244
|
+
writeQueryDevtoolsStore(scope(), STORAGE_KEY, { enabled: true, ops });
|
|
6245
|
+
};
|
|
6246
|
+
const publishRestored = () => restored.set([...carriedOver].map(([id, count]) => ({ id, count, armed: replayed.has(id) })));
|
|
6247
|
+
const forgetCarriedOver = () => {
|
|
6248
|
+
carriedOver.clear();
|
|
6249
|
+
replayed.clear();
|
|
6250
|
+
restoredScope.set(null);
|
|
6251
|
+
publishRestored();
|
|
6252
|
+
};
|
|
6253
|
+
const writeOps = (id, list) => {
|
|
6254
|
+
if (list.length)
|
|
6255
|
+
ops = { ...ops, [id]: list };
|
|
6256
|
+
else {
|
|
6257
|
+
const { [id]: _dropped, ...rest } = ops;
|
|
6258
|
+
ops = rest;
|
|
6259
|
+
}
|
|
6260
|
+
writeStore();
|
|
6261
|
+
};
|
|
6262
|
+
/**
|
|
6263
|
+
* Reads the store, so entries can replay from it as they register. Called by `provideQueryDevtools()`,
|
|
6264
|
+
* which is what keeps an app without devtools from ever touching storage.
|
|
6265
|
+
* @internal
|
|
6266
|
+
*/
|
|
6267
|
+
const initQueryDevtoolsOverridePersistence = () => {
|
|
6268
|
+
const store = readStore();
|
|
6269
|
+
ops = store.enabled ? store.ops : {};
|
|
6270
|
+
armedRecorders.clear();
|
|
6271
|
+
carriedOver.clear();
|
|
6272
|
+
replayed.clear();
|
|
6273
|
+
for (const [id, list] of Object.entries(ops)) {
|
|
6274
|
+
if (list.length)
|
|
6275
|
+
carriedOver.set(id, list.length);
|
|
6276
|
+
}
|
|
6277
|
+
const current = scope();
|
|
6278
|
+
if (current !== 'none')
|
|
6279
|
+
lastPersistentScope = current;
|
|
6280
|
+
restoredScope.set(carriedOver.size ? current : null);
|
|
6281
|
+
publishRestored();
|
|
6282
|
+
};
|
|
6283
|
+
/**
|
|
6284
|
+
* Whether armed response overrides are stored and replayed on the next page load - that is, whether
|
|
6285
|
+
* their scope is anything other than `none`. Off by default.
|
|
6286
|
+
*/
|
|
6287
|
+
const queryDevtoolsOverridePersistence = /* @__PURE__ */ computed(() => scope() !== 'none', /* @ts-ignore */
|
|
6288
|
+
...(ngDevMode ? [{ debugName: "queryDevtoolsOverridePersistence" }] : /* istanbul ignore next */ []));
|
|
6289
|
+
/**
|
|
6290
|
+
* Where armed response overrides are kept. Switching to a scope that keeps them captures whatever is
|
|
6291
|
+
* armed right now, so the choice reads as "keep these" rather than "keep the next ones"; switching
|
|
6292
|
+
* between `session` and `local` moves the store; switching to `none` empties it and leaves every armed
|
|
6293
|
+
* op exactly where it is for the rest of this page.
|
|
6294
|
+
*/
|
|
6295
|
+
const setQueryDevtoolsOverridesScope = (next) => {
|
|
6296
|
+
const previous = scope();
|
|
6297
|
+
if (next === previous)
|
|
6298
|
+
return;
|
|
6299
|
+
// Off both stores first: whichever the previous scope was, its copy must not outlive the change.
|
|
6300
|
+
clearQueryDevtoolsStore(STORAGE_KEY);
|
|
6301
|
+
setQueryDevtoolsSettings({ overrides: next });
|
|
6302
|
+
if (next === 'none') {
|
|
6303
|
+
ops = {};
|
|
6304
|
+
forgetCarriedOver();
|
|
6305
|
+
return;
|
|
6306
|
+
}
|
|
6307
|
+
lastPersistentScope = next;
|
|
6308
|
+
if (previous === 'none') {
|
|
6309
|
+
ops = {};
|
|
6310
|
+
for (const [id, recorder] of armedRecorders) {
|
|
6311
|
+
const list = recorder.list().map((entry) => entry.op);
|
|
6312
|
+
if (list.length)
|
|
6313
|
+
ops[id] = list;
|
|
6314
|
+
}
|
|
6315
|
+
}
|
|
6316
|
+
writeStore();
|
|
6317
|
+
};
|
|
6318
|
+
/**
|
|
6319
|
+
* Turns persistence on or off - the panel's "Keep across reloads" toggle. On restores the last scope
|
|
6320
|
+
* that kept them (`session` unless Settings picked `local`).
|
|
6321
|
+
*/
|
|
6322
|
+
const setQueryDevtoolsOverridePersistence = (value) => {
|
|
6323
|
+
setQueryDevtoolsOverridesScope(value ? lastPersistentScope : 'none');
|
|
6324
|
+
};
|
|
6325
|
+
/**
|
|
6326
|
+
* Empties the override store in both browser stores and disarms everything a reload brought back,
|
|
6327
|
+
* without changing where overrides are kept - the panel's "Reset devtools".
|
|
6328
|
+
*/
|
|
6329
|
+
const clearQueryDevtoolsOverrideStore = () => {
|
|
6330
|
+
clearRestoredQueryDevtoolsOverrides();
|
|
6331
|
+
ops = {};
|
|
6332
|
+
clearQueryDevtoolsStore(STORAGE_KEY);
|
|
6333
|
+
};
|
|
6334
|
+
/**
|
|
6335
|
+
* The ops this page load inherited from the previous one, and for each id whether a live query has taken
|
|
6336
|
+
* it. Overrides that outlive the page that armed them have to announce themselves, because an app being
|
|
6337
|
+
* lied to looks exactly like an app that is broken.
|
|
6338
|
+
*/
|
|
6339
|
+
const restoredQueryDevtoolsOverrides =
|
|
6340
|
+
/* @__PURE__ */ restored.asReadonly();
|
|
6341
|
+
/**
|
|
6342
|
+
* The scope those ops came back from, or `null` when this page inherited none. `local` is the one worth
|
|
6343
|
+
* naming in the banner: it means they outlived closing the tab, not just a reload.
|
|
6344
|
+
*/
|
|
6345
|
+
const queryDevtoolsRestoredOverridesScope =
|
|
6346
|
+
/* @__PURE__ */ restoredScope.asReadonly();
|
|
6347
|
+
/** Disarms every op this page load inherited and empties the store - the banner's one-click way out. */
|
|
6348
|
+
const clearRestoredQueryDevtoolsOverrides = () => {
|
|
6349
|
+
for (const id of replayed) {
|
|
6350
|
+
armedRecorders.get(id)?.clearAll();
|
|
6351
|
+
armedRecorders.delete(id);
|
|
6352
|
+
}
|
|
6353
|
+
for (const id of carriedOver.keys())
|
|
6354
|
+
writeOps(id, []);
|
|
6355
|
+
forgetCarriedOver();
|
|
6356
|
+
};
|
|
6357
|
+
/**
|
|
6358
|
+
* Wraps a query's overrides recorder so its armed ops are stored and replayed. Replay happens here, at
|
|
6359
|
+
* registration, rather than in the panel - a query can run long before the panel is ever opened.
|
|
6360
|
+
*
|
|
6361
|
+
* Ids are reload-deterministic (descriptor + per-descriptor counter), so the same queries created in the
|
|
6362
|
+
* same order re-arm correctly; an id the store holds that nobody claims is reported by
|
|
6363
|
+
* {@link restoredQueryDevtoolsOverrides} rather than guessed at.
|
|
6364
|
+
* @internal
|
|
6365
|
+
*/
|
|
6366
|
+
const withQueryDevtoolsOverridePersistence = (id, recorder) => {
|
|
6367
|
+
const inherited = ops[id];
|
|
6368
|
+
if (scope() !== 'none' && inherited?.length) {
|
|
6369
|
+
for (const op of inherited)
|
|
6370
|
+
recorder.arm(op);
|
|
6371
|
+
armedRecorders.set(id, recorder);
|
|
6372
|
+
replayed.add(id);
|
|
6373
|
+
publishRestored();
|
|
6374
|
+
}
|
|
6375
|
+
const sync = () => {
|
|
6376
|
+
const list = recorder.list().map((entry) => entry.op);
|
|
6377
|
+
if (list.length)
|
|
6378
|
+
armedRecorders.set(id, recorder);
|
|
6379
|
+
else
|
|
6380
|
+
armedRecorders.delete(id);
|
|
6381
|
+
if (scope() !== 'none')
|
|
6382
|
+
writeOps(id, list);
|
|
6383
|
+
};
|
|
6384
|
+
return {
|
|
6385
|
+
...recorder,
|
|
6386
|
+
arm: (op) => {
|
|
6387
|
+
recorder.arm(op);
|
|
6388
|
+
sync();
|
|
6389
|
+
},
|
|
6390
|
+
clear: (opId) => {
|
|
6391
|
+
recorder.clear(opId);
|
|
6392
|
+
sync();
|
|
6393
|
+
},
|
|
6394
|
+
clearAll: () => {
|
|
6395
|
+
recorder.clearAll();
|
|
6396
|
+
if (replayed.delete(id))
|
|
6397
|
+
publishRestored();
|
|
6398
|
+
sync();
|
|
6399
|
+
},
|
|
6400
|
+
};
|
|
6401
|
+
};
|
|
6402
|
+
|
|
5797
6403
|
const isPathAtOrBelow = (path, root) => path.length >= root.length && root.every((step, index) => step === path[index]);
|
|
5798
6404
|
/**
|
|
5799
6405
|
* Whether any of `entries` is armed at `path` or anywhere below it - what the panel's menu asks before
|
|
@@ -5841,6 +6447,30 @@ const withValueAtPath = (root, path, value) => {
|
|
|
5841
6447
|
const container = root && typeof root === 'object' ? root : {};
|
|
5842
6448
|
return { ...container, [head]: withValueAtPath(container[head], rest, value) };
|
|
5843
6449
|
};
|
|
6450
|
+
/**
|
|
6451
|
+
* Returns a copy of `root` with the value at `path` removed from its parent, cloning only the containers
|
|
6452
|
+
* along the way. An array parent splices, so the elements after it shift down instead of the slot
|
|
6453
|
+
* becoming a hole that serializes back as `null`.
|
|
6454
|
+
*/
|
|
6455
|
+
const withoutValueAtPath = (root, path) => {
|
|
6456
|
+
const [head, ...rest] = path;
|
|
6457
|
+
if (rest.length === 0) {
|
|
6458
|
+
if (Array.isArray(root)) {
|
|
6459
|
+
const next = [...root];
|
|
6460
|
+
next.splice(head, 1);
|
|
6461
|
+
return next;
|
|
6462
|
+
}
|
|
6463
|
+
const { [head]: _dropped, ...remaining } = root;
|
|
6464
|
+
return remaining;
|
|
6465
|
+
}
|
|
6466
|
+
if (Array.isArray(root)) {
|
|
6467
|
+
const next = [...root];
|
|
6468
|
+
next[head] = withoutValueAtPath(root[head], rest);
|
|
6469
|
+
return next;
|
|
6470
|
+
}
|
|
6471
|
+
const container = root;
|
|
6472
|
+
return { ...container, [head]: withoutValueAtPath(container[head], rest) };
|
|
6473
|
+
};
|
|
5844
6474
|
const IDENTITY_KEY_PATTERNS = [/^id$/i, /(^|[a-z])Id$/, /^uuid$/i, /^key$/i];
|
|
5845
6475
|
const isIdentityKey = (key) => IDENTITY_KEY_PATTERNS.some((pattern) => pattern.test(key));
|
|
5846
6476
|
/** Whether every sibling in `array` that has `key` holds a distinct string/number value for it. */
|
|
@@ -6133,6 +6763,19 @@ const applyOp = (root, op) => {
|
|
|
6133
6763
|
return { root, ok: false };
|
|
6134
6764
|
}
|
|
6135
6765
|
return { root: withValueAtPath(root, op.path, duplicateOneArrayItem(resolution.value, op.index)), ok: true };
|
|
6766
|
+
case 'pasteArrayItem': {
|
|
6767
|
+
if (!Array.isArray(resolution.value))
|
|
6768
|
+
return { root, ok: false };
|
|
6769
|
+
const next = [...resolution.value];
|
|
6770
|
+
const at = op.index === undefined ? next.length : Math.min(Math.max(0, op.index), next.length);
|
|
6771
|
+
next.splice(at, 0, op.value);
|
|
6772
|
+
return { root: withValueAtPath(root, op.path, next), ok: true };
|
|
6773
|
+
}
|
|
6774
|
+
case 'deleteAt':
|
|
6775
|
+
// The response root has no parent to be removed from, and a key that is already gone stays gone.
|
|
6776
|
+
if (!op.path.length || !resolution.exists)
|
|
6777
|
+
return { root, ok: false };
|
|
6778
|
+
return { root: withoutValueAtPath(root, op.path), ok: true };
|
|
6136
6779
|
case 'duplicateArray':
|
|
6137
6780
|
if (!Array.isArray(resolution.value))
|
|
6138
6781
|
return { root, ok: false };
|
|
@@ -6165,10 +6808,14 @@ const applyQueryDevtoolsOverrides = (entries, raw) => {
|
|
|
6165
6808
|
return { value: root, staleIds };
|
|
6166
6809
|
};
|
|
6167
6810
|
/**
|
|
6168
|
-
* Accumulates the response overrides armed on one query, for the devtools panel.
|
|
6169
|
-
* while {@link provideQueryDevtools} is installed - like {@link createQueryDevtoolsStats},
|
|
6170
|
-
* without devtools pays nothing for it.
|
|
6171
|
-
*
|
|
6811
|
+
* Accumulates the response overrides armed on one query, for the devtools panel. Instrumentation only
|
|
6812
|
+
* creates one while {@link provideQueryDevtools} is installed - like {@link createQueryDevtoolsStats},
|
|
6813
|
+
* an app without devtools pays nothing for it.
|
|
6814
|
+
*
|
|
6815
|
+
* The panel also creates one that belongs to no query at all, to record the edits made to a mock's draft
|
|
6816
|
+
* body: the ops are flattened with {@link applyQueryDevtoolsOverrides} and thrown away on save.
|
|
6817
|
+
*
|
|
6818
|
+
* Part of the devtools contract. **Not part of the general public contract.**
|
|
6172
6819
|
*/
|
|
6173
6820
|
const createQueryDevtoolsOverrides = () => {
|
|
6174
6821
|
const list = signal([], /* @ts-ignore */
|
|
@@ -6187,6 +6834,553 @@ const createQueryDevtoolsOverrides = () => {
|
|
|
6187
6834
|
return { list: list.asReadonly(), arm, clear, clearAll, apply };
|
|
6188
6835
|
};
|
|
6189
6836
|
|
|
6837
|
+
const TRANSFER_KIND = 'ethlete-query-overrides';
|
|
6838
|
+
const TRANSFER_VERSION = 1;
|
|
6839
|
+
const OP_TYPES = /* @__PURE__ */ new Set([
|
|
6840
|
+
'set',
|
|
6841
|
+
'stringPreset',
|
|
6842
|
+
'numberPreset',
|
|
6843
|
+
'booleanFlip',
|
|
6844
|
+
'datePreset',
|
|
6845
|
+
'duplicateArrayItem',
|
|
6846
|
+
'pasteArrayItem',
|
|
6847
|
+
'deleteAt',
|
|
6848
|
+
'duplicateArray',
|
|
6849
|
+
'paginationResize',
|
|
6850
|
+
'reset',
|
|
6851
|
+
]);
|
|
6852
|
+
const isJsonPath = (value) => Array.isArray(value) && value.every((step) => typeof step === 'string' || typeof step === 'number');
|
|
6853
|
+
const isOverrideOp = (value) => {
|
|
6854
|
+
if (!value || typeof value !== 'object' || Array.isArray(value))
|
|
6855
|
+
return false;
|
|
6856
|
+
const op = value;
|
|
6857
|
+
return typeof op.type === 'string' && OP_TYPES.has(op.type) && isJsonPath(op.path);
|
|
6858
|
+
};
|
|
6859
|
+
/**
|
|
6860
|
+
* Serializes the ops armed on one query into clipboard text. `reset` never reaches a recorder's list, so
|
|
6861
|
+
* what comes out is exactly what a paste can put back.
|
|
6862
|
+
*/
|
|
6863
|
+
const serializeQueryDevtoolsOverrideTransfer = (entries, source) => {
|
|
6864
|
+
const payload = {
|
|
6865
|
+
kind: TRANSFER_KIND,
|
|
6866
|
+
version: TRANSFER_VERSION,
|
|
6867
|
+
...(source ? { source } : {}),
|
|
6868
|
+
ops: entries.map((entry) => entry.op),
|
|
6869
|
+
};
|
|
6870
|
+
return JSON.stringify(payload, null, 2);
|
|
6871
|
+
};
|
|
6872
|
+
/**
|
|
6873
|
+
* Reads clipboard text back into ops. A bare array of ops is accepted alongside the full envelope, so a
|
|
6874
|
+
* set pasted out of a ticket and trimmed by hand still lands; an op whose `type` this build doesn't know
|
|
6875
|
+
* is counted in `skipped` rather than failing the whole paste, so an older panel can take a newer one's
|
|
6876
|
+
* set minus the parts it cannot replay.
|
|
6877
|
+
*/
|
|
6878
|
+
const parseQueryDevtoolsOverrideTransfer = (text) => {
|
|
6879
|
+
if (!text.trim())
|
|
6880
|
+
return { ok: false, reason: 'The clipboard is empty' };
|
|
6881
|
+
let parsed;
|
|
6882
|
+
try {
|
|
6883
|
+
parsed = JSON.parse(text);
|
|
6884
|
+
}
|
|
6885
|
+
catch {
|
|
6886
|
+
return { ok: false, reason: 'The clipboard does not hold valid JSON' };
|
|
6887
|
+
}
|
|
6888
|
+
const envelope = !Array.isArray(parsed) && parsed && typeof parsed === 'object' ? parsed : null;
|
|
6889
|
+
const raw = Array.isArray(parsed) ? parsed : envelope?.['ops'];
|
|
6890
|
+
if (!Array.isArray(raw))
|
|
6891
|
+
return { ok: false, reason: 'The clipboard does not hold a copied override set' };
|
|
6892
|
+
const ops = raw.filter(isOverrideOp);
|
|
6893
|
+
const skipped = raw.length - ops.length;
|
|
6894
|
+
if (!ops.length) {
|
|
6895
|
+
return { ok: false, reason: skipped ? 'None of those ops can be replayed by this build' : 'That set is empty' };
|
|
6896
|
+
}
|
|
6897
|
+
const source = envelope?.['source'];
|
|
6898
|
+
return {
|
|
6899
|
+
ok: true,
|
|
6900
|
+
ops,
|
|
6901
|
+
skipped,
|
|
6902
|
+
...(source && typeof source === 'object' && !Array.isArray(source) ? { source: source } : {}),
|
|
6903
|
+
};
|
|
6904
|
+
};
|
|
6905
|
+
/**
|
|
6906
|
+
* How many of `ops` would not resolve against `raw` - what a paste onto a differently-shaped query
|
|
6907
|
+
* reports, so ops sitting on nothing say so instead of looking armed. Replays them the same way the
|
|
6908
|
+
* pipeline will, since an earlier op can be what makes a later one's path exist.
|
|
6909
|
+
*/
|
|
6910
|
+
const countUnresolvedQueryDevtoolsOverrides = (ops, raw) => applyQueryDevtoolsOverrides(ops.map((op, index) => ({ id: `${index}`, op })), raw).staleIds.length;
|
|
6911
|
+
/** Arms every op of a parsed set onto a query, on top of whatever it already has armed. */
|
|
6912
|
+
const armQueryDevtoolsOverrideTransfer = (recorder, ops) => {
|
|
6913
|
+
for (const op of ops)
|
|
6914
|
+
recorder.arm(op);
|
|
6915
|
+
};
|
|
6916
|
+
|
|
6917
|
+
// Generated from package.json by tools/scripts/generate-version.js - do not edit.
|
|
6918
|
+
/** The version of `@ethlete/query` this build was cut from. */
|
|
6919
|
+
const QUERY_VERSION = '6.0.0-next.33';
|
|
6920
|
+
|
|
6921
|
+
/** How deep a schema is expanded before a nested branch is given up on. */
|
|
6922
|
+
const MAX_DEPTH = 12;
|
|
6923
|
+
/** How many elements are generated for an array, whatever its `minItems` says. */
|
|
6924
|
+
const MAX_ITEMS = 3;
|
|
6925
|
+
const HTTP_METHODS = ['get', 'put', 'post', 'delete', 'options', 'head', 'patch', 'trace'];
|
|
6926
|
+
const loader = /* @__PURE__ */ signal(null, /* @ts-ignore */
|
|
6927
|
+
...(ngDevMode ? [{ debugName: "loader" }] : /* istanbul ignore next */ []));
|
|
6928
|
+
const state = /* @__PURE__ */ signal({ status: 'unavailable' }, /* @ts-ignore */
|
|
6929
|
+
...(ngDevMode ? [{ debugName: "state" }] : /* istanbul ignore next */ []));
|
|
6930
|
+
const schemaDocument = /* @__PURE__ */ signal(null, /* @ts-ignore */
|
|
6931
|
+
...(ngDevMode ? [{ debugName: "schemaDocument" }] : /* istanbul ignore next */ []));
|
|
6932
|
+
const isRecord = (value) => !!value && typeof value === 'object' && !Array.isArray(value);
|
|
6933
|
+
/**
|
|
6934
|
+
* Where the API description stands, for the panel's seeding controls.
|
|
6935
|
+
*
|
|
6936
|
+
* Part of the devtools contract. **Not part of the general public contract.**
|
|
6937
|
+
*/
|
|
6938
|
+
const queryDevtoolsSchemaState = /* @__PURE__ */ state.asReadonly();
|
|
6939
|
+
/**
|
|
6940
|
+
* Installs the application's schema loader. Called by `provideQueryDevtools()`; nothing else may call it.
|
|
6941
|
+
* @internal
|
|
6942
|
+
*/
|
|
6943
|
+
const setQueryDevtoolsSchemaLoader = (next) => {
|
|
6944
|
+
loader.set(next ?? null);
|
|
6945
|
+
state.set({ status: next ? 'idle' : 'unavailable' });
|
|
6946
|
+
schemaDocument.set(null);
|
|
6947
|
+
};
|
|
6948
|
+
/** A `() => import('./openapi.json')` loader hands back the module rather than the document inside it. */
|
|
6949
|
+
const unwrapModule = (value) => {
|
|
6950
|
+
if (!isRecord(value))
|
|
6951
|
+
return value;
|
|
6952
|
+
if ('paths' in value || 'components' in value || 'definitions' in value || '$defs' in value)
|
|
6953
|
+
return value;
|
|
6954
|
+
return 'default' in value ? value['default'] : value;
|
|
6955
|
+
};
|
|
6956
|
+
const messageOf = (error) => (error instanceof Error ? error.message : String(error));
|
|
6957
|
+
/**
|
|
6958
|
+
* Loads the API description, once. Calling it while it is loading or after it has loaded does nothing;
|
|
6959
|
+
* calling it after a failure retries.
|
|
6960
|
+
*
|
|
6961
|
+
* Part of the devtools contract. **Not part of the general public contract.**
|
|
6962
|
+
*/
|
|
6963
|
+
const loadQueryDevtoolsSchema = () => {
|
|
6964
|
+
const load = loader();
|
|
6965
|
+
const status = state().status;
|
|
6966
|
+
if (!load || status === 'loading' || status === 'ready')
|
|
6967
|
+
return;
|
|
6968
|
+
state.set({ status: 'loading' });
|
|
6969
|
+
Promise.resolve()
|
|
6970
|
+
.then(() => load())
|
|
6971
|
+
.then((value) => {
|
|
6972
|
+
const parsed = unwrapModule(value);
|
|
6973
|
+
if (!isRecord(parsed))
|
|
6974
|
+
throw new Error('The schema loader did not return an object.');
|
|
6975
|
+
schemaDocument.set(parsed);
|
|
6976
|
+
state.set({ status: 'ready' });
|
|
6977
|
+
})
|
|
6978
|
+
.catch((error) => {
|
|
6979
|
+
schemaDocument.set(null);
|
|
6980
|
+
state.set({ status: 'error', message: messageOf(error) });
|
|
6981
|
+
});
|
|
6982
|
+
};
|
|
6983
|
+
const namedSchemas = /* @__PURE__ */ computed(() => {
|
|
6984
|
+
const doc = schemaDocument();
|
|
6985
|
+
if (!doc)
|
|
6986
|
+
return {};
|
|
6987
|
+
const components = doc['components'];
|
|
6988
|
+
return {
|
|
6989
|
+
...(isRecord(doc['$defs']) ? doc['$defs'] : null),
|
|
6990
|
+
...(isRecord(doc['definitions']) ? doc['definitions'] : null),
|
|
6991
|
+
...(isRecord(components) && isRecord(components['schemas']) ? components['schemas'] : null),
|
|
6992
|
+
};
|
|
6993
|
+
}, /* @ts-ignore */
|
|
6994
|
+
...(ngDevMode ? [{ debugName: "namedSchemas" }] : /* istanbul ignore next */ []));
|
|
6995
|
+
/**
|
|
6996
|
+
* Every schema the description names, sorted - the designer's "seed from a type" list.
|
|
6997
|
+
*
|
|
6998
|
+
* Part of the devtools contract. **Not part of the general public contract.**
|
|
6999
|
+
*/
|
|
7000
|
+
const queryDevtoolsSchemaNames = /* @__PURE__ */ computed(() => Object.keys(namedSchemas()).sort(), /* @ts-ignore */
|
|
7001
|
+
...(ngDevMode ? [{ debugName: "queryDevtoolsSchemaNames" }] : /* istanbul ignore next */ []));
|
|
7002
|
+
const decodePointer = (step) => decodeURIComponent(step.replace(/~1/g, '/').replace(/~0/g, '~'));
|
|
7003
|
+
/** The last segment of a `$ref` - `#/components/schemas/MatchId` is what a field's type is called. */
|
|
7004
|
+
const refLabel = (ref) => decodePointer(ref.split('/').pop() ?? ref);
|
|
7005
|
+
/** Resolves a local JSON pointer against the loaded document. A remote `$ref` cannot be followed. */
|
|
7006
|
+
const resolveRef = (ref) => {
|
|
7007
|
+
if (!ref.startsWith('#/'))
|
|
7008
|
+
return undefined;
|
|
7009
|
+
let current = schemaDocument();
|
|
7010
|
+
for (const step of ref.slice(2).split('/')) {
|
|
7011
|
+
if (!isRecord(current))
|
|
7012
|
+
return undefined;
|
|
7013
|
+
current = current[decodePointer(step)];
|
|
7014
|
+
}
|
|
7015
|
+
return current;
|
|
7016
|
+
};
|
|
7017
|
+
const at = (path) => path || 'the root';
|
|
7018
|
+
/** The declared `type`, tolerating the `['string', 'null']` form JSON Schema and OpenAPI 3.1 both allow. */
|
|
7019
|
+
const typeOf = (schema) => {
|
|
7020
|
+
const type = schema['type'];
|
|
7021
|
+
if (typeof type === 'string')
|
|
7022
|
+
return type;
|
|
7023
|
+
if (Array.isArray(type))
|
|
7024
|
+
return type.find((entry) => typeof entry === 'string' && entry !== 'null');
|
|
7025
|
+
return undefined;
|
|
7026
|
+
};
|
|
7027
|
+
const isNullable = (schema) => {
|
|
7028
|
+
const type = schema['type'];
|
|
7029
|
+
return schema['nullable'] === true || (Array.isArray(type) && type.includes('null'));
|
|
7030
|
+
};
|
|
7031
|
+
const branchesOf = (schema) => {
|
|
7032
|
+
for (const keyword of ['oneOf', 'anyOf']) {
|
|
7033
|
+
const branches = schema[keyword];
|
|
7034
|
+
if (Array.isArray(branches) && branches.length)
|
|
7035
|
+
return { keyword, branches };
|
|
7036
|
+
}
|
|
7037
|
+
return null;
|
|
7038
|
+
};
|
|
7039
|
+
/**
|
|
7040
|
+
* A short label for what a field is declared as: the name of the type it `$ref`s (which is the point -
|
|
7041
|
+
* `MatchId` says more than `string`), a union of its branches, or its primitive type and format.
|
|
7042
|
+
*/
|
|
7043
|
+
const describeSchema = (schema, depth = 0) => {
|
|
7044
|
+
if (!isRecord(schema) || depth > 4)
|
|
7045
|
+
return 'unknown';
|
|
7046
|
+
const ref = schema['$ref'];
|
|
7047
|
+
if (typeof ref === 'string')
|
|
7048
|
+
return refLabel(ref);
|
|
7049
|
+
const nullSuffix = isNullable(schema) ? ' | null' : '';
|
|
7050
|
+
const branches = branchesOf(schema);
|
|
7051
|
+
if (branches) {
|
|
7052
|
+
const labels = [...new Set(branches.branches.slice(0, 3).map((branch) => describeSchema(branch, depth + 1)))];
|
|
7053
|
+
return `${labels.join(' | ')}${branches.branches.length > 3 ? ' | …' : ''}${nullSuffix}`;
|
|
7054
|
+
}
|
|
7055
|
+
const allOf = schema['allOf'];
|
|
7056
|
+
if (Array.isArray(allOf) && allOf.length) {
|
|
7057
|
+
const named = allOf.find((entry) => isRecord(entry) && typeof entry['$ref'] === 'string');
|
|
7058
|
+
return `${describeSchema(named ?? allOf[0], depth + 1)}${nullSuffix}`;
|
|
7059
|
+
}
|
|
7060
|
+
const enumValues = schema['enum'];
|
|
7061
|
+
if (Array.isArray(enumValues) && enumValues.length) {
|
|
7062
|
+
const labels = enumValues.slice(0, 3).map((value) => JSON.stringify(value));
|
|
7063
|
+
return `${labels.join(' | ')}${enumValues.length > 3 ? ' | …' : ''}${nullSuffix}`;
|
|
7064
|
+
}
|
|
7065
|
+
const type = typeOf(schema);
|
|
7066
|
+
if (type === 'array')
|
|
7067
|
+
return `${describeSchema(schema['items'], depth + 1)}[]${nullSuffix}`;
|
|
7068
|
+
if (type === 'string') {
|
|
7069
|
+
const format = schema['format'];
|
|
7070
|
+
return `${typeof format === 'string' ? `string (${format})` : 'string'}${nullSuffix}`;
|
|
7071
|
+
}
|
|
7072
|
+
return `${type ?? 'unknown'}${nullSuffix}`;
|
|
7073
|
+
};
|
|
7074
|
+
const FORMAT_SAMPLES = {
|
|
7075
|
+
'date-time': '2026-01-01T00:00:00.000Z',
|
|
7076
|
+
date: '2026-01-01',
|
|
7077
|
+
time: '00:00:00',
|
|
7078
|
+
duration: 'P1D',
|
|
7079
|
+
uuid: '00000000-0000-4000-8000-000000000000',
|
|
7080
|
+
email: 'user@example.com',
|
|
7081
|
+
hostname: 'example.com',
|
|
7082
|
+
ipv4: '127.0.0.1',
|
|
7083
|
+
ipv6: '::1',
|
|
7084
|
+
uri: 'https://example.com',
|
|
7085
|
+
'uri-reference': '/example',
|
|
7086
|
+
url: 'https://example.com',
|
|
7087
|
+
password: '',
|
|
7088
|
+
byte: '',
|
|
7089
|
+
binary: '',
|
|
7090
|
+
};
|
|
7091
|
+
/**
|
|
7092
|
+
* A placeholder string: the declared format's shape where there is one, otherwise the field's own name -
|
|
7093
|
+
* which reads as obviously unreal while still saying which field you are looking at.
|
|
7094
|
+
*/
|
|
7095
|
+
const stringSample = (node, schema) => {
|
|
7096
|
+
const format = schema['format'];
|
|
7097
|
+
if (typeof format === 'string' && format in FORMAT_SAMPLES)
|
|
7098
|
+
return FORMAT_SAMPLES[format];
|
|
7099
|
+
const title = schema['title'];
|
|
7100
|
+
if (typeof title === 'string' && title)
|
|
7101
|
+
return title;
|
|
7102
|
+
return node.key ?? 'string';
|
|
7103
|
+
};
|
|
7104
|
+
const numberSample = (schema) => {
|
|
7105
|
+
for (const keyword of ['minimum', 'exclusiveMinimum', 'default']) {
|
|
7106
|
+
const value = schema[keyword];
|
|
7107
|
+
if (typeof value === 'number' && Number.isFinite(value))
|
|
7108
|
+
return keyword === 'exclusiveMinimum' ? value + 1 : value;
|
|
7109
|
+
}
|
|
7110
|
+
return 0;
|
|
7111
|
+
};
|
|
7112
|
+
const childNode = (parent, child) => ({
|
|
7113
|
+
schema: child.schema,
|
|
7114
|
+
path: parent.path ? `${parent.path}.${child.key}` : child.key,
|
|
7115
|
+
depth: parent.depth + 1,
|
|
7116
|
+
key: child.key,
|
|
7117
|
+
});
|
|
7118
|
+
const requiredOf = (schema) => {
|
|
7119
|
+
const required = schema['required'];
|
|
7120
|
+
return new Set(Array.isArray(required) ? required.filter((entry) => typeof entry === 'string') : []);
|
|
7121
|
+
};
|
|
7122
|
+
const generateObject = (node, ctx) => {
|
|
7123
|
+
const schema = node.schema;
|
|
7124
|
+
const properties = schema['properties'];
|
|
7125
|
+
if (!isRecord(properties)) {
|
|
7126
|
+
if (isRecord(schema['additionalProperties'])) {
|
|
7127
|
+
ctx.notes.add(`${at(node.path)} is a free-form map - generated as an empty object.`);
|
|
7128
|
+
}
|
|
7129
|
+
return {};
|
|
7130
|
+
}
|
|
7131
|
+
const required = requiredOf(schema);
|
|
7132
|
+
const result = {};
|
|
7133
|
+
for (const [key, child] of Object.entries(properties)) {
|
|
7134
|
+
const target = childNode(node, { schema: child, key });
|
|
7135
|
+
ctx.types.set(target.path, `${describeSchema(child)}${required.has(key) ? '' : '?'}`);
|
|
7136
|
+
result[key] = generate(target, ctx);
|
|
7137
|
+
}
|
|
7138
|
+
return result;
|
|
7139
|
+
};
|
|
7140
|
+
const generateArray = (node, ctx) => {
|
|
7141
|
+
const schema = node.schema;
|
|
7142
|
+
const items = schema['items'];
|
|
7143
|
+
if (!isRecord(items))
|
|
7144
|
+
return [];
|
|
7145
|
+
const minItems = typeof schema['minItems'] === 'number' ? schema['minItems'] : 1;
|
|
7146
|
+
const count = Math.min(Math.max(1, minItems), MAX_ITEMS);
|
|
7147
|
+
const target = { schema: items, path: `${node.path ? `${node.path}.` : ''}*`, depth: node.depth + 1, key: node.key };
|
|
7148
|
+
ctx.types.set(target.path, describeSchema(items));
|
|
7149
|
+
const element = generate(target, ctx);
|
|
7150
|
+
return Array.from({ length: count }, () => structuredCopy(element));
|
|
7151
|
+
};
|
|
7152
|
+
/** Each generated element is its own value, so editing one in the designer does not edit the others. */
|
|
7153
|
+
const structuredCopy = (value) => {
|
|
7154
|
+
if (Array.isArray(value))
|
|
7155
|
+
return value.map(structuredCopy);
|
|
7156
|
+
if (isRecord(value))
|
|
7157
|
+
return Object.fromEntries(Object.entries(value).map(([key, entry]) => [key, structuredCopy(entry)]));
|
|
7158
|
+
return value;
|
|
7159
|
+
};
|
|
7160
|
+
/** Merges what every `allOf` member generates, so a composed schema seeds as one object. */
|
|
7161
|
+
const generateAllOf = (node, ctx) => {
|
|
7162
|
+
const members = node.schema['allOf'];
|
|
7163
|
+
const generated = members.map((member) => generate({ ...node, schema: member }, ctx));
|
|
7164
|
+
const objects = generated.filter(isRecord);
|
|
7165
|
+
if (objects.length !== generated.length)
|
|
7166
|
+
return generated.find((value) => !isRecord(value)) ?? null;
|
|
7167
|
+
return Object.assign({}, ...objects);
|
|
7168
|
+
};
|
|
7169
|
+
const generate = (node, ctx) => {
|
|
7170
|
+
if (node.depth > MAX_DEPTH) {
|
|
7171
|
+
ctx.notes.add(`${at(node.path)} is nested deeper than ${MAX_DEPTH} levels - generated as null.`);
|
|
7172
|
+
return null;
|
|
7173
|
+
}
|
|
7174
|
+
if (!isRecord(node.schema))
|
|
7175
|
+
return null;
|
|
7176
|
+
const schema = node.schema;
|
|
7177
|
+
const ref = schema['$ref'];
|
|
7178
|
+
if (typeof ref === 'string') {
|
|
7179
|
+
const name = refLabel(ref);
|
|
7180
|
+
if (ctx.refs.includes(name)) {
|
|
7181
|
+
ctx.notes.add(`${name} contains itself - the recursion was cut at ${at(node.path)} with null.`);
|
|
7182
|
+
return null;
|
|
7183
|
+
}
|
|
7184
|
+
const resolved = resolveRef(ref);
|
|
7185
|
+
if (resolved === undefined) {
|
|
7186
|
+
ctx.notes.add(`${ref} could not be resolved - generated as null at ${at(node.path)}.`);
|
|
7187
|
+
return null;
|
|
7188
|
+
}
|
|
7189
|
+
ctx.refs.push(name);
|
|
7190
|
+
const value = generate({ ...node, schema: resolved }, ctx);
|
|
7191
|
+
ctx.refs.pop();
|
|
7192
|
+
return value;
|
|
7193
|
+
}
|
|
7194
|
+
for (const keyword of ['example', 'default', 'const']) {
|
|
7195
|
+
if (keyword in schema)
|
|
7196
|
+
return schema[keyword];
|
|
7197
|
+
}
|
|
7198
|
+
const examples = schema['examples'];
|
|
7199
|
+
if (Array.isArray(examples) && examples.length)
|
|
7200
|
+
return examples[0];
|
|
7201
|
+
const enumValues = schema['enum'];
|
|
7202
|
+
if (Array.isArray(enumValues) && enumValues.length)
|
|
7203
|
+
return enumValues[0];
|
|
7204
|
+
const branches = branchesOf(schema);
|
|
7205
|
+
if (branches) {
|
|
7206
|
+
if (branches.branches.length > 1) {
|
|
7207
|
+
ctx.notes.add(`${at(node.path)} is a ${branches.keyword} - the first branch was taken.`);
|
|
7208
|
+
}
|
|
7209
|
+
return generate({ ...node, schema: branches.branches[0] }, ctx);
|
|
7210
|
+
}
|
|
7211
|
+
if (Array.isArray(schema['allOf']) && schema['allOf'].length)
|
|
7212
|
+
return generateAllOf(node, ctx);
|
|
7213
|
+
switch (typeOf(schema)) {
|
|
7214
|
+
case 'object':
|
|
7215
|
+
return generateObject(node, ctx);
|
|
7216
|
+
case 'array':
|
|
7217
|
+
return generateArray(node, ctx);
|
|
7218
|
+
case 'string':
|
|
7219
|
+
return stringSample(node, schema);
|
|
7220
|
+
case 'integer':
|
|
7221
|
+
case 'number':
|
|
7222
|
+
return numberSample(schema);
|
|
7223
|
+
case 'boolean':
|
|
7224
|
+
return false;
|
|
7225
|
+
case 'null':
|
|
7226
|
+
return null;
|
|
7227
|
+
default:
|
|
7228
|
+
break;
|
|
7229
|
+
}
|
|
7230
|
+
if (isRecord(schema['properties']))
|
|
7231
|
+
return generateObject(node, ctx);
|
|
7232
|
+
ctx.notes.add(`${at(node.path)} declares no type - generated as null.`);
|
|
7233
|
+
return null;
|
|
7234
|
+
};
|
|
7235
|
+
const seedFrom = (schema, name) => {
|
|
7236
|
+
const ctx = { types: new Map(), notes: new Set(), refs: name ? [name] : [] };
|
|
7237
|
+
const body = generate({ schema, path: '', depth: 0, key: null }, ctx);
|
|
7238
|
+
return { body, schemaName: name, types: ctx.types, notes: [...ctx.notes] };
|
|
7239
|
+
};
|
|
7240
|
+
/**
|
|
7241
|
+
* Generates a body from one named schema (`MatchView`), or `null` when the description does not name it.
|
|
7242
|
+
*
|
|
7243
|
+
* Part of the devtools contract. **Not part of the general public contract.**
|
|
7244
|
+
*/
|
|
7245
|
+
const seedQueryDevtoolsSchemaBody = (name) => {
|
|
7246
|
+
const schema = namedSchemas()[name];
|
|
7247
|
+
if (schema === undefined)
|
|
7248
|
+
return null;
|
|
7249
|
+
return seedFrom(schema, name);
|
|
7250
|
+
};
|
|
7251
|
+
const segmentsOf = (pattern) => pattern.split('/').filter(Boolean);
|
|
7252
|
+
/** `/matches/{id}` and `/matches/:id` describe the same route; a mock's pattern is written the second way. */
|
|
7253
|
+
const toColonPattern = (path) => path.replace(/\{([^}]+)\}/g, ':$1');
|
|
7254
|
+
const isParam = (segment) => segment.startsWith(':');
|
|
7255
|
+
/** Whether two patterns describe the same route, allowing their path params to be named differently. */
|
|
7256
|
+
const patternsMatch = (a, b) => {
|
|
7257
|
+
const left = segmentsOf(a);
|
|
7258
|
+
const right = segmentsOf(b);
|
|
7259
|
+
if (left.length !== right.length)
|
|
7260
|
+
return false;
|
|
7261
|
+
return left.every((segment, index) => {
|
|
7262
|
+
const other = right[index];
|
|
7263
|
+
return isParam(segment) || isParam(other) ? isParam(segment) && isParam(other) : segment === other;
|
|
7264
|
+
});
|
|
7265
|
+
};
|
|
7266
|
+
/**
|
|
7267
|
+
* Every route the description declares, as the designer offers them - including the ones no query has
|
|
7268
|
+
* ever called, which is the case a mock exists for.
|
|
7269
|
+
*
|
|
7270
|
+
* Part of the devtools contract. **Not part of the general public contract.**
|
|
7271
|
+
*/
|
|
7272
|
+
const queryDevtoolsSchemaRoutes = /* @__PURE__ */ computed(() => {
|
|
7273
|
+
const paths = schemaDocument()?.['paths'];
|
|
7274
|
+
if (!isRecord(paths))
|
|
7275
|
+
return [];
|
|
7276
|
+
const routes = [];
|
|
7277
|
+
for (const [path, item] of Object.entries(paths)) {
|
|
7278
|
+
if (!isRecord(item))
|
|
7279
|
+
continue;
|
|
7280
|
+
for (const method of HTTP_METHODS) {
|
|
7281
|
+
const operation = item[method];
|
|
7282
|
+
if (!isRecord(operation))
|
|
7283
|
+
continue;
|
|
7284
|
+
const summary = operation['summary'] ?? operation['operationId'];
|
|
7285
|
+
routes.push({
|
|
7286
|
+
method: method.toUpperCase(),
|
|
7287
|
+
pattern: toColonPattern(path),
|
|
7288
|
+
summary: typeof summary === 'string' ? summary : '',
|
|
7289
|
+
});
|
|
7290
|
+
}
|
|
7291
|
+
}
|
|
7292
|
+
return routes;
|
|
7293
|
+
}, /* @ts-ignore */
|
|
7294
|
+
...(ngDevMode ? [{ debugName: "queryDevtoolsSchemaRoutes" }] : /* istanbul ignore next */ []));
|
|
7295
|
+
const findOperation = (target) => {
|
|
7296
|
+
const paths = schemaDocument()?.['paths'];
|
|
7297
|
+
if (!isRecord(paths))
|
|
7298
|
+
return null;
|
|
7299
|
+
const method = target.method.toLowerCase();
|
|
7300
|
+
const segments = segmentsOf(target.pattern);
|
|
7301
|
+
// A client's base path (`/api/v2`) is part of the request but not of the document, so a pattern that
|
|
7302
|
+
// does not match is retried without its leading segments before giving up.
|
|
7303
|
+
for (let skip = 0; skip < segments.length; skip++) {
|
|
7304
|
+
const candidate = `/${segments.slice(skip).join('/')}`;
|
|
7305
|
+
for (const [path, item] of Object.entries(paths)) {
|
|
7306
|
+
if (!isRecord(item) || !isRecord(item[method]))
|
|
7307
|
+
continue;
|
|
7308
|
+
if (!patternsMatch(toColonPattern(path), candidate))
|
|
7309
|
+
continue;
|
|
7310
|
+
return {
|
|
7311
|
+
operation: item[method],
|
|
7312
|
+
path,
|
|
7313
|
+
droppedPrefix: skip ? `/${segments.slice(0, skip).join('/')}` : '',
|
|
7314
|
+
};
|
|
7315
|
+
}
|
|
7316
|
+
}
|
|
7317
|
+
return null;
|
|
7318
|
+
};
|
|
7319
|
+
/** The success response of an operation: the lowest 2xx the document declares, else its `default`. */
|
|
7320
|
+
const successResponse = (operation) => {
|
|
7321
|
+
const responses = operation['responses'];
|
|
7322
|
+
if (!isRecord(responses))
|
|
7323
|
+
return null;
|
|
7324
|
+
const status = Object.keys(responses)
|
|
7325
|
+
.filter((key) => /^2\d\d$/.test(key))
|
|
7326
|
+
.sort()[0];
|
|
7327
|
+
const key = status ?? (('default' in responses && 'default') || null);
|
|
7328
|
+
if (!key)
|
|
7329
|
+
return null;
|
|
7330
|
+
const response = responses[key];
|
|
7331
|
+
return isRecord(response) ? { response, status: key } : null;
|
|
7332
|
+
};
|
|
7333
|
+
/** The JSON media type of a response, tolerating `application/vnd.x+json` and Swagger 2's flat `schema`. */
|
|
7334
|
+
const jsonSchemaOf = (response) => {
|
|
7335
|
+
const content = response['content'];
|
|
7336
|
+
if (!isRecord(content))
|
|
7337
|
+
return isRecord(response['schema']) ? { schema: response['schema'], example: undefined } : null;
|
|
7338
|
+
const key = Object.keys(content).find((type) => type.includes('json')) ?? Object.keys(content)[0];
|
|
7339
|
+
if (!key)
|
|
7340
|
+
return null;
|
|
7341
|
+
const media = content[key];
|
|
7342
|
+
if (!isRecord(media))
|
|
7343
|
+
return null;
|
|
7344
|
+
return { schema: media['schema'], example: media['example'] };
|
|
7345
|
+
};
|
|
7346
|
+
/**
|
|
7347
|
+
* Generates a body for one route from the description's own success response - the seed that makes a
|
|
7348
|
+
* designed mock a real view model rather than a guess. `null` when the document does not declare the
|
|
7349
|
+
* route, or declares no JSON response for it.
|
|
7350
|
+
*
|
|
7351
|
+
* Part of the devtools contract. **Not part of the general public contract.**
|
|
7352
|
+
*/
|
|
7353
|
+
const seedQueryDevtoolsSchemaRoute = (target) => {
|
|
7354
|
+
const match = findOperation(target);
|
|
7355
|
+
if (!match)
|
|
7356
|
+
return null;
|
|
7357
|
+
const success = successResponse(match.operation);
|
|
7358
|
+
if (!success)
|
|
7359
|
+
return null;
|
|
7360
|
+
const json = jsonSchemaOf(success.response);
|
|
7361
|
+
if (!json)
|
|
7362
|
+
return null;
|
|
7363
|
+
// A top-level `$ref` is resolved here rather than by the walk, so `seedFrom` always receives the
|
|
7364
|
+
// schema its `name` stands for - otherwise the walk reads the name as one it is already expanding and
|
|
7365
|
+
// cuts the whole body as a cycle.
|
|
7366
|
+
const ref = isRecord(json.schema) ? json.schema['$ref'] : null;
|
|
7367
|
+
const named = typeof ref === 'string' ? { schema: resolveRef(ref), name: refLabel(ref) } : null;
|
|
7368
|
+
if (named && named.schema === undefined)
|
|
7369
|
+
return null;
|
|
7370
|
+
const seed = json.example === undefined
|
|
7371
|
+
? seedFrom(named ? named.schema : json.schema, named ? named.name : null)
|
|
7372
|
+
: {
|
|
7373
|
+
body: json.example,
|
|
7374
|
+
schemaName: null,
|
|
7375
|
+
types: new Map(),
|
|
7376
|
+
notes: ['The document ships an example for this route - it was used as-is.'],
|
|
7377
|
+
};
|
|
7378
|
+
const notes = [...seed.notes, `Generated from ${match.path} ${target.method.toUpperCase()} ${success.status}.`];
|
|
7379
|
+
if (match.droppedPrefix)
|
|
7380
|
+
notes.push(`The document has no ${match.droppedPrefix} prefix - it was ignored when matching.`);
|
|
7381
|
+
return { ...seed, notes };
|
|
7382
|
+
};
|
|
7383
|
+
|
|
6190
7384
|
const EMPTY_STATS = {
|
|
6191
7385
|
executions: 0,
|
|
6192
7386
|
requests: 0,
|
|
@@ -6233,21 +7427,22 @@ const RUN_HISTORY = 25;
|
|
|
6233
7427
|
* decision the app has to make for itself.
|
|
6234
7428
|
*/
|
|
6235
7429
|
const DEFAULT_RESPONSE_HISTORY = 5;
|
|
6236
|
-
let
|
|
7430
|
+
let providedResponseHistory = DEFAULT_RESPONSE_HISTORY;
|
|
6237
7431
|
/**
|
|
6238
|
-
* How many bodies each query retains
|
|
6239
|
-
* that still holds one can be an end of
|
|
6240
|
-
* can no longer be picked.
|
|
7432
|
+
* How many bodies each query retains: what the panel's Settings tab asks for, or
|
|
7433
|
+
* {@link QueryDevtoolsOptions.responseHistory}, or `5`. Only a run that still holds one can be an end of
|
|
7434
|
+
* a response diff, so the panel reads this to say why an older run can no longer be picked.
|
|
6241
7435
|
*
|
|
6242
7436
|
* Part of the devtools contract. **Not part of the general public contract.**
|
|
6243
7437
|
*/
|
|
6244
|
-
const queryDevtoolsResponseHistory = () => responseHistory;
|
|
7438
|
+
const queryDevtoolsResponseHistory = () => queryDevtoolsSettings().responseHistory ?? providedResponseHistory;
|
|
6245
7439
|
/**
|
|
6246
|
-
* Sets
|
|
7440
|
+
* Sets the application's own retention, which the panel's Settings tab can raise for the rest of the
|
|
7441
|
+
* page. Called by `provideQueryDevtools()`; nothing else may call it.
|
|
6247
7442
|
* @internal
|
|
6248
7443
|
*/
|
|
6249
7444
|
const setQueryDevtoolsResponseHistory = (count) => {
|
|
6250
|
-
|
|
7445
|
+
providedResponseHistory = count === undefined ? DEFAULT_RESPONSE_HISTORY : Math.max(1, Math.floor(count));
|
|
6251
7446
|
};
|
|
6252
7447
|
/** The index of the newest run still in flight, or -1 when none is. */
|
|
6253
7448
|
const lastPendingRunIndex = (runs) => {
|
|
@@ -6263,7 +7458,9 @@ const lastPendingRunIndex = (runs) => {
|
|
|
6263
7458
|
* two. Mutates the array it is given, which is always one this module just built.
|
|
6264
7459
|
*/
|
|
6265
7460
|
const trimRetainedBodies = (runs) => {
|
|
6266
|
-
|
|
7461
|
+
// Untracked: a run is recorded from whatever context the query is running in, and reading the setting
|
|
7462
|
+
// there would make that context re-run every time the setting changes.
|
|
7463
|
+
const budget = untracked(queryDevtoolsResponseHistory);
|
|
6267
7464
|
let kept = 0;
|
|
6268
7465
|
for (let index = runs.length - 1; index >= 0; index--) {
|
|
6269
7466
|
const run = runs[index];
|
|
@@ -6682,7 +7879,7 @@ const registerEntry = (registration) => {
|
|
|
6682
7879
|
createdAt: Date.now(),
|
|
6683
7880
|
stats: registration.stats,
|
|
6684
7881
|
formLinks: registration.formLinks,
|
|
6685
|
-
overrides: registration.overrides,
|
|
7882
|
+
overrides: registration.overrides && withQueryDevtoolsOverridePersistence(id, registration.overrides),
|
|
6686
7883
|
};
|
|
6687
7884
|
// A registration that brings its own id can repeat one a tombstone still holds; the live entry wins.
|
|
6688
7885
|
entries.update((list) => [...list.filter((e) => !(e.id === id && e.destroyedAt)), fullEntry]);
|
|
@@ -6736,12 +7933,22 @@ const clearQueryDevtoolsTombstones = (ids) => {
|
|
|
6736
7933
|
* ```
|
|
6737
7934
|
*/
|
|
6738
7935
|
const provideQueryDevtools = (options) => {
|
|
7936
|
+
// First: the stored settings say where the override store lives and whether the panel has raised the
|
|
7937
|
+
// retention this app asked for, and both are read by the calls below.
|
|
7938
|
+
initQueryDevtoolsSettings();
|
|
6739
7939
|
setQueryDevtoolsResponseHistory(options?.responseHistory);
|
|
7940
|
+
registerEthleteVersion('core', CORE_VERSION);
|
|
7941
|
+
registerEthleteVersion('query', QUERY_VERSION);
|
|
7942
|
+
setQueryDevtoolsAppInfo(options?.about);
|
|
7943
|
+
initQueryDevtoolsOverridePersistence();
|
|
7944
|
+
initQueryDevtoolsMocks();
|
|
7945
|
+
setQueryDevtoolsSchemaLoader(options?.schema);
|
|
6740
7946
|
setQueryDevtoolsRegistrar(registerEntry);
|
|
6741
7947
|
setQueryDevtoolsStatsFactory(createQueryDevtoolsStats);
|
|
6742
7948
|
setQueryDevtoolsFormLinksFactory(createQueryDevtoolsFormLinks);
|
|
6743
7949
|
setQueryDevtoolsOverridesFactory(createQueryDevtoolsOverrides);
|
|
6744
7950
|
setQueryDevtoolsFaultResolver(resolveQueryDevtoolsFaultForAttempt);
|
|
7951
|
+
setQueryDevtoolsMockResolver(resolveQueryDevtoolsMockForAttempt);
|
|
6745
7952
|
if (!isDevMode()) {
|
|
6746
7953
|
console.warn('You are using the Query Devtools in production mode. This increases the size of your bundle and should only be used for development purposes.');
|
|
6747
7954
|
}
|
|
@@ -6859,14 +8066,14 @@ const createSecureGqlExecuteFn = (executeOptions) => {
|
|
|
6859
8066
|
state: executeOptions.state,
|
|
6860
8067
|
autoExecutes: shouldAutoExecuteGqlQuery(executeOptions.creatorInternals.method) &&
|
|
6861
8068
|
!executeOptions.queryConfig.onlyManualExecution,
|
|
6862
|
-
transformAuthAndExec: (executeArgs,
|
|
8069
|
+
transformAuthAndExec: (executeArgs, executeState) => {
|
|
6863
8070
|
const { args, options: runOptions } = executeArgs ?? {};
|
|
6864
8071
|
const query = transformGql(executeOptions.creatorInternals.query);
|
|
6865
8072
|
let gqlParams = query(args?.variables);
|
|
6866
8073
|
if (args?.queryParams && executeOptions.creatorInternals.transport === 'GET') {
|
|
6867
8074
|
gqlParams = { ...gqlParams, ...args.queryParams };
|
|
6868
8075
|
}
|
|
6869
|
-
const computedArgs = { ...(args ?? {})
|
|
8076
|
+
const computedArgs = { ...(args ?? {}) };
|
|
6870
8077
|
if (executeOptions.creatorInternals.transport === 'GET') {
|
|
6871
8078
|
computedArgs.queryParams = gqlParams;
|
|
6872
8079
|
}
|
|
@@ -12282,5 +13489,5 @@ const createWebSocketClient = (options) => {
|
|
|
12282
13489
|
* Generated bundle index. Do not edit.
|
|
12283
13490
|
*/
|
|
12284
13491
|
|
|
12285
|
-
export { BasicAuthProvider, BearerAuthFeatureType, BooleanArrayQueryField, CLEAR_QUERY_ARGS, CustomHeaderAuthProvider, DEFAULT_KEEP_UNUSED_FOR, DEFAULT_QUERY_PERSISTENCE_MAX_AGE, DEFAULT_QUERY_PERSISTENCE_MAX_ENTRIES, DEFAULT_QUERY_PERSISTENCE_VERSION, DEFAULT_QUERY_PERSISTENCE_WRITE_DELAY, DateArrayQueryField, DateQueryField, EMPTY_QUERY_DEVTOOLS_FAULT, EntityStore, IGNORED_FILTER_COUNT_FIELDS, INFINITY_QUERY_TOKEN, InfinityQuery, InfinityQueryDirective, InfinityQueryTriggerDirective, LegacyQuery, LegacyQueryCreator, MAX_QUERY_DEVTOOLS_TOMBSTONES, MAX_UNUSED_ENTRIES, NumberArrayQueryField, ParseHttpErrorCodeToMessageDePipe, ParseHttpErrorCodeToMessageEnPipe, ParseHttpErrorCodeToTitleDePipe, ParseHttpErrorCodeToTitleEnPipe, QUERY_CLIENT_DEVTOOLS_TOKEN, QUERY_DEVTOOLS_FAULT_STATUSES, QUERY_PERSISTENCE_STORE_VERSION, QUERY_SYNC_PROTOCOL_VERSION, QueryClientFeatureType, QueryDevtoolsComponent, QueryDirective, 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, applyQueryFeatures, authExtractTokensResponseMissingAccessToken, authExtractTokensResponseMissingRefreshToken, authExtractTokensResponseNotObject, authProviderFeatureUsedMultipleTimes, booleanArrayQueryField, buildQueryCacheKey, buildQueryString, buildRoute, buildTimestampFromSeconds, castQueryCreatorTypes, circularQueryDependency, circularQueryDependencyChecker, clearQueryDevtoolsFaults, clearQueryDevtoolsTombstones, collectLeafPaths, collectQueryFormLinks, computeQueryAuthHeader, computeQueryBody, computeQueryHeaders, computeQueryMethod, computeQueryQueryParams, consumeSuppressQueryStackDevtools, contentfulGqlLikePaginationAdapter, createAuthGuard, createBaseQuery, createBaseQueryCreator, createBearerAuthProvider, createDeleteQuery, createExecuteFn, createGetQuery, createGqlExecuteFn, createGqlMutationViaGet, createGqlMutationViaPost, createGqlQuery, createGqlQueryCreator, createGqlQueryViaGet, createGqlQueryViaPost, createHttpRequest, createIndexedDbQueryPersistenceAdapter, createInfinityQueryConfig, createLegacyQueryCreator, createNoopQueryPersistenceAdapter, createPagedQueryStack, createPatchQuery, createPersistentAuthFeature, createPostQuery, createPutQuery, createQuery, createQueryClient, createQueryCollection, createQueryCollectionSignal, createQueryCollectionSubject, createQueryContext, createQueryCreator, createQueryDevtoolsFormLinks, createQueryDevtoolsFormLinksRecorder, createQueryDevtoolsOverrides, createQueryDevtoolsOverridesRecorder, createQueryDevtoolsStats, createQueryDevtoolsStatsRecorder, createQueryErrorResponse, createQueryFeature, createQueryInvalidationFilter, createQueryKeyLockManager, createQueryObject, createQueryPersistenceEngine, createQueryRepository, createQuerySnapshotFn, createQueryStack, createQuerySyncEngine, createQuerySyncTransport, createSecureDeleteQuery, createSecureExecuteFactory, createSecureExecuteFn, createSecureGetQuery, createSecureGqlExecuteFn, createSecureGqlMutationViaGet, createSecureGqlMutationViaPost, createSecureGqlQuery, createSecureGqlQueryCreator, createSecureGqlQueryViaGet, createSecureGqlQueryViaPost, createSecurePatchQuery, createSecurePostQuery, createSecurePutQuery, createSecureQuery, createSecureQueryCreator, createTrackingFeature, createWebSocketClient, dateArrayQueryField, dateQueryField, decryptBearer, deepFreeze, def, defineQueryForm, describeQueryDevtoolsFeatures, detectContentTypeHeader, detectPaginationShape, dynLikePaginationAdapter, effectComputed, emptyQueryArgs, ethletePaginationAdapter, executeUntilSettled, extractExpiresInSeconds, extractFormViolations, extractHtmlErrorMessage, extractQuery, fakePaginationAdapter, filterFailure, filterNull, filterQueryStates, filterSuccess, forEachHeader, formatQueryDevtoolsDuration, generateQueryDevtoolsNumberPreset, generateQueryDevtoolsStringPreset, getBearerAuthProviderName, getDefaultHeaders, getQueryClientName, getQueryFeatureUsage, getResponseUrl, ggLikePaginationAdapter, gql, gqlDataPropertyMissingInResponse, hasHeader, hasQueryDevtoolsOverridesAtPath, htmlErrorPayload, htmlQueryErrorParser, ignoreAutoRefresh, 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, isQueryDevtoolsFaultInjectionEnabled, isQueryErrorResponse, isQueryStateCancelled, isQueryStateFailure, isQueryStateLoading, isQueryStatePrepared, isQueryStateSuccess, isRequestError, isSymfonyError, isSymfonyFormViolationListError, isSymfonyListError, isSymfonyPagerfantaOutOfRangeError, isUnderInvalidatedUrl, legacyPrepareFallbackInjector, legacyPrepareWithoutInjectionContext, mapToPaginated, mapViolationsToFormErrors, maybeExecute, measureQueryDevtoolsPayload, mergeHeaders, nestedEffect, noteQueryFormRead, numberArrayQueryField, pagedQueryStackNextPageCalledWithoutPreviousPage, pagedQueryStackPageBiggerThanTotalPages, pagedQueryStackPreviousPageCalledButAlreadyAtFirstPage, paginatedEntityValueUpdater, parseAllXhrResponseHeaders, parseHttpErrorCodeToMessageDe, parseHttpErrorCodeToMessageEn, parseHttpErrorCodeToTitleDe, parseHttpErrorCodeToTitleEn, parseQueryRoute, pathParamsMissingInRouteFunctionError, provideInfinityQueryResponseDelay, provideLegacyPrepareFallback, provideQueryClientForDevtools, provideQueryDevtools, queryArrayComputed, queryClientFeatureUsedMultipleTimes, queryComputed, queryComputedTillTruthy, queryDevtoolsEntries, queryDevtoolsFaults, queryDevtoolsFnDetail, queryDevtoolsResponseHistory, queryErrorMessage, queryErrorMessages, queryExecute, queryFeatureUsedMultipleTimes, queryField, querySequence, querySequenceAlreadyRunning, queryStackTotalQueriesAndExpectedQueriesMismatch, queryStackWithArgsUsed, queryStackWithResponseUpdateUsed, queryStateAlreadyHasKeyError, queryStateCannotTransformError, queryStateDoesNotContainKeyError, queryStateErrorSignal, queryStateLoadingSignal, queryStateResponseSignal, queryStateSignal, registerQueryDevtoolsEntry, registerQueryErrorParser, removeFrom, request, resetExecuteState, resetPageOnError, resolveInvalidationUrl, resolveQueryDevtoolsFault, resolveQueryDevtoolsFaultForAttempt, runDefaultQueryRetry, runQueryErrorParsers, searchQueryField, serializeBody, setDefaultQueryRetryFn, setQueryDevtoolsFault, setQueryDevtoolsFaultResolver, setQueryDevtoolsFormLinksFactory, setQueryDevtoolsOverridesFactory, setQueryDevtoolsRegistrar, setQueryDevtoolsResponseHistory, setQueryDevtoolsStatsFactory, 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, withMultiTabSync, withPageResetOnError, withPersistentAuth, withPolling, withPollingUsedOnUnsupportedHttpMethod, withQueryPersistence, withRefreshQuery, withResponseUpdate, withSuccessHandling, withSymfonyErrors, withTokenExpirationWarning, withTokenRevocation, withTracking, wrapAsObservableSignal, wrapQuerySyncMessage };
|
|
13492
|
+
export { BasicAuthProvider, BearerAuthFeatureType, BooleanArrayQueryField, CLEAR_QUERY_ARGS, CustomHeaderAuthProvider, DEFAULT_KEEP_UNUSED_FOR, DEFAULT_QUERY_PERSISTENCE_MAX_AGE, DEFAULT_QUERY_PERSISTENCE_MAX_ENTRIES, DEFAULT_QUERY_PERSISTENCE_VERSION, DEFAULT_QUERY_PERSISTENCE_WRITE_DELAY, DateArrayQueryField, DateQueryField, EMPTY_QUERY_DEVTOOLS_FAULT, EntityStore, IGNORED_FILTER_COUNT_FIELDS, INFINITY_QUERY_TOKEN, InfinityQuery, InfinityQueryDirective, InfinityQueryTriggerDirective, LegacyQuery, LegacyQueryCreator, MAX_QUERY_DEVTOOLS_TOMBSTONES, MAX_UNUSED_ENTRIES, NumberArrayQueryField, ParseHttpErrorCodeToMessageDePipe, ParseHttpErrorCodeToMessageEnPipe, ParseHttpErrorCodeToTitleDePipe, ParseHttpErrorCodeToTitleEnPipe, QUERY_CLIENT_DEVTOOLS_TOKEN, QUERY_DEVTOOLS_FAULT_STATUSES, 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, applyQueryFeatures, armQueryDevtoolsMock, armQueryDevtoolsOverrideTransfer, authExtractTokensResponseMissingAccessToken, authExtractTokensResponseMissingRefreshToken, authExtractTokensResponseNotObject, authProviderFeatureUsedMultipleTimes, booleanArrayQueryField, buildQueryCacheKey, buildQueryString, buildRoute, buildTimestampFromSeconds, castQueryCreatorTypes, circularQueryDependency, circularQueryDependencyChecker, clearQueryDevtoolsArmedMocks, clearQueryDevtoolsFaults, clearQueryDevtoolsMockStore, clearQueryDevtoolsOverrideStore, clearQueryDevtoolsStore, clearQueryDevtoolsTombstones, clearRestoredQueryDevtoolsOverrides, collectLeafPaths, collectQueryFormLinks, computeQueryAuthHeader, computeQueryBody, computeQueryHeaders, computeQueryMethod, computeQueryQueryParams, consumeSuppressQueryStackDevtools, contentfulGqlLikePaginationAdapter, countUnresolvedQueryDevtoolsOverrides, createAuthGuard, createBaseQuery, createBaseQueryCreator, createBearerAuthProvider, createDeleteQuery, createExecuteFn, createGetQuery, createGqlExecuteFn, createGqlMutationViaGet, createGqlMutationViaPost, createGqlQuery, createGqlQueryCreator, createGqlQueryViaGet, createGqlQueryViaPost, createHttpRequest, createIndexedDbQueryPersistenceAdapter, createInfinityQueryConfig, createLegacyQueryCreator, createNoopQueryPersistenceAdapter, createPagedQueryStack, createPatchQuery, createPersistentAuthFeature, createPostQuery, createPutQuery, createQuery, createQueryClient, createQueryCollection, createQueryCollectionSignal, createQueryCollectionSubject, createQueryContext, createQueryCreator, createQueryDevtoolsFormLinks, createQueryDevtoolsFormLinksRecorder, createQueryDevtoolsOverrides, createQueryDevtoolsOverridesRecorder, createQueryDevtoolsStats, createQueryDevtoolsStatsRecorder, createQueryErrorResponse, createQueryFeature, createQueryInvalidationFilter, createQueryKeyLockManager, createQueryObject, createQueryPersistenceEngine, createQueryRepository, createQuerySnapshotFn, createQueryStack, createQuerySyncEngine, createQuerySyncTransport, createSecureDeleteQuery, createSecureExecuteFactory, createSecureExecuteFn, createSecureGetQuery, createSecureGqlExecuteFn, createSecureGqlMutationViaGet, createSecureGqlMutationViaPost, createSecureGqlQuery, createSecureGqlQueryCreator, createSecureGqlQueryViaGet, createSecureGqlQueryViaPost, createSecurePatchQuery, createSecurePostQuery, createSecurePutQuery, createSecureQuery, createSecureQueryCreator, createTrackingFeature, createWebSocketClient, dateArrayQueryField, dateQueryField, decryptBearer, deepFreeze, def, defineQueryForm, deleteQueryDevtoolsMock, describeQueryDevtoolsFeatures, detectContentTypeHeader, detectPaginationShape, dynLikePaginationAdapter, effectComputed, emptyQueryArgs, ethletePaginationAdapter, executeUntilSettled, extractExpiresInSeconds, extractFormViolations, extractHtmlErrorMessage, extractQuery, fakePaginationAdapter, filterFailure, filterNull, filterQueryStates, filterSuccess, forEachHeader, formatQueryDevtoolsDuration, generateQueryDevtoolsNumberPreset, generateQueryDevtoolsStringPreset, getBearerAuthProviderName, getDefaultHeaders, getQueryClientName, getQueryFeatureUsage, getResponseUrl, ggLikePaginationAdapter, gql, gqlDataPropertyMissingInResponse, hasHeader, hasQueryDevtoolsOverridesAtPath, htmlErrorPayload, htmlQueryErrorParser, ignoreAutoRefresh, 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, pathParamsMissingInRouteFunctionError, provideInfinityQueryResponseDelay, provideLegacyPrepareFallback, provideQueryClientForDevtools, provideQueryDevtools, queryArrayComputed, queryClientFeatureUsedMultipleTimes, queryComputed, queryComputedTillTruthy, queryDevtoolsAbout, queryDevtoolsArmedMocks, queryDevtoolsEntries, queryDevtoolsFaults, queryDevtoolsFnDetail, queryDevtoolsMockId, queryDevtoolsMocks, queryDevtoolsOverridePersistence, queryDevtoolsRequestPath, queryDevtoolsResponseHistory, queryDevtoolsRestoredOverridesScope, queryDevtoolsSchemaNames, queryDevtoolsSchemaRoutes, queryDevtoolsSchemaState, queryDevtoolsSettings, queryDevtoolsStorage, 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, runQueryErrorParsers, saveQueryDevtoolsMock, searchQueryField, seedQueryDevtoolsSchemaBody, seedQueryDevtoolsSchemaRoute, serializeBody, serializeQueryDevtoolsOverrideTransfer, setDefaultQueryRetryFn, setQueryDevtoolsAppInfo, setQueryDevtoolsFault, setQueryDevtoolsFaultResolver, setQueryDevtoolsFormLinksFactory, setQueryDevtoolsMockResolver, setQueryDevtoolsOverridePersistence, setQueryDevtoolsOverridesFactory, setQueryDevtoolsOverridesScope, setQueryDevtoolsRegistrar, setQueryDevtoolsResponseHistory, setQueryDevtoolsSchemaLoader, setQueryDevtoolsSettings, setQueryDevtoolsStatsFactory, 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, withMultiTabSync, withPageResetOnError, withPersistentAuth, withPolling, withPollingUsedOnUnsupportedHttpMethod, withQueryDevtoolsOverridePersistence, withQueryPersistence, withRefreshQuery, withResponseUpdate, withSuccessHandling, withSymfonyErrors, withTokenExpirationWarning, withTokenRevocation, withTracking, wrapAsObservableSignal, wrapQuerySyncMessage, writeQueryDevtoolsStore };
|
|
12286
13493
|
//# sourceMappingURL=ethlete-query.mjs.map
|