@ethlete/query 6.0.0-next.31 → 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 +36 -0
- package/fesm2022/ethlete-query.mjs +1291 -80
- 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
|
});
|
|
@@ -4625,7 +4682,11 @@ const createBearerAuthProviderImpl = (config) => {
|
|
|
4625
4682
|
refreshToken.set(refresh);
|
|
4626
4683
|
sessionStatus.set('authenticated');
|
|
4627
4684
|
sessionEndCause.set(null);
|
|
4628
|
-
|
|
4685
|
+
// Emit on the next microtask so that synchronous reactive work triggered by applying
|
|
4686
|
+
// the tokens (signals/effects) has a chance to settle before subscribers react.
|
|
4687
|
+
// This avoids rare races where a subscriber reacting to the emission would run
|
|
4688
|
+
// before other effects have fully applied the new token state.
|
|
4689
|
+
queueMicrotask(() => afterTokenRefresh$.next());
|
|
4629
4690
|
};
|
|
4630
4691
|
// Public entry point only - a query-driven login already reports its own, more specific
|
|
4631
4692
|
// executionState (e.g. `{ type: 'login', state: 'success' }`) via setupBearerQueryRegistry's
|
|
@@ -4664,6 +4725,7 @@ const createBearerAuthProviderImpl = (config) => {
|
|
|
4664
4725
|
name: config.name,
|
|
4665
4726
|
applyTokens,
|
|
4666
4727
|
logout,
|
|
4728
|
+
sessionEndCause: sessionEndCause.asReadonly(),
|
|
4667
4729
|
});
|
|
4668
4730
|
const querySetupContext = {
|
|
4669
4731
|
accessToken,
|
|
@@ -4739,6 +4801,13 @@ const createBearerAuthProviderImpl = (config) => {
|
|
|
4739
4801
|
};
|
|
4740
4802
|
const createBearerAuthProvider = (config) => defineRootProvider(() => createBearerAuthProviderImpl(config), { name: `BearerAuthProvider_${config.name}` });
|
|
4741
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;
|
|
4742
4811
|
const withAuthenticationQuery = (key, config, setup) => ({
|
|
4743
4812
|
_type: 'authQuery',
|
|
4744
4813
|
key,
|
|
@@ -4786,25 +4855,44 @@ const withRefreshQuery = (key, config) => {
|
|
|
4786
4855
|
const executeRefresh = (reason) => {
|
|
4787
4856
|
const currentRefreshToken = context.refreshToken();
|
|
4788
4857
|
if (!currentRefreshToken)
|
|
4789
|
-
return;
|
|
4858
|
+
return 'noToken';
|
|
4790
4859
|
if (!context.isLeader()) {
|
|
4791
4860
|
// Only the leader may spend a single-use refresh token, but the event is still real - hand it
|
|
4792
4861
|
// over instead of dropping it, or this tab waits out the leader's timer with a dead token.
|
|
4793
4862
|
context.refreshCoordination?.request();
|
|
4794
|
-
return;
|
|
4863
|
+
return 'delegated';
|
|
4795
4864
|
}
|
|
4796
4865
|
// Any token-issuing execution, not just another refresh: a login already in flight is about to
|
|
4797
4866
|
// issue a token pair of its own, and the refresh token this would spend belongs to the session
|
|
4798
4867
|
// that login is replacing.
|
|
4799
4868
|
if (context.hasTokenIssuingExecutionInFlight())
|
|
4800
|
-
return;
|
|
4869
|
+
return 'busy';
|
|
4801
4870
|
if (reason === 'scheduled') {
|
|
4802
4871
|
const now = Date.now();
|
|
4803
4872
|
if (now - lastRefreshTime < minRefreshInterval)
|
|
4804
|
-
return;
|
|
4873
|
+
return 'throttled';
|
|
4805
4874
|
lastRefreshTime = now;
|
|
4806
4875
|
}
|
|
4807
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
|
+
}
|
|
4808
4896
|
};
|
|
4809
4897
|
const calculateRefreshBuffer = (tokenLifetimeMs) => {
|
|
4810
4898
|
if (typeof config.refreshStrategy === 'number') {
|
|
@@ -4816,11 +4904,8 @@ const withRefreshQuery = (key, config) => {
|
|
|
4816
4904
|
const calculatedBuffer = tokenLifetimeMs * (1 - percentage);
|
|
4817
4905
|
return Math.max(minBufferMs, Math.min(maxBufferMs, calculatedBuffer));
|
|
4818
4906
|
};
|
|
4819
|
-
|
|
4820
|
-
|
|
4821
|
-
.pipe(switchMap((token) => {
|
|
4822
|
-
if (!token)
|
|
4823
|
-
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) => {
|
|
4824
4909
|
try {
|
|
4825
4910
|
const bearerDataValue = context.bearerDecryptFn ? context.bearerDecryptFn(token) : decryptBearer(token);
|
|
4826
4911
|
const expiresIn = bearerDataValue?.[expiresInPropertyName];
|
|
@@ -4828,36 +4913,40 @@ const withRefreshQuery = (key, config) => {
|
|
|
4828
4913
|
if (isDevMode()) {
|
|
4829
4914
|
console.warn(`Token does not contain valid ${expiresInPropertyName} property for auto-refresh`);
|
|
4830
4915
|
}
|
|
4831
|
-
return
|
|
4916
|
+
return null;
|
|
4832
4917
|
}
|
|
4833
|
-
const
|
|
4834
|
-
const now = Date.now();
|
|
4835
|
-
const tokenLifetimeMs = expiresAtMs - now;
|
|
4918
|
+
const tokenLifetimeMs = expiresIn * 1000 - Date.now();
|
|
4836
4919
|
if (tokenLifetimeMs <= 0) {
|
|
4837
|
-
if (refreshIfExpired)
|
|
4838
|
-
|
|
4839
|
-
|
|
4840
|
-
|
|
4841
|
-
return of(true);
|
|
4920
|
+
if (!refreshIfExpired)
|
|
4921
|
+
return null;
|
|
4922
|
+
if (isDevMode()) {
|
|
4923
|
+
console.warn('Token is already expired, triggering immediate refresh');
|
|
4842
4924
|
}
|
|
4843
|
-
return
|
|
4925
|
+
return 0;
|
|
4844
4926
|
}
|
|
4845
|
-
|
|
4846
|
-
const timeUntilRefresh = tokenLifetimeMs - refreshBufferMs;
|
|
4847
|
-
if (timeUntilRefresh <= 0) {
|
|
4848
|
-
return of(true);
|
|
4849
|
-
}
|
|
4850
|
-
return timer(timeUntilRefresh).pipe(tap(() => true));
|
|
4927
|
+
return Math.max(tokenLifetimeMs - calculateRefreshBuffer(tokenLifetimeMs), 0);
|
|
4851
4928
|
}
|
|
4852
4929
|
catch {
|
|
4853
|
-
return
|
|
4930
|
+
return null;
|
|
4854
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);
|
|
4855
4948
|
}), takeUntilDestroyed())
|
|
4856
|
-
.subscribe(
|
|
4857
|
-
if (shouldRefresh) {
|
|
4858
|
-
executeRefresh('scheduled');
|
|
4859
|
-
}
|
|
4860
|
-
});
|
|
4949
|
+
.subscribe();
|
|
4861
4950
|
context.refreshCoordination?.requests$.pipe(takeUntilDestroyed()).subscribe(() => executeRefresh('unauthorized'));
|
|
4862
4951
|
const onRefreshFailure = config.onRefreshFailure ??
|
|
4863
4952
|
(({ error, logout }) => {
|
|
@@ -5172,6 +5261,12 @@ const resetEncryptionKey = () => {
|
|
|
5172
5261
|
}
|
|
5173
5262
|
};
|
|
5174
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;
|
|
5175
5270
|
/**
|
|
5176
5271
|
* The channel a provider syncs on unless the consumer names one. Carries the provider's name, so two
|
|
5177
5272
|
* providers reachable from the same origin do not push each other's tokens around.
|
|
@@ -5206,7 +5301,7 @@ const setupMultiTabSync = (config, context) => {
|
|
|
5206
5301
|
if (message.type === 'logout' && syncLogout) {
|
|
5207
5302
|
lastSyncedState = LOGGED_OUT;
|
|
5208
5303
|
hadTokens = false;
|
|
5209
|
-
context.logout(
|
|
5304
|
+
context.logout(incomingCause(message.cause));
|
|
5210
5305
|
return;
|
|
5211
5306
|
}
|
|
5212
5307
|
if (message.type !== 'tokens-updated' || !syncTokens)
|
|
@@ -5254,7 +5349,7 @@ const setupMultiTabSync = (config, context) => {
|
|
|
5254
5349
|
if (lastSyncedState === LOGGED_OUT)
|
|
5255
5350
|
return;
|
|
5256
5351
|
lastSyncedState = LOGGED_OUT;
|
|
5257
|
-
const message = { type: 'logout' };
|
|
5352
|
+
const message = { type: 'logout', cause: untracked(context.sessionEndCause) ?? undefined };
|
|
5258
5353
|
channel.postMessage(message);
|
|
5259
5354
|
});
|
|
5260
5355
|
}
|
|
@@ -5310,6 +5405,7 @@ const withBearerAuthMultiTabSync = (config = {}) => {
|
|
|
5310
5405
|
setupMultiTabSync({ channelName, syncTokens: config.syncTokens, syncLogout: config.syncLogout }, {
|
|
5311
5406
|
accessToken: context.accessToken,
|
|
5312
5407
|
refreshToken: context.refreshToken,
|
|
5408
|
+
sessionEndCause: context.sessionEndCause,
|
|
5313
5409
|
name: context.name,
|
|
5314
5410
|
applyTokens: context.applyTokens,
|
|
5315
5411
|
logout: context.logout,
|
|
@@ -5346,6 +5442,7 @@ const withPersistentAuth = (config) => {
|
|
|
5346
5442
|
...(config.autoLogin.excludeRoutes?.length
|
|
5347
5443
|
? [{ label: 'excluded routes', value: config.autoLogin.excludeRoutes.join(', ') }]
|
|
5348
5444
|
: []),
|
|
5445
|
+
...(config.autoLogin.shouldAutoLogin ? [{ label: 'auto login predicate', value: 'custom' }] : []),
|
|
5349
5446
|
],
|
|
5350
5447
|
};
|
|
5351
5448
|
};
|
|
@@ -5406,6 +5503,9 @@ const createPersistentAuthFeature = (config, context) => {
|
|
|
5406
5503
|
if (shouldExclude) {
|
|
5407
5504
|
return;
|
|
5408
5505
|
}
|
|
5506
|
+
if (config.autoLogin.shouldAutoLogin && !config.autoLogin.shouldAutoLogin(currentRoute)) {
|
|
5507
|
+
return;
|
|
5508
|
+
}
|
|
5409
5509
|
const storedToken = getCookie(cookieName);
|
|
5410
5510
|
if (storedToken) {
|
|
5411
5511
|
const decryptedToken = decryptToken(storedToken);
|
|
@@ -5677,6 +5777,40 @@ const createTrackingFeature = (context, config) => {
|
|
|
5677
5777
|
};
|
|
5678
5778
|
};
|
|
5679
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
|
+
|
|
5680
5814
|
/** The neutral fault: nothing armed. */
|
|
5681
5815
|
const EMPTY_QUERY_DEVTOOLS_FAULT = {
|
|
5682
5816
|
latencyMs: 0,
|
|
@@ -5790,6 +5924,482 @@ const createQueryDevtoolsFormLinks = () => {
|
|
|
5790
5924
|
};
|
|
5791
5925
|
};
|
|
5792
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
|
+
|
|
5793
6403
|
const isPathAtOrBelow = (path, root) => path.length >= root.length && root.every((step, index) => step === path[index]);
|
|
5794
6404
|
/**
|
|
5795
6405
|
* Whether any of `entries` is armed at `path` or anywhere below it - what the panel's menu asks before
|
|
@@ -5837,6 +6447,30 @@ const withValueAtPath = (root, path, value) => {
|
|
|
5837
6447
|
const container = root && typeof root === 'object' ? root : {};
|
|
5838
6448
|
return { ...container, [head]: withValueAtPath(container[head], rest, value) };
|
|
5839
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
|
+
};
|
|
5840
6474
|
const IDENTITY_KEY_PATTERNS = [/^id$/i, /(^|[a-z])Id$/, /^uuid$/i, /^key$/i];
|
|
5841
6475
|
const isIdentityKey = (key) => IDENTITY_KEY_PATTERNS.some((pattern) => pattern.test(key));
|
|
5842
6476
|
/** Whether every sibling in `array` that has `key` holds a distinct string/number value for it. */
|
|
@@ -6129,6 +6763,19 @@ const applyOp = (root, op) => {
|
|
|
6129
6763
|
return { root, ok: false };
|
|
6130
6764
|
}
|
|
6131
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 };
|
|
6132
6779
|
case 'duplicateArray':
|
|
6133
6780
|
if (!Array.isArray(resolution.value))
|
|
6134
6781
|
return { root, ok: false };
|
|
@@ -6161,10 +6808,14 @@ const applyQueryDevtoolsOverrides = (entries, raw) => {
|
|
|
6161
6808
|
return { value: root, staleIds };
|
|
6162
6809
|
};
|
|
6163
6810
|
/**
|
|
6164
|
-
* Accumulates the response overrides armed on one query, for the devtools panel.
|
|
6165
|
-
* while {@link provideQueryDevtools} is installed - like {@link createQueryDevtoolsStats},
|
|
6166
|
-
* without devtools pays nothing for it.
|
|
6167
|
-
*
|
|
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.**
|
|
6168
6819
|
*/
|
|
6169
6820
|
const createQueryDevtoolsOverrides = () => {
|
|
6170
6821
|
const list = signal([], /* @ts-ignore */
|
|
@@ -6183,6 +6834,553 @@ const createQueryDevtoolsOverrides = () => {
|
|
|
6183
6834
|
return { list: list.asReadonly(), arm, clear, clearAll, apply };
|
|
6184
6835
|
};
|
|
6185
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
|
+
|
|
6186
7384
|
const EMPTY_STATS = {
|
|
6187
7385
|
executions: 0,
|
|
6188
7386
|
requests: 0,
|
|
@@ -6229,21 +7427,22 @@ const RUN_HISTORY = 25;
|
|
|
6229
7427
|
* decision the app has to make for itself.
|
|
6230
7428
|
*/
|
|
6231
7429
|
const DEFAULT_RESPONSE_HISTORY = 5;
|
|
6232
|
-
let
|
|
7430
|
+
let providedResponseHistory = DEFAULT_RESPONSE_HISTORY;
|
|
6233
7431
|
/**
|
|
6234
|
-
* How many bodies each query retains
|
|
6235
|
-
* that still holds one can be an end of
|
|
6236
|
-
* 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.
|
|
6237
7435
|
*
|
|
6238
7436
|
* Part of the devtools contract. **Not part of the general public contract.**
|
|
6239
7437
|
*/
|
|
6240
|
-
const queryDevtoolsResponseHistory = () => responseHistory;
|
|
7438
|
+
const queryDevtoolsResponseHistory = () => queryDevtoolsSettings().responseHistory ?? providedResponseHistory;
|
|
6241
7439
|
/**
|
|
6242
|
-
* 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.
|
|
6243
7442
|
* @internal
|
|
6244
7443
|
*/
|
|
6245
7444
|
const setQueryDevtoolsResponseHistory = (count) => {
|
|
6246
|
-
|
|
7445
|
+
providedResponseHistory = count === undefined ? DEFAULT_RESPONSE_HISTORY : Math.max(1, Math.floor(count));
|
|
6247
7446
|
};
|
|
6248
7447
|
/** The index of the newest run still in flight, or -1 when none is. */
|
|
6249
7448
|
const lastPendingRunIndex = (runs) => {
|
|
@@ -6259,7 +7458,9 @@ const lastPendingRunIndex = (runs) => {
|
|
|
6259
7458
|
* two. Mutates the array it is given, which is always one this module just built.
|
|
6260
7459
|
*/
|
|
6261
7460
|
const trimRetainedBodies = (runs) => {
|
|
6262
|
-
|
|
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);
|
|
6263
7464
|
let kept = 0;
|
|
6264
7465
|
for (let index = runs.length - 1; index >= 0; index--) {
|
|
6265
7466
|
const run = runs[index];
|
|
@@ -6678,7 +7879,7 @@ const registerEntry = (registration) => {
|
|
|
6678
7879
|
createdAt: Date.now(),
|
|
6679
7880
|
stats: registration.stats,
|
|
6680
7881
|
formLinks: registration.formLinks,
|
|
6681
|
-
overrides: registration.overrides,
|
|
7882
|
+
overrides: registration.overrides && withQueryDevtoolsOverridePersistence(id, registration.overrides),
|
|
6682
7883
|
};
|
|
6683
7884
|
// A registration that brings its own id can repeat one a tombstone still holds; the live entry wins.
|
|
6684
7885
|
entries.update((list) => [...list.filter((e) => !(e.id === id && e.destroyedAt)), fullEntry]);
|
|
@@ -6732,12 +7933,22 @@ const clearQueryDevtoolsTombstones = (ids) => {
|
|
|
6732
7933
|
* ```
|
|
6733
7934
|
*/
|
|
6734
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();
|
|
6735
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);
|
|
6736
7946
|
setQueryDevtoolsRegistrar(registerEntry);
|
|
6737
7947
|
setQueryDevtoolsStatsFactory(createQueryDevtoolsStats);
|
|
6738
7948
|
setQueryDevtoolsFormLinksFactory(createQueryDevtoolsFormLinks);
|
|
6739
7949
|
setQueryDevtoolsOverridesFactory(createQueryDevtoolsOverrides);
|
|
6740
7950
|
setQueryDevtoolsFaultResolver(resolveQueryDevtoolsFaultForAttempt);
|
|
7951
|
+
setQueryDevtoolsMockResolver(resolveQueryDevtoolsMockForAttempt);
|
|
6741
7952
|
if (!isDevMode()) {
|
|
6742
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.');
|
|
6743
7954
|
}
|
|
@@ -6855,14 +8066,14 @@ const createSecureGqlExecuteFn = (executeOptions) => {
|
|
|
6855
8066
|
state: executeOptions.state,
|
|
6856
8067
|
autoExecutes: shouldAutoExecuteGqlQuery(executeOptions.creatorInternals.method) &&
|
|
6857
8068
|
!executeOptions.queryConfig.onlyManualExecution,
|
|
6858
|
-
transformAuthAndExec: (executeArgs,
|
|
8069
|
+
transformAuthAndExec: (executeArgs, executeState) => {
|
|
6859
8070
|
const { args, options: runOptions } = executeArgs ?? {};
|
|
6860
8071
|
const query = transformGql(executeOptions.creatorInternals.query);
|
|
6861
8072
|
let gqlParams = query(args?.variables);
|
|
6862
8073
|
if (args?.queryParams && executeOptions.creatorInternals.transport === 'GET') {
|
|
6863
8074
|
gqlParams = { ...gqlParams, ...args.queryParams };
|
|
6864
8075
|
}
|
|
6865
|
-
const computedArgs = { ...(args ?? {})
|
|
8076
|
+
const computedArgs = { ...(args ?? {}) };
|
|
6866
8077
|
if (executeOptions.creatorInternals.transport === 'GET') {
|
|
6867
8078
|
computedArgs.queryParams = gqlParams;
|
|
6868
8079
|
}
|
|
@@ -12278,5 +13489,5 @@ const createWebSocketClient = (options) => {
|
|
|
12278
13489
|
* Generated bundle index. Do not edit.
|
|
12279
13490
|
*/
|
|
12280
13491
|
|
|
12281
|
-
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 };
|
|
12282
13493
|
//# sourceMappingURL=ethlete-query.mjs.map
|