@ethlete/query 6.0.0-next.54 → 6.0.0-next.55
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 +49 -0
- package/fesm2022/ethlete-query-devtools-contract.mjs +6 -0
- package/fesm2022/ethlete-query-devtools-contract.mjs.map +1 -0
- package/fesm2022/ethlete-query-ngrx-toolkit.mjs +2 -2
- package/fesm2022/ethlete-query-ngrx-toolkit.mjs.map +1 -1
- package/fesm2022/ethlete-query-testing.mjs +39 -21
- package/fesm2022/ethlete-query-testing.mjs.map +1 -1
- package/fesm2022/ethlete-query.mjs +1804 -1130
- package/fesm2022/ethlete-query.mjs.map +1 -1
- package/generators/generators.json +10 -0
- package/generators/migrate-query-client-features/migration.js +10 -8
- package/generators/migrate-to-query-v3/legacy-query-creator-migration.js +1 -1
- package/generators/migrate-to-query-v3/report.js +1 -1
- package/generators/move-devtools-contract-imports/migration.js +155 -0
- package/generators/move-devtools-contract-imports/moved-names.js +153 -0
- package/generators/move-devtools-contract-imports/schema.json +26 -0
- package/generators/report-legacy-query-apis/migration.js +256 -0
- package/generators/report-legacy-query-apis/schema.json +25 -0
- package/migrations.json +26 -0
- package/package.json +12 -16
- package/types/ethlete-query-devtools-contract.d.ts +1 -0
- package/types/ethlete-query-ngrx-toolkit.d.ts +2 -2
- package/types/ethlete-query-testing.d.ts +42 -82
- package/types/ethlete-query.d.ts +1030 -773
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
import * as i0 from '@angular/core';
|
|
2
2
|
import { signal, inject, DestroyRef, computed, effect, isDevMode, untracked, ApplicationRef, Injector, runInInjectionContext, InjectionToken, EnvironmentInjector, ElementRef, ErrorHandler, createEnvironmentInjector, linkedSignal, PLATFORM_ID, resource, DOCUMENT, VERSION, provideEnvironmentInitializer, CSP_NONCE, afterNextRender, makeEnvironmentProviders, assertInInjectionContext, ChangeDetectorRef, ViewContainerRef, TemplateRef, Input, Directive, input, Pipe, ViewEncapsulation, Component, NgZone } from '@angular/core';
|
|
3
3
|
import { UrlTree, Router, RedirectCommand, ActivatedRoute } from '@angular/router';
|
|
4
|
-
import { Subject, Observable, defer, from, filter, Subscription, of, throwError, timer, switchMap, tap, retry, catchError, EMPTY as EMPTY$1, take, takeUntil, finalize, mergeMap, toArray, map, race, NEVER, merge, takeWhile, defaultIfEmpty, firstValueFrom, concatMap, fromEvent, throttleTime, interval, startWith, pairwise, BehaviorSubject, combineLatest,
|
|
5
|
-
import {
|
|
4
|
+
import { Subject, Observable, defer, from, ReplaySubject, filter, Subscription, of, throwError, timer, switchMap, tap, retry, catchError, EMPTY as EMPTY$1, take, takeUntil, finalize, mergeMap, toArray, map, race, NEVER, merge, takeWhile, share, defaultIfEmpty, firstValueFrom, concatMap, fromEvent, throttleTime, interval, startWith, pairwise, BehaviorSubject, combineLatest, distinctUntilChanged, shareReplay, withLatestFrom, debounceTime } from 'rxjs';
|
|
5
|
+
import { RuntimeError, defineStaticProvider, equal, clamp, randomId, defineRootProvider, isObject, injectUnsavedChangesCoordinator, injectRoute, getCookie, setCookie, deleteCookie, CORE_VERSION, createDestroy, computedTillTruthy, defineProvider, toProvideFn, toInjectFn, nextFrame, injectQueryParams, injectQueryParamChanges, clone, ET_PROPERTY_REMOVED } from '@ethlete/core';
|
|
6
|
+
import { HttpErrorResponse, HttpClient, HttpEventType, HttpHeaders, HttpContextToken, HttpContext, HttpResponse, HttpStatusCode } from '@angular/common/http';
|
|
6
7
|
import { getActiveConsumer, setActiveConsumer, SIGNAL, createWatch } from '@angular/core/primitives/signals';
|
|
7
|
-
import {
|
|
8
|
-
import { toObservable, takeUntilDestroyed, toSignal } from '@angular/core/rxjs-interop';
|
|
8
|
+
import { takeUntilDestroyed, toObservable, toSignal } from '@angular/core/rxjs-interop';
|
|
9
9
|
import { isPlatformBrowser, isPlatformServer, NgClass, AsyncPipe, JsonPipe } from '@angular/common';
|
|
10
10
|
import { validateAsync, form } from '@angular/forms/signals';
|
|
11
11
|
import * as i1 from '@angular/forms';
|
|
@@ -703,6 +703,585 @@ const registerSessionEnd = (owner, sessionEnd) => {
|
|
|
703
703
|
};
|
|
704
704
|
const readSessionEnd = (owner) => sessionEnds.get(owner) ?? null;
|
|
705
705
|
|
|
706
|
+
const noop$2 = () => undefined;
|
|
707
|
+
let registrar = null;
|
|
708
|
+
/**
|
|
709
|
+
* Installs the real registry. Called by `provideQueryDevtools()`; nothing else may call it.
|
|
710
|
+
* @internal
|
|
711
|
+
*/
|
|
712
|
+
const setQueryDevtoolsRegistrar = (fn) => {
|
|
713
|
+
registrar = fn;
|
|
714
|
+
};
|
|
715
|
+
/**
|
|
716
|
+
* Whether `provideQueryDevtools()` has been called on this page. When `false` every instrumentation
|
|
717
|
+
* site is a no-op, which is what a devtools UI gates itself on so a build that omits the provider
|
|
718
|
+
* renders nothing.
|
|
719
|
+
*
|
|
720
|
+
* It is answered by the `provideQueryDevtools()` call itself, which runs while the providers array is
|
|
721
|
+
* built - so it is already settled by the time an application bootstraps and needs no signal.
|
|
722
|
+
*/
|
|
723
|
+
const isQueryDevtoolsEnabled = () => registrar !== null;
|
|
724
|
+
/**
|
|
725
|
+
* Registers an entry with the devtools registry. Returns an unregister callback. A no-op (returning
|
|
726
|
+
* a no-op callback) unless {@link provideQueryDevtools} has been called.
|
|
727
|
+
* @internal
|
|
728
|
+
*/
|
|
729
|
+
const registerQueryDevtoolsEntry = (entry) => registrar?.(entry) ?? noop$2;
|
|
730
|
+
let statsFactory = null;
|
|
731
|
+
/**
|
|
732
|
+
* Installs the stats recorder factory. Called by `provideQueryDevtools()`; nothing else may call it.
|
|
733
|
+
* @internal
|
|
734
|
+
*/
|
|
735
|
+
const setQueryDevtoolsStatsFactory = (fn) => {
|
|
736
|
+
statsFactory = fn;
|
|
737
|
+
};
|
|
738
|
+
/**
|
|
739
|
+
* A stats recorder for one entry, or `null` unless {@link provideQueryDevtools} has been called.
|
|
740
|
+
*
|
|
741
|
+
* Instrumentation sites must go through this instead of importing `createQueryDevtoolsStats`: a static
|
|
742
|
+
* import pins the whole stats module (~1.4 kB) into every bundle that touches a query, and a runtime
|
|
743
|
+
* `isQueryDevtoolsEnabled()` guard at the call site does not undo that. Reached only from the provider,
|
|
744
|
+
* it is dead code in an app without devtools.
|
|
745
|
+
* @internal
|
|
746
|
+
*/
|
|
747
|
+
const createQueryDevtoolsStatsRecorder = () => statsFactory?.() ?? null;
|
|
748
|
+
let formLinksFactory = null;
|
|
749
|
+
/**
|
|
750
|
+
* Installs the form-links recorder factory. Called by `provideQueryDevtools()`; nothing else may call it.
|
|
751
|
+
* @internal
|
|
752
|
+
*/
|
|
753
|
+
const setQueryDevtoolsFormLinksFactory = (fn) => {
|
|
754
|
+
formLinksFactory = fn;
|
|
755
|
+
};
|
|
756
|
+
/**
|
|
757
|
+
* A form-links recorder for one query, or `null` unless {@link provideQueryDevtools} has been called.
|
|
758
|
+
* Reached only from the provider for the same reason {@link createQueryDevtoolsStatsRecorder} is.
|
|
759
|
+
* @internal
|
|
760
|
+
*/
|
|
761
|
+
const createQueryDevtoolsFormLinksRecorder = () => formLinksFactory?.() ?? null;
|
|
762
|
+
let overridesFactory = null;
|
|
763
|
+
/**
|
|
764
|
+
* Installs the overrides recorder factory. Called by `provideQueryDevtools()`; nothing else may call it.
|
|
765
|
+
* @internal
|
|
766
|
+
*/
|
|
767
|
+
const setQueryDevtoolsOverridesFactory = (fn) => {
|
|
768
|
+
overridesFactory = fn;
|
|
769
|
+
};
|
|
770
|
+
/**
|
|
771
|
+
* An overrides recorder for one query, or `null` unless {@link provideQueryDevtools} has been called.
|
|
772
|
+
* Reached only from the provider for the same reason {@link createQueryDevtoolsStatsRecorder} is.
|
|
773
|
+
* @internal
|
|
774
|
+
*/
|
|
775
|
+
const createQueryDevtoolsOverridesRecorder = () => overridesFactory?.() ?? null;
|
|
776
|
+
/** The ids of the query forms read by the args build currently running, or `null` outside of one. */
|
|
777
|
+
let formLinkSink = null;
|
|
778
|
+
/**
|
|
779
|
+
* Runs `build` and reports which query forms it read. Nested builds are supported - the outer one
|
|
780
|
+
* resumes collecting once the inner one is done.
|
|
781
|
+
* @internal
|
|
782
|
+
*/
|
|
783
|
+
const collectQueryFormLinks = (build) => {
|
|
784
|
+
const outer = formLinkSink;
|
|
785
|
+
const sink = new Set();
|
|
786
|
+
formLinkSink = sink;
|
|
787
|
+
try {
|
|
788
|
+
return { value: build(), ids: [...sink] };
|
|
789
|
+
}
|
|
790
|
+
finally {
|
|
791
|
+
formLinkSink = outer;
|
|
792
|
+
}
|
|
793
|
+
};
|
|
794
|
+
/**
|
|
795
|
+
* Records that a query form's committed value was read. The form calls this from its `value` signal, so
|
|
796
|
+
* a read that happens while a query builds its args is what links the two - no naming convention, and
|
|
797
|
+
* nothing to keep in sync when the args change.
|
|
798
|
+
* @internal
|
|
799
|
+
*/
|
|
800
|
+
const noteQueryFormRead = (id) => {
|
|
801
|
+
formLinkSink?.add(id);
|
|
802
|
+
};
|
|
803
|
+
let faultResolver = null;
|
|
804
|
+
let mockResolver = null;
|
|
805
|
+
/**
|
|
806
|
+
* Installs the fault resolver. Called by `provideQueryDevtools()`; nothing else may call it.
|
|
807
|
+
* @internal
|
|
808
|
+
*/
|
|
809
|
+
const setQueryDevtoolsFaultResolver = (fn) => {
|
|
810
|
+
faultResolver = fn;
|
|
811
|
+
};
|
|
812
|
+
/**
|
|
813
|
+
* Installs the mock resolver. Called by `provideQueryDevtools()`; nothing else may call it.
|
|
814
|
+
* @internal
|
|
815
|
+
*/
|
|
816
|
+
const setQueryDevtoolsMockResolver = (fn) => {
|
|
817
|
+
mockResolver = fn;
|
|
818
|
+
};
|
|
819
|
+
/**
|
|
820
|
+
* Whether the devtools can do anything to a request at all - inject a fault, or serve a mock instead of
|
|
821
|
+
* sending it. Checked before building the wrapper that resolves both per attempt, so a request in an app
|
|
822
|
+
* without devtools keeps the pipeline it always had.
|
|
823
|
+
* @internal
|
|
824
|
+
*/
|
|
825
|
+
const isQueryDevtoolsRequestInterceptionEnabled = () => faultResolver !== null || mockResolver !== null;
|
|
826
|
+
/**
|
|
827
|
+
* The mock to serve one upcoming attempt instead of sending it, or `null` to let it through.
|
|
828
|
+
* @internal
|
|
829
|
+
*/
|
|
830
|
+
const resolveQueryDevtoolsMock = (target) => mockResolver?.(target) ?? null;
|
|
831
|
+
/**
|
|
832
|
+
* Resolves the fault to apply to one upcoming attempt, or `null` when nothing is armed. Consumes a
|
|
833
|
+
* `failNext` budget, so call it exactly once per attempt.
|
|
834
|
+
* @internal
|
|
835
|
+
*/
|
|
836
|
+
const resolveQueryDevtoolsFault = (target) => faultResolver?.(target) ?? null;
|
|
837
|
+
let tokenPayloadPatcher = null;
|
|
838
|
+
/**
|
|
839
|
+
* Installs the access-token payload patcher. Called by `provideQueryDevtools()`; nothing else may call it.
|
|
840
|
+
* @internal
|
|
841
|
+
*/
|
|
842
|
+
const setQueryDevtoolsTokenPayloadPatcher = (fn) => {
|
|
843
|
+
tokenPayloadPatcher = fn;
|
|
844
|
+
};
|
|
845
|
+
/**
|
|
846
|
+
* A decoded access token as the devtools want it seen, which today means with an overridden lifetime
|
|
847
|
+
* applied. Returns the payload as decoded unless `provideQueryDevtools()` installed a patcher.
|
|
848
|
+
*
|
|
849
|
+
* Auth code must reach the override through this rather than importing the registry it lives in: a
|
|
850
|
+
* static import would pin that module into every bundle that builds an auth provider, and a runtime
|
|
851
|
+
* guard at the call site does not undo that.
|
|
852
|
+
* @internal
|
|
853
|
+
*/
|
|
854
|
+
const patchQueryDevtoolsTokenPayload = (options) => (tokenPayloadPatcher ? tokenPayloadPatcher(options) : options.payload);
|
|
855
|
+
/** The batch item being created right now, or `null` outside one. */
|
|
856
|
+
let batchOwner = null;
|
|
857
|
+
/**
|
|
858
|
+
* Runs `create` with every query it creates attributed to `owner`'s batch and item. The attribution has
|
|
859
|
+
* to be ambient: a batch's item query is built by the query creator the caller handed it, which takes no
|
|
860
|
+
* devtools argument - so there is nothing to thread the owner through. Nested runs restore the outer one.
|
|
861
|
+
* @internal
|
|
862
|
+
*/
|
|
863
|
+
const runInQueryDevtoolsBatch = (owner, create) => {
|
|
864
|
+
const outer = batchOwner;
|
|
865
|
+
batchOwner = owner;
|
|
866
|
+
try {
|
|
867
|
+
return create();
|
|
868
|
+
}
|
|
869
|
+
finally {
|
|
870
|
+
batchOwner = outer;
|
|
871
|
+
}
|
|
872
|
+
};
|
|
873
|
+
/** The batch item a query being created belongs to, or `null` for one created outside a batch. @internal */
|
|
874
|
+
const currentQueryDevtoolsBatch = () => batchOwner;
|
|
875
|
+
let suppressStackRegistration = false;
|
|
876
|
+
/**
|
|
877
|
+
* Suppresses devtools registration for the next {@link createQueryStack} call. Used by
|
|
878
|
+
* `createPagedQueryStack` so its backing stack is not registered as a separate `query-stack` entry.
|
|
879
|
+
* @internal
|
|
880
|
+
*/
|
|
881
|
+
const suppressNextQueryStackDevtools = () => {
|
|
882
|
+
suppressStackRegistration = true;
|
|
883
|
+
};
|
|
884
|
+
/**
|
|
885
|
+
* Reads and clears the suppress-next-stack flag. Always call this from `createQueryStack` (even when
|
|
886
|
+
* devtools are disabled) so a suppression request never leaks into a later stack.
|
|
887
|
+
* @internal
|
|
888
|
+
*/
|
|
889
|
+
const consumeSuppressQueryStackDevtools = () => {
|
|
890
|
+
const value = suppressStackRegistration;
|
|
891
|
+
suppressStackRegistration = false;
|
|
892
|
+
return value;
|
|
893
|
+
};
|
|
894
|
+
let authProviderRegistrar = null;
|
|
895
|
+
/**
|
|
896
|
+
* Installs the session vault's provider registrar. Called by `provideQueryDevtools()`; nothing else may
|
|
897
|
+
* call it.
|
|
898
|
+
* @internal
|
|
899
|
+
*/
|
|
900
|
+
const setQueryDevtoolsAuthProviderRegistrar = (fn) => {
|
|
901
|
+
authProviderRegistrar = fn;
|
|
902
|
+
};
|
|
903
|
+
/**
|
|
904
|
+
* Hands one auth provider to the devtools' session vault, which then keeps the live session's tokens in
|
|
905
|
+
* step with the stored one. A no-op without `provideQueryDevtools()`.
|
|
906
|
+
* @internal
|
|
907
|
+
*/
|
|
908
|
+
const registerQueryDevtoolsAuthProvider = (registration) => authProviderRegistrar?.(registration) ?? (() => undefined);
|
|
909
|
+
let authSeedReader = null;
|
|
910
|
+
/**
|
|
911
|
+
* Installs the tab-local session reader. Called by `provideQueryDevtools()`; nothing else may call it.
|
|
912
|
+
* @internal
|
|
913
|
+
*/
|
|
914
|
+
const setQueryDevtoolsAuthSeedReader = (fn) => {
|
|
915
|
+
authSeedReader = fn;
|
|
916
|
+
};
|
|
917
|
+
/**
|
|
918
|
+
* The session the panel put in this tab alone, or `null` for a tab on the shared one. Read once while a
|
|
919
|
+
* provider is built, before its features run: the tokens have to be in place before `withPersistentAuth`
|
|
920
|
+
* decides whether to spend its cookie.
|
|
921
|
+
* @internal
|
|
922
|
+
*/
|
|
923
|
+
const readQueryDevtoolsAuthSeed = (providerName) => authSeedReader?.(providerName) ?? null;
|
|
924
|
+
|
|
925
|
+
// codes 0-999
|
|
926
|
+
const QueryRuntimeErrorCode = {
|
|
927
|
+
// Query
|
|
928
|
+
QUERY_FEATURE_USED_MULTIPLE_TIMES: 0,
|
|
929
|
+
QUERY_CREATED_IN_REACTIVE_CONTEXT: 1,
|
|
930
|
+
INVALIDATES_USED_ON_READ: 2,
|
|
931
|
+
QUERY_CREATED_OUTSIDE_INJECTION_CONTEXT: 3,
|
|
932
|
+
// Routes
|
|
933
|
+
INVALID_BASE_URL: 10,
|
|
934
|
+
INVALID_ROUTE: 11,
|
|
935
|
+
PATH_PARAMS_MISSING_IN_ROUTE_FUNCTION: 12,
|
|
936
|
+
// Query features
|
|
937
|
+
WITH_ARGS_QUERY_FEATURE_MISSING_BUT_ROUTE_IS_FUNCTION: 100,
|
|
938
|
+
WITH_POLLING_USED_ON_UNSUPPORTED_HTTP_METHOD: 101,
|
|
939
|
+
WITH_AUTO_REFRESH_USED_ON_UNSUPPORTED_HTTP_METHOD: 102,
|
|
940
|
+
WITH_AUTO_REFRESH_USED_IN_MANUAL_QUERY: 103,
|
|
941
|
+
SILENCE_MISSING_WITH_ARGS_FEATURE_ERROR_USED_BUT_WITH_ARGS_PRESENT: 104,
|
|
942
|
+
WITH_LONG_POLLING_USED_ON_UNSUPPORTED_HTTP_METHOD: 105,
|
|
943
|
+
WITH_LONG_POLLING_USED_WITH_POLLING: 106,
|
|
944
|
+
WITH_OPTIMISTIC_UPDATE_USED_ON_READ: 107,
|
|
945
|
+
// Auth provider
|
|
946
|
+
AUTH_EXTRACT_TOKENS_RESPONSE_NOT_OBJECT: 200,
|
|
947
|
+
AUTH_EXTRACT_TOKENS_RESPONSE_MISSING_ACCESS_TOKEN: 201,
|
|
948
|
+
AUTH_EXTRACT_TOKENS_RESPONSE_MISSING_REFRESH_TOKEN: 202,
|
|
949
|
+
AUTH_PROVIDER_FEATURE_USED_MULTIPLE_TIMES: 203,
|
|
950
|
+
AUTH_ACCESS_TOKEN_EXPIRY_UNREADABLE: 204,
|
|
951
|
+
// Query client
|
|
952
|
+
QUERY_CLIENT_FEATURE_USED_MULTIPLE_TIMES: 250,
|
|
953
|
+
// Query Repository
|
|
954
|
+
UNCACHEABLE_REQUEST_HAS_CACHE_KEY_PARAM: 300,
|
|
955
|
+
UNCACHEABLE_REQUEST_HAS_ALLOW_CACHE_PARAM: 301,
|
|
956
|
+
// Paged Query Stack
|
|
957
|
+
PAGED_QUERY_STACK_PAGE_BIGGER_THAN_TOTAL_PAGES: 400,
|
|
958
|
+
PAGED_QUERY_STACK_NEXT_PAGE_CALLED_WITHOUT_PREVIOUS_PAGE: 401,
|
|
959
|
+
PAGED_QUERY_STACK_PREVIOUS_PAGE_CALLED_BUT_ALREADY_AT_FIRST_PAGE: 402,
|
|
960
|
+
// Query Stack
|
|
961
|
+
QUERY_STACK_WITH_ARGS_USED: 500,
|
|
962
|
+
QUERY_STACK_WITH_RESPONSE_UPDATE_USED: 501,
|
|
963
|
+
// GQL
|
|
964
|
+
GQL_DATA_PROPERTY_MISSING_IN_RESPONSE: 600,
|
|
965
|
+
GQL_ERRORS_IN_RESPONSE: 601,
|
|
966
|
+
// Secure Execute
|
|
967
|
+
TOKENS_NOT_AVAILABLE_INSIDE_AUTH_AND_EXEC: 700,
|
|
968
|
+
INVALID_STATE_INSIDE_SECURE_EXECUTE_FACTORY: 701,
|
|
969
|
+
// Query Execution
|
|
970
|
+
CIRCULAR_QUERY_DEPENDENCY: 800,
|
|
971
|
+
// Query Sequence
|
|
972
|
+
QUERY_SEQUENCE_ALREADY_RUNNING: 900,
|
|
973
|
+
// Query Batch
|
|
974
|
+
QUERY_BATCH_ALREADY_RUNNING: 910,
|
|
975
|
+
QUERY_BATCH_WITH_ARGS_USED: 911,
|
|
976
|
+
// Legacy interop
|
|
977
|
+
LEGACY_PREPARE_WITHOUT_INJECTION_CONTEXT: 950,
|
|
978
|
+
LEGACY_QUERY_STATE_ALREADY_HAS_KEY: 951,
|
|
979
|
+
LEGACY_QUERY_STATE_DOES_NOT_CONTAIN_KEY: 952,
|
|
980
|
+
LEGACY_QUERY_STATE_CANNOT_TRANSFORM: 953,
|
|
981
|
+
LEGACY_INVALID_BODY: 954,
|
|
982
|
+
};
|
|
983
|
+
const queryFeatureUsedMultipleTimes = (type) => {
|
|
984
|
+
return new RuntimeError(QueryRuntimeErrorCode.QUERY_FEATURE_USED_MULTIPLE_TIMES, `The query feature "${type}()" was used multiple times.`);
|
|
985
|
+
};
|
|
986
|
+
const queryCreatedInReactiveContext = () => {
|
|
987
|
+
return new RuntimeError(QueryRuntimeErrorCode.QUERY_CREATED_IN_REACTIVE_CONTEXT, `A query was created inside a reactive context (a computed, an effect or a template). Every re-run would create another query that lives until its injector is destroyed. Create the query in a field initializer or constructor and change its args instead.`);
|
|
988
|
+
};
|
|
989
|
+
const invalidatesUsedOnRead = (method) => {
|
|
990
|
+
return new RuntimeError(QueryRuntimeErrorCode.INVALIDATES_USED_ON_READ, `This is a "${method}" request, "invalidates" is only supported on mutations. Give the read "tags" and invalidate them from the mutation.`);
|
|
991
|
+
};
|
|
992
|
+
const withOptimisticUpdateUsedOnRead = (method) => {
|
|
993
|
+
return new RuntimeError(QueryRuntimeErrorCode.WITH_OPTIMISTIC_UPDATE_USED_ON_READ, `This is a "${method}" request, "withOptimisticUpdate()" is only supported on mutations.`);
|
|
994
|
+
};
|
|
995
|
+
const withArgsQueryFeatureMissingButRouteIsFunction = () => {
|
|
996
|
+
return new RuntimeError(QueryRuntimeErrorCode.WITH_ARGS_QUERY_FEATURE_MISSING_BUT_ROUTE_IS_FUNCTION, `This queries route is a function, but a "withArgs()" feature is missing. Mutations need one too: create the query with "withArgs(() => ({ body: form.value() }))", then call ".execute()". The "silenceMissingWithArgsFeatureError" config is an escape hatch for args that only exist at call time.`);
|
|
997
|
+
};
|
|
998
|
+
const withPollingUsedOnUnsupportedHttpMethod = (method) => {
|
|
999
|
+
return new RuntimeError(QueryRuntimeErrorCode.WITH_POLLING_USED_ON_UNSUPPORTED_HTTP_METHOD, `This is a "${method}" request, "withPolling()" is only supported for GET, HEAD, OPTIONS requests and GQL queries.`);
|
|
1000
|
+
};
|
|
1001
|
+
const withAutoRefreshUsedOnUnsupportedHttpMethod = (method) => {
|
|
1002
|
+
return new RuntimeError(QueryRuntimeErrorCode.WITH_AUTO_REFRESH_USED_ON_UNSUPPORTED_HTTP_METHOD, `This is a "${method}" request, "withAutoRefresh()" is only supported for GET, HEAD, OPTIONS requests and GQL queries.`);
|
|
1003
|
+
};
|
|
1004
|
+
const withAutoRefreshUsedInManualQuery = () => {
|
|
1005
|
+
return new RuntimeError(QueryRuntimeErrorCode.WITH_AUTO_REFRESH_USED_IN_MANUAL_QUERY, `"withAutoRefresh()" has been used inside a query that has "onlyManualExecution" set to true.` +
|
|
1006
|
+
` If this is intentional, set "ignoreOnlyManualExecution" to true inside the auto refresh config.`);
|
|
1007
|
+
};
|
|
1008
|
+
const withLongPollingUsedOnUnsupportedHttpMethod = (method) => {
|
|
1009
|
+
return new RuntimeError(QueryRuntimeErrorCode.WITH_LONG_POLLING_USED_ON_UNSUPPORTED_HTTP_METHOD, `This is a "${method}" request, "withLongPolling()" is only supported for GET, HEAD, OPTIONS requests and GQL queries.`);
|
|
1010
|
+
};
|
|
1011
|
+
const withLongPollingUsedWithPolling = () => {
|
|
1012
|
+
return new RuntimeError(QueryRuntimeErrorCode.WITH_LONG_POLLING_USED_WITH_POLLING, `"withLongPolling()" and "withPolling()" cannot be used on the same query - both would drive its re-execution.` +
|
|
1013
|
+
` Keep the long polling feature and drop the interval, or the other way around.`);
|
|
1014
|
+
};
|
|
1015
|
+
const silenceMissingWithArgsFeatureErrorUsedButWithArgsPresent = () => {
|
|
1016
|
+
return new RuntimeError(QueryRuntimeErrorCode.SILENCE_MISSING_WITH_ARGS_FEATURE_ERROR_USED_BUT_WITH_ARGS_PRESENT, `The "silenceMissingWithArgsFeatureError" config is set to true, but a "withArgs()" feature is present.`);
|
|
1017
|
+
};
|
|
1018
|
+
const authExtractTokensResponseNotObject = () => {
|
|
1019
|
+
return new RuntimeError(QueryRuntimeErrorCode.AUTH_EXTRACT_TOKENS_RESPONSE_NOT_OBJECT, `The auth token extractor expects the response to be an object.`);
|
|
1020
|
+
};
|
|
1021
|
+
const authExtractTokensResponseMissingAccessToken = () => {
|
|
1022
|
+
return new RuntimeError(QueryRuntimeErrorCode.AUTH_EXTRACT_TOKENS_RESPONSE_MISSING_ACCESS_TOKEN, `The auth token extractor expects the response to contain a string "accessToken" property.`);
|
|
1023
|
+
};
|
|
1024
|
+
const authExtractTokensResponseMissingRefreshToken = () => {
|
|
1025
|
+
return new RuntimeError(QueryRuntimeErrorCode.AUTH_EXTRACT_TOKENS_RESPONSE_MISSING_REFRESH_TOKEN, `The auth token extractor expects the response to contain a string "refreshToken" property.`);
|
|
1026
|
+
};
|
|
1027
|
+
const authProviderFeatureUsedMultipleTimes = (type) => {
|
|
1028
|
+
return new RuntimeError(QueryRuntimeErrorCode.AUTH_PROVIDER_FEATURE_USED_MULTIPLE_TIMES, `Bearer auth feature "${type}" was used multiple times.`);
|
|
1029
|
+
};
|
|
1030
|
+
const authAccessTokenExpiryUnreadable = (claim) => {
|
|
1031
|
+
return new RuntimeError(QueryRuntimeErrorCode.AUTH_ACCESS_TOKEN_EXPIRY_UNREADABLE, `The access token has no numeric "${claim}" claim, neither in what bearerDecryptFn returned nor in its JWT payload, so it is never refreshed before it expires. Keep the claim in the bearerDecryptFn result, or set the refresh query's expiresInPropertyName to the claim that carries the expiry.`);
|
|
1032
|
+
};
|
|
1033
|
+
const queryClientFeatureUsedMultipleTimes = (type) => {
|
|
1034
|
+
return new RuntimeError(QueryRuntimeErrorCode.QUERY_CLIENT_FEATURE_USED_MULTIPLE_TIMES, `Query client feature "${type}" was used multiple times.`);
|
|
1035
|
+
};
|
|
1036
|
+
const uncacheableRequestHasCacheKeyParam = (key) => {
|
|
1037
|
+
return new RuntimeError(QueryRuntimeErrorCode.UNCACHEABLE_REQUEST_HAS_CACHE_KEY_PARAM, `This request is uncacheable, but a cache key was provided: ${key}. Please remove it.`);
|
|
1038
|
+
};
|
|
1039
|
+
const uncacheableRequestHasAllowCacheParam = () => {
|
|
1040
|
+
return new RuntimeError(QueryRuntimeErrorCode.UNCACHEABLE_REQUEST_HAS_ALLOW_CACHE_PARAM, `This request is uncacheable, but allowCache is set to true. Please remove it.`);
|
|
1041
|
+
};
|
|
1042
|
+
const pagedQueryStackPageBiggerThanTotalPages = (page, totalPages) => {
|
|
1043
|
+
return new RuntimeError(QueryRuntimeErrorCode.PAGED_QUERY_STACK_PAGE_BIGGER_THAN_TOTAL_PAGES, `The page "${page}" is bigger than the total pages "${totalPages}".`);
|
|
1044
|
+
};
|
|
1045
|
+
const pagedQueryStackNextPageCalledWithoutPreviousPage = () => {
|
|
1046
|
+
return new RuntimeError(QueryRuntimeErrorCode.PAGED_QUERY_STACK_NEXT_PAGE_CALLED_WITHOUT_PREVIOUS_PAGE, `fetchNextPage() has been called but the current page is not yet loaded. Please call it after the previous page has been loaded.`);
|
|
1047
|
+
};
|
|
1048
|
+
const pagedQueryStackPreviousPageCalledButAlreadyAtFirstPage = () => {
|
|
1049
|
+
return new RuntimeError(QueryRuntimeErrorCode.PAGED_QUERY_STACK_PREVIOUS_PAGE_CALLED_BUT_ALREADY_AT_FIRST_PAGE, `fetchPreviousPage() has been called but the current page is already the first page.`);
|
|
1050
|
+
};
|
|
1051
|
+
const queryStackWithArgsUsed = () => {
|
|
1052
|
+
return new RuntimeError(QueryRuntimeErrorCode.QUERY_STACK_WITH_ARGS_USED, `withArgs() has been used in a query stack or a paged query stack. This is not supported.`);
|
|
1053
|
+
};
|
|
1054
|
+
const queryStackWithResponseUpdateUsed = () => {
|
|
1055
|
+
return new RuntimeError(QueryRuntimeErrorCode.QUERY_STACK_WITH_RESPONSE_UPDATE_USED, `withResponseUpdate() has been used in a query stack or a paged query stack. This is not supported.`);
|
|
1056
|
+
};
|
|
1057
|
+
const gqlDataPropertyMissingInResponse = () => {
|
|
1058
|
+
return new RuntimeError(QueryRuntimeErrorCode.GQL_DATA_PROPERTY_MISSING_IN_RESPONSE, `The GraphQL response is missing the required "data" property. Please add a custom transformResponse param to the query creator to handle this response format.`);
|
|
1059
|
+
};
|
|
1060
|
+
const gqlErrorsInResponse = (errors) => {
|
|
1061
|
+
return new HttpErrorResponse({
|
|
1062
|
+
error: errors,
|
|
1063
|
+
status: 0,
|
|
1064
|
+
statusText: new RuntimeError(QueryRuntimeErrorCode.GQL_ERRORS_IN_RESPONSE, `The GraphQL response carries errors and no data. The server's errors are in raw.error.`).message,
|
|
1065
|
+
});
|
|
1066
|
+
};
|
|
1067
|
+
const tokensNotAvailableInsideAuthAndExec = () => {
|
|
1068
|
+
return new RuntimeError(QueryRuntimeErrorCode.TOKENS_NOT_AVAILABLE_INSIDE_AUTH_AND_EXEC, `Tokens are not available inside authAndExec.`);
|
|
1069
|
+
};
|
|
1070
|
+
const invalidStateInsideSecureExecuteFactory = () => {
|
|
1071
|
+
return new RuntimeError(QueryRuntimeErrorCode.INVALID_STATE_INSIDE_SECURE_EXECUTE_FACTORY, `An invalid state occurred inside the secure execute factory.`);
|
|
1072
|
+
};
|
|
1073
|
+
const circularQueryDependency = () => {
|
|
1074
|
+
return new RuntimeError(QueryRuntimeErrorCode.CIRCULAR_QUERY_DEPENDENCY, `The same query was executed with identical args more than 5 times in a row, less than 100ms apart. This is usually a sign of a circular dependency.`);
|
|
1075
|
+
};
|
|
1076
|
+
const queryExecutedAfterDestroyMessage = (route) => {
|
|
1077
|
+
const target = typeof route === 'string' ? `The query for "${route}"` : 'A query with a function route';
|
|
1078
|
+
return `${target} was executed after the scope that created it was destroyed. The call was ignored - drop the reference to the query when its component or injector goes away.`;
|
|
1079
|
+
};
|
|
1080
|
+
const queryExecutedWhileParkedMessage = (route) => {
|
|
1081
|
+
const target = typeof route === 'string' ? `The query for "${route}"` : 'A query with a function route';
|
|
1082
|
+
return `${target} was executed while its withArgs source returned null (parked). The call was ignored - pass args to execute(), or let the source return them.`;
|
|
1083
|
+
};
|
|
1084
|
+
const legacyPrepareWithoutInjectionContext = (creatorName, method = 'prepare') => {
|
|
1085
|
+
const label = creatorName ? `"${creatorName}"` : 'A legacy query creator';
|
|
1086
|
+
const example = method === 'prepare'
|
|
1087
|
+
? ` search = (term: string) => myQuery.prepare({ queryParams: { term }, injector: this.injector });`
|
|
1088
|
+
: ` container = myQuery.${method}(null, { injector: this.injector });`;
|
|
1089
|
+
return new RuntimeError(QueryRuntimeErrorCode.LEGACY_PREPARE_WITHOUT_INJECTION_CONTEXT, `${label}.${method}() was called outside of an injection context. Unlike the v2 client, the legacy interop layer needs an injector to build the underlying query.\n\n` +
|
|
1090
|
+
`Capture one where a context does exist (field initializer, constructor) and pass it along:\n\n` +
|
|
1091
|
+
` private injector = inject(Injector);\n` +
|
|
1092
|
+
`${example}\n\n` +
|
|
1093
|
+
`Prefer an injector that outlives the call site. A component injector passed from a callback that can still fire after the component is gone leaves prepare() with a destroyed injector, which returns an inert query instead of executing.`);
|
|
1094
|
+
};
|
|
1095
|
+
const querySequenceAlreadyRunning = () => {
|
|
1096
|
+
return new RuntimeError(QueryRuntimeErrorCode.QUERY_SEQUENCE_ALREADY_RUNNING, `run() was called on a query sequence that is already running. Wait for the current run to settle before starting another.`);
|
|
1097
|
+
};
|
|
1098
|
+
const queryBatchAlreadyRunning = () => {
|
|
1099
|
+
return new RuntimeError(QueryRuntimeErrorCode.QUERY_BATCH_ALREADY_RUNNING, `run() or retryFailed() was called on a query batch that is already running. Await the current run, or call cancel() first.`);
|
|
1100
|
+
};
|
|
1101
|
+
const queryBatchWithArgsUsed = () => {
|
|
1102
|
+
return new RuntimeError(QueryRuntimeErrorCode.QUERY_BATCH_WITH_ARGS_USED, `The "withArgs()" feature cannot be used inside a query batch. Each item's args come from the batch's "args" option.`);
|
|
1103
|
+
};
|
|
1104
|
+
|
|
1105
|
+
/**
|
|
1106
|
+
* Route building, query-string serialization and the token clock - shared by the current generation and
|
|
1107
|
+
* the legacy V2 client, which re-exports them under their original names.
|
|
1108
|
+
*/
|
|
1109
|
+
const invalidBaseRouteError$1 = (base) => new RuntimeError(QueryRuntimeErrorCode.INVALID_BASE_URL, `The client base URL "${base}" must not end with "/". Drop the trailing slash.`);
|
|
1110
|
+
const invalidRouteError$1 = (route) => new RuntimeError(QueryRuntimeErrorCode.INVALID_ROUTE, `The route "${route}" must start with "/". Write it as "/${route}".`);
|
|
1111
|
+
const pathParamsMissingInRouteFunctionError$1 = (route) => new RuntimeError(QueryRuntimeErrorCode.PATH_PARAMS_MISSING_IN_ROUTE_FUNCTION, `The route "${route}" is a function, but the query was executed without pathParams. Pass them through withArgs(() => ({ pathParams })) or execute({ args: { pathParams } }).`);
|
|
1112
|
+
const isNaN$2 = (value) => typeof value === 'number' && Number.isNaN(value);
|
|
1113
|
+
const isEmptyString$1 = (value) => typeof value === 'string' && value.trim() === '';
|
|
1114
|
+
const buildQueryString$1 = (params, config) => {
|
|
1115
|
+
const objectNotation = config?.objectNotation ?? 'bracket';
|
|
1116
|
+
const writeArrayIndexes = config?.writeArrayIndexes ?? false;
|
|
1117
|
+
const ignoredValues = config?.ignoredValues ?? [undefined, null, Infinity, -Infinity];
|
|
1118
|
+
const ignoredValuesFns = config?.ignoredValuesFns ?? [isNaN$2, isEmptyString$1];
|
|
1119
|
+
const queryParams = [];
|
|
1120
|
+
function processValue(key, rawValue) {
|
|
1121
|
+
if (rawValue instanceof Date && Number.isNaN(rawValue.getTime()))
|
|
1122
|
+
return false;
|
|
1123
|
+
const value = rawValue instanceof Date ? rawValue.toISOString() : rawValue;
|
|
1124
|
+
if (config?.objectNotation === 'json-stringify') {
|
|
1125
|
+
if (value === undefined) {
|
|
1126
|
+
return false;
|
|
1127
|
+
}
|
|
1128
|
+
if (ignoredValues.includes(value)) {
|
|
1129
|
+
return false;
|
|
1130
|
+
}
|
|
1131
|
+
if (ignoredValuesFns.some((fn) => fn(value))) {
|
|
1132
|
+
return false;
|
|
1133
|
+
}
|
|
1134
|
+
const encodedKey = encodeURIComponent(key);
|
|
1135
|
+
const val = typeof value === 'object' ? JSON.stringify(value) : value;
|
|
1136
|
+
const encodedValue = encodeURIComponent(val);
|
|
1137
|
+
queryParams.push(`${encodedKey}=${encodedValue}`);
|
|
1138
|
+
return true;
|
|
1139
|
+
}
|
|
1140
|
+
else if (Array.isArray(value)) {
|
|
1141
|
+
let currentFilteredIndex = 0;
|
|
1142
|
+
let didAddAnyValue = false;
|
|
1143
|
+
for (const arrayValue of value) {
|
|
1144
|
+
const nestedKey = writeArrayIndexes ? `${key}[${currentFilteredIndex}]` : `${key}[]`;
|
|
1145
|
+
const didAddValue = processValue(nestedKey, arrayValue);
|
|
1146
|
+
if (didAddValue) {
|
|
1147
|
+
currentFilteredIndex++;
|
|
1148
|
+
didAddAnyValue = true;
|
|
1149
|
+
}
|
|
1150
|
+
}
|
|
1151
|
+
return didAddAnyValue;
|
|
1152
|
+
}
|
|
1153
|
+
else if (typeof value === 'object' && value !== null) {
|
|
1154
|
+
let didAddAnyValue = false;
|
|
1155
|
+
for (const [objKey, val] of Object.entries(value)) {
|
|
1156
|
+
const nestedKey = objectNotation === 'dot' ? `${key}.${objKey}` : `${key}[${objKey}]`;
|
|
1157
|
+
didAddAnyValue = processValue(nestedKey, val) || didAddAnyValue;
|
|
1158
|
+
}
|
|
1159
|
+
return didAddAnyValue;
|
|
1160
|
+
}
|
|
1161
|
+
else {
|
|
1162
|
+
if (ignoredValues.includes(value)) {
|
|
1163
|
+
return false;
|
|
1164
|
+
}
|
|
1165
|
+
if (ignoredValuesFns.some((fn) => fn(value))) {
|
|
1166
|
+
return false;
|
|
1167
|
+
}
|
|
1168
|
+
const encodedKey = encodeURIComponent(key);
|
|
1169
|
+
const encodedValue = encodeURIComponent(value);
|
|
1170
|
+
queryParams.push(`${encodedKey}=${encodedValue}`);
|
|
1171
|
+
return true;
|
|
1172
|
+
}
|
|
1173
|
+
}
|
|
1174
|
+
for (const [key, val] of Object.entries(params)) {
|
|
1175
|
+
processValue(key, val);
|
|
1176
|
+
}
|
|
1177
|
+
return queryParams.length ? queryParams.join('&') : null;
|
|
1178
|
+
};
|
|
1179
|
+
const isPlainObject = (value) => {
|
|
1180
|
+
if (!value || typeof value !== 'object')
|
|
1181
|
+
return false;
|
|
1182
|
+
const proto = Object.getPrototypeOf(value);
|
|
1183
|
+
return proto === Object.prototype || proto === null;
|
|
1184
|
+
};
|
|
1185
|
+
/** Copies query params with every plain object's keys sorted, so key order never reaches a cache key. */
|
|
1186
|
+
const sortQueryParamKeys = (value) => {
|
|
1187
|
+
if (Array.isArray(value))
|
|
1188
|
+
return value.map(sortQueryParamKeys);
|
|
1189
|
+
if (!isPlainObject(value))
|
|
1190
|
+
return value;
|
|
1191
|
+
return Object.fromEntries(Object.keys(value)
|
|
1192
|
+
.sort()
|
|
1193
|
+
.map((key) => [key, sortQueryParamKeys(value[key])]));
|
|
1194
|
+
};
|
|
1195
|
+
const buildRoute$1 = (options) => {
|
|
1196
|
+
if (options.base.endsWith('/')) {
|
|
1197
|
+
throw invalidBaseRouteError$1(options.base);
|
|
1198
|
+
}
|
|
1199
|
+
let route;
|
|
1200
|
+
if (typeof options.route === 'function') {
|
|
1201
|
+
if (!options.pathParams) {
|
|
1202
|
+
throw pathParamsMissingInRouteFunctionError$1(options.route({}));
|
|
1203
|
+
}
|
|
1204
|
+
route = options.route(options.pathParams);
|
|
1205
|
+
}
|
|
1206
|
+
else {
|
|
1207
|
+
route = options.route ?? null;
|
|
1208
|
+
}
|
|
1209
|
+
if (route && !route.startsWith('/')) {
|
|
1210
|
+
throw invalidRouteError$1(route);
|
|
1211
|
+
}
|
|
1212
|
+
if (options.queryParams) {
|
|
1213
|
+
const queryString = buildQueryString$1(options.queryParams, options.queryParamConfig);
|
|
1214
|
+
if (queryString) {
|
|
1215
|
+
route = route ? `${route}${route.includes('?') ? '&' : '?'}${queryString}` : `/?${queryString}`;
|
|
1216
|
+
}
|
|
1217
|
+
}
|
|
1218
|
+
return `${options.base}${route ?? ''}`;
|
|
1219
|
+
};
|
|
1220
|
+
const buildTimestampFromSeconds$1 = (seconds) => {
|
|
1221
|
+
if (seconds === null || Number.isNaN(seconds)) {
|
|
1222
|
+
return null;
|
|
1223
|
+
}
|
|
1224
|
+
return new Date(Date.now() + seconds * 1000).getTime();
|
|
1225
|
+
};
|
|
1226
|
+
/** Reads a bearer token's payload. */
|
|
1227
|
+
const decryptBearer$1 = (token) => {
|
|
1228
|
+
try {
|
|
1229
|
+
const base64Url = token.split('.')[1];
|
|
1230
|
+
if (!base64Url) {
|
|
1231
|
+
return null;
|
|
1232
|
+
}
|
|
1233
|
+
const base64 = base64Url.replace(/-/g, '+').replace(/_/g, '/');
|
|
1234
|
+
const jsonPayload = decodeURIComponent(atob(base64)
|
|
1235
|
+
.split('')
|
|
1236
|
+
.map((c) => '%' + ('00' + c.charCodeAt(0).toString(16)).slice(-2))
|
|
1237
|
+
.join(''));
|
|
1238
|
+
return JSON.parse(jsonPayload);
|
|
1239
|
+
}
|
|
1240
|
+
catch (error) {
|
|
1241
|
+
console.error('Invalid bearer token', error);
|
|
1242
|
+
return null;
|
|
1243
|
+
}
|
|
1244
|
+
};
|
|
1245
|
+
|
|
1246
|
+
const readClaim = (payload, claim) => {
|
|
1247
|
+
const value = payload?.[claim];
|
|
1248
|
+
return typeof value === 'number' ? value : null;
|
|
1249
|
+
};
|
|
1250
|
+
/**
|
|
1251
|
+
* The expiry and issue time of an access token. `decoded` (what `bearerDecryptFn` returned) is tried
|
|
1252
|
+
* first and the raw JWT payload second, so a `bearerDecryptFn` that maps the claims to an app user does
|
|
1253
|
+
* not hide the expiry.
|
|
1254
|
+
*/
|
|
1255
|
+
const readAccessTokenTimes = (options) => {
|
|
1256
|
+
const { token, claim, providerName } = options;
|
|
1257
|
+
const patch = (payload) => patchQueryDevtoolsTokenPayload({ payload, providerName, expiresInPropertyName: claim });
|
|
1258
|
+
const candidates = [() => patch(options.decoded), () => patch(decryptBearer$1(token))];
|
|
1259
|
+
for (const candidate of candidates) {
|
|
1260
|
+
let payload;
|
|
1261
|
+
try {
|
|
1262
|
+
payload = candidate();
|
|
1263
|
+
}
|
|
1264
|
+
catch {
|
|
1265
|
+
continue;
|
|
1266
|
+
}
|
|
1267
|
+
const exp = readClaim(payload, claim);
|
|
1268
|
+
if (exp === null)
|
|
1269
|
+
continue;
|
|
1270
|
+
const iat = readClaim(payload, 'iat');
|
|
1271
|
+
return { expiresAtMs: exp * 1000, issuedAtMs: iat === null ? null : iat * 1000 };
|
|
1272
|
+
}
|
|
1273
|
+
return null;
|
|
1274
|
+
};
|
|
1275
|
+
const refreshBufferMs = (strategy, tokenLifetimeMs) => {
|
|
1276
|
+
if (typeof strategy === 'number') {
|
|
1277
|
+
return strategy >= 0 && strategy <= 1 ? tokenLifetimeMs * (1 - strategy) : Math.max(strategy, 0);
|
|
1278
|
+
}
|
|
1279
|
+
const percentage = strategy?.percentage ?? 0.75;
|
|
1280
|
+
const minBufferMs = strategy?.minBufferMs ?? 60_000;
|
|
1281
|
+
const maxBufferMs = strategy?.maxBufferMs ?? 600_000;
|
|
1282
|
+
return Math.max(minBufferMs, Math.min(maxBufferMs, tokenLifetimeMs * (1 - percentage)));
|
|
1283
|
+
};
|
|
1284
|
+
|
|
706
1285
|
const DEFAULT_RETURN_URL_PARAM = 'returnUrl';
|
|
707
1286
|
const DEFAULT_URL = '/';
|
|
708
1287
|
const DEFAULT_RESTORE_TIMEOUT_MS = 10000;
|
|
@@ -903,248 +1482,29 @@ const formatQueryDevtoolsDuration = (ms) => {
|
|
|
903
1482
|
if (ms < 1000)
|
|
904
1483
|
return `${ms}ms`;
|
|
905
1484
|
const round = (value, unit) => `${Number(value.toFixed(1))}${unit}`;
|
|
906
|
-
if (ms < 60_000)
|
|
907
|
-
return round(ms / 1000, 's');
|
|
908
|
-
if (ms < 3_600_000)
|
|
909
|
-
return round(ms / 60_000, 'm');
|
|
910
|
-
return round(ms / 3_600_000, 'h');
|
|
911
|
-
};
|
|
912
|
-
/**
|
|
913
|
-
* The name of a function passed as `options.<key>`, or `null` when it is an inline lambda. An inline
|
|
914
|
-
* `{ handler: (e) => … }` inherits the name `handler` from the key it is assigned to, which tells the
|
|
915
|
-
* reader nothing - a reference to a declared function does.
|
|
916
|
-
*/
|
|
917
|
-
const queryDevtoolsFnName = (fn, key) => {
|
|
918
|
-
const name = typeof fn === 'function' ? fn.name.replace(/^bound /, '') : '';
|
|
919
|
-
return !name || name === key ? null : name;
|
|
920
|
-
};
|
|
921
|
-
/**
|
|
922
|
-
* A detail for the function passed as `options.<key>`, omitted when it is an inline lambda.
|
|
923
|
-
* @internal
|
|
924
|
-
*/
|
|
925
|
-
const queryDevtoolsFnDetail = (fn, key) => {
|
|
926
|
-
const name = queryDevtoolsFnName(fn, key);
|
|
927
|
-
return name ? [{ label: key, value: name }] : [];
|
|
928
|
-
};
|
|
929
|
-
|
|
930
|
-
const noop$2 = () => undefined;
|
|
931
|
-
let registrar = null;
|
|
932
|
-
/**
|
|
933
|
-
* Installs the real registry. Called by `provideQueryDevtools()`; nothing else may call it.
|
|
934
|
-
* @internal
|
|
935
|
-
*/
|
|
936
|
-
const setQueryDevtoolsRegistrar = (fn) => {
|
|
937
|
-
registrar = fn;
|
|
938
|
-
};
|
|
939
|
-
/**
|
|
940
|
-
* Whether `provideQueryDevtools()` has been called on this page. When `false` every instrumentation
|
|
941
|
-
* site is a no-op, which is what a devtools UI gates itself on so a build that omits the provider
|
|
942
|
-
* renders nothing.
|
|
943
|
-
*
|
|
944
|
-
* It is answered by the `provideQueryDevtools()` call itself, which runs while the providers array is
|
|
945
|
-
* built - so it is already settled by the time an application bootstraps and needs no signal.
|
|
946
|
-
*/
|
|
947
|
-
const isQueryDevtoolsEnabled = () => registrar !== null;
|
|
948
|
-
/**
|
|
949
|
-
* Registers an entry with the devtools registry. Returns an unregister callback. A no-op (returning
|
|
950
|
-
* a no-op callback) unless {@link provideQueryDevtools} has been called.
|
|
951
|
-
* @internal
|
|
952
|
-
*/
|
|
953
|
-
const registerQueryDevtoolsEntry = (entry) => registrar?.(entry) ?? noop$2;
|
|
954
|
-
let statsFactory = null;
|
|
955
|
-
/**
|
|
956
|
-
* Installs the stats recorder factory. Called by `provideQueryDevtools()`; nothing else may call it.
|
|
957
|
-
* @internal
|
|
958
|
-
*/
|
|
959
|
-
const setQueryDevtoolsStatsFactory = (fn) => {
|
|
960
|
-
statsFactory = fn;
|
|
961
|
-
};
|
|
962
|
-
/**
|
|
963
|
-
* A stats recorder for one entry, or `null` unless {@link provideQueryDevtools} has been called.
|
|
964
|
-
*
|
|
965
|
-
* Instrumentation sites must go through this instead of importing `createQueryDevtoolsStats`: a static
|
|
966
|
-
* import pins the whole stats module (~1.4 kB) into every bundle that touches a query, and a runtime
|
|
967
|
-
* `isQueryDevtoolsEnabled()` guard at the call site does not undo that. Reached only from the provider,
|
|
968
|
-
* it is dead code in an app without devtools.
|
|
969
|
-
* @internal
|
|
970
|
-
*/
|
|
971
|
-
const createQueryDevtoolsStatsRecorder = () => statsFactory?.() ?? null;
|
|
972
|
-
let formLinksFactory = null;
|
|
973
|
-
/**
|
|
974
|
-
* Installs the form-links recorder factory. Called by `provideQueryDevtools()`; nothing else may call it.
|
|
975
|
-
* @internal
|
|
976
|
-
*/
|
|
977
|
-
const setQueryDevtoolsFormLinksFactory = (fn) => {
|
|
978
|
-
formLinksFactory = fn;
|
|
979
|
-
};
|
|
980
|
-
/**
|
|
981
|
-
* A form-links recorder for one query, or `null` unless {@link provideQueryDevtools} has been called.
|
|
982
|
-
* Reached only from the provider for the same reason {@link createQueryDevtoolsStatsRecorder} is.
|
|
983
|
-
* @internal
|
|
984
|
-
*/
|
|
985
|
-
const createQueryDevtoolsFormLinksRecorder = () => formLinksFactory?.() ?? null;
|
|
986
|
-
let overridesFactory = null;
|
|
987
|
-
/**
|
|
988
|
-
* Installs the overrides recorder factory. Called by `provideQueryDevtools()`; nothing else may call it.
|
|
989
|
-
* @internal
|
|
990
|
-
*/
|
|
991
|
-
const setQueryDevtoolsOverridesFactory = (fn) => {
|
|
992
|
-
overridesFactory = fn;
|
|
993
|
-
};
|
|
994
|
-
/**
|
|
995
|
-
* An overrides recorder for one query, or `null` unless {@link provideQueryDevtools} has been called.
|
|
996
|
-
* Reached only from the provider for the same reason {@link createQueryDevtoolsStatsRecorder} is.
|
|
997
|
-
* @internal
|
|
998
|
-
*/
|
|
999
|
-
const createQueryDevtoolsOverridesRecorder = () => overridesFactory?.() ?? null;
|
|
1000
|
-
/** The ids of the query forms read by the args build currently running, or `null` outside of one. */
|
|
1001
|
-
let formLinkSink = null;
|
|
1002
|
-
/**
|
|
1003
|
-
* Runs `build` and reports which query forms it read. Nested builds are supported - the outer one
|
|
1004
|
-
* resumes collecting once the inner one is done.
|
|
1005
|
-
* @internal
|
|
1006
|
-
*/
|
|
1007
|
-
const collectQueryFormLinks = (build) => {
|
|
1008
|
-
const outer = formLinkSink;
|
|
1009
|
-
const sink = new Set();
|
|
1010
|
-
formLinkSink = sink;
|
|
1011
|
-
try {
|
|
1012
|
-
return { value: build(), ids: [...sink] };
|
|
1013
|
-
}
|
|
1014
|
-
finally {
|
|
1015
|
-
formLinkSink = outer;
|
|
1016
|
-
}
|
|
1017
|
-
};
|
|
1018
|
-
/**
|
|
1019
|
-
* Records that a query form's committed value was read. The form calls this from its `value` signal, so
|
|
1020
|
-
* a read that happens while a query builds its args is what links the two - no naming convention, and
|
|
1021
|
-
* nothing to keep in sync when the args change.
|
|
1022
|
-
* @internal
|
|
1023
|
-
*/
|
|
1024
|
-
const noteQueryFormRead = (id) => {
|
|
1025
|
-
formLinkSink?.add(id);
|
|
1026
|
-
};
|
|
1027
|
-
let faultResolver = null;
|
|
1028
|
-
let mockResolver = null;
|
|
1029
|
-
/**
|
|
1030
|
-
* Installs the fault resolver. Called by `provideQueryDevtools()`; nothing else may call it.
|
|
1031
|
-
* @internal
|
|
1032
|
-
*/
|
|
1033
|
-
const setQueryDevtoolsFaultResolver = (fn) => {
|
|
1034
|
-
faultResolver = fn;
|
|
1035
|
-
};
|
|
1036
|
-
/**
|
|
1037
|
-
* Installs the mock resolver. Called by `provideQueryDevtools()`; nothing else may call it.
|
|
1038
|
-
* @internal
|
|
1039
|
-
*/
|
|
1040
|
-
const setQueryDevtoolsMockResolver = (fn) => {
|
|
1041
|
-
mockResolver = fn;
|
|
1042
|
-
};
|
|
1043
|
-
/**
|
|
1044
|
-
* Whether the devtools can do anything to a request at all - inject a fault, or serve a mock instead of
|
|
1045
|
-
* sending it. Checked before building the wrapper that resolves both per attempt, so a request in an app
|
|
1046
|
-
* without devtools keeps the pipeline it always had.
|
|
1047
|
-
* @internal
|
|
1048
|
-
*/
|
|
1049
|
-
const isQueryDevtoolsRequestInterceptionEnabled = () => faultResolver !== null || mockResolver !== null;
|
|
1050
|
-
/**
|
|
1051
|
-
* The mock to serve one upcoming attempt instead of sending it, or `null` to let it through.
|
|
1052
|
-
* @internal
|
|
1053
|
-
*/
|
|
1054
|
-
const resolveQueryDevtoolsMock = (target) => mockResolver?.(target) ?? null;
|
|
1055
|
-
/**
|
|
1056
|
-
* Resolves the fault to apply to one upcoming attempt, or `null` when nothing is armed. Consumes a
|
|
1057
|
-
* `failNext` budget, so call it exactly once per attempt.
|
|
1058
|
-
* @internal
|
|
1059
|
-
*/
|
|
1060
|
-
const resolveQueryDevtoolsFault = (target) => faultResolver?.(target) ?? null;
|
|
1061
|
-
let tokenPayloadPatcher = null;
|
|
1062
|
-
/**
|
|
1063
|
-
* Installs the access-token payload patcher. Called by `provideQueryDevtools()`; nothing else may call it.
|
|
1064
|
-
* @internal
|
|
1065
|
-
*/
|
|
1066
|
-
const setQueryDevtoolsTokenPayloadPatcher = (fn) => {
|
|
1067
|
-
tokenPayloadPatcher = fn;
|
|
1068
|
-
};
|
|
1069
|
-
/**
|
|
1070
|
-
* A decoded access token as the devtools want it seen, which today means with an overridden lifetime
|
|
1071
|
-
* applied. Returns the payload as decoded unless `provideQueryDevtools()` installed a patcher.
|
|
1072
|
-
*
|
|
1073
|
-
* Auth code must reach the override through this rather than importing the registry it lives in: a
|
|
1074
|
-
* static import would pin that module into every bundle that builds an auth provider, and a runtime
|
|
1075
|
-
* guard at the call site does not undo that.
|
|
1076
|
-
* @internal
|
|
1077
|
-
*/
|
|
1078
|
-
const patchQueryDevtoolsTokenPayload = (options) => (tokenPayloadPatcher ? tokenPayloadPatcher(options) : options.payload);
|
|
1079
|
-
/** The batch item being created right now, or `null` outside one. */
|
|
1080
|
-
let batchOwner = null;
|
|
1081
|
-
/**
|
|
1082
|
-
* Runs `create` with every query it creates attributed to `owner`'s batch and item. The attribution has
|
|
1083
|
-
* to be ambient: a batch's item query is built by the query creator the caller handed it, which takes no
|
|
1084
|
-
* devtools argument - so there is nothing to thread the owner through. Nested runs restore the outer one.
|
|
1085
|
-
* @internal
|
|
1086
|
-
*/
|
|
1087
|
-
const runInQueryDevtoolsBatch = (owner, create) => {
|
|
1088
|
-
const outer = batchOwner;
|
|
1089
|
-
batchOwner = owner;
|
|
1090
|
-
try {
|
|
1091
|
-
return create();
|
|
1092
|
-
}
|
|
1093
|
-
finally {
|
|
1094
|
-
batchOwner = outer;
|
|
1095
|
-
}
|
|
1096
|
-
};
|
|
1097
|
-
/** The batch item a query being created belongs to, or `null` for one created outside a batch. @internal */
|
|
1098
|
-
const currentQueryDevtoolsBatch = () => batchOwner;
|
|
1099
|
-
let suppressStackRegistration = false;
|
|
1100
|
-
/**
|
|
1101
|
-
* Suppresses devtools registration for the next {@link createQueryStack} call. Used by
|
|
1102
|
-
* `createPagedQueryStack` so its backing stack is not registered as a separate `query-stack` entry.
|
|
1103
|
-
* @internal
|
|
1104
|
-
*/
|
|
1105
|
-
const suppressNextQueryStackDevtools = () => {
|
|
1106
|
-
suppressStackRegistration = true;
|
|
1107
|
-
};
|
|
1108
|
-
/**
|
|
1109
|
-
* Reads and clears the suppress-next-stack flag. Always call this from `createQueryStack` (even when
|
|
1110
|
-
* devtools are disabled) so a suppression request never leaks into a later stack.
|
|
1111
|
-
* @internal
|
|
1112
|
-
*/
|
|
1113
|
-
const consumeSuppressQueryStackDevtools = () => {
|
|
1114
|
-
const value = suppressStackRegistration;
|
|
1115
|
-
suppressStackRegistration = false;
|
|
1116
|
-
return value;
|
|
1117
|
-
};
|
|
1118
|
-
let authProviderRegistrar = null;
|
|
1119
|
-
/**
|
|
1120
|
-
* Installs the session vault's provider registrar. Called by `provideQueryDevtools()`; nothing else may
|
|
1121
|
-
* call it.
|
|
1122
|
-
* @internal
|
|
1123
|
-
*/
|
|
1124
|
-
const setQueryDevtoolsAuthProviderRegistrar = (fn) => {
|
|
1125
|
-
authProviderRegistrar = fn;
|
|
1485
|
+
if (ms < 60_000)
|
|
1486
|
+
return round(ms / 1000, 's');
|
|
1487
|
+
if (ms < 3_600_000)
|
|
1488
|
+
return round(ms / 60_000, 'm');
|
|
1489
|
+
return round(ms / 3_600_000, 'h');
|
|
1126
1490
|
};
|
|
1127
1491
|
/**
|
|
1128
|
-
*
|
|
1129
|
-
*
|
|
1130
|
-
*
|
|
1131
|
-
*/
|
|
1132
|
-
const registerQueryDevtoolsAuthProvider = (registration) => authProviderRegistrar?.(registration) ?? (() => undefined);
|
|
1133
|
-
let authSeedReader = null;
|
|
1134
|
-
/**
|
|
1135
|
-
* Installs the tab-local session reader. Called by `provideQueryDevtools()`; nothing else may call it.
|
|
1136
|
-
* @internal
|
|
1492
|
+
* The name of a function passed as `options.<key>`, or `null` when it is an inline lambda. An inline
|
|
1493
|
+
* `{ handler: (e) => … }` inherits the name `handler` from the key it is assigned to, which tells the
|
|
1494
|
+
* reader nothing - a reference to a declared function does.
|
|
1137
1495
|
*/
|
|
1138
|
-
const
|
|
1139
|
-
|
|
1496
|
+
const queryDevtoolsFnName = (fn, key) => {
|
|
1497
|
+
const name = typeof fn === 'function' ? fn.name.replace(/^bound /, '') : '';
|
|
1498
|
+
return !name || name === key ? null : name;
|
|
1140
1499
|
};
|
|
1141
1500
|
/**
|
|
1142
|
-
*
|
|
1143
|
-
* provider is built, before its features run: the tokens have to be in place before `withPersistentAuth`
|
|
1144
|
-
* decides whether to spend its cookie.
|
|
1501
|
+
* A detail for the function passed as `options.<key>`, omitted when it is an inline lambda.
|
|
1145
1502
|
* @internal
|
|
1146
1503
|
*/
|
|
1147
|
-
const
|
|
1504
|
+
const queryDevtoolsFnDetail = (fn, key) => {
|
|
1505
|
+
const name = queryDevtoolsFnName(fn, key);
|
|
1506
|
+
return name ? [{ label: key, value: name }] : [];
|
|
1507
|
+
};
|
|
1148
1508
|
|
|
1149
1509
|
/**
|
|
1150
1510
|
* Whether a create-query options object came from the gql layer - only gql's `creatorInternals` carries
|
|
@@ -1153,21 +1513,105 @@ const readQueryDevtoolsAuthSeed = (providerName) => authSeedReader?.(providerNam
|
|
|
1153
1513
|
*/
|
|
1154
1514
|
const isCreateGqlQueryOptions = (options) => 'transport' in options.creatorInternals;
|
|
1155
1515
|
|
|
1156
|
-
const
|
|
1157
|
-
const
|
|
1158
|
-
const
|
|
1159
|
-
const
|
|
1160
|
-
|
|
1161
|
-
|
|
1516
|
+
const createObservableSignalWatcher = (injector) => {
|
|
1517
|
+
const destroyRef = injector.get(DestroyRef);
|
|
1518
|
+
const watched = [];
|
|
1519
|
+
const registrations = signal(0, /* @ts-ignore */
|
|
1520
|
+
...(ngDevMode ? [{ debugName: "registrations" }] : /* istanbul ignore next */ []));
|
|
1521
|
+
let watching = false;
|
|
1522
|
+
let destroyed = false;
|
|
1523
|
+
const push = (entry) => {
|
|
1524
|
+
if (entry.subject.closed || entry.subject.isStopped)
|
|
1525
|
+
return;
|
|
1526
|
+
let value;
|
|
1527
|
+
try {
|
|
1528
|
+
value = entry.source();
|
|
1529
|
+
}
|
|
1530
|
+
catch (error) {
|
|
1531
|
+
untracked(() => entry.subject.error(error));
|
|
1532
|
+
return;
|
|
1533
|
+
}
|
|
1534
|
+
if (entry.hasValue && Object.is(value, entry.last))
|
|
1535
|
+
return;
|
|
1536
|
+
entry.hasValue = true;
|
|
1537
|
+
entry.last = value;
|
|
1538
|
+
untracked(() => entry.subject.next(value));
|
|
1539
|
+
};
|
|
1540
|
+
const startWatching = () => {
|
|
1541
|
+
if (watching)
|
|
1542
|
+
return;
|
|
1543
|
+
watching = true;
|
|
1544
|
+
effect(() => {
|
|
1545
|
+
registrations();
|
|
1546
|
+
for (const entry of watched)
|
|
1547
|
+
push(entry);
|
|
1548
|
+
}, { injector });
|
|
1549
|
+
destroyRef.onDestroy(() => {
|
|
1550
|
+
destroyed = true;
|
|
1551
|
+
for (const entry of watched)
|
|
1552
|
+
entry.subject.complete();
|
|
1553
|
+
});
|
|
1554
|
+
};
|
|
1555
|
+
const watch = (source) => {
|
|
1556
|
+
const entry = { source, subject: new ReplaySubject(1), hasValue: false, last: undefined };
|
|
1557
|
+
if (destroyed || destroyRef.destroyed) {
|
|
1558
|
+
push(entry);
|
|
1559
|
+
entry.subject.complete();
|
|
1560
|
+
return entry;
|
|
1561
|
+
}
|
|
1562
|
+
startWatching();
|
|
1563
|
+
watched.push(entry);
|
|
1564
|
+
push(entry);
|
|
1565
|
+
registrations.update((count) => count + 1);
|
|
1566
|
+
return entry;
|
|
1567
|
+
};
|
|
1568
|
+
const wrap = (source) => {
|
|
1569
|
+
let default$ = null;
|
|
1570
|
+
const overrides = new WeakMap();
|
|
1571
|
+
const getDefault = () => {
|
|
1572
|
+
if (default$)
|
|
1573
|
+
return default$;
|
|
1574
|
+
const entry = untracked(() => watch(source));
|
|
1575
|
+
default$ = new Observable((subscriber) => {
|
|
1576
|
+
untracked(() => push(entry));
|
|
1577
|
+
return entry.subject.subscribe(subscriber);
|
|
1578
|
+
});
|
|
1162
1579
|
return default$;
|
|
1163
|
-
|
|
1164
|
-
|
|
1165
|
-
|
|
1166
|
-
|
|
1167
|
-
|
|
1168
|
-
|
|
1580
|
+
};
|
|
1581
|
+
const asObservable = (options) => {
|
|
1582
|
+
const overrideInjector = options?.injector;
|
|
1583
|
+
if (!overrideInjector)
|
|
1584
|
+
return getDefault();
|
|
1585
|
+
const existing = overrides.get(overrideInjector);
|
|
1586
|
+
if (existing)
|
|
1587
|
+
return existing;
|
|
1588
|
+
const override$ = getDefault().pipe(takeUntilDestroyed(overrideInjector.get(DestroyRef)));
|
|
1589
|
+
overrides.set(overrideInjector, override$);
|
|
1590
|
+
return override$;
|
|
1591
|
+
};
|
|
1592
|
+
return Object.assign(computed(() => source()), { asObservable });
|
|
1169
1593
|
};
|
|
1170
|
-
return
|
|
1594
|
+
return { wrap };
|
|
1595
|
+
};
|
|
1596
|
+
|
|
1597
|
+
// Angular has no public check for an injection context; `inject()` outside one throws NG0203 (`code: -203`).
|
|
1598
|
+
const MISSING_INJECTION_CONTEXT = -203;
|
|
1599
|
+
const queryCreatedOutsideInjectionContext = (kind) => {
|
|
1600
|
+
const fix = kind === 'query'
|
|
1601
|
+
? 'or pass an injector as the first creator argument: getPost({ injector: this.injector }, withArgs(...))'
|
|
1602
|
+
: 'or wrap the call in runInInjectionContext(this.injector, () => ...)';
|
|
1603
|
+
return new RuntimeError(QueryRuntimeErrorCode.QUERY_CREATED_OUTSIDE_INJECTION_CONTEXT, `A ${kind} was created outside an injection context (in ngOnInit, an event handler or a timer). Create it in a field initializer or constructor, ${fix}.`);
|
|
1604
|
+
};
|
|
1605
|
+
const injectInQueryContext = (token, kind) => {
|
|
1606
|
+
try {
|
|
1607
|
+
return inject(token);
|
|
1608
|
+
}
|
|
1609
|
+
catch (error) {
|
|
1610
|
+
if (error?.code === MISSING_INJECTION_CONTEXT) {
|
|
1611
|
+
throw queryCreatedOutsideInjectionContext(kind);
|
|
1612
|
+
}
|
|
1613
|
+
throw error;
|
|
1614
|
+
}
|
|
1171
1615
|
};
|
|
1172
1616
|
|
|
1173
1617
|
const QUERY_CONTEXT_TOKEN = new InjectionToken('QUERY_CONTEXT');
|
|
@@ -1181,7 +1625,7 @@ const injectQueryContext = () => inject(QUERY_CONTEXT_TOKEN);
|
|
|
1181
1625
|
|
|
1182
1626
|
/** @internal */
|
|
1183
1627
|
const setupQueryDependencies = (options) => {
|
|
1184
|
-
const hostInjector = options.queryConfig?.injector ??
|
|
1628
|
+
const hostInjector = options.queryConfig?.injector ?? injectInQueryContext(Injector, 'query');
|
|
1185
1629
|
const environmentInjector = options.queryConfig?.injector?.get(EnvironmentInjector) ?? hostInjector.get(EnvironmentInjector);
|
|
1186
1630
|
const clientToken = options.client.token;
|
|
1187
1631
|
const dependencies = {
|
|
@@ -1223,165 +1667,6 @@ const setupQueryDependencies = (options) => {
|
|
|
1223
1667
|
return dependencies;
|
|
1224
1668
|
};
|
|
1225
1669
|
|
|
1226
|
-
// codes 0-999
|
|
1227
|
-
const QueryRuntimeErrorCode = {
|
|
1228
|
-
// Query
|
|
1229
|
-
QUERY_FEATURE_USED_MULTIPLE_TIMES: 0,
|
|
1230
|
-
QUERY_CREATED_IN_REACTIVE_CONTEXT: 1,
|
|
1231
|
-
// Query features
|
|
1232
|
-
WITH_ARGS_QUERY_FEATURE_MISSING_BUT_ROUTE_IS_FUNCTION: 100,
|
|
1233
|
-
WITH_POLLING_USED_ON_UNSUPPORTED_HTTP_METHOD: 101,
|
|
1234
|
-
WITH_AUTO_REFRESH_USED_ON_UNSUPPORTED_HTTP_METHOD: 102,
|
|
1235
|
-
WITH_AUTO_REFRESH_USED_IN_MANUAL_QUERY: 103,
|
|
1236
|
-
SILENCE_MISSING_WITH_ARGS_FEATURE_ERROR_USED_BUT_WITH_ARGS_PRESENT: 104,
|
|
1237
|
-
WITH_LONG_POLLING_USED_ON_UNSUPPORTED_HTTP_METHOD: 105,
|
|
1238
|
-
WITH_LONG_POLLING_USED_WITH_POLLING: 106,
|
|
1239
|
-
// Auth provider
|
|
1240
|
-
AUTH_EXTRACT_TOKENS_RESPONSE_NOT_OBJECT: 200,
|
|
1241
|
-
AUTH_EXTRACT_TOKENS_RESPONSE_MISSING_ACCESS_TOKEN: 201,
|
|
1242
|
-
AUTH_EXTRACT_TOKENS_RESPONSE_MISSING_REFRESH_TOKEN: 202,
|
|
1243
|
-
AUTH_PROVIDER_FEATURE_USED_MULTIPLE_TIMES: 203,
|
|
1244
|
-
// Query client
|
|
1245
|
-
QUERY_CLIENT_FEATURE_USED_MULTIPLE_TIMES: 250,
|
|
1246
|
-
// Query Repository
|
|
1247
|
-
UNCACHEABLE_REQUEST_HAS_CACHE_KEY_PARAM: 300,
|
|
1248
|
-
UNCACHEABLE_REQUEST_HAS_ALLOW_CACHE_PARAM: 301,
|
|
1249
|
-
// Paged Query Stack
|
|
1250
|
-
PAGED_QUERY_STACK_PAGE_BIGGER_THAN_TOTAL_PAGES: 400,
|
|
1251
|
-
PAGED_QUERY_STACK_NEXT_PAGE_CALLED_WITHOUT_PREVIOUS_PAGE: 401,
|
|
1252
|
-
PAGED_QUERY_STACK_PREVIOUS_PAGE_CALLED_BUT_ALREADY_AT_FIRST_PAGE: 402,
|
|
1253
|
-
// Query Stack
|
|
1254
|
-
QUERY_STACK_WITH_ARGS_USED: 500,
|
|
1255
|
-
QUERY_STACK_WITH_RESPONSE_UPDATE_USED: 501,
|
|
1256
|
-
// GQL
|
|
1257
|
-
GQL_DATA_PROPERTY_MISSING_IN_RESPONSE: 600,
|
|
1258
|
-
GQL_ERRORS_IN_RESPONSE: 601,
|
|
1259
|
-
// Secure Execute
|
|
1260
|
-
TOKENS_NOT_AVAILABLE_INSIDE_AUTH_AND_EXEC: 700,
|
|
1261
|
-
INVALID_STATE_INSIDE_SECURE_EXECUTE_FACTORY: 701,
|
|
1262
|
-
// Query Execution
|
|
1263
|
-
CIRCULAR_QUERY_DEPENDENCY: 800,
|
|
1264
|
-
// Query Sequence
|
|
1265
|
-
QUERY_SEQUENCE_ALREADY_RUNNING: 900,
|
|
1266
|
-
// Query Batch
|
|
1267
|
-
QUERY_BATCH_ALREADY_RUNNING: 910,
|
|
1268
|
-
QUERY_BATCH_WITH_ARGS_USED: 911,
|
|
1269
|
-
// Legacy interop
|
|
1270
|
-
LEGACY_PREPARE_WITHOUT_INJECTION_CONTEXT: 950,
|
|
1271
|
-
LEGACY_QUERY_STATE_ALREADY_HAS_KEY: 951,
|
|
1272
|
-
LEGACY_QUERY_STATE_DOES_NOT_CONTAIN_KEY: 952,
|
|
1273
|
-
LEGACY_QUERY_STATE_CANNOT_TRANSFORM: 953,
|
|
1274
|
-
LEGACY_INVALID_BODY: 954,
|
|
1275
|
-
};
|
|
1276
|
-
const queryFeatureUsedMultipleTimes = (type) => {
|
|
1277
|
-
return new RuntimeError(QueryRuntimeErrorCode.QUERY_FEATURE_USED_MULTIPLE_TIMES, `The query feature "${type}()" was used multiple times.`);
|
|
1278
|
-
};
|
|
1279
|
-
const queryCreatedInReactiveContext = () => {
|
|
1280
|
-
return new RuntimeError(QueryRuntimeErrorCode.QUERY_CREATED_IN_REACTIVE_CONTEXT, `A query was created inside a reactive context (a computed, an effect or a template). Every re-run would create another query that lives until its injector is destroyed. Create the query in a field initializer or constructor and change its args instead.`);
|
|
1281
|
-
};
|
|
1282
|
-
const withArgsQueryFeatureMissingButRouteIsFunction = () => {
|
|
1283
|
-
return new RuntimeError(QueryRuntimeErrorCode.WITH_ARGS_QUERY_FEATURE_MISSING_BUT_ROUTE_IS_FUNCTION, `This queries route is a function, but a "withArgs()" feature is missing. Mutations need one too: create the query with "withArgs(() => ({ body: form.value() }))", then call ".execute()". The "silenceMissingWithArgsFeatureError" config is an escape hatch for args that only exist at call time.`);
|
|
1284
|
-
};
|
|
1285
|
-
const withPollingUsedOnUnsupportedHttpMethod = (method) => {
|
|
1286
|
-
return new RuntimeError(QueryRuntimeErrorCode.WITH_POLLING_USED_ON_UNSUPPORTED_HTTP_METHOD, `This is a "${method}" request, "withPolling()" is only supported for GET, HEAD, OPTIONS requests and GQL queries.`);
|
|
1287
|
-
};
|
|
1288
|
-
const withAutoRefreshUsedOnUnsupportedHttpMethod = (method) => {
|
|
1289
|
-
return new RuntimeError(QueryRuntimeErrorCode.WITH_AUTO_REFRESH_USED_ON_UNSUPPORTED_HTTP_METHOD, `This is a "${method}" request, "withAutoRefresh()" is only supported for GET, HEAD, OPTIONS requests and GQL queries.`);
|
|
1290
|
-
};
|
|
1291
|
-
const withAutoRefreshUsedInManualQuery = () => {
|
|
1292
|
-
return new RuntimeError(QueryRuntimeErrorCode.WITH_AUTO_REFRESH_USED_IN_MANUAL_QUERY, `"withAutoRefresh()" has been used inside a query that has "onlyManualExecution" set to true.` +
|
|
1293
|
-
` If this is intentional, set "ignoreOnlyManualExecution" to true inside the auto refresh config.`);
|
|
1294
|
-
};
|
|
1295
|
-
const withLongPollingUsedOnUnsupportedHttpMethod = (method) => {
|
|
1296
|
-
return new RuntimeError(QueryRuntimeErrorCode.WITH_LONG_POLLING_USED_ON_UNSUPPORTED_HTTP_METHOD, `This is a "${method}" request, "withLongPolling()" is only supported for GET, HEAD, OPTIONS requests and GQL queries.`);
|
|
1297
|
-
};
|
|
1298
|
-
const withLongPollingUsedWithPolling = () => {
|
|
1299
|
-
return new RuntimeError(QueryRuntimeErrorCode.WITH_LONG_POLLING_USED_WITH_POLLING, `"withLongPolling()" and "withPolling()" cannot be used on the same query - both would drive its re-execution.` +
|
|
1300
|
-
` Keep the long polling feature and drop the interval, or the other way around.`);
|
|
1301
|
-
};
|
|
1302
|
-
const silenceMissingWithArgsFeatureErrorUsedButWithArgsPresent = () => {
|
|
1303
|
-
return new RuntimeError(QueryRuntimeErrorCode.SILENCE_MISSING_WITH_ARGS_FEATURE_ERROR_USED_BUT_WITH_ARGS_PRESENT, `The "silenceMissingWithArgsFeatureError" config is set to true, but a "withArgs()" feature is present.`);
|
|
1304
|
-
};
|
|
1305
|
-
const authExtractTokensResponseNotObject = () => {
|
|
1306
|
-
return new RuntimeError(QueryRuntimeErrorCode.AUTH_EXTRACT_TOKENS_RESPONSE_NOT_OBJECT, `The auth token extractor expects the response to be an object.`);
|
|
1307
|
-
};
|
|
1308
|
-
const authExtractTokensResponseMissingAccessToken = () => {
|
|
1309
|
-
return new RuntimeError(QueryRuntimeErrorCode.AUTH_EXTRACT_TOKENS_RESPONSE_MISSING_ACCESS_TOKEN, `The auth token extractor expects the response to contain a string "accessToken" property.`);
|
|
1310
|
-
};
|
|
1311
|
-
const authExtractTokensResponseMissingRefreshToken = () => {
|
|
1312
|
-
return new RuntimeError(QueryRuntimeErrorCode.AUTH_EXTRACT_TOKENS_RESPONSE_MISSING_REFRESH_TOKEN, `The auth token extractor expects the response to contain a string "refreshToken" property.`);
|
|
1313
|
-
};
|
|
1314
|
-
const authProviderFeatureUsedMultipleTimes = (type) => {
|
|
1315
|
-
return new RuntimeError(QueryRuntimeErrorCode.AUTH_PROVIDER_FEATURE_USED_MULTIPLE_TIMES, `Bearer auth feature "${type}" was used multiple times.`);
|
|
1316
|
-
};
|
|
1317
|
-
const queryClientFeatureUsedMultipleTimes = (type) => {
|
|
1318
|
-
return new RuntimeError(QueryRuntimeErrorCode.QUERY_CLIENT_FEATURE_USED_MULTIPLE_TIMES, `Query client feature "${type}" was used multiple times.`);
|
|
1319
|
-
};
|
|
1320
|
-
const uncacheableRequestHasCacheKeyParam = (key) => {
|
|
1321
|
-
return new RuntimeError(QueryRuntimeErrorCode.UNCACHEABLE_REQUEST_HAS_CACHE_KEY_PARAM, `This request is uncacheable, but a cache key was provided: ${key}. Please remove it.`);
|
|
1322
|
-
};
|
|
1323
|
-
const uncacheableRequestHasAllowCacheParam = () => {
|
|
1324
|
-
return new RuntimeError(QueryRuntimeErrorCode.UNCACHEABLE_REQUEST_HAS_ALLOW_CACHE_PARAM, `This request is uncacheable, but allowCache is set to true. Please remove it.`);
|
|
1325
|
-
};
|
|
1326
|
-
const pagedQueryStackPageBiggerThanTotalPages = (page, totalPages) => {
|
|
1327
|
-
return new RuntimeError(QueryRuntimeErrorCode.PAGED_QUERY_STACK_PAGE_BIGGER_THAN_TOTAL_PAGES, `The page "${page}" is bigger than the total pages "${totalPages}".`);
|
|
1328
|
-
};
|
|
1329
|
-
const pagedQueryStackNextPageCalledWithoutPreviousPage = () => {
|
|
1330
|
-
return new RuntimeError(QueryRuntimeErrorCode.PAGED_QUERY_STACK_NEXT_PAGE_CALLED_WITHOUT_PREVIOUS_PAGE, `fetchNextPage() has been called but the current page is not yet loaded. Please call it after the previous page has been loaded.`);
|
|
1331
|
-
};
|
|
1332
|
-
const pagedQueryStackPreviousPageCalledButAlreadyAtFirstPage = () => {
|
|
1333
|
-
return new RuntimeError(QueryRuntimeErrorCode.PAGED_QUERY_STACK_PREVIOUS_PAGE_CALLED_BUT_ALREADY_AT_FIRST_PAGE, `fetchPreviousPage() has been called but the current page is already the first page.`);
|
|
1334
|
-
};
|
|
1335
|
-
const queryStackWithArgsUsed = () => {
|
|
1336
|
-
return new RuntimeError(QueryRuntimeErrorCode.QUERY_STACK_WITH_ARGS_USED, `withArgs() has been used in a query stack or a paged query stack. This is not supported.`);
|
|
1337
|
-
};
|
|
1338
|
-
const queryStackWithResponseUpdateUsed = () => {
|
|
1339
|
-
return new RuntimeError(QueryRuntimeErrorCode.QUERY_STACK_WITH_RESPONSE_UPDATE_USED, `withResponseUpdate() has been used in a query stack or a paged query stack. This is not supported.`);
|
|
1340
|
-
};
|
|
1341
|
-
const gqlDataPropertyMissingInResponse = () => {
|
|
1342
|
-
return new RuntimeError(QueryRuntimeErrorCode.GQL_DATA_PROPERTY_MISSING_IN_RESPONSE, `The GraphQL response is missing the required "data" property. Please add a custom transformResponse param to the query creator to handle this response format.`);
|
|
1343
|
-
};
|
|
1344
|
-
const gqlErrorsInResponse = (errors) => {
|
|
1345
|
-
return new HttpErrorResponse({
|
|
1346
|
-
error: errors,
|
|
1347
|
-
status: 0,
|
|
1348
|
-
statusText: new RuntimeError(QueryRuntimeErrorCode.GQL_ERRORS_IN_RESPONSE, `The GraphQL response carries errors and no data. The server's errors are in raw.error.`).message,
|
|
1349
|
-
});
|
|
1350
|
-
};
|
|
1351
|
-
const tokensNotAvailableInsideAuthAndExec = () => {
|
|
1352
|
-
return new RuntimeError(QueryRuntimeErrorCode.TOKENS_NOT_AVAILABLE_INSIDE_AUTH_AND_EXEC, `Tokens are not available inside authAndExec.`);
|
|
1353
|
-
};
|
|
1354
|
-
const invalidStateInsideSecureExecuteFactory = () => {
|
|
1355
|
-
return new RuntimeError(QueryRuntimeErrorCode.INVALID_STATE_INSIDE_SECURE_EXECUTE_FACTORY, `An invalid state occurred inside the secure execute factory.`);
|
|
1356
|
-
};
|
|
1357
|
-
const circularQueryDependency = () => {
|
|
1358
|
-
return new RuntimeError(QueryRuntimeErrorCode.CIRCULAR_QUERY_DEPENDENCY, `The same query was executed with identical args more than 5 times in a row, less than 100ms apart. This is usually a sign of a circular dependency.`);
|
|
1359
|
-
};
|
|
1360
|
-
const queryExecutedAfterDestroyMessage = (route) => {
|
|
1361
|
-
const target = typeof route === 'string' ? `The query for "${route}"` : 'A query with a function route';
|
|
1362
|
-
return `${target} was executed after the scope that created it was destroyed. The call was ignored - drop the reference to the query when its component or injector goes away.`;
|
|
1363
|
-
};
|
|
1364
|
-
const legacyPrepareWithoutInjectionContext = (creatorName, method = 'prepare') => {
|
|
1365
|
-
const label = creatorName ? `"${creatorName}"` : 'A legacy query creator';
|
|
1366
|
-
const example = method === 'prepare'
|
|
1367
|
-
? ` search = (term: string) => myQuery.prepare({ queryParams: { term }, injector: this.injector });`
|
|
1368
|
-
: ` container = myQuery.${method}(null, { injector: this.injector });`;
|
|
1369
|
-
return new RuntimeError(QueryRuntimeErrorCode.LEGACY_PREPARE_WITHOUT_INJECTION_CONTEXT, `${label}.${method}() was called outside of an injection context. Unlike the v2 client, the legacy interop layer needs an injector to build the underlying query.\n\n` +
|
|
1370
|
-
`Capture one where a context does exist (field initializer, constructor) and pass it along:\n\n` +
|
|
1371
|
-
` private injector = inject(Injector);\n` +
|
|
1372
|
-
`${example}\n\n` +
|
|
1373
|
-
`Prefer an injector that outlives the call site. A component injector passed from a callback that can still fire after the component is gone leaves prepare() with a destroyed injector, which returns an inert query instead of executing.`);
|
|
1374
|
-
};
|
|
1375
|
-
const querySequenceAlreadyRunning = () => {
|
|
1376
|
-
return new RuntimeError(QueryRuntimeErrorCode.QUERY_SEQUENCE_ALREADY_RUNNING, `run() was called on a query sequence that is already running. Wait for the current run to settle before starting another.`);
|
|
1377
|
-
};
|
|
1378
|
-
const queryBatchAlreadyRunning = () => {
|
|
1379
|
-
return new RuntimeError(QueryRuntimeErrorCode.QUERY_BATCH_ALREADY_RUNNING, `run() or retryFailed() was called on a query batch that is already running. Await the current run, or call cancel() first.`);
|
|
1380
|
-
};
|
|
1381
|
-
const queryBatchWithArgsUsed = () => {
|
|
1382
|
-
return new RuntimeError(QueryRuntimeErrorCode.QUERY_BATCH_WITH_ARGS_USED, `The "withArgs()" feature cannot be used inside a query batch. Each item's args come from the batch's "args" option.`);
|
|
1383
|
-
};
|
|
1384
|
-
|
|
1385
1670
|
const isClassValidatorError = (error) => {
|
|
1386
1671
|
return (typeof error === 'object' &&
|
|
1387
1672
|
error !== null &&
|
|
@@ -1448,6 +1733,7 @@ const QueryFeatureType = {
|
|
|
1448
1733
|
WITH_AUTO_REFRESH: 'WITH_AUTO_REFRESH',
|
|
1449
1734
|
WITH_RESPONSE_UPDATE: 'WITH_RESPONSE_UPDATE',
|
|
1450
1735
|
WITH_PAGE_RESET_ON_ERROR: 'WITH_PAGE_RESET_ON_ERROR',
|
|
1736
|
+
WITH_OPTIMISTIC_UPDATE: 'WITH_OPTIMISTIC_UPDATE',
|
|
1451
1737
|
};
|
|
1452
1738
|
const createQueryFeature = (config) => {
|
|
1453
1739
|
return config;
|
|
@@ -1458,7 +1744,8 @@ const createQueryFeature = (config) => {
|
|
|
1458
1744
|
*
|
|
1459
1745
|
* Return `null` to park the query: it is reset (`response()` and `executionState()` become `null`),
|
|
1460
1746
|
* and polling and auto refresh pause until args are set again. A mutation that is still in flight
|
|
1461
|
-
* finishes first and the query parks once it has settled.
|
|
1747
|
+
* finishes first and the query parks once it has settled. `execute({ args })` runs and keeps other args
|
|
1748
|
+
* until this function produces new non-null ones.
|
|
1462
1749
|
*
|
|
1463
1750
|
* Changing arguments will automatically trigger a new execution of the query if it is eligible for auto execution (e.g. a GET request).
|
|
1464
1751
|
*/
|
|
@@ -1484,6 +1771,8 @@ const withArgs = (args) => {
|
|
|
1484
1771
|
return;
|
|
1485
1772
|
}
|
|
1486
1773
|
untracked(() => {
|
|
1774
|
+
if (context.state.subtle.isExplicitArgs(currArgsNow))
|
|
1775
|
+
return;
|
|
1487
1776
|
const guard = context.state.subtle.autoExecuteGuard();
|
|
1488
1777
|
if (context.flags.shouldAutoExecute && (guard === null || guard()))
|
|
1489
1778
|
context.execute({ args: currArgsNow });
|
|
@@ -1593,9 +1882,9 @@ const withPolling = (options) => {
|
|
|
1593
1882
|
}, Math.max(0, firstDelay));
|
|
1594
1883
|
};
|
|
1595
1884
|
const remainingDelay = () => lastTickAt + currentInterval() - Date.now();
|
|
1596
|
-
//
|
|
1597
|
-
|
|
1598
|
-
|
|
1885
|
+
// Args an `execute({ args })` recorded already went out with that execution.
|
|
1886
|
+
const wasJustExecutedWith = (args) => context.state.subtle.isExplicitArgs(args) ||
|
|
1887
|
+
(!context.state.subtle.hasArgsSource() && args !== null && equal(context.state.subtle.request()?.args, args));
|
|
1599
1888
|
nestedEffect(() => {
|
|
1600
1889
|
const args = context.state.args();
|
|
1601
1890
|
untracked(() => {
|
|
@@ -1898,7 +2187,7 @@ const withResponseUpdate = (options) => {
|
|
|
1898
2187
|
if (response === null)
|
|
1899
2188
|
return;
|
|
1900
2189
|
untracked(() => {
|
|
1901
|
-
context.state.
|
|
2190
|
+
context.state.response.set(response);
|
|
1902
2191
|
});
|
|
1903
2192
|
}, { injector: context.deps.injector });
|
|
1904
2193
|
},
|
|
@@ -1963,6 +2252,62 @@ const withPageResetOnError = (options) => {
|
|
|
1963
2252
|
});
|
|
1964
2253
|
};
|
|
1965
2254
|
|
|
2255
|
+
/**
|
|
2256
|
+
* Resolves an {@link QueryInvalidationOptions.url} to the absolute form request URLs are built in, so
|
|
2257
|
+
* the comparison - and the message the other tabs receive - never depends on who resolves it.
|
|
2258
|
+
*/
|
|
2259
|
+
const resolveInvalidationUrl = (baseUrl, url) => {
|
|
2260
|
+
const absolute = url.startsWith('/') ? `${baseUrl}${url}` : url;
|
|
2261
|
+
// A trailing slash would put the boundary check below one character past where the URL actually
|
|
2262
|
+
// ends, which is the one way `/players/` could fail to match `/players/1`.
|
|
2263
|
+
return absolute.endsWith('/') ? absolute.slice(0, -1) : absolute;
|
|
2264
|
+
};
|
|
2265
|
+
/**
|
|
2266
|
+
* Whether a query URL is the invalidated one or sits below it. The check on what follows the prefix
|
|
2267
|
+
* is what keeps `/players` from matching `/players-archive`, which a `startsWith` alone would.
|
|
2268
|
+
*/
|
|
2269
|
+
const isUnderInvalidatedUrl = (queryUrl, invalidatedUrl) => {
|
|
2270
|
+
if (!queryUrl.startsWith(invalidatedUrl))
|
|
2271
|
+
return false;
|
|
2272
|
+
const rest = queryUrl.slice(invalidatedUrl.length);
|
|
2273
|
+
return rest === '' || rest.startsWith('/') || rest.startsWith('?') || rest.startsWith('#');
|
|
2274
|
+
};
|
|
2275
|
+
/**
|
|
2276
|
+
* Turns an invalidation into the filter the repository refreshes by, or `undefined` when nothing
|
|
2277
|
+
* narrows it - "everything in use" is the repository's own cheapest path.
|
|
2278
|
+
*/
|
|
2279
|
+
const createQueryInvalidationFilter = (options) => {
|
|
2280
|
+
const { url, tag, filter } = options;
|
|
2281
|
+
if (!url && !tag && !filter)
|
|
2282
|
+
return undefined;
|
|
2283
|
+
return (request, tags) => {
|
|
2284
|
+
if (url && !isUnderInvalidatedUrl(request.url, url))
|
|
2285
|
+
return false;
|
|
2286
|
+
if (tag && !tags?.includes(tag))
|
|
2287
|
+
return false;
|
|
2288
|
+
return filter ? filter({ method: request.method, url: request.url }) : true;
|
|
2289
|
+
};
|
|
2290
|
+
};
|
|
2291
|
+
/** @internal */
|
|
2292
|
+
const resolveQueryTags = (tags, args) => (typeof tags === 'function' ? tags({ args: args ?? {} }) : tags);
|
|
2293
|
+
/** @internal */
|
|
2294
|
+
const invalidateOnSuccess = (options) => {
|
|
2295
|
+
const { invalidates, state, deps } = options;
|
|
2296
|
+
const subscription = state.events$.subscribe((event) => {
|
|
2297
|
+
if (event.type !== HttpEventType.Response)
|
|
2298
|
+
return;
|
|
2299
|
+
const targets = typeof invalidates === 'function'
|
|
2300
|
+
? invalidates({
|
|
2301
|
+
args: state.subtle.request()?.args ?? {},
|
|
2302
|
+
response: state.response(),
|
|
2303
|
+
})
|
|
2304
|
+
: invalidates;
|
|
2305
|
+
for (const target of targets)
|
|
2306
|
+
deps.client.invalidateQueries(target);
|
|
2307
|
+
});
|
|
2308
|
+
deps.destroyRef.onDestroy(() => subscription.unsubscribe());
|
|
2309
|
+
};
|
|
2310
|
+
|
|
1966
2311
|
const parsers = [];
|
|
1967
2312
|
/**
|
|
1968
2313
|
* Adds a parser to the pipeline `createQueryErrorResponse` runs, ahead of the built-in ladder.
|
|
@@ -2019,7 +2364,11 @@ const createDefaultRetryFn = (options = {}) => {
|
|
|
2019
2364
|
const jitter = clamp(options.jitter ?? DEFAULT_JITTER, 0, 1);
|
|
2020
2365
|
const retryableStatusCodes = options.retryableStatusCodes;
|
|
2021
2366
|
const retryNonIdempotent = options.retryNonIdempotent ?? false;
|
|
2022
|
-
const isRetryableStatus = (status) =>
|
|
2367
|
+
const isRetryableStatus = (status) => {
|
|
2368
|
+
if (typeof retryableStatusCodes === 'function')
|
|
2369
|
+
return retryableStatusCodes(status, isRetryableByDefault(status));
|
|
2370
|
+
return retryableStatusCodes ? retryableStatusCodes.includes(status) : isRetryableByDefault(status);
|
|
2371
|
+
};
|
|
2023
2372
|
const backoff = (retryCount) => {
|
|
2024
2373
|
const exponential = baseDelayMs * Math.pow(2, retryCount);
|
|
2025
2374
|
const spread = jitter === 0 ? 1 : 1 + (Math.random() * 2 - 1) * jitter;
|
|
@@ -2069,6 +2418,7 @@ const isQueryErrorResponse = (error) => {
|
|
|
2069
2418
|
/**
|
|
2070
2419
|
* Every message carried by an error, flattened into a plain list - one entry for a single error,
|
|
2071
2420
|
* one per violation for a list (a form response with several field violations), empty for `null`.
|
|
2421
|
+
* Blank messages are dropped; an error left with none yields the raw response's message instead.
|
|
2072
2422
|
*
|
|
2073
2423
|
* The single/list split exists because that is how APIs answer; UI almost never wants to branch on
|
|
2074
2424
|
* it. Reach for this when rendering, and keep {@link QueryErrorResponse} for anything that needs
|
|
@@ -2084,7 +2434,9 @@ const isQueryErrorResponse = (error) => {
|
|
|
2084
2434
|
const queryErrorMessages = (error) => {
|
|
2085
2435
|
if (!error)
|
|
2086
2436
|
return [];
|
|
2087
|
-
|
|
2437
|
+
const items = error.isList ? error.errors : [error.error];
|
|
2438
|
+
const messages = items.map((item) => item.message).filter((message) => message.trim());
|
|
2439
|
+
return messages.length ? messages : [error.raw.message];
|
|
2088
2440
|
};
|
|
2089
2441
|
/**
|
|
2090
2442
|
* The first message of an error, or `null` when there is none - the single-line counterpart to
|
|
@@ -2213,14 +2565,24 @@ const setupQueryState = (options) => {
|
|
|
2213
2565
|
// set is overridden again the next time the source's dependencies change.
|
|
2214
2566
|
const argsSource = signal(() => null, /* @ts-ignore */
|
|
2215
2567
|
...(ngDevMode ? [{ debugName: "argsSource" }] : /* istanbul ignore next */ []));
|
|
2216
|
-
|
|
2217
|
-
...(ngDevMode ?
|
|
2568
|
+
let explicitArgs = null;
|
|
2569
|
+
const args = linkedSignal({ ...(ngDevMode ? { debugName: "args" } : /* istanbul ignore next */ {}), source: () => argsSource()(),
|
|
2570
|
+
computation: (next, previous) => {
|
|
2571
|
+
if (next === null && explicitArgs !== null && previous)
|
|
2572
|
+
return previous.value;
|
|
2573
|
+
explicitArgs = null;
|
|
2574
|
+
return next;
|
|
2575
|
+
} });
|
|
2218
2576
|
const hasArgsSource = signal(false, /* @ts-ignore */
|
|
2219
2577
|
...(ngDevMode ? [{ debugName: "hasArgsSource" }] : /* istanbul ignore next */ []));
|
|
2220
2578
|
const setArgsSource = (source) => {
|
|
2221
2579
|
argsSource.set(source);
|
|
2222
2580
|
hasArgsSource.set(true);
|
|
2223
2581
|
};
|
|
2582
|
+
const setExplicitArgs = (value) => {
|
|
2583
|
+
explicitArgs = value;
|
|
2584
|
+
args.set(value);
|
|
2585
|
+
};
|
|
2224
2586
|
const lastTimeExecutedAt = signal(null, /* @ts-ignore */
|
|
2225
2587
|
...(ngDevMode ? [{ debugName: "lastTimeExecutedAt" }] : /* istanbul ignore next */ []));
|
|
2226
2588
|
const lastTriggeredBy = signal(null, /* @ts-ignore */
|
|
@@ -2239,6 +2601,8 @@ const setupQueryState = (options) => {
|
|
|
2239
2601
|
...(ngDevMode ? [{ debugName: "defaultRunOptions" }] : /* istanbul ignore next */ []));
|
|
2240
2602
|
const autoExecuteGuard = signal(null, /* @ts-ignore */
|
|
2241
2603
|
...(ngDevMode ? [{ debugName: "autoExecuteGuard" }] : /* istanbul ignore next */ []));
|
|
2604
|
+
const beforeExecute = signal(null, /* @ts-ignore */
|
|
2605
|
+
...(ngDevMode ? [{ debugName: "beforeExecute" }] : /* istanbul ignore next */ []));
|
|
2242
2606
|
options.destroyRef?.onDestroy(() => {
|
|
2243
2607
|
requestEventsSubscription.unsubscribe();
|
|
2244
2608
|
requestEvents$.complete();
|
|
@@ -2309,8 +2673,11 @@ const setupQueryState = (options) => {
|
|
|
2309
2673
|
unbindRequestEvents,
|
|
2310
2674
|
defaultRunOptions,
|
|
2311
2675
|
autoExecuteGuard,
|
|
2676
|
+
beforeExecute,
|
|
2312
2677
|
setArgsSource,
|
|
2313
2678
|
hasArgsSource: hasArgsSource.asReadonly(),
|
|
2679
|
+
setExplicitArgs,
|
|
2680
|
+
isExplicitArgs: (value) => value !== null && value === explicitArgs,
|
|
2314
2681
|
devtoolsStats: options.devtoolsStats ?? null,
|
|
2315
2682
|
devtoolsFormLinks: options.devtoolsFormLinks ?? null,
|
|
2316
2683
|
},
|
|
@@ -2409,17 +2776,18 @@ const createQuerySnapshotFn = (options) => {
|
|
|
2409
2776
|
// snapshot has to settle itself - otherwise `executeUntilSettled` resolves with a snapshot still
|
|
2410
2777
|
// reporting the execution as loading.
|
|
2411
2778
|
unregisterScopeListener = context.deps.destroyRef.onDestroy(cancel);
|
|
2779
|
+
const watcher = createObservableSignalWatcher(context.deps.injector);
|
|
2412
2780
|
const snapshot = {
|
|
2413
|
-
args:
|
|
2414
|
-
response:
|
|
2415
|
-
latestHttpEvent:
|
|
2416
|
-
loading:
|
|
2417
|
-
error:
|
|
2418
|
-
lastTimeExecutedAt:
|
|
2419
|
-
triggeredBy:
|
|
2420
|
-
isAlive:
|
|
2421
|
-
id:
|
|
2422
|
-
executionState:
|
|
2781
|
+
args: watcher.wrap(snapshotState.args.asReadonly()),
|
|
2782
|
+
response: watcher.wrap(snapshotState.response),
|
|
2783
|
+
latestHttpEvent: watcher.wrap(snapshotState.latestHttpEvent.asReadonly()),
|
|
2784
|
+
loading: watcher.wrap(snapshotState.loading.asReadonly()),
|
|
2785
|
+
error: watcher.wrap(snapshotState.error.asReadonly()),
|
|
2786
|
+
lastTimeExecutedAt: watcher.wrap(snapshotState.lastTimeExecutedAt.asReadonly()),
|
|
2787
|
+
triggeredBy: watcher.wrap(snapshotState.lastTriggeredBy.asReadonly()),
|
|
2788
|
+
isAlive: watcher.wrap(isAlive.asReadonly()),
|
|
2789
|
+
id: watcher.wrap(options.execute.currentRepositoryKey),
|
|
2790
|
+
executionState: watcher.wrap(snapshotState.executionState),
|
|
2423
2791
|
};
|
|
2424
2792
|
return snapshot;
|
|
2425
2793
|
});
|
|
@@ -2507,16 +2875,18 @@ const createQueryObject = (options) => {
|
|
|
2507
2875
|
const setError = (error) => state.error.set(error);
|
|
2508
2876
|
const createSnapshot = createQuerySnapshotFn({ state, execute, deps });
|
|
2509
2877
|
// Pre-wrap all public signals once so both `query` and `roQuery` share the same instances.
|
|
2510
|
-
const
|
|
2511
|
-
const
|
|
2512
|
-
const
|
|
2513
|
-
const
|
|
2514
|
-
const
|
|
2515
|
-
const
|
|
2516
|
-
const
|
|
2517
|
-
const
|
|
2518
|
-
const
|
|
2519
|
-
const
|
|
2878
|
+
const watcher = createObservableSignalWatcher(deps.injector);
|
|
2879
|
+
const wrappedArgs = watcher.wrap(state.args.asReadonly());
|
|
2880
|
+
const wrappedResponse = watcher.wrap(state.response);
|
|
2881
|
+
const wrappedLatestHttpEvent = watcher.wrap(state.latestHttpEvent.asReadonly());
|
|
2882
|
+
const wrappedLoading = watcher.wrap(state.loading.asReadonly());
|
|
2883
|
+
const wrappedError = watcher.wrap(state.error.asReadonly());
|
|
2884
|
+
const wrappedLastTimeExecutedAt = watcher.wrap(state.lastTimeExecutedAt.asReadonly());
|
|
2885
|
+
const wrappedTriggeredBy = watcher.wrap(state.lastTriggeredBy.asReadonly());
|
|
2886
|
+
const wrappedId = watcher.wrap(execute.currentRepositoryKey);
|
|
2887
|
+
const wrappedExecutionState = watcher.wrap(state.executionState);
|
|
2888
|
+
const wrappedRequest = watcher.wrap(state.subtle.request.asReadonly());
|
|
2889
|
+
const wrappedUrl = watcher.wrap(computed(() => state.subtle.request()?.url ?? null));
|
|
2520
2890
|
const asReadonly = () => {
|
|
2521
2891
|
const roQuery = {
|
|
2522
2892
|
args: wrappedArgs,
|
|
@@ -2528,6 +2898,7 @@ const createQueryObject = (options) => {
|
|
|
2528
2898
|
triggeredBy: wrappedTriggeredBy,
|
|
2529
2899
|
id: wrappedId,
|
|
2530
2900
|
createSnapshot,
|
|
2901
|
+
url: wrappedUrl,
|
|
2531
2902
|
executionState: wrappedExecutionState,
|
|
2532
2903
|
};
|
|
2533
2904
|
return roQuery;
|
|
@@ -2543,6 +2914,7 @@ const createQueryObject = (options) => {
|
|
|
2543
2914
|
triggeredBy: wrappedTriggeredBy,
|
|
2544
2915
|
id: wrappedId,
|
|
2545
2916
|
createSnapshot,
|
|
2917
|
+
url: wrappedUrl,
|
|
2546
2918
|
reset: execute.reset,
|
|
2547
2919
|
abort: execute.abort,
|
|
2548
2920
|
asReadonly,
|
|
@@ -2593,6 +2965,12 @@ const createBaseQuery = (options) => {
|
|
|
2593
2965
|
deps,
|
|
2594
2966
|
};
|
|
2595
2967
|
applyQueryFeatures(options.features, featureFnContext);
|
|
2968
|
+
const invalidates = options.creator?.invalidates;
|
|
2969
|
+
if (invalidates) {
|
|
2970
|
+
if (flags.shouldAutoExecuteMethod)
|
|
2971
|
+
throw invalidatesUsedOnRead(flags.method);
|
|
2972
|
+
invalidateOnSuccess({ invalidates, state, deps });
|
|
2973
|
+
}
|
|
2596
2974
|
maybeExecute({ execute, flags, state });
|
|
2597
2975
|
trackRefreshedExecutions({ state, deps });
|
|
2598
2976
|
const query = createQueryObject({ state, execute, deps });
|
|
@@ -2704,7 +3082,7 @@ const extractExpiresInSeconds = (headers) => {
|
|
|
2704
3082
|
if (cacheControl && /(?:^|,)\s*no-(?:cache|store)(?:\s|,|$)/i.test(cacheControl)) {
|
|
2705
3083
|
return null;
|
|
2706
3084
|
}
|
|
2707
|
-
const maxAgeMatch = cacheControl?.match(/(?:^|,)\s*(
|
|
3085
|
+
const maxAgeMatch = cacheControl?.match(/(?:^|,)\s*max-age=(\d+)/i) ?? cacheControl?.match(/(?:^|,)\s*s-maxage=(\d+)/i);
|
|
2708
3086
|
if (maxAgeMatch?.[1]) {
|
|
2709
3087
|
maxAge = parseInt(maxAgeMatch[1]);
|
|
2710
3088
|
}
|
|
@@ -2800,6 +3178,7 @@ const queryExecute = (options) => {
|
|
|
2800
3178
|
}
|
|
2801
3179
|
const defaultRunOptions = state.subtle.defaultRunOptions();
|
|
2802
3180
|
const runQueryOptions = defaultRunOptions || options.options ? { ...defaultRunOptions, ...options.options } : undefined;
|
|
3181
|
+
const onRequest = state.subtle.beforeExecute()?.(args);
|
|
2803
3182
|
const { key, request, executed } = deps.client.repository.request({
|
|
2804
3183
|
route: creatorInternals.route,
|
|
2805
3184
|
method: creatorInternals.method,
|
|
@@ -2813,25 +3192,44 @@ const queryExecute = (options) => {
|
|
|
2813
3192
|
isSecure,
|
|
2814
3193
|
isRefreshable,
|
|
2815
3194
|
silenceUncacheableAllowCacheError: queryConfig.silenceUncacheableAllowCacheError,
|
|
3195
|
+
tags: creator?.tags ? resolveQueryTags(creator.tags, args) : undefined,
|
|
2816
3196
|
});
|
|
2817
3197
|
executeState.previousKey.set(key);
|
|
2818
3198
|
state.lastTimeExecutedAt.set(Date.now());
|
|
2819
3199
|
state.lastTriggeredBy.set(runQueryOptions?.triggeredBy ?? null);
|
|
2820
3200
|
state.subtle.request.set(request);
|
|
3201
|
+
onRequest?.(request);
|
|
2821
3202
|
state.subtle.bindRequestEvents(request);
|
|
2822
3203
|
state.subtle.devtoolsStats?.recordExecution({ didRequest: executed, body: args?.body, url: request.url });
|
|
2823
3204
|
};
|
|
3205
|
+
/**
|
|
3206
|
+
* A query over a `withArgs` source that returns `null` is parked. Running it would send `null` args, so the
|
|
3207
|
+
* call is dropped with a dev-mode warning.
|
|
3208
|
+
*/
|
|
3209
|
+
const skipParkedExecution = (state, args, route) => {
|
|
3210
|
+
if ((args !== null && args !== undefined) || !state.subtle.hasArgsSource())
|
|
3211
|
+
return false;
|
|
3212
|
+
if (isDevMode())
|
|
3213
|
+
console.warn(queryExecutedWhileParkedMessage(route));
|
|
3214
|
+
return true;
|
|
3215
|
+
};
|
|
2824
3216
|
/**
|
|
2825
3217
|
* Writes the args an execution runs with to `state.args`, so a later bare `execute()` re-sends them.
|
|
2826
|
-
* A `
|
|
2827
|
-
* args
|
|
3218
|
+
* A `withLongPolling` round must not look like a new args value, so it is not written. Over a `withArgs`
|
|
3219
|
+
* source only the caller's own non-null args are written, never what a feature re-sends.
|
|
2828
3220
|
*/
|
|
2829
3221
|
const recordExecutionArgs = (state, args, options) => {
|
|
2830
|
-
|
|
3222
|
+
const triggeredBy = options?.triggeredBy;
|
|
3223
|
+
if (triggeredBy === LONG_POLLING_TRIGGER)
|
|
2831
3224
|
return;
|
|
2832
|
-
state.
|
|
3225
|
+
if (!state.subtle.hasArgsSource())
|
|
3226
|
+
return state.args.set(args);
|
|
3227
|
+
if (args === null || args === state.args() || (triggeredBy && FEATURE_TRIGGERS.includes(triggeredBy)))
|
|
3228
|
+
return;
|
|
3229
|
+
state.subtle.setExplicitArgs(args);
|
|
2833
3230
|
};
|
|
2834
3231
|
const LONG_POLLING_TRIGGER = 'long-polling';
|
|
3232
|
+
const FEATURE_TRIGGERS = ['polling', 'auto-refresh'];
|
|
2835
3233
|
const CIRCULAR_DEPENDENCY_WINDOW_MS = 100;
|
|
2836
3234
|
const CIRCULAR_DEPENDENCY_MAX_REPEATS = 5;
|
|
2837
3235
|
/**
|
|
@@ -2868,6 +3266,8 @@ const createExecuteFn = (executeOptions) => {
|
|
|
2868
3266
|
const reset = () => untracked(() => resetExecuteState({ executeState, executeOptions }));
|
|
2869
3267
|
const exec = (executeArgs) => untracked(() => {
|
|
2870
3268
|
const { args = executeOptions.state.args(), options } = executeArgs ?? {};
|
|
3269
|
+
if (skipParkedExecution(executeOptions.state, args, executeOptions.creatorInternals.route))
|
|
3270
|
+
return;
|
|
2871
3271
|
circularChecker.check(args);
|
|
2872
3272
|
aborter.capture();
|
|
2873
3273
|
recordExecutionArgs(executeOptions.state, args, options);
|
|
@@ -2949,153 +3349,6 @@ const createBaseQueryCreator = (config) => {
|
|
|
2949
3349
|
return queryCreator;
|
|
2950
3350
|
};
|
|
2951
3351
|
|
|
2952
|
-
/**
|
|
2953
|
-
* Route building, query-string serialization and the token clock - shared by the current generation and
|
|
2954
|
-
* the legacy V2 client, which re-exports them under their original names.
|
|
2955
|
-
*/
|
|
2956
|
-
// codes 1-99 of the shared 0-999 `@ethlete/query` range - see `query-errors.ts` for the rest
|
|
2957
|
-
const RouteRuntimeErrorCode = {
|
|
2958
|
-
INVALID_BASE_ROUTE: 1,
|
|
2959
|
-
INVALID_ROUTE: 2,
|
|
2960
|
-
PATH_PARAMS_MISSING_IN_ROUTE_FUNCTION: 3,
|
|
2961
|
-
};
|
|
2962
|
-
const invalidBaseRouteError$1 = (data) => new RuntimeError(RouteRuntimeErrorCode.INVALID_BASE_ROUTE, 'The baseRoute must not end with a slash', data);
|
|
2963
|
-
const invalidRouteError$1 = (data) => new RuntimeError(RouteRuntimeErrorCode.INVALID_ROUTE, 'The route must start with a slash', data);
|
|
2964
|
-
const pathParamsMissingInRouteFunctionError$1 = (data) => new RuntimeError(RouteRuntimeErrorCode.PATH_PARAMS_MISSING_IN_ROUTE_FUNCTION, 'The route is a function but pathParams are missing', data);
|
|
2965
|
-
const isNaN$2 = (value) => typeof value === 'number' && Number.isNaN(value);
|
|
2966
|
-
const isEmptyString$1 = (value) => typeof value === 'string' && value.trim() === '';
|
|
2967
|
-
const buildQueryString$1 = (params, config) => {
|
|
2968
|
-
const objectNotation = config?.objectNotation ?? 'bracket';
|
|
2969
|
-
const writeArrayIndexes = config?.writeArrayIndexes ?? false;
|
|
2970
|
-
const ignoredValues = config?.ignoredValues ?? [undefined, null, Infinity, -Infinity];
|
|
2971
|
-
const ignoredValuesFns = config?.ignoredValuesFns ?? [isNaN$2, isEmptyString$1];
|
|
2972
|
-
const queryParams = [];
|
|
2973
|
-
function processValue(key, rawValue) {
|
|
2974
|
-
if (rawValue instanceof Date && Number.isNaN(rawValue.getTime()))
|
|
2975
|
-
return false;
|
|
2976
|
-
const value = rawValue instanceof Date ? rawValue.toISOString() : rawValue;
|
|
2977
|
-
if (config?.objectNotation === 'json-stringify') {
|
|
2978
|
-
if (value === undefined) {
|
|
2979
|
-
return false;
|
|
2980
|
-
}
|
|
2981
|
-
if (ignoredValues.includes(value)) {
|
|
2982
|
-
return false;
|
|
2983
|
-
}
|
|
2984
|
-
if (ignoredValuesFns.some((fn) => fn(value))) {
|
|
2985
|
-
return false;
|
|
2986
|
-
}
|
|
2987
|
-
const encodedKey = encodeURIComponent(key);
|
|
2988
|
-
const val = typeof value === 'object' ? JSON.stringify(value) : value;
|
|
2989
|
-
const encodedValue = encodeURIComponent(val);
|
|
2990
|
-
queryParams.push(`${encodedKey}=${encodedValue}`);
|
|
2991
|
-
return true;
|
|
2992
|
-
}
|
|
2993
|
-
else if (Array.isArray(value)) {
|
|
2994
|
-
let currentFilteredIndex = 0;
|
|
2995
|
-
let didAddAnyValue = false;
|
|
2996
|
-
for (const arrayValue of value) {
|
|
2997
|
-
const nestedKey = writeArrayIndexes ? `${key}[${currentFilteredIndex}]` : `${key}[]`;
|
|
2998
|
-
const didAddValue = processValue(nestedKey, arrayValue);
|
|
2999
|
-
if (didAddValue) {
|
|
3000
|
-
currentFilteredIndex++;
|
|
3001
|
-
didAddAnyValue = true;
|
|
3002
|
-
}
|
|
3003
|
-
}
|
|
3004
|
-
return didAddAnyValue;
|
|
3005
|
-
}
|
|
3006
|
-
else if (typeof value === 'object' && value !== null) {
|
|
3007
|
-
let didAddAnyValue = false;
|
|
3008
|
-
for (const [objKey, val] of Object.entries(value)) {
|
|
3009
|
-
const nestedKey = objectNotation === 'dot' ? `${key}.${objKey}` : `${key}[${objKey}]`;
|
|
3010
|
-
didAddAnyValue = processValue(nestedKey, val) || didAddAnyValue;
|
|
3011
|
-
}
|
|
3012
|
-
return didAddAnyValue;
|
|
3013
|
-
}
|
|
3014
|
-
else {
|
|
3015
|
-
if (ignoredValues.includes(value)) {
|
|
3016
|
-
return false;
|
|
3017
|
-
}
|
|
3018
|
-
if (ignoredValuesFns.some((fn) => fn(value))) {
|
|
3019
|
-
return false;
|
|
3020
|
-
}
|
|
3021
|
-
const encodedKey = encodeURIComponent(key);
|
|
3022
|
-
const encodedValue = encodeURIComponent(value);
|
|
3023
|
-
queryParams.push(`${encodedKey}=${encodedValue}`);
|
|
3024
|
-
return true;
|
|
3025
|
-
}
|
|
3026
|
-
}
|
|
3027
|
-
for (const [key, val] of Object.entries(params)) {
|
|
3028
|
-
processValue(key, val);
|
|
3029
|
-
}
|
|
3030
|
-
return queryParams.length ? queryParams.join('&') : null;
|
|
3031
|
-
};
|
|
3032
|
-
const isPlainObject = (value) => {
|
|
3033
|
-
if (!value || typeof value !== 'object')
|
|
3034
|
-
return false;
|
|
3035
|
-
const proto = Object.getPrototypeOf(value);
|
|
3036
|
-
return proto === Object.prototype || proto === null;
|
|
3037
|
-
};
|
|
3038
|
-
/** Copies query params with every plain object's keys sorted, so key order never reaches a cache key. */
|
|
3039
|
-
const sortQueryParamKeys = (value) => {
|
|
3040
|
-
if (Array.isArray(value))
|
|
3041
|
-
return value.map(sortQueryParamKeys);
|
|
3042
|
-
if (!isPlainObject(value))
|
|
3043
|
-
return value;
|
|
3044
|
-
return Object.fromEntries(Object.keys(value)
|
|
3045
|
-
.sort()
|
|
3046
|
-
.map((key) => [key, sortQueryParamKeys(value[key])]));
|
|
3047
|
-
};
|
|
3048
|
-
const buildRoute$1 = (options) => {
|
|
3049
|
-
if (options.base.endsWith('/')) {
|
|
3050
|
-
throw invalidBaseRouteError$1(options.base);
|
|
3051
|
-
}
|
|
3052
|
-
let route;
|
|
3053
|
-
if (typeof options.route === 'function') {
|
|
3054
|
-
if (!options.pathParams) {
|
|
3055
|
-
throw pathParamsMissingInRouteFunctionError$1(options.route({}));
|
|
3056
|
-
}
|
|
3057
|
-
route = options.route(options.pathParams);
|
|
3058
|
-
}
|
|
3059
|
-
else {
|
|
3060
|
-
route = options.route ?? null;
|
|
3061
|
-
}
|
|
3062
|
-
if (route && !route.startsWith('/')) {
|
|
3063
|
-
throw invalidRouteError$1(route);
|
|
3064
|
-
}
|
|
3065
|
-
if (options.queryParams) {
|
|
3066
|
-
const queryString = buildQueryString$1(options.queryParams, options.queryParamConfig);
|
|
3067
|
-
if (queryString) {
|
|
3068
|
-
route = route ? `${route}?${queryString}` : `/?${queryString}`;
|
|
3069
|
-
}
|
|
3070
|
-
}
|
|
3071
|
-
return `${options.base}${route ?? ''}`;
|
|
3072
|
-
};
|
|
3073
|
-
const buildTimestampFromSeconds$1 = (seconds) => {
|
|
3074
|
-
if (seconds === null) {
|
|
3075
|
-
return null;
|
|
3076
|
-
}
|
|
3077
|
-
return new Date(Date.now() + seconds * 1000).getTime();
|
|
3078
|
-
};
|
|
3079
|
-
/** Reads a bearer token's payload. */
|
|
3080
|
-
const decryptBearer$1 = (token) => {
|
|
3081
|
-
try {
|
|
3082
|
-
const base64Url = token.split('.')[1];
|
|
3083
|
-
if (!base64Url) {
|
|
3084
|
-
return null;
|
|
3085
|
-
}
|
|
3086
|
-
const base64 = base64Url.replace(/-/g, '+').replace(/_/g, '/');
|
|
3087
|
-
const jsonPayload = decodeURIComponent(atob(base64)
|
|
3088
|
-
.split('')
|
|
3089
|
-
.map((c) => '%' + ('00' + c.charCodeAt(0).toString(16)).slice(-2))
|
|
3090
|
-
.join(''));
|
|
3091
|
-
return JSON.parse(jsonPayload);
|
|
3092
|
-
}
|
|
3093
|
-
catch (error) {
|
|
3094
|
-
console.error('Invalid bearer token', error);
|
|
3095
|
-
return null;
|
|
3096
|
-
}
|
|
3097
|
-
};
|
|
3098
|
-
|
|
3099
3352
|
/**
|
|
3100
3353
|
* `true` on every request `@ethlete/query` sends through `HttpClient`, so an app interceptor can skip
|
|
3101
3354
|
* query requests: `if (req.context.get(IS_QUERY_REQUEST)) return next(req);`.
|
|
@@ -3449,11 +3702,18 @@ const createHttpRequest = (options) => {
|
|
|
3449
3702
|
retryState: retryState.asReadonly(),
|
|
3450
3703
|
lastDurationMs: lastDurationMs.asReadonly(),
|
|
3451
3704
|
abort,
|
|
3705
|
+
markStale: () => {
|
|
3706
|
+
if (!isStale())
|
|
3707
|
+
expiresIn.set(Date.now());
|
|
3708
|
+
},
|
|
3709
|
+
setResponse: (body) => response.set(body),
|
|
3452
3710
|
},
|
|
3453
3711
|
};
|
|
3454
3712
|
return httpRequest;
|
|
3455
3713
|
};
|
|
3456
3714
|
|
|
3715
|
+
const wrapAsObservableSignal = (source, defaultInjector) => createObservableSignalWatcher(defaultInjector).wrap(source);
|
|
3716
|
+
|
|
3457
3717
|
/* eslint-disable @typescript-eslint/no-explicit-any */
|
|
3458
3718
|
/** Transforms an array of arrays into a single array and filters out `null` values. */
|
|
3459
3719
|
const transformArrayResponse = (responses) => responses.filter((r) => !!r).flatMap((r) => r);
|
|
@@ -3465,7 +3725,7 @@ const createQueryStack = (options) => {
|
|
|
3465
3725
|
const lastQuery = newQueries[newQueries.length - 1] ?? oldQueries[oldQueries.length - 1] ?? null;
|
|
3466
3726
|
return { queries, lastQuery };
|
|
3467
3727
|
}, transform, argsKeyFn, deduplicateArgs, maxQueries = Infinity, removeStrategy = 'oldest', } = options ?? {};
|
|
3468
|
-
const injector =
|
|
3728
|
+
const injector = injectInQueryContext(Injector, 'query stack');
|
|
3469
3729
|
const queries = signal([], /* @ts-ignore */
|
|
3470
3730
|
...(ngDevMode ? [{ debugName: "queries" }] : /* istanbul ignore next */ []));
|
|
3471
3731
|
const lastQuery = signal(null, /* @ts-ignore */
|
|
@@ -3473,11 +3733,11 @@ const createQueryStack = (options) => {
|
|
|
3473
3733
|
const firstQuery = computed(() => queries()[0] ?? null, /* @ts-ignore */
|
|
3474
3734
|
...(ngDevMode ? [{ debugName: "firstQuery" }] : /* istanbul ignore next */ []));
|
|
3475
3735
|
const hasWithArgsFeature = features.some((f) => f.type === QueryFeatureType.WITH_ARGS);
|
|
3476
|
-
const
|
|
3736
|
+
const hasWithResponseUpdateFeature = features.some((f) => f.type === QueryFeatureType.WITH_RESPONSE_UPDATE);
|
|
3477
3737
|
if (hasWithArgsFeature) {
|
|
3478
3738
|
throw queryStackWithArgsUsed();
|
|
3479
3739
|
}
|
|
3480
|
-
if (
|
|
3740
|
+
if (hasWithResponseUpdateFeature) {
|
|
3481
3741
|
throw queryStackWithResponseUpdateUsed();
|
|
3482
3742
|
}
|
|
3483
3743
|
const runWithArgs = (args) => {
|
|
@@ -3730,6 +3990,7 @@ const createPagedQueryStack = (options) => {
|
|
|
3730
3990
|
const args = options.args(initialPage(), []);
|
|
3731
3991
|
untracked(() => {
|
|
3732
3992
|
stack.clear();
|
|
3993
|
+
pageDirection.set('next');
|
|
3733
3994
|
currentPageArgs.set(args);
|
|
3734
3995
|
loadedMinPage.set(initialPage());
|
|
3735
3996
|
loadedMaxPage.set(initialPage());
|
|
@@ -4505,7 +4766,8 @@ const createIndexedDbQueryPersistenceAdapter = (options) => {
|
|
|
4505
4766
|
*/
|
|
4506
4767
|
const createQueryBatch = (options) => {
|
|
4507
4768
|
const { queryCreator, args: mapArgs, stopOnError, allowCache, onItemSettled, features = [] } = options;
|
|
4508
|
-
const
|
|
4769
|
+
const requestedConcurrency = options.concurrency ?? 4;
|
|
4770
|
+
const concurrency = Number.isNaN(requestedConcurrency) ? 4 : Math.max(1, Math.floor(requestedConcurrency));
|
|
4509
4771
|
if (features.some((f) => f.type === QueryFeatureType.WITH_ARGS)) {
|
|
4510
4772
|
throw queryBatchWithArgsUsed();
|
|
4511
4773
|
}
|
|
@@ -4786,41 +5048,6 @@ const isQueryDevtoolsRepositoryLive = (repository) => {
|
|
|
4786
5048
|
return live$1.has(repository);
|
|
4787
5049
|
};
|
|
4788
5050
|
|
|
4789
|
-
/**
|
|
4790
|
-
* Resolves an {@link QueryInvalidationOptions.url} to the absolute form request URLs are built in, so
|
|
4791
|
-
* the comparison - and the message the other tabs receive - never depends on who resolves it.
|
|
4792
|
-
*/
|
|
4793
|
-
const resolveInvalidationUrl = (baseUrl, url) => {
|
|
4794
|
-
const absolute = url.startsWith('/') ? `${baseUrl}${url}` : url;
|
|
4795
|
-
// A trailing slash would put the boundary check below one character past where the URL actually
|
|
4796
|
-
// ends, which is the one way `/players/` could fail to match `/players/1`.
|
|
4797
|
-
return absolute.endsWith('/') ? absolute.slice(0, -1) : absolute;
|
|
4798
|
-
};
|
|
4799
|
-
/**
|
|
4800
|
-
* Whether a query URL is the invalidated one or sits below it. The check on what follows the prefix
|
|
4801
|
-
* is what keeps `/players` from matching `/players-archive`, which a `startsWith` alone would.
|
|
4802
|
-
*/
|
|
4803
|
-
const isUnderInvalidatedUrl = (queryUrl, invalidatedUrl) => {
|
|
4804
|
-
if (!queryUrl.startsWith(invalidatedUrl))
|
|
4805
|
-
return false;
|
|
4806
|
-
const rest = queryUrl.slice(invalidatedUrl.length);
|
|
4807
|
-
return rest === '' || rest.startsWith('/') || rest.startsWith('?') || rest.startsWith('#');
|
|
4808
|
-
};
|
|
4809
|
-
/**
|
|
4810
|
-
* Turns an invalidation into the filter the repository refreshes by, or `undefined` when nothing
|
|
4811
|
-
* narrows it - "everything in use" is the repository's own cheapest path.
|
|
4812
|
-
*/
|
|
4813
|
-
const createQueryInvalidationFilter = (options) => {
|
|
4814
|
-
const { url, filter } = options;
|
|
4815
|
-
if (!url && !filter)
|
|
4816
|
-
return undefined;
|
|
4817
|
-
return (request) => {
|
|
4818
|
-
if (url && !isUnderInvalidatedUrl(request.url, url))
|
|
4819
|
-
return false;
|
|
4820
|
-
return filter ? filter({ method: request.method, url: request.url }) : true;
|
|
4821
|
-
};
|
|
4822
|
-
};
|
|
4823
|
-
|
|
4824
5051
|
/**
|
|
4825
5052
|
* An error body can be an HTML page instead of JSON: a proxy's 502, a load balancer's maintenance page, a
|
|
4826
5053
|
* platform's "service temporarily unavailable". The markup is never worth showing, but the sentence inside it
|
|
@@ -5002,6 +5229,14 @@ const createQuerySyncEngine = (options) => {
|
|
|
5002
5229
|
// response sharing would just make a tab stop updating. Enforced here rather than documented.
|
|
5003
5230
|
const isPollingDedupeEnabled = (config.dedupePolling ?? true) && syncResponses && transport.isSupported && lockManager.isSupported;
|
|
5004
5231
|
const mutationFilter = typeof refreshOnMutation === 'object' ? refreshOnMutation.filter : null;
|
|
5232
|
+
let outgoingBatch = null;
|
|
5233
|
+
const currentOutgoingBatch = () => {
|
|
5234
|
+
if (outgoingBatch === null) {
|
|
5235
|
+
outgoingBatch = randomId();
|
|
5236
|
+
queueMicrotask(() => (outgoingBatch = null));
|
|
5237
|
+
}
|
|
5238
|
+
return outgoingBatch;
|
|
5239
|
+
};
|
|
5005
5240
|
const broadcast = (event) => {
|
|
5006
5241
|
if (event.type !== 'request-success' || !event.isMultiTabSyncEnabled)
|
|
5007
5242
|
return;
|
|
@@ -5023,7 +5258,12 @@ const createQuerySyncEngine = (options) => {
|
|
|
5023
5258
|
// worth telling the other tabs about - and an uncacheable read has no shared key to send anyway.
|
|
5024
5259
|
if (!refreshOnMutation || shouldCacheQuery(event.request.method))
|
|
5025
5260
|
return;
|
|
5026
|
-
transport.post({
|
|
5261
|
+
transport.post({
|
|
5262
|
+
type: 'mutation',
|
|
5263
|
+
method: event.request.method,
|
|
5264
|
+
url: event.request.url,
|
|
5265
|
+
batch: currentOutgoingBatch(),
|
|
5266
|
+
});
|
|
5027
5267
|
};
|
|
5028
5268
|
const apply = (message) => {
|
|
5029
5269
|
if (message.type === 'response') {
|
|
@@ -5035,23 +5275,20 @@ const createQuerySyncEngine = (options) => {
|
|
|
5035
5275
|
// An invalidation is something the other tab's app code asked for explicitly, so unlike the
|
|
5036
5276
|
// mutation heuristic it is not something `refreshOnMutation: false` opts out of.
|
|
5037
5277
|
if (message.type === 'invalidate') {
|
|
5038
|
-
repository.refreshInUse(createQueryInvalidationFilter({ url: message.url }), {
|
|
5278
|
+
repository.refreshInUse(createQueryInvalidationFilter({ url: message.url, tag: message.tag }), {
|
|
5039
5279
|
type: 'invalidation',
|
|
5040
5280
|
url: message.url,
|
|
5041
5281
|
otherTab: true,
|
|
5042
|
-
|
|
5282
|
+
...(message.tag && { tag: message.tag }),
|
|
5283
|
+
}, message.batch);
|
|
5043
5284
|
return;
|
|
5044
5285
|
}
|
|
5045
5286
|
if (!refreshOnMutation)
|
|
5046
5287
|
return;
|
|
5047
5288
|
const mutation = { method: message.method, url: message.url };
|
|
5048
|
-
repository.refreshInUse(mutationFilter ? (request) => mutationFilter(mutation, request) : undefined, {
|
|
5049
|
-
type: 'mutation',
|
|
5050
|
-
url: message.url,
|
|
5051
|
-
otherTab: true,
|
|
5052
|
-
});
|
|
5289
|
+
repository.refreshInUse(mutationFilter ? (request) => mutationFilter(mutation, request) : undefined, { type: 'mutation', url: message.url, otherTab: true }, message.batch);
|
|
5053
5290
|
};
|
|
5054
|
-
const postInvalidation = (url) => transport.post({ type: 'invalidate', url });
|
|
5291
|
+
const postInvalidation = (url, tag) => transport.post({ type: 'invalidate', url, ...(tag && { tag }), batch: currentOutgoingBatch() });
|
|
5055
5292
|
const eventSubscription = repository.events$.subscribe(broadcast);
|
|
5056
5293
|
const unlisten = transport.listen(apply);
|
|
5057
5294
|
const destroy = () => {
|
|
@@ -5104,11 +5341,13 @@ const unwrapQuerySyncMessage = (data) => {
|
|
|
5104
5341
|
expiresAt: typeof expiresAt === 'number' ? expiresAt : null,
|
|
5105
5342
|
};
|
|
5106
5343
|
}
|
|
5344
|
+
const batch = typeof raw['batch'] === 'string' ? { batch: raw['batch'] } : {};
|
|
5107
5345
|
if (type === 'mutation' && typeof method === 'string' && typeof url === 'string') {
|
|
5108
|
-
return { type: 'mutation', method: method, url };
|
|
5346
|
+
return { type: 'mutation', method: method, url, ...batch };
|
|
5109
5347
|
}
|
|
5110
5348
|
if (type === 'invalidate' && (typeof url === 'string' || url === null)) {
|
|
5111
|
-
|
|
5349
|
+
const tag = raw['tag'];
|
|
5350
|
+
return { type: 'invalidate', url, tag: typeof tag === 'string' ? tag : null, ...batch };
|
|
5112
5351
|
}
|
|
5113
5352
|
return null;
|
|
5114
5353
|
};
|
|
@@ -5370,6 +5609,16 @@ const withEthleteApiErrors = (options) => () => {
|
|
|
5370
5609
|
};
|
|
5371
5610
|
};
|
|
5372
5611
|
|
|
5612
|
+
const functionIds = /* @__PURE__ */ new WeakMap();
|
|
5613
|
+
let nextFunctionId = 1;
|
|
5614
|
+
const functionId = (fn) => {
|
|
5615
|
+
let id = functionIds.get(fn);
|
|
5616
|
+
if (id === undefined) {
|
|
5617
|
+
id = nextFunctionId++;
|
|
5618
|
+
functionIds.set(fn, id);
|
|
5619
|
+
}
|
|
5620
|
+
return id;
|
|
5621
|
+
};
|
|
5373
5622
|
const generateUuid = () => randomId();
|
|
5374
5623
|
/** @see CreateQueryClientConfigOptions.keepUnusedFor */
|
|
5375
5624
|
const DEFAULT_KEEP_UNUSED_FOR = 300_000;
|
|
@@ -5421,8 +5670,17 @@ const createQueryRepository = (config) => {
|
|
|
5421
5670
|
queryParamConfig: config.queryString,
|
|
5422
5671
|
})
|
|
5423
5672
|
: route;
|
|
5673
|
+
const wireKey = [
|
|
5674
|
+
creatorOptions?.responseType && creatorOptions.responseType !== 'json' ? creatorOptions.responseType : '',
|
|
5675
|
+
creatorOptions?.withCredentials ? 'credentials' : '',
|
|
5676
|
+
creatorOptions?.reportErrors === false ? 'silent' : '',
|
|
5677
|
+
creatorOptions?.reportProgress ? 'progress' : '',
|
|
5678
|
+
options.retryFn ? `retry${functionId(options.retryFn)}` : '',
|
|
5679
|
+
]
|
|
5680
|
+
.filter(Boolean)
|
|
5681
|
+
.join('_');
|
|
5424
5682
|
const cacheKey = shouldCache
|
|
5425
|
-
? buildQueryCacheKey(`${options.key ? options.key + '_' : ''}${keyRoute}`, {
|
|
5683
|
+
? buildQueryCacheKey(`${wireKey ? wireKey + '_' : ''}${options.key ? options.key + '_' : ''}${keyRoute}`, {
|
|
5426
5684
|
body: args?.body,
|
|
5427
5685
|
queryParams: args?.queryParams,
|
|
5428
5686
|
pathParams: args?.pathParams,
|
|
@@ -5433,6 +5691,7 @@ const createQueryRepository = (config) => {
|
|
|
5433
5691
|
const keepUnusedFor = resolveKeepUnusedFor(creatorOptions, shouldCache, runQueryOptions?.keepUnusedFor);
|
|
5434
5692
|
const isMultiTabSyncEnabled = creatorOptions?.multiTabSync !== false;
|
|
5435
5693
|
const isSecure = options.isSecure ?? false;
|
|
5694
|
+
const tags = options.tags ?? [];
|
|
5436
5695
|
// A secure response needs an explicit `persistence: true` to reach the disk: leaving an
|
|
5437
5696
|
// authenticated user's data there is a decision per endpoint, not a default. Everything else
|
|
5438
5697
|
// persists unless it opted out.
|
|
@@ -5457,6 +5716,7 @@ const createQueryRepository = (config) => {
|
|
|
5457
5716
|
isMultiTabSyncEnabled,
|
|
5458
5717
|
isPersistEnabled,
|
|
5459
5718
|
keepUnusedFor,
|
|
5719
|
+
tags,
|
|
5460
5720
|
});
|
|
5461
5721
|
const executed = !runQueryOptions?.allowCache || cacheEntry.request.isStale()
|
|
5462
5722
|
? cacheEntry.request.execute({ allowCache: runQueryOptions?.allowCache })
|
|
@@ -5489,6 +5749,7 @@ const createQueryRepository = (config) => {
|
|
|
5489
5749
|
isMultiTabSyncEnabled,
|
|
5490
5750
|
isPersistEnabled,
|
|
5491
5751
|
keepUnusedFor,
|
|
5752
|
+
tags,
|
|
5492
5753
|
});
|
|
5493
5754
|
// Announced after the entry is bound and its request is on its way, because the one thing that acts
|
|
5494
5755
|
// on it - hydration from the persisted store - is only ever allowed to fill a gap this request has
|
|
@@ -5550,6 +5811,7 @@ const createQueryRepository = (config) => {
|
|
|
5550
5811
|
cacheEntry.isPersistEnabled = bindings.some((binding) => binding.isPersistEnabled);
|
|
5551
5812
|
cacheEntry.isMultiTabSyncEnabled = bindings.every((binding) => binding.isMultiTabSyncEnabled);
|
|
5552
5813
|
cacheEntry.keepUnusedFor = Math.min(...bindings.map((binding) => binding.keepUnusedFor));
|
|
5814
|
+
cacheEntry.tags = bindings.flatMap((binding) => binding.tags);
|
|
5553
5815
|
};
|
|
5554
5816
|
const unbind = (key, consumerDestroyRef) => {
|
|
5555
5817
|
if (key === null)
|
|
@@ -5592,10 +5854,30 @@ const createQueryRepository = (config) => {
|
|
|
5592
5854
|
bumpCacheVersion();
|
|
5593
5855
|
eventsSubject.next({ type: 'unbind-all-secure' });
|
|
5594
5856
|
};
|
|
5595
|
-
|
|
5857
|
+
let restartedBatch = null;
|
|
5858
|
+
let turnBatchKey = null;
|
|
5859
|
+
const currentTurnBatchKey = () => {
|
|
5860
|
+
if (turnBatchKey === null) {
|
|
5861
|
+
turnBatchKey = generateUuid();
|
|
5862
|
+
queueMicrotask(() => (turnBatchKey = null));
|
|
5863
|
+
}
|
|
5864
|
+
return turnBatchKey;
|
|
5865
|
+
};
|
|
5866
|
+
const resolveRestartedBatch = (cause, batch) => {
|
|
5867
|
+
if (!cause || cause.type === 'refresh')
|
|
5868
|
+
return null;
|
|
5869
|
+
const key = batch ?? currentTurnBatchKey();
|
|
5870
|
+
if (restartedBatch?.key !== key)
|
|
5871
|
+
restartedBatch = { key, requests: new WeakSet() };
|
|
5872
|
+
return restartedBatch.requests;
|
|
5873
|
+
};
|
|
5874
|
+
const refreshInUse = (filter, cause, batch) => {
|
|
5596
5875
|
const refreshed = [];
|
|
5876
|
+
const marksUnusedStale = cause?.type === 'invalidation';
|
|
5877
|
+
const restartedInBatch = resolveRestartedBatch(cause, batch);
|
|
5597
5878
|
for (const cacheEntry of cache.values()) {
|
|
5598
|
-
|
|
5879
|
+
const isUnused = cacheEntry.consumers.size === 0;
|
|
5880
|
+
if (isUnused && !marksUnusedStale)
|
|
5599
5881
|
continue;
|
|
5600
5882
|
// Re-firing a mutation would be a side effect nobody asked for, so only reads are refreshed:
|
|
5601
5883
|
// a cacheable method, or a GQL query transported via POST. Opting into the cache alone does not
|
|
@@ -5603,8 +5885,15 @@ const createQueryRepository = (config) => {
|
|
|
5603
5885
|
// end the session.
|
|
5604
5886
|
if (!cacheEntry.isRefreshable)
|
|
5605
5887
|
continue;
|
|
5606
|
-
if (filter && !filter(cacheEntry.request))
|
|
5888
|
+
if (filter && !filter(cacheEntry.request, cacheEntry.tags))
|
|
5889
|
+
continue;
|
|
5890
|
+
if (isUnused) {
|
|
5891
|
+
cacheEntry.request.subtle.markStale();
|
|
5607
5892
|
continue;
|
|
5893
|
+
}
|
|
5894
|
+
if (restartedInBatch?.has(cacheEntry.request))
|
|
5895
|
+
continue;
|
|
5896
|
+
restartedInBatch?.add(cacheEntry.request);
|
|
5608
5897
|
refreshed.push(cacheEntry.request);
|
|
5609
5898
|
cacheEntry.request.execute({ force: true });
|
|
5610
5899
|
}
|
|
@@ -5635,7 +5924,7 @@ const createQueryRepository = (config) => {
|
|
|
5635
5924
|
return true;
|
|
5636
5925
|
};
|
|
5637
5926
|
const bind = (options) => {
|
|
5638
|
-
const { key, consumerDestroyRef, request, isSecure, isCached, isRefreshable, isMultiTabSyncEnabled, isPersistEnabled, keepUnusedFor, } = options;
|
|
5927
|
+
const { key, consumerDestroyRef, request, isSecure, isCached, isRefreshable, isMultiTabSyncEnabled, isPersistEnabled, keepUnusedFor, tags, } = options;
|
|
5639
5928
|
const cacheEntry = cache.get(key);
|
|
5640
5929
|
if (cacheEntry && isCached) {
|
|
5641
5930
|
const existingBinding = cacheEntry.consumers.get(consumerDestroyRef);
|
|
@@ -5645,6 +5934,7 @@ const createQueryRepository = (config) => {
|
|
|
5645
5934
|
existingBinding.isMultiTabSyncEnabled = isMultiTabSyncEnabled;
|
|
5646
5935
|
existingBinding.isPersistEnabled = isPersistEnabled;
|
|
5647
5936
|
existingBinding.keepUnusedFor = keepUnusedFor;
|
|
5937
|
+
existingBinding.tags = tags;
|
|
5648
5938
|
}
|
|
5649
5939
|
else {
|
|
5650
5940
|
cacheEntry.consumers.set(consumerDestroyRef, {
|
|
@@ -5654,6 +5944,7 @@ const createQueryRepository = (config) => {
|
|
|
5654
5944
|
isMultiTabSyncEnabled,
|
|
5655
5945
|
isPersistEnabled,
|
|
5656
5946
|
keepUnusedFor,
|
|
5947
|
+
tags,
|
|
5657
5948
|
});
|
|
5658
5949
|
}
|
|
5659
5950
|
mergeConsumerPolicies(cacheEntry);
|
|
@@ -5669,6 +5960,7 @@ const createQueryRepository = (config) => {
|
|
|
5669
5960
|
isMultiTabSyncEnabled,
|
|
5670
5961
|
isPersistEnabled,
|
|
5671
5962
|
keepUnusedFor,
|
|
5963
|
+
tags,
|
|
5672
5964
|
},
|
|
5673
5965
|
],
|
|
5674
5966
|
]);
|
|
@@ -5715,6 +6007,7 @@ const createQueryRepository = (config) => {
|
|
|
5715
6007
|
isRefreshable,
|
|
5716
6008
|
isMultiTabSyncEnabled,
|
|
5717
6009
|
isPersistEnabled,
|
|
6010
|
+
tags,
|
|
5718
6011
|
});
|
|
5719
6012
|
}
|
|
5720
6013
|
bumpCacheVersion();
|
|
@@ -5724,6 +6017,8 @@ const createQueryRepository = (config) => {
|
|
|
5724
6017
|
isSecure: entry.isSecure,
|
|
5725
6018
|
consumerCount: entry.consumers.size,
|
|
5726
6019
|
isUnused: entry.consumers.size === 0,
|
|
6020
|
+
isRefreshable: entry.isRefreshable,
|
|
6021
|
+
tags: entry.tags,
|
|
5727
6022
|
request: entry.request,
|
|
5728
6023
|
}));
|
|
5729
6024
|
const evict = (key, cause = 'manual') => {
|
|
@@ -5750,7 +6045,21 @@ const createQueryRepository = (config) => {
|
|
|
5750
6045
|
return repository;
|
|
5751
6046
|
};
|
|
5752
6047
|
|
|
6048
|
+
const clientNamesByInjector = /* @__PURE__ */ new WeakMap();
|
|
6049
|
+
const warnOnDuplicateClientName = (name, destroyRef) => {
|
|
6050
|
+
const environmentInjector = inject(EnvironmentInjector);
|
|
6051
|
+
const names = clientNamesByInjector.get(environmentInjector) ?? new Set();
|
|
6052
|
+
clientNamesByInjector.set(environmentInjector, names);
|
|
6053
|
+
if (names.has(name)) {
|
|
6054
|
+
console.warn(`Two query clients are named "${name}". The name keys multi-tab sync, persistence and the devtools, so both clients share them. Give each createQueryClient() a unique name.`);
|
|
6055
|
+
return;
|
|
6056
|
+
}
|
|
6057
|
+
names.add(name);
|
|
6058
|
+
destroyRef.onDestroy(() => names.delete(name));
|
|
6059
|
+
};
|
|
5753
6060
|
const createQueryClient = (options) => defineRootProvider(() => {
|
|
6061
|
+
if (options.baseUrl.endsWith('/'))
|
|
6062
|
+
throw invalidBaseRouteError$1(options.baseUrl);
|
|
5754
6063
|
const httpClient = inject(HttpClient);
|
|
5755
6064
|
const ngErrorHandler = inject(ErrorHandler);
|
|
5756
6065
|
const injector = inject(Injector);
|
|
@@ -5768,6 +6077,8 @@ const createQueryClient = (options) => defineRootProvider(() => {
|
|
|
5768
6077
|
dependencies: { httpClient, ngErrorHandler, injector },
|
|
5769
6078
|
});
|
|
5770
6079
|
const destroyRef = inject(DestroyRef);
|
|
6080
|
+
if (isDevMode())
|
|
6081
|
+
warnOnDuplicateClientName(options.name, destroyRef);
|
|
5771
6082
|
destroyRef.onDestroy(() => {
|
|
5772
6083
|
for (const entry of repository.subtle.cacheEntries()) {
|
|
5773
6084
|
if (entry.isUnused)
|
|
@@ -5807,15 +6118,17 @@ const createQueryClient = (options) => defineRootProvider(() => {
|
|
|
5807
6118
|
refreshQueriesInUse: () => repository.refreshInUse(),
|
|
5808
6119
|
invalidateQueries: (invalidation) => {
|
|
5809
6120
|
const url = invalidation?.url ? resolveInvalidationUrl(options.baseUrl, invalidation.url) : null;
|
|
5810
|
-
|
|
6121
|
+
const tag = invalidation?.tag ?? null;
|
|
6122
|
+
repository.refreshInUse(createQueryInvalidationFilter({ url, tag, filter: invalidation?.filter }), {
|
|
5811
6123
|
type: 'invalidation',
|
|
5812
6124
|
url,
|
|
5813
6125
|
otherTab: false,
|
|
6126
|
+
...(tag && { tag }),
|
|
5814
6127
|
});
|
|
5815
6128
|
// The resolved URL is what travels: the other tabs are the same client, so they would
|
|
5816
6129
|
// resolve it identically, and a message that needs no interpretation cannot drift.
|
|
5817
6130
|
if (invalidation?.otherTabs ?? true)
|
|
5818
|
-
sync?.postInvalidation(url);
|
|
6131
|
+
sync?.postInvalidation(url, tag);
|
|
5819
6132
|
},
|
|
5820
6133
|
clearPersistedQueries: () => persistenceEngine?.clear() ?? Promise.resolve(),
|
|
5821
6134
|
whenPersistenceReady: persistenceEngine?.whenReady ?? Promise.resolve(),
|
|
@@ -5959,6 +6272,8 @@ const createSecureExecuteFactory = (options) => {
|
|
|
5959
6272
|
args: executeArgs?.args ?? options.state.args(),
|
|
5960
6273
|
options: executeArgs?.options,
|
|
5961
6274
|
};
|
|
6275
|
+
if (skipParkedExecution(options.state, execArgsWithDefaults.args, options.route))
|
|
6276
|
+
return;
|
|
5962
6277
|
circularChecker.check(execArgsWithDefaults.args);
|
|
5963
6278
|
hasExecuted = true;
|
|
5964
6279
|
lastExecuteArgs = execArgsWithDefaults;
|
|
@@ -6048,6 +6363,7 @@ const createSecureExecuteFn = (executeOptions) => {
|
|
|
6048
6363
|
authProvider,
|
|
6049
6364
|
deps: executeOptions.deps,
|
|
6050
6365
|
state: executeOptions.state,
|
|
6366
|
+
route: executeOptions.creatorInternals.route,
|
|
6051
6367
|
autoExecutes: shouldAutoExecuteQuery(executeOptions.creatorInternals.method) && !executeOptions.queryConfig.onlyManualExecution,
|
|
6052
6368
|
transformAuthAndExec: (executeArgs, executeState) => {
|
|
6053
6369
|
queryExecute({
|
|
@@ -6110,6 +6426,181 @@ const createPatchQuery = /* @__PURE__ */ createQueryTemplate('PATCH');
|
|
|
6110
6426
|
/** A query creator that creates a secure PATCH query (requires authentication) */
|
|
6111
6427
|
const createSecurePatchQuery = /* @__PURE__ */ createSecureQueryTemplate('PATCH');
|
|
6112
6428
|
|
|
6429
|
+
/**
|
|
6430
|
+
* Groups a fixed set of queries of which the one executed last wins. The group fits `etQueryButton` and
|
|
6431
|
+
* `et-query-error`'s `[query]`, which retries the member that ran last. Call it in an injection context.
|
|
6432
|
+
*
|
|
6433
|
+
* @example
|
|
6434
|
+
* readonly actions = createQueryGroup({
|
|
6435
|
+
* accept: postAcceptInvitation(withArgs(() => ({ pathParams: { id: this.id() } }))),
|
|
6436
|
+
* decline: postDeclineInvitation(withArgs(() => ({ pathParams: { id: this.id() } }))),
|
|
6437
|
+
* });
|
|
6438
|
+
*
|
|
6439
|
+
* accept() {
|
|
6440
|
+
* this.actions.members.accept.execute();
|
|
6441
|
+
* }
|
|
6442
|
+
*/
|
|
6443
|
+
const createQueryGroup = (members) => {
|
|
6444
|
+
const executions = new Subject();
|
|
6445
|
+
const latestMember = signal(null, /* @ts-ignore */
|
|
6446
|
+
...(ngDevMode ? [{ debugName: "latestMember" }] : /* istanbul ignore next */ []));
|
|
6447
|
+
injectInQueryContext(DestroyRef, 'query group').onDestroy(() => executions.complete());
|
|
6448
|
+
const wrap = (key, query) => {
|
|
6449
|
+
const member = {
|
|
6450
|
+
key,
|
|
6451
|
+
query,
|
|
6452
|
+
execute: (executeArgs) => {
|
|
6453
|
+
query.execute(executeArgs);
|
|
6454
|
+
latestMember.set(member);
|
|
6455
|
+
executions.next(member);
|
|
6456
|
+
},
|
|
6457
|
+
};
|
|
6458
|
+
return { ...query, execute: member.execute };
|
|
6459
|
+
};
|
|
6460
|
+
const wrappedMembers = Object.fromEntries(Object.entries(members).map(([key, query]) => [key, wrap(key, query)]));
|
|
6461
|
+
const queries = Object.values(members);
|
|
6462
|
+
const loading = computed(() => latestMember()?.query.loading() ??
|
|
6463
|
+
queries.map((query) => query.loading()).find((state) => state !== null) ??
|
|
6464
|
+
null, /* @ts-ignore */
|
|
6465
|
+
...(ngDevMode ? [{ debugName: "loading" }] : /* istanbul ignore next */ []));
|
|
6466
|
+
const error = computed(() => latestMember()?.query.error() ?? null, /* @ts-ignore */
|
|
6467
|
+
...(ngDevMode ? [{ debugName: "error" }] : /* istanbul ignore next */ []));
|
|
6468
|
+
const latest = computed(() => {
|
|
6469
|
+
const member = latestMember();
|
|
6470
|
+
return member ? { key: member.key, response: member.query.response() } : null;
|
|
6471
|
+
}, /* @ts-ignore */
|
|
6472
|
+
...(ngDevMode ? [{ debugName: "latest" }] : /* istanbul ignore next */ []));
|
|
6473
|
+
const execute = (executeArgs) => untracked(latestMember)?.execute(executeArgs);
|
|
6474
|
+
const succeeded$ = executions.pipe(mergeMap(({ key, query }) => {
|
|
6475
|
+
const snapshot = query.createSnapshot();
|
|
6476
|
+
return snapshot.isAlive.asObservable().pipe(filter((isAlive) => !isAlive), take(1), map(() => untracked(() => snapshot.executionState())), filter((state) => state?.type === 'success'), map((state) => ({ key, response: state.response })));
|
|
6477
|
+
}), share());
|
|
6478
|
+
return { members: wrappedMembers, loading, error, latest, execute, succeeded$ };
|
|
6479
|
+
};
|
|
6480
|
+
|
|
6481
|
+
const optimisticLayers = /* @__PURE__ */ new WeakMap();
|
|
6482
|
+
const liveLayer = (request) => {
|
|
6483
|
+
const layer = optimisticLayers.get(request);
|
|
6484
|
+
if (layer && layer.written === request.response())
|
|
6485
|
+
return layer;
|
|
6486
|
+
optimisticLayers.delete(request);
|
|
6487
|
+
return null;
|
|
6488
|
+
};
|
|
6489
|
+
const rewrite = (request, layer) => {
|
|
6490
|
+
layer.written = layer.ops.reduce((current, op) => op.apply(current) ?? current, layer.base);
|
|
6491
|
+
request.subtle.setResponse(layer.written);
|
|
6492
|
+
if (layer.ops.every((op) => op.settled))
|
|
6493
|
+
optimisticLayers.delete(request);
|
|
6494
|
+
};
|
|
6495
|
+
const applyOp$1 = (request, op) => {
|
|
6496
|
+
const current = request.response();
|
|
6497
|
+
if (current === null)
|
|
6498
|
+
return false;
|
|
6499
|
+
const layer = liveLayer(request) ?? { base: current, written: current, ops: [] };
|
|
6500
|
+
const next = op.apply(layer.written);
|
|
6501
|
+
if (next === null)
|
|
6502
|
+
return false;
|
|
6503
|
+
layer.ops.push(op);
|
|
6504
|
+
layer.written = next;
|
|
6505
|
+
optimisticLayers.set(request, layer);
|
|
6506
|
+
request.subtle.setResponse(next);
|
|
6507
|
+
return true;
|
|
6508
|
+
};
|
|
6509
|
+
const settleOp = (request, op, confirmed) => {
|
|
6510
|
+
const layer = liveLayer(request);
|
|
6511
|
+
const index = layer?.ops.indexOf(op) ?? -1;
|
|
6512
|
+
if (!layer || index === -1)
|
|
6513
|
+
return;
|
|
6514
|
+
if (confirmed) {
|
|
6515
|
+
op.apply = confirmed;
|
|
6516
|
+
op.settled = true;
|
|
6517
|
+
}
|
|
6518
|
+
else {
|
|
6519
|
+
layer.ops.splice(index, 1);
|
|
6520
|
+
}
|
|
6521
|
+
rewrite(request, layer);
|
|
6522
|
+
};
|
|
6523
|
+
const findTargetRequests = (deps, targets) => {
|
|
6524
|
+
const filters = targets.map((target) => createQueryInvalidationFilter('url' in target
|
|
6525
|
+
? { url: resolveInvalidationUrl(deps.client.baseUrl, target.url) }
|
|
6526
|
+
: { url: null, tag: target.tag }));
|
|
6527
|
+
return deps.client.repository.subtle
|
|
6528
|
+
.cacheEntries()
|
|
6529
|
+
.filter((entry) => entry.isRefreshable && filters.some((filter) => filter?.(entry.request, entry.tags)))
|
|
6530
|
+
.map((entry) => entry.request);
|
|
6531
|
+
};
|
|
6532
|
+
/**
|
|
6533
|
+
* Writes the expected result of a mutation into the cached responses of the reads `target` matches before
|
|
6534
|
+
* the request leaves, and rolls those writes back if it fails or is cancelled. Pair it with the creator's
|
|
6535
|
+
* `invalidates`, whose refetch corrects a wrong guess. The writes stay in this tab; the invalidation reaches
|
|
6536
|
+
* the others.
|
|
6537
|
+
*
|
|
6538
|
+
* A rollback leaves an entry alone that a refetch or another tab wrote in the meantime; other optimistic
|
|
6539
|
+
* updates of the entry still pending are re-applied on top of what it held before.
|
|
6540
|
+
*
|
|
6541
|
+
* @example
|
|
6542
|
+
* patchOpportunityPerson(
|
|
6543
|
+
* withArgs(() => ({ pathParams: { uuid, peopleUuid }, body })),
|
|
6544
|
+
* withOptimisticUpdate({
|
|
6545
|
+
* read: getOpportunity,
|
|
6546
|
+
* target: ({ args }) => ({ tag: `opportunity:${args.pathParams.uuid}` }),
|
|
6547
|
+
* update: ({ current, args }) => ({ ...current, people: toggle(current.people, args.pathParams.peopleUuid) }),
|
|
6548
|
+
* }),
|
|
6549
|
+
* );
|
|
6550
|
+
*
|
|
6551
|
+
* @throws If the query is a read (e.g. a GET request)
|
|
6552
|
+
*/
|
|
6553
|
+
const withOptimisticUpdate = (options) => {
|
|
6554
|
+
const update = options.update;
|
|
6555
|
+
return createQueryFeature({
|
|
6556
|
+
type: QueryFeatureType.WITH_OPTIMISTIC_UPDATE,
|
|
6557
|
+
devtools: () => queryDevtoolsFnDetail(options.update, 'update'),
|
|
6558
|
+
fn: (context) => {
|
|
6559
|
+
if (context.flags.shouldAutoExecuteMethod)
|
|
6560
|
+
throw withOptimisticUpdateUsedOnRead(context.flags.method);
|
|
6561
|
+
context.state.subtle.beforeExecute.set((executeArgs) => {
|
|
6562
|
+
const args = executeArgs ?? {};
|
|
6563
|
+
const target = options.target({ args });
|
|
6564
|
+
const targets = Array.isArray(target) ? target : [target];
|
|
6565
|
+
const optimistic = (current) => update({ current, args });
|
|
6566
|
+
const applied = findTargetRequests(context.deps, targets)
|
|
6567
|
+
.map((request) => ({ request, op: { apply: optimistic, settled: false } }))
|
|
6568
|
+
.filter(({ request, op }) => applyOp$1(request, op));
|
|
6569
|
+
if (!applied.length)
|
|
6570
|
+
return null;
|
|
6571
|
+
const settle = (confirmed) => {
|
|
6572
|
+
for (const { request, op } of applied)
|
|
6573
|
+
settleOp(request, op, confirmed);
|
|
6574
|
+
};
|
|
6575
|
+
return (request) => {
|
|
6576
|
+
if (!request.loading())
|
|
6577
|
+
return settle(null);
|
|
6578
|
+
let settled = false;
|
|
6579
|
+
const subscription = request.events$.subscribe({
|
|
6580
|
+
next: (event) => {
|
|
6581
|
+
if (event.type === HttpEventType.Response) {
|
|
6582
|
+
const response = context.state.subtle.request() === request ? context.state.response() : null;
|
|
6583
|
+
settled = true;
|
|
6584
|
+
settle((current) => update({ current, args, response }) ?? optimistic(current));
|
|
6585
|
+
}
|
|
6586
|
+
else if (event.type === 'error' || event.type === 'cancel') {
|
|
6587
|
+
settled = true;
|
|
6588
|
+
settle(null);
|
|
6589
|
+
}
|
|
6590
|
+
if (settled)
|
|
6591
|
+
subscription.unsubscribe();
|
|
6592
|
+
},
|
|
6593
|
+
complete: () => {
|
|
6594
|
+
if (!settled)
|
|
6595
|
+
settle(null);
|
|
6596
|
+
},
|
|
6597
|
+
});
|
|
6598
|
+
};
|
|
6599
|
+
});
|
|
6600
|
+
},
|
|
6601
|
+
});
|
|
6602
|
+
};
|
|
6603
|
+
|
|
6113
6604
|
const isAbortedSnapshot = (snapshot) => snapshot.latestHttpEvent()?.type === 'cancel' && snapshot.error() === null;
|
|
6114
6605
|
const executeAndSettle$ = (query, executeArgs, emitAborted) => defer(() => {
|
|
6115
6606
|
query.execute(executeArgs);
|
|
@@ -6749,16 +7240,18 @@ const createBearerAuthProviderImpl = (config) => {
|
|
|
6749
7240
|
const refreshToken = signal(null, /* @ts-ignore */
|
|
6750
7241
|
...(ngDevMode ? [{ debugName: "refreshToken" }] : /* istanbul ignore next */ []));
|
|
6751
7242
|
const afterTokenRefresh$ = new Subject();
|
|
7243
|
+
const expiresInPropertyName = signal('exp', /* @ts-ignore */
|
|
7244
|
+
...(ngDevMode ? [{ debugName: "expiresInPropertyName" }] : /* istanbul ignore next */ []));
|
|
6752
7245
|
const bearerData = computed(() => {
|
|
6753
7246
|
const token = accessToken();
|
|
6754
7247
|
if (!token)
|
|
6755
7248
|
return null;
|
|
6756
7249
|
try {
|
|
6757
|
-
const decoded = config.bearerDecryptFn
|
|
7250
|
+
const decoded = config.bearerDecryptFn ? config.bearerDecryptFn(token) : decryptBearer$1(token);
|
|
6758
7251
|
return patchQueryDevtoolsTokenPayload({
|
|
6759
7252
|
payload: decoded,
|
|
6760
7253
|
providerName: config.name,
|
|
6761
|
-
expiresInPropertyName:
|
|
7254
|
+
expiresInPropertyName: expiresInPropertyName(),
|
|
6762
7255
|
});
|
|
6763
7256
|
}
|
|
6764
7257
|
catch (error) {
|
|
@@ -6891,6 +7384,7 @@ const createBearerAuthProviderImpl = (config) => {
|
|
|
6891
7384
|
reportAccessTokenExpiry: (isExpired) => {
|
|
6892
7385
|
readAccessTokenExpiry = isExpired;
|
|
6893
7386
|
},
|
|
7387
|
+
reportExpiresInPropertyName: (claim) => expiresInPropertyName.set(claim),
|
|
6894
7388
|
};
|
|
6895
7389
|
for (const builder of config.queries) {
|
|
6896
7390
|
builder.setup?.(querySetupContext);
|
|
@@ -7081,6 +7575,7 @@ const withRefreshQuery = (key, config) => {
|
|
|
7081
7575
|
const buildArgs = (refreshToken) => (config.buildArgs?.(refreshToken) ?? { body: { token: refreshToken } });
|
|
7082
7576
|
const setup = (context) => {
|
|
7083
7577
|
const expiresInPropertyName = config.expiresInPropertyName ?? 'exp';
|
|
7578
|
+
context.reportExpiresInPropertyName(expiresInPropertyName);
|
|
7084
7579
|
const minRefreshInterval = config.minRefreshInterval ?? 30000; // 30 seconds default
|
|
7085
7580
|
const refreshIfExpired = config.refreshIfExpired ?? true;
|
|
7086
7581
|
const destroyRef = inject(DestroyRef);
|
|
@@ -7266,47 +7761,50 @@ const withRefreshQuery = (key, config) => {
|
|
|
7266
7761
|
return Math.min(Math.max(msUntilAccessTokenIsStale() ?? minRefreshInterval, rescheduleDelayMs), maxTimerDelayMs);
|
|
7267
7762
|
}
|
|
7268
7763
|
};
|
|
7269
|
-
|
|
7270
|
-
|
|
7271
|
-
|
|
7272
|
-
|
|
7273
|
-
|
|
7764
|
+
let expiryWarnedFor = null;
|
|
7765
|
+
const tokenTimes = (token) => {
|
|
7766
|
+
let decoded;
|
|
7767
|
+
try {
|
|
7768
|
+
decoded = context.bearerDecryptFn ? context.bearerDecryptFn(token) : decryptBearer$1(token);
|
|
7769
|
+
}
|
|
7770
|
+
catch {
|
|
7771
|
+
decoded = null;
|
|
7772
|
+
}
|
|
7773
|
+
const times = readAccessTokenTimes({
|
|
7774
|
+
token,
|
|
7775
|
+
decoded,
|
|
7776
|
+
claim: expiresInPropertyName,
|
|
7777
|
+
providerName: context.name,
|
|
7778
|
+
});
|
|
7779
|
+
if (!times && isDevMode() && expiryWarnedFor !== token) {
|
|
7780
|
+
expiryWarnedFor = token;
|
|
7781
|
+
console.warn(authAccessTokenExpiryUnreadable(expiresInPropertyName).message);
|
|
7274
7782
|
}
|
|
7275
|
-
|
|
7276
|
-
const minBufferMs = config.refreshStrategy?.minBufferMs ?? 60000; // 1 minute
|
|
7277
|
-
const maxBufferMs = config.refreshStrategy?.maxBufferMs ?? 600000; // 10 minutes
|
|
7278
|
-
const calculatedBuffer = tokenLifetimeMs * (1 - percentage);
|
|
7279
|
-
return Math.max(minBufferMs, Math.min(maxBufferMs, calculatedBuffer));
|
|
7783
|
+
return times;
|
|
7280
7784
|
};
|
|
7281
7785
|
/** When this token expires, as a timestamp, or `null` if the expiry cannot be read off it. */
|
|
7282
|
-
const tokenExpiresAt = (token) =>
|
|
7283
|
-
|
|
7284
|
-
|
|
7285
|
-
|
|
7286
|
-
|
|
7287
|
-
|
|
7288
|
-
|
|
7289
|
-
|
|
7290
|
-
|
|
7291
|
-
if (typeof expiresIn !== 'number') {
|
|
7292
|
-
if (isDevMode()) {
|
|
7293
|
-
console.warn(`Token does not contain valid ${expiresInPropertyName} property for auto-refresh`);
|
|
7294
|
-
}
|
|
7295
|
-
return null;
|
|
7296
|
-
}
|
|
7297
|
-
return expiresIn * 1000;
|
|
7786
|
+
const tokenExpiresAt = (token) => tokenTimes(token)?.expiresAtMs ?? null;
|
|
7787
|
+
let measuredLifetime = null;
|
|
7788
|
+
/**
|
|
7789
|
+
* The whole lifetime of a token, not what is left of it: a schedule recomputed later (the tab came
|
|
7790
|
+
* back to the foreground) must land at the same instant as the first one.
|
|
7791
|
+
*/
|
|
7792
|
+
const tokenLifetimeMs = (token, times) => {
|
|
7793
|
+
if (times.issuedAtMs !== null && times.expiresAtMs > times.issuedAtMs) {
|
|
7794
|
+
return times.expiresAtMs - times.issuedAtMs;
|
|
7298
7795
|
}
|
|
7299
|
-
|
|
7300
|
-
|
|
7796
|
+
if (measuredLifetime?.token !== token) {
|
|
7797
|
+
measuredLifetime = { token, lifetimeMs: times.expiresAtMs - Date.now() };
|
|
7301
7798
|
}
|
|
7799
|
+
return measuredLifetime.lifetimeMs;
|
|
7302
7800
|
};
|
|
7303
7801
|
/** When this access token is due to be refreshed, or `null` if it cannot be scheduled at all. */
|
|
7304
7802
|
const scheduledRefreshDelay = (token) => {
|
|
7305
|
-
const
|
|
7306
|
-
if (
|
|
7803
|
+
const times = tokenTimes(token);
|
|
7804
|
+
if (times === null)
|
|
7307
7805
|
return null;
|
|
7308
|
-
const
|
|
7309
|
-
if (
|
|
7806
|
+
const remainingMs = times.expiresAtMs - Date.now();
|
|
7807
|
+
if (remainingMs <= 0) {
|
|
7310
7808
|
if (!refreshIfExpired)
|
|
7311
7809
|
return null;
|
|
7312
7810
|
if (isDevMode()) {
|
|
@@ -7314,7 +7812,7 @@ const withRefreshQuery = (key, config) => {
|
|
|
7314
7812
|
}
|
|
7315
7813
|
return 0;
|
|
7316
7814
|
}
|
|
7317
|
-
return Math.max(
|
|
7815
|
+
return Math.max(remainingMs - refreshBufferMs(config.refreshStrategy, tokenLifetimeMs(token, times)), 0);
|
|
7318
7816
|
};
|
|
7319
7817
|
/**
|
|
7320
7818
|
* How long this tab can still work with the access token it holds, or `null` without a readable
|
|
@@ -7915,40 +8413,59 @@ const buildPersistentAuthFeature = (config, context) => {
|
|
|
7915
8413
|
*/
|
|
7916
8414
|
const createPersistentAuthFeature = (config, context) => runInInjectionContext(context.injector, () => buildPersistentAuthFeature(config, context));
|
|
7917
8415
|
|
|
8416
|
+
const RENEWAL_GRACE_MS = 10_000;
|
|
7918
8417
|
const withTokenExpirationWarning = (config = {}) => {
|
|
7919
8418
|
return (context) => {
|
|
7920
8419
|
const warningThreshold = config.warningThreshold ?? 5 * 60 * 1000;
|
|
7921
8420
|
const checkInterval = config.checkInterval ?? 1000;
|
|
7922
8421
|
const refreshBuilder = context.queryBuilders.find((builder) => builder._type === 'tokenRefreshQuery');
|
|
7923
|
-
const
|
|
7924
|
-
|
|
7925
|
-
|
|
7926
|
-
const
|
|
7927
|
-
const
|
|
7928
|
-
if (!
|
|
8422
|
+
const refreshConfig = refreshBuilder?._type === 'tokenRefreshQuery' ? refreshBuilder.config : null;
|
|
8423
|
+
const expiresInPropertyName = config.expiresInPropertyName ?? refreshConfig?.expiresInPropertyName ?? 'exp';
|
|
8424
|
+
let measuredLifetime = null;
|
|
8425
|
+
const tokenTimes = computed(() => {
|
|
8426
|
+
const token = context.accessToken();
|
|
8427
|
+
if (!token)
|
|
7929
8428
|
return null;
|
|
7930
|
-
const
|
|
7931
|
-
|
|
8429
|
+
const times = readAccessTokenTimes({
|
|
8430
|
+
token,
|
|
8431
|
+
decoded: context.bearerData(),
|
|
8432
|
+
claim: expiresInPropertyName,
|
|
8433
|
+
providerName: context.name,
|
|
8434
|
+
});
|
|
8435
|
+
if (!times)
|
|
7932
8436
|
return null;
|
|
7933
|
-
|
|
8437
|
+
if (times.issuedAtMs !== null && times.expiresAtMs > times.issuedAtMs) {
|
|
8438
|
+
return { ...times, lifetimeMs: times.expiresAtMs - times.issuedAtMs };
|
|
8439
|
+
}
|
|
8440
|
+
if (measuredLifetime?.token !== token) {
|
|
8441
|
+
measuredLifetime = { token, lifetimeMs: times.expiresAtMs - Date.now() };
|
|
8442
|
+
}
|
|
8443
|
+
return { ...times, lifetimeMs: measuredLifetime.lifetimeMs };
|
|
8444
|
+
}, /* @ts-ignore */
|
|
8445
|
+
...(ngDevMode ? [{ debugName: "tokenTimes" }] : /* istanbul ignore next */ []));
|
|
8446
|
+
const expiresAt = computed(() => {
|
|
8447
|
+
const times = tokenTimes();
|
|
8448
|
+
return times ? new Date(times.expiresAtMs) : null;
|
|
7934
8449
|
}, /* @ts-ignore */
|
|
7935
8450
|
...(ngDevMode ? [{ debugName: "expiresAt" }] : /* istanbul ignore next */ []));
|
|
7936
|
-
const expiresIn
|
|
8451
|
+
const expiresIn = toSignal(toObservable(tokenTimes).pipe(switchMap((times) => times
|
|
7937
8452
|
? timer(0, checkInterval).pipe(map(() => {
|
|
7938
|
-
const
|
|
7939
|
-
if (!expiry)
|
|
7940
|
-
return null;
|
|
7941
|
-
const msUntilExpiry = expiry.getTime() - Date.now();
|
|
8453
|
+
const msUntilExpiry = times.expiresAtMs - Date.now();
|
|
7942
8454
|
return msUntilExpiry > 0 ? msUntilExpiry : null;
|
|
7943
8455
|
}))
|
|
7944
|
-
: of(null)));
|
|
7945
|
-
const isExpiringSoon
|
|
7946
|
-
|
|
8456
|
+
: of(null))), { initialValue: null });
|
|
8457
|
+
const isExpiringSoon = computed(() => {
|
|
8458
|
+
const msUntilExpiry = expiresIn();
|
|
8459
|
+
const times = tokenTimes();
|
|
8460
|
+
if (msUntilExpiry === null || !times || msUntilExpiry > warningThreshold)
|
|
7947
8461
|
return false;
|
|
7948
|
-
|
|
7949
|
-
|
|
7950
|
-
|
|
7951
|
-
|
|
8462
|
+
if (!refreshConfig || !context.refreshToken())
|
|
8463
|
+
return true;
|
|
8464
|
+
const bufferMs = refreshBufferMs(refreshConfig.refreshStrategy, times.lifetimeMs);
|
|
8465
|
+
const msOverdue = bufferMs - msUntilExpiry;
|
|
8466
|
+
return msOverdue >= Math.min(RENEWAL_GRACE_MS, bufferMs / 2);
|
|
8467
|
+
}, /* @ts-ignore */
|
|
8468
|
+
...(ngDevMode ? [{ debugName: "isExpiringSoon" }] : /* istanbul ignore next */ []));
|
|
7952
8469
|
const instance = {
|
|
7953
8470
|
expiresAt,
|
|
7954
8471
|
expiresIn,
|
|
@@ -8001,10 +8518,11 @@ const withTokenRevocation = (config) => {
|
|
|
8001
8518
|
...(ngDevMode ? [{ debugName: "target" }] : /* istanbul ignore next */ []));
|
|
8002
8519
|
const dropped = signal(false, /* @ts-ignore */
|
|
8003
8520
|
...(ngDevMode ? [{ debugName: "dropped" }] : /* istanbul ignore next */ []));
|
|
8004
|
-
const
|
|
8521
|
+
const watcher = createObservableSignalWatcher(context.injector);
|
|
8522
|
+
const follow = (read) => watcher.wrap(computed(() => {
|
|
8005
8523
|
const snapshot = target();
|
|
8006
8524
|
return snapshot ? read(snapshot) : null;
|
|
8007
|
-
})
|
|
8525
|
+
}));
|
|
8008
8526
|
const snapshot = {
|
|
8009
8527
|
args: follow((s) => s.args()),
|
|
8010
8528
|
response: follow((s) => s.response()),
|
|
@@ -8015,7 +8533,7 @@ const withTokenRevocation = (config) => {
|
|
|
8015
8533
|
triggeredBy: follow((s) => s.triggeredBy()),
|
|
8016
8534
|
id: follow((s) => s.id()),
|
|
8017
8535
|
executionState: follow((s) => s.executionState()),
|
|
8018
|
-
isAlive:
|
|
8536
|
+
isAlive: watcher.wrap(computed(() => target()?.isAlive() ?? !(dropped() || queueDropped()))),
|
|
8019
8537
|
};
|
|
8020
8538
|
const queued = {
|
|
8021
8539
|
tokens,
|
|
@@ -8152,19 +8670,18 @@ const buildTrackingFeature = (context, config) => {
|
|
|
8152
8670
|
}
|
|
8153
8671
|
}
|
|
8154
8672
|
};
|
|
8155
|
-
|
|
8156
|
-
|
|
8157
|
-
|
|
8158
|
-
|
|
8159
|
-
|
|
8160
|
-
|
|
8161
|
-
|
|
8162
|
-
|
|
8163
|
-
|
|
8164
|
-
|
|
8165
|
-
|
|
8166
|
-
|
|
8167
|
-
};
|
|
8673
|
+
// Handlers run outside the reactive context of the effects that fire them, so one that ends the
|
|
8674
|
+
// session or creates a query does not throw NG0602.
|
|
8675
|
+
const fireHandlers = (event, data) => untracked(() => {
|
|
8676
|
+
handlers.get(event)?.forEach((handler) => {
|
|
8677
|
+
try {
|
|
8678
|
+
handler(data);
|
|
8679
|
+
}
|
|
8680
|
+
catch (error) {
|
|
8681
|
+
console.error(`Error in tracking event handler for "${event}":`, error);
|
|
8682
|
+
}
|
|
8683
|
+
});
|
|
8684
|
+
});
|
|
8168
8685
|
const pendingForwardedMessages = [];
|
|
8169
8686
|
const forwardingChannel = context.leaderElection && typeof BroadcastChannel !== 'undefined'
|
|
8170
8687
|
? new BroadcastChannel(`ethlete-auth-tracking:${context.name}`)
|
|
@@ -8298,6 +8815,10 @@ const buildTrackingFeature = (context, config) => {
|
|
|
8298
8815
|
*/
|
|
8299
8816
|
const createTrackingFeature = (context, config) => runInInjectionContext(context.injector, () => buildTrackingFeature(context, config));
|
|
8300
8817
|
|
|
8818
|
+
// Generated from package.json by tools/scripts/generate-version.js - do not edit.
|
|
8819
|
+
/** The version of `@ethlete/query` this build was cut from. */
|
|
8820
|
+
const QUERY_VERSION = '6.0.0-next.55';
|
|
8821
|
+
|
|
8301
8822
|
const ethlete = {};
|
|
8302
8823
|
let app = null;
|
|
8303
8824
|
/** What `window.ethlete` exposes - the same object {@link queryDevtoolsAbout} returns. */
|
|
@@ -9321,12 +9842,23 @@ const sanitizeSession = (value) => {
|
|
|
9321
9842
|
savedAt: typeof session.savedAt === 'number' ? session.savedAt : 0,
|
|
9322
9843
|
};
|
|
9323
9844
|
};
|
|
9845
|
+
const isLocalAccount = (value) => {
|
|
9846
|
+
const account = value;
|
|
9847
|
+
return (typeof account === 'object' &&
|
|
9848
|
+
account !== null &&
|
|
9849
|
+
typeof account.id === 'string' &&
|
|
9850
|
+
typeof account.provider === 'string' &&
|
|
9851
|
+
typeof account.label === 'string' &&
|
|
9852
|
+
typeof account.loginQuery === 'string' &&
|
|
9853
|
+
typeof account.scope === 'string' &&
|
|
9854
|
+
(account.fields === undefined || Array.isArray(account.fields)));
|
|
9855
|
+
};
|
|
9324
9856
|
const sanitize = (value) => ({
|
|
9325
9857
|
sessions: Array.isArray(value?.sessions)
|
|
9326
9858
|
? value.sessions.map(sanitizeSession).filter((session) => session !== null)
|
|
9327
9859
|
: [],
|
|
9328
9860
|
credentials: typeof value?.credentials === 'object' && value.credentials !== null ? value.credentials : {},
|
|
9329
|
-
accounts: Array.isArray(value?.accounts) ? value.accounts : [],
|
|
9861
|
+
accounts: Array.isArray(value?.accounts) ? value.accounts.filter(isLocalAccount) : [],
|
|
9330
9862
|
});
|
|
9331
9863
|
/**
|
|
9332
9864
|
* The vault is one key every tab writes, so a tab holding a stale copy of it would drop whatever another
|
|
@@ -9979,266 +10511,59 @@ const resolveQueryDevtoolsFaultForAttempt = (target) => {
|
|
|
9979
10511
|
if (fault.failNext > 0) {
|
|
9980
10512
|
shouldFail = true;
|
|
9981
10513
|
// Not the public setter: spending the budget is the armed fault doing its job, not someone re-arming
|
|
9982
|
-
// it, so it must not clear what the panel's bar says about where the fault came from.
|
|
9983
|
-
patchFault({ clientName: target.clientName, patch: { failNext: fault.failNext - 1 } });
|
|
9984
|
-
}
|
|
9985
|
-
else if (fault.failRate > 0) {
|
|
9986
|
-
shouldFail = Math.random() * 100 < fault.failRate;
|
|
9987
|
-
}
|
|
9988
|
-
return {
|
|
9989
|
-
latencyMs: fault.latencyMs,
|
|
9990
|
-
status: shouldFail ? fault.status : null,
|
|
9991
|
-
};
|
|
9992
|
-
};
|
|
9993
|
-
|
|
9994
|
-
/**
|
|
9995
|
-
* Wraps the committed value of the query form `formId` in a signal that reports every read to the
|
|
9996
|
-
* devtools.
|
|
9997
|
-
* @internal
|
|
9998
|
-
*/
|
|
9999
|
-
const noteQueryFormReads = (formId, source) => {
|
|
10000
|
-
const memoized = computed(() => source(), /* @ts-ignore */
|
|
10001
|
-
...(ngDevMode ? [{ debugName: "memoized" }] : /* istanbul ignore next */ []));
|
|
10002
|
-
// The read must be noted per read, not per recomputation of the value: the second query to build
|
|
10003
|
-
// args reads a memoized value, and a note that only fires on a recomputation never reaches its
|
|
10004
|
-
// collection window. Carrying the memo's signal node over keeps this function a real `Signal`.
|
|
10005
|
-
const read = () => {
|
|
10006
|
-
noteQueryFormRead(formId);
|
|
10007
|
-
return memoized();
|
|
10008
|
-
};
|
|
10009
|
-
return Object.assign(read, { [SIGNAL]: memoized[SIGNAL] });
|
|
10010
|
-
};
|
|
10011
|
-
/**
|
|
10012
|
-
* Records which query forms feed one query's args, so the devtools can answer "which query does this
|
|
10013
|
-
* form drive?" from the reads themselves rather than from a naming convention.
|
|
10014
|
-
* @internal
|
|
10015
|
-
*/
|
|
10016
|
-
const createQueryDevtoolsFormLinks = () => {
|
|
10017
|
-
const ids = signal([], /* @ts-ignore */
|
|
10018
|
-
...(ngDevMode ? [{ debugName: "ids" }] : /* istanbul ignore next */ []));
|
|
10019
|
-
return {
|
|
10020
|
-
ids: ids.asReadonly(),
|
|
10021
|
-
track: (build) => {
|
|
10022
|
-
const collected = collectQueryFormLinks(build);
|
|
10023
|
-
// Args are built inside a reactive computation, which may not write to a signal - `untracked`
|
|
10024
|
-
// lifts that restriction, and skipping the write unless something is new keeps the panel from
|
|
10025
|
-
// being notified on every evaluation.
|
|
10026
|
-
untracked(() => {
|
|
10027
|
-
const known = ids();
|
|
10028
|
-
const added = collected.ids.filter((id) => !known.includes(id));
|
|
10029
|
-
if (added.length)
|
|
10030
|
-
ids.set([...known, ...added]);
|
|
10031
|
-
});
|
|
10032
|
-
return collected.value;
|
|
10033
|
-
},
|
|
10034
|
-
};
|
|
10035
|
-
};
|
|
10036
|
-
|
|
10037
|
-
/**
|
|
10038
|
-
* The designed library. Which of them is **armed** is a separate store under a separate scope - see
|
|
10039
|
-
* {@link queryDevtoolsArmedMocks}.
|
|
10040
|
-
*/
|
|
10041
|
-
const STORAGE_KEY$1 = 'ethlete:query:devtools:mocks:v1';
|
|
10042
|
-
const ARMED_STORAGE_KEY = 'ethlete:query:devtools:mocks:armed:v1';
|
|
10043
|
-
const mocks = /* @__PURE__ */ signal([], /* @ts-ignore */
|
|
10044
|
-
...(ngDevMode ? [{ debugName: "mocks" }] : /* istanbul ignore next */ []));
|
|
10045
|
-
const armed = /* @__PURE__ */ signal(/* @__PURE__ */ new Set(), /* @ts-ignore */
|
|
10046
|
-
...(ngDevMode ? [{ debugName: "armed" }] : /* istanbul ignore next */ []));
|
|
10047
|
-
const armedRestored = /* @__PURE__ */ signal(false, /* @ts-ignore */
|
|
10048
|
-
...(ngDevMode ? [{ debugName: "armedRestored" }] : /* istanbul ignore next */ []));
|
|
10049
|
-
const scope$1 = () => queryDevtoolsSettings().mocks;
|
|
10050
|
-
const armedScope = () => queryDevtoolsSettings().armedMocks;
|
|
10051
|
-
/** The designed mocks, armed or not. Persisted, because losing an hour of authoring is unacceptable. */
|
|
10052
|
-
const queryDevtoolsMocks = /* @__PURE__ */ mocks.asReadonly();
|
|
10053
|
-
/**
|
|
10054
|
-
* The ids currently being served. Not kept across page loads unless the `armedMocks` setting asks for it -
|
|
10055
|
-
* an app that silently serves designed data tomorrow morning is worse than losing which mocks were armed.
|
|
10056
|
-
*/
|
|
10057
|
-
const queryDevtoolsArmedMocks = /* @__PURE__ */ armed.asReadonly();
|
|
10058
|
-
/**
|
|
10059
|
-
* Whether what is armed right now came back from a previous page load rather than being armed by hand.
|
|
10060
|
-
* The panel's armed bar says so, because mocks nobody remembers arming look exactly like a broken API.
|
|
10061
|
-
*/
|
|
10062
|
-
const queryDevtoolsArmedMocksRestored = /* @__PURE__ */ armedRestored.asReadonly();
|
|
10063
|
-
/**
|
|
10064
|
-
* The identity of a designed mock: one per client, method, route **and** declared query. The query is part
|
|
10065
|
-
* of it so `/posts?page=1` and `/posts?page=2` can hold different bodies.
|
|
10066
|
-
*/
|
|
10067
|
-
const queryDevtoolsMockId = (parts) => `${parts.clientName}|${parts.method}|${parts.pattern}${parts.query ? `?${parts.query}` : ''}`;
|
|
10068
|
-
const write = () => {
|
|
10069
|
-
if (scope$1() === 'none') {
|
|
10070
|
-
clearQueryDevtoolsStore(STORAGE_KEY$1);
|
|
10071
|
-
return;
|
|
10072
|
-
}
|
|
10073
|
-
writeQueryDevtoolsStore(scope$1(), STORAGE_KEY$1, mocks());
|
|
10074
|
-
};
|
|
10075
|
-
const writeArmed = () => {
|
|
10076
|
-
if (armedScope() === 'none') {
|
|
10077
|
-
clearQueryDevtoolsStore(ARMED_STORAGE_KEY);
|
|
10078
|
-
return;
|
|
10079
|
-
}
|
|
10080
|
-
writeQueryDevtoolsStore(armedScope(), ARMED_STORAGE_KEY, [...armed()]);
|
|
10081
|
-
};
|
|
10082
|
-
/** Every change made by hand goes through here, so none of them can leave the store or the bar behind. */
|
|
10083
|
-
const setArmed = (next) => {
|
|
10084
|
-
armed.set(next);
|
|
10085
|
-
armedRestored.set(false);
|
|
10086
|
-
writeArmed();
|
|
10087
|
-
};
|
|
10088
|
-
const isMock = (value) => {
|
|
10089
|
-
const mock = value;
|
|
10090
|
-
return (!!mock &&
|
|
10091
|
-
typeof mock.id === 'string' &&
|
|
10092
|
-
typeof mock.clientName === 'string' &&
|
|
10093
|
-
typeof mock.method === 'string' &&
|
|
10094
|
-
typeof mock.pattern === 'string' &&
|
|
10095
|
-
typeof mock.status === 'number');
|
|
10096
|
-
};
|
|
10097
|
-
/**
|
|
10098
|
-
* Reads the designed library, so a mock can be armed without authoring it again. Called by
|
|
10099
|
-
* `provideQueryDevtools()` after the settings it takes its scope from; nothing else may call it.
|
|
10100
|
-
* @internal
|
|
10101
|
-
*/
|
|
10102
|
-
const initQueryDevtoolsMocks = () => {
|
|
10103
|
-
const stored = readQueryDevtoolsStore(scope$1(), STORAGE_KEY$1);
|
|
10104
|
-
// `query` was added after the first version of the store, so an entry written before it still loads.
|
|
10105
|
-
mocks.set(Array.isArray(stored) ? stored.filter(isMock).map((mock) => ({ ...mock, query: mock.query ?? '' })) : []);
|
|
10106
|
-
const kept = readQueryDevtoolsStore(armedScope(), ARMED_STORAGE_KEY);
|
|
10107
|
-
const library = new Set(mocks().map((mock) => mock.id));
|
|
10108
|
-
// An id the library no longer holds would arm nothing, so it is dropped rather than kept waiting for a
|
|
10109
|
-
// mock that may never be designed again.
|
|
10110
|
-
const inherited = Array.isArray(kept)
|
|
10111
|
-
? kept.filter((id) => typeof id === 'string' && library.has(id))
|
|
10112
|
-
: [];
|
|
10113
|
-
armed.set(new Set(inherited));
|
|
10114
|
-
armedRestored.set(inherited.length > 0);
|
|
10115
|
-
};
|
|
10116
|
-
/**
|
|
10117
|
-
* The path of a request URL relative to the client's `baseUrl` (base path included), without its query
|
|
10118
|
-
* string - what a pattern is matched against. A URL outside `baseUrl`, or no `baseUrl` at all, falls
|
|
10119
|
-
* back to the path without its origin.
|
|
10120
|
-
*/
|
|
10121
|
-
const queryDevtoolsRequestPath = (url, baseUrl) => {
|
|
10122
|
-
const withoutQuery = url.split('?')[0] ?? '';
|
|
10123
|
-
const base = baseUrl?.replace(/\/+$/, '');
|
|
10124
|
-
if (base && withoutQuery.startsWith(base)) {
|
|
10125
|
-
const rest = withoutQuery.slice(base.length);
|
|
10126
|
-
if (!rest)
|
|
10127
|
-
return '/';
|
|
10128
|
-
if (rest.startsWith('/'))
|
|
10129
|
-
return rest;
|
|
10130
|
-
}
|
|
10131
|
-
const schemeEnd = withoutQuery.indexOf('://');
|
|
10132
|
-
if (schemeEnd === -1)
|
|
10133
|
-
return withoutQuery;
|
|
10134
|
-
const pathStart = withoutQuery.indexOf('/', schemeEnd + 3);
|
|
10135
|
-
return pathStart === -1 ? '/' : withoutQuery.slice(pathStart);
|
|
10136
|
-
};
|
|
10137
|
-
const segmentsOf$1 = (path) => path.split('/').filter(Boolean);
|
|
10138
|
-
/** The query string of a request URL, without the `?`. */
|
|
10139
|
-
const queryOf = (url) => url.split('?')[1] ?? '';
|
|
10140
|
-
/**
|
|
10141
|
-
* Whether a request carries every query parameter a mock declares. Extra parameters are ignored - a mock
|
|
10142
|
-
* says what the request must ask for, not everything it may.
|
|
10143
|
-
*/
|
|
10144
|
-
const matchesQueryDevtoolsMockQuery = (declared, url) => {
|
|
10145
|
-
if (!declared)
|
|
10146
|
-
return true;
|
|
10147
|
-
const asked = new URLSearchParams(queryOf(url));
|
|
10148
|
-
return [...new URLSearchParams(declared)].every(([key, value]) => asked.get(key) === value);
|
|
10149
|
-
};
|
|
10150
|
-
/**
|
|
10151
|
-
* Whether a request path is the route a pattern describes. A `:param` segment matches any single
|
|
10152
|
-
* non-empty segment, which is the whole point: a mock is armed for a route, not for the one URL a query
|
|
10153
|
-
* happened to build.
|
|
10154
|
-
*/
|
|
10155
|
-
const matchesQueryDevtoolsMockPattern = (pattern, path) => {
|
|
10156
|
-
const patternSegments = segmentsOf$1(pattern);
|
|
10157
|
-
const pathSegments = segmentsOf$1(path);
|
|
10158
|
-
if (patternSegments.length !== pathSegments.length)
|
|
10159
|
-
return false;
|
|
10160
|
-
return patternSegments.every((segment, index) => segment.startsWith(':') || segment === pathSegments[index]);
|
|
10161
|
-
};
|
|
10162
|
-
/**
|
|
10163
|
-
* Adds a designed mock, or replaces the one already under its id. Arming is untouched: re-designing a
|
|
10164
|
-
* body while it is being served changes what the next request gets.
|
|
10165
|
-
*/
|
|
10166
|
-
const saveQueryDevtoolsMock = (mock) => {
|
|
10167
|
-
mocks.update((current) => {
|
|
10168
|
-
const index = current.findIndex((entry) => entry.id === mock.id);
|
|
10169
|
-
if (index === -1)
|
|
10170
|
-
return [...current, mock];
|
|
10171
|
-
return current.map((entry) => (entry.id === mock.id ? mock : entry));
|
|
10172
|
-
});
|
|
10173
|
-
write();
|
|
10174
|
-
};
|
|
10175
|
-
/** Removes a designed mock, disarming it on the way out. */
|
|
10176
|
-
const deleteQueryDevtoolsMock = (id) => {
|
|
10177
|
-
mocks.update((current) => current.filter((entry) => entry.id !== id));
|
|
10178
|
-
armQueryDevtoolsMock(id, false);
|
|
10179
|
-
write();
|
|
10180
|
-
};
|
|
10181
|
-
/** Starts or stops serving one designed mock. Arming a mock that is not in the library does nothing. */
|
|
10182
|
-
const armQueryDevtoolsMock = (id, isArmed) => {
|
|
10183
|
-
if (isArmed && !mocks().some((mock) => mock.id === id))
|
|
10184
|
-
return;
|
|
10185
|
-
const next = new Set(armed());
|
|
10186
|
-
if (isArmed)
|
|
10187
|
-
next.add(id);
|
|
10188
|
-
else
|
|
10189
|
-
next.delete(id);
|
|
10190
|
-
setArmed(next);
|
|
10514
|
+
// it, so it must not clear what the panel's bar says about where the fault came from.
|
|
10515
|
+
patchFault({ clientName: target.clientName, patch: { failNext: fault.failNext - 1 } });
|
|
10516
|
+
}
|
|
10517
|
+
else if (fault.failRate > 0) {
|
|
10518
|
+
shouldFail = Math.random() * 100 < fault.failRate;
|
|
10519
|
+
}
|
|
10520
|
+
return {
|
|
10521
|
+
latencyMs: fault.latencyMs,
|
|
10522
|
+
status: shouldFail ? fault.status : null,
|
|
10523
|
+
};
|
|
10191
10524
|
};
|
|
10192
|
-
|
|
10193
|
-
const armAllQueryDevtoolsMocks = () => setArmed(new Set(mocks().map((mock) => mock.id)));
|
|
10194
|
-
/** Stops serving every mock, leaving the library alone - the armed bar's one-click way out. */
|
|
10195
|
-
const clearQueryDevtoolsArmedMocks = () => setArmed(new Set());
|
|
10525
|
+
|
|
10196
10526
|
/**
|
|
10197
|
-
*
|
|
10198
|
-
*
|
|
10199
|
-
*
|
|
10527
|
+
* Wraps the committed value of the query form `formId` in a signal that reports every read to the
|
|
10528
|
+
* devtools.
|
|
10529
|
+
* @internal
|
|
10200
10530
|
*/
|
|
10201
|
-
const
|
|
10202
|
-
|
|
10203
|
-
|
|
10204
|
-
//
|
|
10205
|
-
|
|
10206
|
-
|
|
10207
|
-
|
|
10208
|
-
|
|
10209
|
-
|
|
10210
|
-
|
|
10211
|
-
|
|
10212
|
-
armed.set(new Set());
|
|
10213
|
-
armedRestored.set(false);
|
|
10214
|
-
clearQueryDevtoolsStore(STORAGE_KEY$1);
|
|
10215
|
-
clearQueryDevtoolsStore(ARMED_STORAGE_KEY);
|
|
10531
|
+
const noteQueryFormReads = (formId, source) => {
|
|
10532
|
+
const memoized = computed(() => source(), /* @ts-ignore */
|
|
10533
|
+
...(ngDevMode ? [{ debugName: "memoized" }] : /* istanbul ignore next */ []));
|
|
10534
|
+
// The read must be noted per read, not per recomputation of the value: the second query to build
|
|
10535
|
+
// args reads a memoized value, and a note that only fires on a recomputation never reaches its
|
|
10536
|
+
// collection window. Carrying the memo's signal node over keeps this function a real `Signal`.
|
|
10537
|
+
const read = () => {
|
|
10538
|
+
noteQueryFormRead(formId);
|
|
10539
|
+
return memoized();
|
|
10540
|
+
};
|
|
10541
|
+
return Object.assign(read, { [SIGNAL]: memoized[SIGNAL] });
|
|
10216
10542
|
};
|
|
10217
|
-
const declaredParamCount = (mock) => (mock.query ? [...new URLSearchParams(mock.query)].length : 0);
|
|
10218
10543
|
/**
|
|
10219
|
-
*
|
|
10220
|
-
*
|
|
10221
|
-
*
|
|
10222
|
-
* Installed as the mock resolver by `provideQueryDevtools()`; nothing else may call it.
|
|
10544
|
+
* Records which query forms feed one query's args, so the devtools can answer "which query does this
|
|
10545
|
+
* form drive?" from the reads themselves rather than from a naming convention.
|
|
10223
10546
|
* @internal
|
|
10224
10547
|
*/
|
|
10225
|
-
const
|
|
10226
|
-
const ids =
|
|
10227
|
-
|
|
10228
|
-
|
|
10229
|
-
|
|
10230
|
-
|
|
10231
|
-
|
|
10232
|
-
|
|
10233
|
-
|
|
10234
|
-
|
|
10235
|
-
|
|
10236
|
-
|
|
10237
|
-
|
|
10238
|
-
|
|
10239
|
-
|
|
10240
|
-
|
|
10241
|
-
|
|
10548
|
+
const createQueryDevtoolsFormLinks = () => {
|
|
10549
|
+
const ids = signal([], /* @ts-ignore */
|
|
10550
|
+
...(ngDevMode ? [{ debugName: "ids" }] : /* istanbul ignore next */ []));
|
|
10551
|
+
return {
|
|
10552
|
+
ids: ids.asReadonly(),
|
|
10553
|
+
track: (build) => {
|
|
10554
|
+
const collected = collectQueryFormLinks(build);
|
|
10555
|
+
// Args are built inside a reactive computation, which may not write to a signal - `untracked`
|
|
10556
|
+
// lifts that restriction, and skipping the write unless something is new keeps the panel from
|
|
10557
|
+
// being notified on every evaluation.
|
|
10558
|
+
untracked(() => {
|
|
10559
|
+
const known = ids();
|
|
10560
|
+
const added = collected.ids.filter((id) => !known.includes(id));
|
|
10561
|
+
if (added.length)
|
|
10562
|
+
ids.set([...known, ...added]);
|
|
10563
|
+
});
|
|
10564
|
+
return collected.value;
|
|
10565
|
+
},
|
|
10566
|
+
};
|
|
10242
10567
|
};
|
|
10243
10568
|
|
|
10244
10569
|
const isPathAtOrBelow = (path, root) => path.length >= root.length && root.every((step, index) => step === path[index]);
|
|
@@ -10475,19 +10800,19 @@ const resizePagination = (value, shape, mode, amount) => {
|
|
|
10475
10800
|
}
|
|
10476
10801
|
return result;
|
|
10477
10802
|
};
|
|
10478
|
-
const STRING_PRESETS = {
|
|
10803
|
+
const STRING_PRESETS$1 = {
|
|
10479
10804
|
short: 'Ab',
|
|
10480
10805
|
long: /* @__PURE__ */ 'Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt. '.repeat(4),
|
|
10481
10806
|
longWord: 'Rindfleischetikettierungsüberwachungsaufgabenübertragungsgesetz',
|
|
10482
10807
|
unicode: 'مرحبا بالعالم 👋 日本語テスト 🎉 Ñoño',
|
|
10483
10808
|
};
|
|
10484
|
-
const stringPresetValue = (preset, custom) => custom ?? (preset === 'custom' ? '' : STRING_PRESETS[preset]);
|
|
10485
|
-
const NUMBER_PRESETS = {
|
|
10809
|
+
const stringPresetValue = (preset, custom) => custom ?? (preset === 'custom' ? '' : STRING_PRESETS$1[preset]);
|
|
10810
|
+
const NUMBER_PRESETS$1 = {
|
|
10486
10811
|
zero: 0,
|
|
10487
10812
|
negative: -1,
|
|
10488
10813
|
huge: Number.MAX_SAFE_INTEGER,
|
|
10489
10814
|
};
|
|
10490
|
-
const numberPresetValue = (preset, custom) => custom ?? (preset === 'custom' ? 0 : NUMBER_PRESETS[preset]);
|
|
10815
|
+
const numberPresetValue = (preset, custom) => custom ?? (preset === 'custom' ? 0 : NUMBER_PRESETS$1[preset]);
|
|
10491
10816
|
const pickRandom = (values) => values[Math.floor(Math.random() * values.length)];
|
|
10492
10817
|
const randomIntBetween = (min, max) => min + Math.floor(Math.random() * (max - min + 1));
|
|
10493
10818
|
const randomHex = (length) => {
|
|
@@ -10703,6 +11028,30 @@ const OP_TYPES = /* @__PURE__ */ new Set([
|
|
|
10703
11028
|
'reset',
|
|
10704
11029
|
]);
|
|
10705
11030
|
const isJsonPath = (value) => Array.isArray(value) && value.every((step) => typeof step === 'string' || typeof step === 'number');
|
|
11031
|
+
const STRING_PRESETS = ['short', 'long', 'longWord', 'unicode', 'custom'];
|
|
11032
|
+
const NUMBER_PRESETS = ['zero', 'negative', 'huge', 'custom'];
|
|
11033
|
+
const DATE_PRESETS = ['now', 'plusDay', 'minusDay', 'farFuture', 'farPast', 'invalid'];
|
|
11034
|
+
const isIndex = (value) => Number.isInteger(value) && value >= 0;
|
|
11035
|
+
const hasReplayableFields = (op) => {
|
|
11036
|
+
switch (op['type']) {
|
|
11037
|
+
case 'set':
|
|
11038
|
+
return 'value' in op;
|
|
11039
|
+
case 'stringPreset':
|
|
11040
|
+
return STRING_PRESETS.includes(op['preset']) && (op['custom'] === undefined || typeof op['custom'] === 'string');
|
|
11041
|
+
case 'numberPreset':
|
|
11042
|
+
return NUMBER_PRESETS.includes(op['preset']) && (op['custom'] === undefined || Number.isFinite(op['custom']));
|
|
11043
|
+
case 'datePreset':
|
|
11044
|
+
return DATE_PRESETS.includes(op['preset']);
|
|
11045
|
+
case 'duplicateArrayItem':
|
|
11046
|
+
return isIndex(op['index']);
|
|
11047
|
+
case 'pasteArrayItem':
|
|
11048
|
+
return 'value' in op && (op['index'] === undefined || isIndex(op['index']));
|
|
11049
|
+
case 'paginationResize':
|
|
11050
|
+
return (op['mode'] === 'shrink' || op['mode'] === 'extend') && isIndex(op['amount']);
|
|
11051
|
+
default:
|
|
11052
|
+
return true;
|
|
11053
|
+
}
|
|
11054
|
+
};
|
|
10706
11055
|
/**
|
|
10707
11056
|
* Whether a value parsed from outside - a paste, a stored set - is an op this build can replay.
|
|
10708
11057
|
* @internal
|
|
@@ -10711,7 +11060,7 @@ const isOverrideOp = (value) => {
|
|
|
10711
11060
|
if (!value || typeof value !== 'object' || Array.isArray(value))
|
|
10712
11061
|
return false;
|
|
10713
11062
|
const op = value;
|
|
10714
|
-
return typeof op
|
|
11063
|
+
return (typeof op['type'] === 'string' && OP_TYPES.has(op['type']) && isJsonPath(op['path']) && hasReplayableFields(op));
|
|
10715
11064
|
};
|
|
10716
11065
|
/**
|
|
10717
11066
|
* Serializes the ops armed on one query into clipboard text. `reset` never reaches a recorder's list, so
|
|
@@ -10777,12 +11126,12 @@ const armQueryDevtoolsOverrideTransfer = (recorder, ops) => {
|
|
|
10777
11126
|
* debugging the wrong thing. `local` is offered in Settings for the dev who has a reason, and the
|
|
10778
11127
|
* restored-overrides bar names the scope whatever it is.
|
|
10779
11128
|
*/
|
|
10780
|
-
const STORAGE_KEY = 'ethlete:query:devtools:overrides:v1';
|
|
11129
|
+
const STORAGE_KEY$1 = 'ethlete:query:devtools:overrides:v1';
|
|
10781
11130
|
const restored = /* @__PURE__ */ signal([], /* @ts-ignore */
|
|
10782
11131
|
...(ngDevMode ? [{ debugName: "restored" }] : /* istanbul ignore next */ []));
|
|
10783
11132
|
const restoredScope = /* @__PURE__ */ signal(null, /* @ts-ignore */
|
|
10784
11133
|
...(ngDevMode ? [{ debugName: "restoredScope" }] : /* istanbul ignore next */ []));
|
|
10785
|
-
const scope = () => queryDevtoolsSettings().overrides;
|
|
11134
|
+
const scope$1 = () => queryDevtoolsSettings().overrides;
|
|
10786
11135
|
/** Where "Keep across reloads" puts them back when it is switched on again. */
|
|
10787
11136
|
let lastPersistentScope = 'session';
|
|
10788
11137
|
/** Every recorder that currently holds at least one op, so switching persistence on can capture them. */
|
|
@@ -10793,7 +11142,7 @@ const carriedOver = /* @__PURE__ */ new Map();
|
|
|
10793
11142
|
const replayed = /* @__PURE__ */ new Set();
|
|
10794
11143
|
let ops = {};
|
|
10795
11144
|
const readStore = () => {
|
|
10796
|
-
const parsed = readQueryDevtoolsStore(scope(), STORAGE_KEY);
|
|
11145
|
+
const parsed = readQueryDevtoolsStore(scope$1(), STORAGE_KEY$1);
|
|
10797
11146
|
const stored = parsed?.ops;
|
|
10798
11147
|
const usable = !!stored && typeof stored === 'object' && !Array.isArray(stored);
|
|
10799
11148
|
const valid = {};
|
|
@@ -10805,11 +11154,11 @@ const readStore = () => {
|
|
|
10805
11154
|
return { enabled: parsed?.enabled === true, ops: valid };
|
|
10806
11155
|
};
|
|
10807
11156
|
const writeStore = () => {
|
|
10808
|
-
if (scope() === 'none') {
|
|
10809
|
-
clearQueryDevtoolsStore(STORAGE_KEY);
|
|
11157
|
+
if (scope$1() === 'none') {
|
|
11158
|
+
clearQueryDevtoolsStore(STORAGE_KEY$1);
|
|
10810
11159
|
return;
|
|
10811
11160
|
}
|
|
10812
|
-
writeQueryDevtoolsStore(scope(), STORAGE_KEY, { enabled: true, ops });
|
|
11161
|
+
writeQueryDevtoolsStore(scope$1(), STORAGE_KEY$1, { enabled: true, ops });
|
|
10813
11162
|
};
|
|
10814
11163
|
const publishRestored = () => restored.set([...carriedOver].map(([id, count]) => ({ id, count, armed: replayed.has(id) })));
|
|
10815
11164
|
const forgetCarriedOver = () => {
|
|
@@ -10842,7 +11191,7 @@ const initQueryDevtoolsOverridePersistence = () => {
|
|
|
10842
11191
|
if (list.length)
|
|
10843
11192
|
carriedOver.set(id, list.length);
|
|
10844
11193
|
}
|
|
10845
|
-
const current = scope();
|
|
11194
|
+
const current = scope$1();
|
|
10846
11195
|
if (current !== 'none')
|
|
10847
11196
|
lastPersistentScope = current;
|
|
10848
11197
|
restoredScope.set(carriedOver.size ? current : null);
|
|
@@ -10852,7 +11201,7 @@ const initQueryDevtoolsOverridePersistence = () => {
|
|
|
10852
11201
|
* Whether armed response overrides are stored and replayed on the next page load - that is, whether
|
|
10853
11202
|
* their scope is anything other than `none`. Off by default.
|
|
10854
11203
|
*/
|
|
10855
|
-
const queryDevtoolsOverridePersistence = /* @__PURE__ */ computed(() => scope() !== 'none', /* @ts-ignore */
|
|
11204
|
+
const queryDevtoolsOverridePersistence = /* @__PURE__ */ computed(() => scope$1() !== 'none', /* @ts-ignore */
|
|
10856
11205
|
...(ngDevMode ? [{ debugName: "queryDevtoolsOverridePersistence" }] : /* istanbul ignore next */ []));
|
|
10857
11206
|
/**
|
|
10858
11207
|
* Where armed response overrides are kept. Switching to a scope that keeps them captures whatever is
|
|
@@ -10861,11 +11210,11 @@ const queryDevtoolsOverridePersistence = /* @__PURE__ */ computed(() => scope()
|
|
|
10861
11210
|
* op exactly where it is for the rest of this page.
|
|
10862
11211
|
*/
|
|
10863
11212
|
const setQueryDevtoolsOverridesScope = (next) => {
|
|
10864
|
-
const previous = scope();
|
|
11213
|
+
const previous = scope$1();
|
|
10865
11214
|
if (next === previous)
|
|
10866
11215
|
return;
|
|
10867
11216
|
// Off both stores first: whichever the previous scope was, its copy must not outlive the change.
|
|
10868
|
-
clearQueryDevtoolsStore(STORAGE_KEY);
|
|
11217
|
+
clearQueryDevtoolsStore(STORAGE_KEY$1);
|
|
10869
11218
|
setQueryDevtoolsSettings({ overrides: next });
|
|
10870
11219
|
if (next === 'none') {
|
|
10871
11220
|
ops = {};
|
|
@@ -10884,106 +11233,309 @@ const setQueryDevtoolsOverridesScope = (next) => {
|
|
|
10884
11233
|
writeStore();
|
|
10885
11234
|
};
|
|
10886
11235
|
/**
|
|
10887
|
-
* Turns persistence on or off - the panel's "Keep across reloads" toggle. On restores the last scope
|
|
10888
|
-
* that kept them (`session` unless Settings picked `local`).
|
|
11236
|
+
* Turns persistence on or off - the panel's "Keep across reloads" toggle. On restores the last scope
|
|
11237
|
+
* that kept them (`session` unless Settings picked `local`).
|
|
11238
|
+
*/
|
|
11239
|
+
const setQueryDevtoolsOverridePersistence = (value) => {
|
|
11240
|
+
setQueryDevtoolsOverridesScope(value ? lastPersistentScope : 'none');
|
|
11241
|
+
};
|
|
11242
|
+
/**
|
|
11243
|
+
* Empties the override store in both browser stores and disarms everything a reload brought back,
|
|
11244
|
+
* without changing where overrides are kept - the panel's "Reset devtools".
|
|
11245
|
+
*/
|
|
11246
|
+
const clearQueryDevtoolsOverrideStore = () => {
|
|
11247
|
+
clearRestoredQueryDevtoolsOverrides();
|
|
11248
|
+
ops = {};
|
|
11249
|
+
clearQueryDevtoolsStore(STORAGE_KEY$1);
|
|
11250
|
+
};
|
|
11251
|
+
/**
|
|
11252
|
+
* The ops this page load inherited from the previous one, and for each id whether a live query has taken
|
|
11253
|
+
* it. Overrides that outlive the page that armed them have to announce themselves, because an app being
|
|
11254
|
+
* lied to looks exactly like an app that is broken.
|
|
11255
|
+
*/
|
|
11256
|
+
const restoredQueryDevtoolsOverrides =
|
|
11257
|
+
/* @__PURE__ */ restored.asReadonly();
|
|
11258
|
+
/**
|
|
11259
|
+
* The scope those ops came back from, or `null` when this page inherited none. `local` is the one worth
|
|
11260
|
+
* naming in the banner: it means they outlived closing the tab, not just a reload.
|
|
11261
|
+
*/
|
|
11262
|
+
const queryDevtoolsRestoredOverridesScope =
|
|
11263
|
+
/* @__PURE__ */ restoredScope.asReadonly();
|
|
11264
|
+
/** Disarms every op this page load inherited and empties the store - the banner's one-click way out. */
|
|
11265
|
+
const clearRestoredQueryDevtoolsOverrides = () => {
|
|
11266
|
+
for (const id of replayed) {
|
|
11267
|
+
armedRecorders.get(id)?.clearAll();
|
|
11268
|
+
armedRecorders.delete(id);
|
|
11269
|
+
}
|
|
11270
|
+
for (const id of carriedOver.keys())
|
|
11271
|
+
writeOps(id, []);
|
|
11272
|
+
forgetCarriedOver();
|
|
11273
|
+
};
|
|
11274
|
+
/**
|
|
11275
|
+
* Forgets a destroyed query's recorder. Ops already persisted stay in the store on purpose - they are
|
|
11276
|
+
* keyed by an id the next page load re-derives, which is what "Keep across reloads" replays them from.
|
|
11277
|
+
* @internal
|
|
11278
|
+
*/
|
|
11279
|
+
const releaseQueryDevtoolsOverridePersistence = (id) => {
|
|
11280
|
+
armedRecorders.delete(id);
|
|
11281
|
+
if (scope$1() !== 'none')
|
|
11282
|
+
return;
|
|
11283
|
+
const { [id]: _dropped, ...rest } = ops;
|
|
11284
|
+
ops = rest;
|
|
11285
|
+
};
|
|
11286
|
+
/**
|
|
11287
|
+
* Wraps a query's overrides recorder so its armed ops are stored and replayed. Replay happens here, at
|
|
11288
|
+
* registration, rather than in the panel - a query can run long before the panel is ever opened.
|
|
11289
|
+
*
|
|
11290
|
+
* Ids are reload-deterministic (descriptor + per-descriptor counter), so the same queries created in the
|
|
11291
|
+
* same order re-arm correctly; an id the store holds that nobody claims is reported by
|
|
11292
|
+
* {@link restoredQueryDevtoolsOverrides} rather than guessed at.
|
|
11293
|
+
* @internal
|
|
11294
|
+
*/
|
|
11295
|
+
const withQueryDevtoolsOverridePersistence = (id, recorder) => {
|
|
11296
|
+
const inherited = ops[id];
|
|
11297
|
+
if (scope$1() !== 'none' && inherited?.length) {
|
|
11298
|
+
for (const op of inherited)
|
|
11299
|
+
recorder.arm(op);
|
|
11300
|
+
armedRecorders.set(id, recorder);
|
|
11301
|
+
replayed.add(id);
|
|
11302
|
+
publishRestored();
|
|
11303
|
+
}
|
|
11304
|
+
const sync = () => {
|
|
11305
|
+
const list = recorder.list().map((entry) => entry.op);
|
|
11306
|
+
if (list.length)
|
|
11307
|
+
armedRecorders.set(id, recorder);
|
|
11308
|
+
else
|
|
11309
|
+
armedRecorders.delete(id);
|
|
11310
|
+
if (scope$1() !== 'none')
|
|
11311
|
+
writeOps(id, list);
|
|
11312
|
+
};
|
|
11313
|
+
return {
|
|
11314
|
+
...recorder,
|
|
11315
|
+
arm: (op) => {
|
|
11316
|
+
recorder.arm(op);
|
|
11317
|
+
sync();
|
|
11318
|
+
},
|
|
11319
|
+
clear: (opId) => {
|
|
11320
|
+
recorder.clear(opId);
|
|
11321
|
+
sync();
|
|
11322
|
+
},
|
|
11323
|
+
clearAll: () => {
|
|
11324
|
+
recorder.clearAll();
|
|
11325
|
+
if (replayed.delete(id))
|
|
11326
|
+
publishRestored();
|
|
11327
|
+
sync();
|
|
11328
|
+
},
|
|
11329
|
+
};
|
|
11330
|
+
};
|
|
11331
|
+
|
|
11332
|
+
/**
|
|
11333
|
+
* The designed library. Which of them is **armed** is a separate store under a separate scope - see
|
|
11334
|
+
* {@link queryDevtoolsArmedMocks}.
|
|
11335
|
+
*/
|
|
11336
|
+
const STORAGE_KEY = 'ethlete:query:devtools:mocks:v1';
|
|
11337
|
+
const ARMED_STORAGE_KEY = 'ethlete:query:devtools:mocks:armed:v1';
|
|
11338
|
+
const mocks = /* @__PURE__ */ signal([], /* @ts-ignore */
|
|
11339
|
+
...(ngDevMode ? [{ debugName: "mocks" }] : /* istanbul ignore next */ []));
|
|
11340
|
+
const armed = /* @__PURE__ */ signal(/* @__PURE__ */ new Set(), /* @ts-ignore */
|
|
11341
|
+
...(ngDevMode ? [{ debugName: "armed" }] : /* istanbul ignore next */ []));
|
|
11342
|
+
const armedRestored = /* @__PURE__ */ signal(false, /* @ts-ignore */
|
|
11343
|
+
...(ngDevMode ? [{ debugName: "armedRestored" }] : /* istanbul ignore next */ []));
|
|
11344
|
+
const scope = () => queryDevtoolsSettings().mocks;
|
|
11345
|
+
const armedScope = () => queryDevtoolsSettings().armedMocks;
|
|
11346
|
+
/** The designed mocks, armed or not. Persisted, because losing an hour of authoring is unacceptable. */
|
|
11347
|
+
const queryDevtoolsMocks = /* @__PURE__ */ mocks.asReadonly();
|
|
11348
|
+
/**
|
|
11349
|
+
* The ids currently being served. Not kept across page loads unless the `armedMocks` setting asks for it -
|
|
11350
|
+
* an app that silently serves designed data tomorrow morning is worse than losing which mocks were armed.
|
|
11351
|
+
*/
|
|
11352
|
+
const queryDevtoolsArmedMocks = /* @__PURE__ */ armed.asReadonly();
|
|
11353
|
+
/**
|
|
11354
|
+
* Whether what is armed right now came back from a previous page load rather than being armed by hand.
|
|
11355
|
+
* The panel's armed bar says so, because mocks nobody remembers arming look exactly like a broken API.
|
|
11356
|
+
*/
|
|
11357
|
+
const queryDevtoolsArmedMocksRestored = /* @__PURE__ */ armedRestored.asReadonly();
|
|
11358
|
+
/**
|
|
11359
|
+
* The identity of a designed mock: one per client, method, route **and** declared query. The query is part
|
|
11360
|
+
* of it so `/posts?page=1` and `/posts?page=2` can hold different bodies.
|
|
11361
|
+
*/
|
|
11362
|
+
const queryDevtoolsMockId = (parts) => `${parts.clientName}|${parts.method}|${parts.pattern}${parts.query ? `?${parts.query}` : ''}`;
|
|
11363
|
+
const write = () => {
|
|
11364
|
+
if (scope() === 'none') {
|
|
11365
|
+
clearQueryDevtoolsStore(STORAGE_KEY);
|
|
11366
|
+
return;
|
|
11367
|
+
}
|
|
11368
|
+
writeQueryDevtoolsStore(scope(), STORAGE_KEY, mocks());
|
|
11369
|
+
};
|
|
11370
|
+
const writeArmed = () => {
|
|
11371
|
+
if (armedScope() === 'none') {
|
|
11372
|
+
clearQueryDevtoolsStore(ARMED_STORAGE_KEY);
|
|
11373
|
+
return;
|
|
11374
|
+
}
|
|
11375
|
+
writeQueryDevtoolsStore(armedScope(), ARMED_STORAGE_KEY, [...armed()]);
|
|
11376
|
+
};
|
|
11377
|
+
/** Every change made by hand goes through here, so none of them can leave the store or the bar behind. */
|
|
11378
|
+
const setArmed = (next) => {
|
|
11379
|
+
armed.set(next);
|
|
11380
|
+
armedRestored.set(false);
|
|
11381
|
+
writeArmed();
|
|
11382
|
+
};
|
|
11383
|
+
const isMock = (value) => {
|
|
11384
|
+
const mock = value;
|
|
11385
|
+
return (!!mock &&
|
|
11386
|
+
typeof mock.id === 'string' &&
|
|
11387
|
+
typeof mock.clientName === 'string' &&
|
|
11388
|
+
typeof mock.method === 'string' &&
|
|
11389
|
+
typeof mock.pattern === 'string' &&
|
|
11390
|
+
typeof mock.status === 'number');
|
|
11391
|
+
};
|
|
11392
|
+
/**
|
|
11393
|
+
* Reads the designed library, so a mock can be armed without authoring it again. Called by
|
|
11394
|
+
* `provideQueryDevtools()` after the settings it takes its scope from; nothing else may call it.
|
|
11395
|
+
* @internal
|
|
10889
11396
|
*/
|
|
10890
|
-
const
|
|
10891
|
-
|
|
11397
|
+
const initQueryDevtoolsMocks = () => {
|
|
11398
|
+
const stored = readQueryDevtoolsStore(scope(), STORAGE_KEY);
|
|
11399
|
+
// `query` was added after the first version of the store, so an entry written before it still loads.
|
|
11400
|
+
mocks.set(Array.isArray(stored) ? stored.filter(isMock).map((mock) => ({ ...mock, query: mock.query ?? '' })) : []);
|
|
11401
|
+
const kept = readQueryDevtoolsStore(armedScope(), ARMED_STORAGE_KEY);
|
|
11402
|
+
const library = new Set(mocks().map((mock) => mock.id));
|
|
11403
|
+
// An id the library no longer holds would arm nothing, so it is dropped rather than kept waiting for a
|
|
11404
|
+
// mock that may never be designed again.
|
|
11405
|
+
const inherited = Array.isArray(kept)
|
|
11406
|
+
? kept.filter((id) => typeof id === 'string' && library.has(id))
|
|
11407
|
+
: [];
|
|
11408
|
+
armed.set(new Set(inherited));
|
|
11409
|
+
armedRestored.set(inherited.length > 0);
|
|
10892
11410
|
};
|
|
10893
11411
|
/**
|
|
10894
|
-
*
|
|
10895
|
-
*
|
|
11412
|
+
* The path of a request URL relative to the client's `baseUrl` (base path included), without its query
|
|
11413
|
+
* string - what a pattern is matched against. A URL outside `baseUrl`, or no `baseUrl` at all, falls
|
|
11414
|
+
* back to the path without its origin.
|
|
10896
11415
|
*/
|
|
10897
|
-
const
|
|
10898
|
-
|
|
10899
|
-
|
|
10900
|
-
|
|
11416
|
+
const queryDevtoolsRequestPath = (url, baseUrl) => {
|
|
11417
|
+
const withoutQuery = url.split('?')[0] ?? '';
|
|
11418
|
+
const base = baseUrl?.replace(/\/+$/, '');
|
|
11419
|
+
if (base && withoutQuery.startsWith(base)) {
|
|
11420
|
+
const rest = withoutQuery.slice(base.length);
|
|
11421
|
+
if (!rest)
|
|
11422
|
+
return '/';
|
|
11423
|
+
if (rest.startsWith('/'))
|
|
11424
|
+
return rest;
|
|
11425
|
+
}
|
|
11426
|
+
const schemeEnd = withoutQuery.indexOf('://');
|
|
11427
|
+
if (schemeEnd === -1)
|
|
11428
|
+
return withoutQuery;
|
|
11429
|
+
const pathStart = withoutQuery.indexOf('/', schemeEnd + 3);
|
|
11430
|
+
return pathStart === -1 ? '/' : withoutQuery.slice(pathStart);
|
|
10901
11431
|
};
|
|
11432
|
+
const segmentsOf$1 = (path) => path.split('/').filter(Boolean);
|
|
11433
|
+
/** The query string of a request URL, without the `?`. */
|
|
11434
|
+
const queryOf = (url) => url.split('?')[1] ?? '';
|
|
10902
11435
|
/**
|
|
10903
|
-
*
|
|
10904
|
-
*
|
|
10905
|
-
* lied to looks exactly like an app that is broken.
|
|
11436
|
+
* Whether a request carries every query parameter a mock declares. Extra parameters are ignored - a mock
|
|
11437
|
+
* says what the request must ask for, not everything it may.
|
|
10906
11438
|
*/
|
|
10907
|
-
const
|
|
10908
|
-
|
|
11439
|
+
const matchesQueryDevtoolsMockQuery = (declared, url) => {
|
|
11440
|
+
if (!declared)
|
|
11441
|
+
return true;
|
|
11442
|
+
const asked = new URLSearchParams(queryOf(url));
|
|
11443
|
+
return [...new URLSearchParams(declared)].every(([key, value]) => asked.getAll(key).includes(value));
|
|
11444
|
+
};
|
|
10909
11445
|
/**
|
|
10910
|
-
*
|
|
10911
|
-
*
|
|
11446
|
+
* Whether a request path is the route a pattern describes. A `:param` segment matches any single
|
|
11447
|
+
* non-empty segment, which is the whole point: a mock is armed for a route, not for the one URL a query
|
|
11448
|
+
* happened to build.
|
|
10912
11449
|
*/
|
|
10913
|
-
const
|
|
10914
|
-
|
|
10915
|
-
|
|
10916
|
-
|
|
10917
|
-
|
|
10918
|
-
|
|
10919
|
-
armedRecorders.delete(id);
|
|
10920
|
-
}
|
|
10921
|
-
for (const id of carriedOver.keys())
|
|
10922
|
-
writeOps(id, []);
|
|
10923
|
-
forgetCarriedOver();
|
|
11450
|
+
const matchesQueryDevtoolsMockPattern = (pattern, path) => {
|
|
11451
|
+
const patternSegments = segmentsOf$1(pattern);
|
|
11452
|
+
const pathSegments = segmentsOf$1(path);
|
|
11453
|
+
if (patternSegments.length !== pathSegments.length)
|
|
11454
|
+
return false;
|
|
11455
|
+
return patternSegments.every((segment, index) => segment.startsWith(':') || segment === pathSegments[index]);
|
|
10924
11456
|
};
|
|
10925
11457
|
/**
|
|
10926
|
-
*
|
|
10927
|
-
*
|
|
10928
|
-
* @internal
|
|
11458
|
+
* Adds a designed mock, or replaces the one already under its id. Arming is untouched: re-designing a
|
|
11459
|
+
* body while it is being served changes what the next request gets.
|
|
10929
11460
|
*/
|
|
10930
|
-
const
|
|
10931
|
-
|
|
10932
|
-
|
|
11461
|
+
const saveQueryDevtoolsMock = (mock) => {
|
|
11462
|
+
mocks.update((current) => {
|
|
11463
|
+
const index = current.findIndex((entry) => entry.id === mock.id);
|
|
11464
|
+
if (index === -1)
|
|
11465
|
+
return [...current, mock];
|
|
11466
|
+
return current.map((entry) => (entry.id === mock.id ? mock : entry));
|
|
11467
|
+
});
|
|
11468
|
+
write();
|
|
11469
|
+
};
|
|
11470
|
+
/** Removes a designed mock, disarming it on the way out. */
|
|
11471
|
+
const deleteQueryDevtoolsMock = (id) => {
|
|
11472
|
+
mocks.update((current) => current.filter((entry) => entry.id !== id));
|
|
11473
|
+
armQueryDevtoolsMock(id, false);
|
|
11474
|
+
write();
|
|
11475
|
+
};
|
|
11476
|
+
/** Starts or stops serving one designed mock. Arming a mock that is not in the library does nothing. */
|
|
11477
|
+
const armQueryDevtoolsMock = (id, isArmed) => {
|
|
11478
|
+
if (isArmed && !mocks().some((mock) => mock.id === id))
|
|
10933
11479
|
return;
|
|
10934
|
-
const
|
|
10935
|
-
|
|
11480
|
+
const next = new Set(armed());
|
|
11481
|
+
if (isArmed)
|
|
11482
|
+
next.add(id);
|
|
11483
|
+
else
|
|
11484
|
+
next.delete(id);
|
|
11485
|
+
setArmed(next);
|
|
10936
11486
|
};
|
|
11487
|
+
/** Starts serving every mock in the library - the counterpart of {@link clearQueryDevtoolsArmedMocks}. */
|
|
11488
|
+
const armAllQueryDevtoolsMocks = () => setArmed(new Set(mocks().map((mock) => mock.id)));
|
|
11489
|
+
/** Stops serving every mock, leaving the library alone - the armed bar's one-click way out. */
|
|
11490
|
+
const clearQueryDevtoolsArmedMocks = () => setArmed(new Set());
|
|
10937
11491
|
/**
|
|
10938
|
-
*
|
|
10939
|
-
*
|
|
11492
|
+
* Where the armed set is kept. Switching to a scope that keeps it stores whatever is armed right now, so
|
|
11493
|
+
* the choice reads as "keep these"; switching to `none` empties the store and leaves this page serving
|
|
11494
|
+
* exactly what it was already serving.
|
|
11495
|
+
*/
|
|
11496
|
+
const setQueryDevtoolsArmedMocksScope = (next) => {
|
|
11497
|
+
if (next === armedScope())
|
|
11498
|
+
return;
|
|
11499
|
+
// Off both stores first: whichever the previous scope was, its copy must not outlive the change.
|
|
11500
|
+
clearQueryDevtoolsStore(ARMED_STORAGE_KEY);
|
|
11501
|
+
setQueryDevtoolsSettings({ armedMocks: next });
|
|
11502
|
+
writeArmed();
|
|
11503
|
+
};
|
|
11504
|
+
/** Empties the designed library and disarms everything - the panel's "Reset devtools". */
|
|
11505
|
+
const clearQueryDevtoolsMockStore = () => {
|
|
11506
|
+
mocks.set([]);
|
|
11507
|
+
armed.set(new Set());
|
|
11508
|
+
armedRestored.set(false);
|
|
11509
|
+
clearQueryDevtoolsStore(STORAGE_KEY);
|
|
11510
|
+
clearQueryDevtoolsStore(ARMED_STORAGE_KEY);
|
|
11511
|
+
};
|
|
11512
|
+
const declaredParamCount = (mock) => (mock.query ? [...new URLSearchParams(mock.query)].length : 0);
|
|
11513
|
+
/**
|
|
11514
|
+
* The mock to serve one upcoming attempt, or `null` to let it reach the network. The armed mock declaring
|
|
11515
|
+
* the most query parameters wins, so a special case armed on top of a general one is what answers.
|
|
10940
11516
|
*
|
|
10941
|
-
*
|
|
10942
|
-
* same order re-arm correctly; an id the store holds that nobody claims is reported by
|
|
10943
|
-
* {@link restoredQueryDevtoolsOverrides} rather than guessed at.
|
|
11517
|
+
* Installed as the mock resolver by `provideQueryDevtools()`; nothing else may call it.
|
|
10944
11518
|
* @internal
|
|
10945
11519
|
*/
|
|
10946
|
-
const
|
|
10947
|
-
const
|
|
10948
|
-
if (
|
|
10949
|
-
|
|
10950
|
-
|
|
10951
|
-
|
|
10952
|
-
|
|
10953
|
-
|
|
10954
|
-
|
|
10955
|
-
|
|
10956
|
-
|
|
10957
|
-
|
|
10958
|
-
|
|
10959
|
-
|
|
10960
|
-
|
|
10961
|
-
|
|
10962
|
-
|
|
10963
|
-
};
|
|
10964
|
-
return {
|
|
10965
|
-
...recorder,
|
|
10966
|
-
arm: (op) => {
|
|
10967
|
-
recorder.arm(op);
|
|
10968
|
-
sync();
|
|
10969
|
-
},
|
|
10970
|
-
clear: (opId) => {
|
|
10971
|
-
recorder.clear(opId);
|
|
10972
|
-
sync();
|
|
10973
|
-
},
|
|
10974
|
-
clearAll: () => {
|
|
10975
|
-
recorder.clearAll();
|
|
10976
|
-
if (replayed.delete(id))
|
|
10977
|
-
publishRestored();
|
|
10978
|
-
sync();
|
|
10979
|
-
},
|
|
10980
|
-
};
|
|
11520
|
+
const resolveQueryDevtoolsMockForAttempt = (target) => {
|
|
11521
|
+
const ids = armed();
|
|
11522
|
+
if (!ids.size)
|
|
11523
|
+
return null;
|
|
11524
|
+
const path = queryDevtoolsRequestPath(target.url, target.baseUrl);
|
|
11525
|
+
const mock = mocks()
|
|
11526
|
+
.filter((candidate) => ids.has(candidate.id) &&
|
|
11527
|
+
candidate.clientName === target.clientName &&
|
|
11528
|
+
candidate.method === target.method &&
|
|
11529
|
+
matchesQueryDevtoolsMockPattern(candidate.pattern, path) &&
|
|
11530
|
+
matchesQueryDevtoolsMockQuery(candidate.query, target.url))
|
|
11531
|
+
// The most specific armed mock answers: one that names `page=2` beats one that takes any query, so
|
|
11532
|
+
// arming a general mock does not shadow the special case someone armed on top of it.
|
|
11533
|
+
.sort((a, b) => declaredParamCount(b) - declaredParamCount(a))[0];
|
|
11534
|
+
if (!mock)
|
|
11535
|
+
return null;
|
|
11536
|
+
return { status: mock.status, body: mock.body, latencyMs: mock.latencyMs };
|
|
10981
11537
|
};
|
|
10982
11538
|
|
|
10983
|
-
// Generated from package.json by tools/scripts/generate-version.js - do not edit.
|
|
10984
|
-
/** The version of `@ethlete/query` this build was cut from. */
|
|
10985
|
-
const QUERY_VERSION = '6.0.0-next.54';
|
|
10986
|
-
|
|
10987
11539
|
/** How deep a schema is expanded before a nested branch is given up on. */
|
|
10988
11540
|
const MAX_DEPTH = 12;
|
|
10989
11541
|
/** How many elements are generated for an array, whatever its `minItems` says. */
|
|
@@ -11202,9 +11754,7 @@ const stringSample = (node, schema, ctx) => {
|
|
|
11202
11754
|
if (ctx.style === 'stress')
|
|
11203
11755
|
return fitLength(generateQueryDevtoolsStringPreset(pickStressStringPreset()), schema);
|
|
11204
11756
|
const title = schema['title'];
|
|
11205
|
-
|
|
11206
|
-
return title;
|
|
11207
|
-
return node.key ?? 'string';
|
|
11757
|
+
return fitLength(typeof title === 'string' && title ? title : (node.key ?? 'string'), schema);
|
|
11208
11758
|
};
|
|
11209
11759
|
const boundsOf = (schema) => {
|
|
11210
11760
|
const exclusiveMin = finiteNumber(schema['exclusiveMinimum']);
|
|
@@ -11224,9 +11774,9 @@ const clampToBounds = (value, bounds) => {
|
|
|
11224
11774
|
const numberSample = (schema, ctx) => {
|
|
11225
11775
|
const bounds = boundsOf(schema);
|
|
11226
11776
|
if (ctx.style === 'placeholder')
|
|
11227
|
-
return bounds.min ?? 0;
|
|
11777
|
+
return clampToBounds(bounds.min ?? 0, bounds);
|
|
11228
11778
|
if (ctx.style === 'realistic') {
|
|
11229
|
-
return generateQueryDevtoolsSampleNumber({ ...bounds, fractional: typeOf(schema) === 'number' });
|
|
11779
|
+
return clampToBounds(generateQueryDevtoolsSampleNumber({ ...bounds, fractional: typeOf(schema) === 'number' }), bounds);
|
|
11230
11780
|
}
|
|
11231
11781
|
const allowsNegative = bounds.min === null || bounds.min < 0;
|
|
11232
11782
|
return clampToBounds(generateQueryDevtoolsNumberPreset(allowsNegative && Math.random() < 0.5 ? 'negative' : 'huge'), bounds);
|
|
@@ -11667,7 +12217,8 @@ const queryDevtoolsResponseHistory = () => queryDevtoolsSettings().responseHisto
|
|
|
11667
12217
|
* @internal
|
|
11668
12218
|
*/
|
|
11669
12219
|
const setQueryDevtoolsResponseHistory = (count) => {
|
|
11670
|
-
providedResponseHistory =
|
|
12220
|
+
providedResponseHistory =
|
|
12221
|
+
count === undefined || Number.isNaN(count) ? DEFAULT_RESPONSE_HISTORY : Math.max(1, Math.floor(count));
|
|
11671
12222
|
};
|
|
11672
12223
|
/** The index of the newest run still in flight, or -1 when none is. */
|
|
11673
12224
|
const lastPendingRunIndex = (runs) => {
|
|
@@ -12353,7 +12904,7 @@ const provideQueryDevtools = (options) => {
|
|
|
12353
12904
|
return makeEnvironmentProviders([settleThePillsOnFirstRender()]);
|
|
12354
12905
|
};
|
|
12355
12906
|
|
|
12356
|
-
const getOpName = /\b(?:query|mutation)\s+([\w-]+)(?:\s*\([^)]*\))
|
|
12907
|
+
const getOpName = /\b(?:query|mutation)\s+([\w-]+)(?:\s*\([^)]*\))?(?:\s*@\w+(?:\s*\([^)]*\))?)*\s*\{/;
|
|
12357
12908
|
const STRING_LITERAL = '"""[\\s\\S]*?"""|"(?:[^"\\\\\\n]|\\\\.)*"';
|
|
12358
12909
|
const commentOutsideString = /* @__PURE__ */ new RegExp(`(${STRING_LITERAL})|#[^\\n\\r]*`, 'g');
|
|
12359
12910
|
const whitespaceOutsideString = /* @__PURE__ */ new RegExp(`(${STRING_LITERAL})|\\s+`, 'g');
|
|
@@ -12361,6 +12912,19 @@ const minifyGql = (document) => document
|
|
|
12361
12912
|
.replace(commentOutsideString, (_match, literal) => literal ?? '')
|
|
12362
12913
|
.replace(whitespaceOutsideString, (_match, literal) => literal ?? ' ')
|
|
12363
12914
|
.trim();
|
|
12915
|
+
/** Blanks everything inside braces, so a field named `query` or `mutation` cannot pass for an operation. */
|
|
12916
|
+
const topLevelOnly = (document) => {
|
|
12917
|
+
let depth = 0;
|
|
12918
|
+
let result = '';
|
|
12919
|
+
for (const char of document) {
|
|
12920
|
+
if (char === '}')
|
|
12921
|
+
depth = Math.max(depth - 1, 0);
|
|
12922
|
+
result += depth === 0 ? char : ' ';
|
|
12923
|
+
if (char === '{')
|
|
12924
|
+
depth++;
|
|
12925
|
+
}
|
|
12926
|
+
return result;
|
|
12927
|
+
};
|
|
12364
12928
|
/**
|
|
12365
12929
|
* Builds the GraphQL-over-HTTP payload of a document. `variables` comes back as a JSON string for
|
|
12366
12930
|
* the `GET` transport (the default), where the payload travels as query parameters, and as the map
|
|
@@ -12368,7 +12932,7 @@ const minifyGql = (document) => document
|
|
|
12368
12932
|
*/
|
|
12369
12933
|
const transformGql = (str) => {
|
|
12370
12934
|
const normalizedStr = Array.isArray(str) ? str.join('') : str;
|
|
12371
|
-
const operationName = getOpName.exec(normalizedStr.replace(commentOutsideString, (_match, literal) => (literal ? '""' : '')))?.[1];
|
|
12935
|
+
const operationName = getOpName.exec(topLevelOnly(normalizedStr.replace(commentOutsideString, (_match, literal) => (literal ? '""' : ''))))?.[1];
|
|
12372
12936
|
let minified;
|
|
12373
12937
|
return (variables, transport = 'GET') => {
|
|
12374
12938
|
const data = { query: isDevMode() ? normalizedStr : (minified ??= minifyGql(normalizedStr)) };
|
|
@@ -12409,9 +12973,11 @@ const createGqlExecuteFn = (executeOptions) => {
|
|
|
12409
12973
|
const executeState = setupQueryExecuteState();
|
|
12410
12974
|
const circularChecker = circularQueryDependencyChecker();
|
|
12411
12975
|
const aborter = createQueryExecutionAborter(executeOptions.state);
|
|
12412
|
-
const reset = () => resetExecuteState({ executeState, executeOptions });
|
|
12413
|
-
const exec = (executeArgs) => {
|
|
12976
|
+
const reset = () => untracked(() => resetExecuteState({ executeState, executeOptions }));
|
|
12977
|
+
const exec = (executeArgs) => untracked(() => {
|
|
12414
12978
|
const { args = executeOptions.state.args(), options } = executeArgs ?? {};
|
|
12979
|
+
if (skipParkedExecution(executeOptions.state, args, executeOptions.creator?.route))
|
|
12980
|
+
return;
|
|
12415
12981
|
circularChecker.check(args);
|
|
12416
12982
|
let gqlParams = gqlTransformerFor(executeOptions.creatorInternals)(args?.variables, executeOptions.creatorInternals.transport);
|
|
12417
12983
|
if (args?.queryParams && executeOptions.creatorInternals.transport === 'GET') {
|
|
@@ -12451,7 +13017,7 @@ const createGqlExecuteFn = (executeOptions) => {
|
|
|
12451
13017
|
options,
|
|
12452
13018
|
isRefreshable: executeOptions.creatorInternals.method === 'QUERY',
|
|
12453
13019
|
});
|
|
12454
|
-
};
|
|
13020
|
+
});
|
|
12455
13021
|
exec['reset'] = reset;
|
|
12456
13022
|
exec['abort'] = () => aborter.abort();
|
|
12457
13023
|
exec['aborted$'] = aborter.aborted$;
|
|
@@ -12501,6 +13067,7 @@ const createSecureGqlExecuteFn = (executeOptions) => {
|
|
|
12501
13067
|
authProvider,
|
|
12502
13068
|
deps: executeOptions.deps,
|
|
12503
13069
|
state: executeOptions.state,
|
|
13070
|
+
route: executeOptions.creator?.route,
|
|
12504
13071
|
autoExecutes: shouldAutoExecuteGqlQuery(executeOptions.creatorInternals.method) &&
|
|
12505
13072
|
!executeOptions.queryConfig.onlyManualExecution,
|
|
12506
13073
|
transformAuthAndExec: (executeArgs, executeState) => {
|
|
@@ -13932,6 +14499,10 @@ class LegacyQuery {
|
|
|
13932
14499
|
// NG0205 ("Injector has already been destroyed") from inside the view's cleanup phase.
|
|
13933
14500
|
this.newQuery.subtle.destroyRef.onDestroy(() => this.teardown());
|
|
13934
14501
|
}
|
|
14502
|
+
/** The full URL of the request the underlying query is bound to, `null` before the first execution. */
|
|
14503
|
+
get url() {
|
|
14504
|
+
return this.newQuery.url();
|
|
14505
|
+
}
|
|
13935
14506
|
get rawState() {
|
|
13936
14507
|
return this.toQueryState(this.newQuery.executionState());
|
|
13937
14508
|
}
|
|
@@ -14190,9 +14761,10 @@ const createInertQuery = () => {
|
|
|
14190
14761
|
...base,
|
|
14191
14762
|
execute: noop,
|
|
14192
14763
|
createSnapshot,
|
|
14764
|
+
url: inertSignal(null),
|
|
14193
14765
|
reset: noop,
|
|
14194
14766
|
abort: () => false,
|
|
14195
|
-
asReadonly: () => ({ ...base, createSnapshot }),
|
|
14767
|
+
asReadonly: () => ({ ...base, createSnapshot, url: inertSignal(null) }),
|
|
14196
14768
|
subtle: {
|
|
14197
14769
|
destroy: noop,
|
|
14198
14770
|
setResponse: noop,
|
|
@@ -15871,7 +16443,8 @@ class QueryDirective {
|
|
|
15871
16443
|
this.queryState = queryStateSignal(this.query);
|
|
15872
16444
|
inject(ViewContainerRef).createEmbeddedView(inject(TemplateRef), this.viewContext);
|
|
15873
16445
|
effect(() => {
|
|
15874
|
-
const
|
|
16446
|
+
const rawQuery = this.query();
|
|
16447
|
+
const query = extractQuery(rawQuery);
|
|
15875
16448
|
untracked(() => {
|
|
15876
16449
|
if (!query) {
|
|
15877
16450
|
this.viewContext.$implicit = null;
|
|
@@ -15887,7 +16460,7 @@ class QueryDirective {
|
|
|
15887
16460
|
if (isQueryStatePrepared(query.rawState) || isQueryStateCancelled(query.rawState)) {
|
|
15888
16461
|
query.execute();
|
|
15889
16462
|
}
|
|
15890
|
-
this.viewContext.scope = isQueryCollection(
|
|
16463
|
+
this.viewContext.scope = isQueryCollection(rawQuery) ? rawQuery.type : null;
|
|
15891
16464
|
this.viewContext.query = query;
|
|
15892
16465
|
});
|
|
15893
16466
|
});
|
|
@@ -16957,13 +17530,13 @@ const transformToStringArray = (value) => {
|
|
|
16957
17530
|
};
|
|
16958
17531
|
const transformToNumber = (value) => {
|
|
16959
17532
|
if (typeof value === 'number') {
|
|
16960
|
-
return value;
|
|
17533
|
+
return Number.isFinite(value) ? value : null;
|
|
16961
17534
|
}
|
|
16962
17535
|
if (typeof value === 'string') {
|
|
16963
17536
|
if (!value.trim())
|
|
16964
17537
|
return null;
|
|
16965
17538
|
const number = Number(value);
|
|
16966
|
-
return Number.
|
|
17539
|
+
return Number.isFinite(number) ? number : null;
|
|
16967
17540
|
}
|
|
16968
17541
|
return null;
|
|
16969
17542
|
};
|
|
@@ -16999,7 +17572,7 @@ const transformToBooleanArray = (value) => {
|
|
|
16999
17572
|
const DATE_ONLY_PATTERN = /^(\d{4})-(\d{2})-(\d{2})$/;
|
|
17000
17573
|
const transformToDate = (value) => {
|
|
17001
17574
|
if (value instanceof Date) {
|
|
17002
|
-
return value;
|
|
17575
|
+
return Number.isNaN(value.getTime()) ? null : value;
|
|
17003
17576
|
}
|
|
17004
17577
|
if (typeof value === 'string') {
|
|
17005
17578
|
const date = new Date(value);
|
|
@@ -17026,7 +17599,9 @@ const transformToDateArray = (value) => {
|
|
|
17026
17599
|
};
|
|
17027
17600
|
const transformToSort = (value) => {
|
|
17028
17601
|
if (typeof value === 'string') {
|
|
17029
|
-
const
|
|
17602
|
+
const separator = value.lastIndexOf(':');
|
|
17603
|
+
const active = separator === -1 ? value : value.slice(0, separator);
|
|
17604
|
+
const direction = separator === -1 ? '' : value.slice(separator + 1);
|
|
17030
17605
|
if (!active) {
|
|
17031
17606
|
return null;
|
|
17032
17607
|
}
|
|
@@ -17729,6 +18304,8 @@ const MAX_RESET_PASSES = 10;
|
|
|
17729
18304
|
const changedKeysBetween = (previous, current) => Object.keys(current).filter((key) => !equal(previous?.[key], current[key]));
|
|
17730
18305
|
/** Reads a URL string into the type of the field's default; `null` means the default's type cannot hold it. */
|
|
17731
18306
|
const autoCoerce = (raw, defaultValue) => {
|
|
18307
|
+
if (Array.isArray(raw))
|
|
18308
|
+
return Array.isArray(defaultValue) ? raw : null;
|
|
17732
18309
|
if (typeof raw !== 'string')
|
|
17733
18310
|
return raw;
|
|
17734
18311
|
if (Array.isArray(defaultValue))
|
|
@@ -17756,10 +18333,13 @@ const resolveDebounce = (fieldDefs, changedKeys, live) => {
|
|
|
17756
18333
|
}
|
|
17757
18334
|
return Math.min(...times);
|
|
17758
18335
|
};
|
|
18336
|
+
const isUnserializable = (value) => value === undefined ||
|
|
18337
|
+
(typeof value === 'number' && Number.isNaN(value)) ||
|
|
18338
|
+
(value instanceof Date && Number.isNaN(value.getTime()));
|
|
17759
18339
|
/**
|
|
17760
18340
|
* A value that cannot be told apart from the default once serialized - a cleared text (`''`), an emptied list
|
|
17761
|
-
* (`[]`) or one whose `valueToQueryParam` yields nothing - commits as the
|
|
17762
|
-
* committed state and the filter count does not report it.
|
|
18341
|
+
* (`[]`), `undefined`, `NaN`, an invalid `Date` or one whose `valueToQueryParam` yields nothing - commits as the
|
|
18342
|
+
* default, so the URL reproduces the committed state and the filter count does not report it.
|
|
17763
18343
|
*/
|
|
17764
18344
|
const normalizeLive = (fieldDefs, rawLive, defaults) => {
|
|
17765
18345
|
const live = { ...rawLive };
|
|
@@ -17770,7 +18350,7 @@ const normalizeLive = (fieldDefs, rawLive, defaults) => {
|
|
|
17770
18350
|
const serialized = def.valueToQueryParam?.(value);
|
|
17771
18351
|
const serializedToNothing = !!def.valueToQueryParam && (serialized === null || serialized === undefined);
|
|
17772
18352
|
const clearedNullable = defaults[key] === null && (value === '' || (Array.isArray(value) && value.length === 0));
|
|
17773
|
-
if (serializedToNothing || clearedNullable) {
|
|
18353
|
+
if (serializedToNothing || clearedNullable || isUnserializable(value)) {
|
|
17774
18354
|
live[key] = defaults[key];
|
|
17775
18355
|
}
|
|
17776
18356
|
}
|
|
@@ -18261,6 +18841,13 @@ const defineQueryForm = (config) => {
|
|
|
18261
18841
|
}
|
|
18262
18842
|
observeOptions = options;
|
|
18263
18843
|
observing.set(true);
|
|
18844
|
+
if (isDevMode()) {
|
|
18845
|
+
for (const key of options?.persistence?.fields ?? []) {
|
|
18846
|
+
if (!(key in fieldDefs)) {
|
|
18847
|
+
console.warn(`defineQueryForm: persistence.fields references unknown field "${key}". Is it a typo?`);
|
|
18848
|
+
}
|
|
18849
|
+
}
|
|
18850
|
+
}
|
|
18264
18851
|
changesSeenAtObserve = untracked(queryParamChanges);
|
|
18265
18852
|
if (options?.persistence) {
|
|
18266
18853
|
restoreFromStorage(options.persistence, route.snapshot.queryParams);
|
|
@@ -18387,6 +18974,11 @@ const normalizeConfig = (config) => {
|
|
|
18387
18974
|
isResetBy: isResetBy === undefined ? undefined : Array.isArray(isResetBy) ? isResetBy : [isResetBy],
|
|
18388
18975
|
};
|
|
18389
18976
|
};
|
|
18977
|
+
const dateToQueryParam = (value) => {
|
|
18978
|
+
if (value === null)
|
|
18979
|
+
return ET_NULL_VALUE;
|
|
18980
|
+
return value instanceof Date && !Number.isNaN(value.getTime()) ? value.toISOString() : value;
|
|
18981
|
+
};
|
|
18390
18982
|
function queryField(config) {
|
|
18391
18983
|
return {
|
|
18392
18984
|
defaultValue: null,
|
|
@@ -18463,6 +19055,7 @@ function dateQueryField(config) {
|
|
|
18463
19055
|
return {
|
|
18464
19056
|
defaultValue: null,
|
|
18465
19057
|
queryParamToValue: transformToDate,
|
|
19058
|
+
valueToQueryParam: dateToQueryParam,
|
|
18466
19059
|
...normalizeConfig(rest),
|
|
18467
19060
|
};
|
|
18468
19061
|
}
|
|
@@ -18470,6 +19063,11 @@ function dateQueryField(config) {
|
|
|
18470
19063
|
const dateArrayQueryField = (config) => ({
|
|
18471
19064
|
defaultValue: null,
|
|
18472
19065
|
queryParamToValue: transformToDateArray,
|
|
19066
|
+
valueToQueryParam: (value) => {
|
|
19067
|
+
if (value === null)
|
|
19068
|
+
return ET_NULL_VALUE;
|
|
19069
|
+
return value.length ? value.map(dateToQueryParam) : ET_EMPTY_ARRAY_VALUE;
|
|
19070
|
+
},
|
|
18473
19071
|
...normalizeConfig(config),
|
|
18474
19072
|
});
|
|
18475
19073
|
|
|
@@ -18478,6 +19076,7 @@ const WebSocketRuntimeErrorCode = {
|
|
|
18478
19076
|
// Web Socket Client
|
|
18479
19077
|
ROOM_NOT_JOINED: 1000,
|
|
18480
19078
|
MESSAGE_MALFORMED: 1001,
|
|
19079
|
+
AUTH_AND_AUTH_PROVIDER: 1002,
|
|
18481
19080
|
};
|
|
18482
19081
|
const roomNotJoined = (room) => {
|
|
18483
19082
|
return new RuntimeError(WebSocketRuntimeErrorCode.ROOM_NOT_JOINED, `Tried leaving the room "${room}" but it has not been joined.`);
|
|
@@ -18485,6 +19084,9 @@ const roomNotJoined = (room) => {
|
|
|
18485
19084
|
const messageMalformed = () => {
|
|
18486
19085
|
return new RuntimeError(WebSocketRuntimeErrorCode.MESSAGE_MALFORMED, 'A message has been received but it is malformed. Expected a JSON string shaped like { room: string; event: string; data: unknown }.');
|
|
18487
19086
|
};
|
|
19087
|
+
const authAndAuthProvider = (name) => {
|
|
19088
|
+
return new RuntimeError(WebSocketRuntimeErrorCode.AUTH_AND_AUTH_PROVIDER, `The web socket client "${name}" sets both \`auth\` and \`authProvider\`. \`authProvider\` sends the access token itself, so \`auth\` is ignored. Remove one of them.`);
|
|
19089
|
+
};
|
|
18488
19090
|
|
|
18489
19091
|
const MAX_DEVTOOLS_MESSAGES = 100;
|
|
18490
19092
|
const RECONNECT_BASE_DELAY = 1000;
|
|
@@ -18494,7 +19096,10 @@ const createWebSocketClient = (options) => {
|
|
|
18494
19096
|
return defineRootProvider(() => {
|
|
18495
19097
|
if (isPlatformServer(inject(PLATFORM_ID)))
|
|
18496
19098
|
return createServerWebSocketClient();
|
|
18497
|
-
|
|
19099
|
+
if (options.auth && options.authProvider && isDevMode())
|
|
19100
|
+
throw authAndAuthProvider(options.name);
|
|
19101
|
+
const authProvider = options.authProvider?.inject();
|
|
19102
|
+
const auth = authProvider ? () => ({ token: authProvider.accessToken() }) : options.auth;
|
|
18498
19103
|
const socket = options.io(options.url, {
|
|
18499
19104
|
withCredentials: options.withCredentials ?? true,
|
|
18500
19105
|
autoConnect: false,
|
|
@@ -18502,6 +19107,7 @@ const createWebSocketClient = (options) => {
|
|
|
18502
19107
|
auth: auth ? (cb) => cb(typeof auth === 'function' ? auth() : auth) : undefined,
|
|
18503
19108
|
});
|
|
18504
19109
|
const rooms = new Map();
|
|
19110
|
+
const endedRoomHolders = new Map();
|
|
18505
19111
|
const joinsDeliveredThisConnection = new Set();
|
|
18506
19112
|
const joinsDeliveredToClosedConnection = new Set();
|
|
18507
19113
|
const isConnected = signal(false, /* @ts-ignore */
|
|
@@ -18532,50 +19138,54 @@ const createWebSocketClient = (options) => {
|
|
|
18532
19138
|
direction: 'out',
|
|
18533
19139
|
});
|
|
18534
19140
|
};
|
|
19141
|
+
const join = (name) => {
|
|
19142
|
+
const existingRoom = rooms.get(name);
|
|
19143
|
+
if (existingRoom) {
|
|
19144
|
+
existingRoom.joinCount++;
|
|
19145
|
+
return existingRoom;
|
|
19146
|
+
}
|
|
19147
|
+
emit({ event: 'join-room', data: name, room: name });
|
|
19148
|
+
joinsDeliveredToClosedConnection.delete(name);
|
|
19149
|
+
const messages = new Subject();
|
|
19150
|
+
const newRoom = {
|
|
19151
|
+
latestMessage: signal(null),
|
|
19152
|
+
messages,
|
|
19153
|
+
messages$: messages.asObservable(),
|
|
19154
|
+
joinCount: 1,
|
|
19155
|
+
};
|
|
19156
|
+
rooms.set(name, newRoom);
|
|
19157
|
+
syncDevtoolsRooms();
|
|
19158
|
+
return newRoom;
|
|
19159
|
+
};
|
|
19160
|
+
const joinStaticRoom = (name) => {
|
|
19161
|
+
const destroyRef = inject(DestroyRef);
|
|
19162
|
+
const roomData = signal(join(name), /* @ts-ignore */
|
|
19163
|
+
...(ngDevMode ? [{ debugName: "roomData" }] : /* istanbul ignore next */ []));
|
|
19164
|
+
destroyRef.onDestroy(() => {
|
|
19165
|
+
leaveRoom(name);
|
|
19166
|
+
roomData.set(null);
|
|
19167
|
+
});
|
|
19168
|
+
return roomData.asReadonly();
|
|
19169
|
+
};
|
|
18535
19170
|
const joinRoom = (room) => {
|
|
18536
|
-
|
|
18537
|
-
|
|
18538
|
-
|
|
18539
|
-
// effect joins the new room without ever leaving the old one.
|
|
18540
|
-
const pre = previousSignalValue(computed(() => roomFn()));
|
|
19171
|
+
if (typeof room === 'string')
|
|
19172
|
+
return joinStaticRoom(room);
|
|
19173
|
+
const roomFn = room;
|
|
18541
19174
|
const roomData = signal(null, /* @ts-ignore */
|
|
18542
19175
|
...(ngDevMode ? [{ debugName: "roomData" }] : /* istanbul ignore next */ []));
|
|
18543
19176
|
let joinedRoomName = null;
|
|
18544
|
-
const join = (name) => {
|
|
18545
|
-
const existingRoom = rooms.get(name);
|
|
18546
|
-
if (existingRoom) {
|
|
18547
|
-
existingRoom.joinCount++;
|
|
18548
|
-
return existingRoom;
|
|
18549
|
-
}
|
|
18550
|
-
emit({ event: 'join-room', data: name, room: name });
|
|
18551
|
-
joinsDeliveredToClosedConnection.delete(name);
|
|
18552
|
-
const messages = new Subject();
|
|
18553
|
-
const newRoom = {
|
|
18554
|
-
latestMessage: signal(null),
|
|
18555
|
-
messages,
|
|
18556
|
-
messages$: messages.asObservable(),
|
|
18557
|
-
joinCount: 1,
|
|
18558
|
-
};
|
|
18559
|
-
rooms.set(name, newRoom);
|
|
18560
|
-
syncDevtoolsRooms();
|
|
18561
|
-
return newRoom;
|
|
18562
|
-
};
|
|
18563
19177
|
effect(() => {
|
|
18564
|
-
const current = roomFn();
|
|
19178
|
+
const current = roomFn() || null;
|
|
18565
19179
|
untracked(() => {
|
|
18566
|
-
|
|
18567
|
-
if (previous === current)
|
|
19180
|
+
if (current === joinedRoomName)
|
|
18568
19181
|
return;
|
|
18569
|
-
if (
|
|
18570
|
-
leaveRoom(
|
|
19182
|
+
if (joinedRoomName) {
|
|
19183
|
+
leaveRoom(joinedRoomName);
|
|
18571
19184
|
joinedRoomName = null;
|
|
18572
19185
|
}
|
|
18573
19186
|
if (current) {
|
|
18574
|
-
|
|
18575
|
-
|
|
18576
|
-
joinedRoomName = current;
|
|
18577
|
-
roomData.set(joinedRoom);
|
|
18578
|
-
}
|
|
19187
|
+
joinedRoomName = current;
|
|
19188
|
+
roomData.set(join(current));
|
|
18579
19189
|
}
|
|
18580
19190
|
else {
|
|
18581
19191
|
roomData.set(null);
|
|
@@ -18592,6 +19202,14 @@ const createWebSocketClient = (options) => {
|
|
|
18592
19202
|
return roomData.asReadonly();
|
|
18593
19203
|
};
|
|
18594
19204
|
const leaveRoom = (room) => {
|
|
19205
|
+
const endedHolders = endedRoomHolders.get(room);
|
|
19206
|
+
if (endedHolders !== undefined) {
|
|
19207
|
+
if (endedHolders > 1)
|
|
19208
|
+
endedRoomHolders.set(room, endedHolders - 1);
|
|
19209
|
+
else
|
|
19210
|
+
endedRoomHolders.delete(room);
|
|
19211
|
+
return;
|
|
19212
|
+
}
|
|
18595
19213
|
const joinedRoom = rooms.get(room);
|
|
18596
19214
|
if (!joinedRoom) {
|
|
18597
19215
|
if (isDevMode())
|
|
@@ -18612,9 +19230,10 @@ const createWebSocketClient = (options) => {
|
|
|
18612
19230
|
let reconnectTimer = null;
|
|
18613
19231
|
let connectedAt = null;
|
|
18614
19232
|
let destroyed = false;
|
|
19233
|
+
let suspended = false;
|
|
18615
19234
|
/** socket.io stops reconnecting after a server disconnect or a rejected handshake, so the client takes over. */
|
|
18616
19235
|
const scheduleReconnect = () => {
|
|
18617
|
-
if (reconnectTimer !== null || destroyed)
|
|
19236
|
+
if (reconnectTimer !== null || destroyed || suspended)
|
|
18618
19237
|
return;
|
|
18619
19238
|
const delay = Math.min(RECONNECT_BASE_DELAY * 2 ** reconnectAttempt, RECONNECT_MAX_DELAY);
|
|
18620
19239
|
reconnectAttempt++;
|
|
@@ -18704,7 +19323,62 @@ const createWebSocketClient = (options) => {
|
|
|
18704
19323
|
});
|
|
18705
19324
|
setupWebSocketConnectionListener();
|
|
18706
19325
|
setupWebSocketListener();
|
|
18707
|
-
|
|
19326
|
+
const endSession = () => {
|
|
19327
|
+
suspended = true;
|
|
19328
|
+
if (reconnectTimer !== null) {
|
|
19329
|
+
clearTimeout(reconnectTimer);
|
|
19330
|
+
reconnectTimer = null;
|
|
19331
|
+
}
|
|
19332
|
+
socket.disconnect();
|
|
19333
|
+
for (const [name, room] of rooms) {
|
|
19334
|
+
endedRoomHolders.set(name, (endedRoomHolders.get(name) ?? 0) + room.joinCount);
|
|
19335
|
+
room.messages.complete();
|
|
19336
|
+
}
|
|
19337
|
+
rooms.clear();
|
|
19338
|
+
syncDevtoolsRooms();
|
|
19339
|
+
};
|
|
19340
|
+
const startConnection = () => {
|
|
19341
|
+
suspended = false;
|
|
19342
|
+
reconnectAttempt = 0;
|
|
19343
|
+
socket.connect();
|
|
19344
|
+
};
|
|
19345
|
+
if (authProvider) {
|
|
19346
|
+
let hasConnected = false;
|
|
19347
|
+
let wasAuthenticated = false;
|
|
19348
|
+
let handledLogin = null;
|
|
19349
|
+
effect(() => {
|
|
19350
|
+
const status = authProvider.sessionStatus();
|
|
19351
|
+
const execution = authProvider.executionState();
|
|
19352
|
+
untracked(() => {
|
|
19353
|
+
if (destroyed || status === 'unknown' || status === 'restoring')
|
|
19354
|
+
return;
|
|
19355
|
+
if (status === 'anonymous') {
|
|
19356
|
+
if (wasAuthenticated)
|
|
19357
|
+
endSession();
|
|
19358
|
+
else if (!hasConnected)
|
|
19359
|
+
startConnection();
|
|
19360
|
+
wasAuthenticated = false;
|
|
19361
|
+
hasConnected = true;
|
|
19362
|
+
return;
|
|
19363
|
+
}
|
|
19364
|
+
const login = execution?.type === 'login' && execution.state === 'success' ? execution : null;
|
|
19365
|
+
const startedSession = !wasAuthenticated || (login !== null && login !== handledLogin);
|
|
19366
|
+
if (login)
|
|
19367
|
+
handledLogin = login;
|
|
19368
|
+
wasAuthenticated = true;
|
|
19369
|
+
if (!hasConnected)
|
|
19370
|
+
startConnection();
|
|
19371
|
+
else if (startedSession) {
|
|
19372
|
+
socket.disconnect();
|
|
19373
|
+
startConnection();
|
|
19374
|
+
}
|
|
19375
|
+
hasConnected = true;
|
|
19376
|
+
});
|
|
19377
|
+
});
|
|
19378
|
+
}
|
|
19379
|
+
else {
|
|
19380
|
+
socket.connect();
|
|
19381
|
+
}
|
|
18708
19382
|
const client = {
|
|
18709
19383
|
joinRoom,
|
|
18710
19384
|
isConnected: isConnected.asReadonly(),
|
|
@@ -18751,5 +19425,5 @@ const createServerWebSocketClient = () => {
|
|
|
18751
19425
|
* Generated bundle index. Do not edit.
|
|
18752
19426
|
*/
|
|
18753
19427
|
|
|
18754
|
-
export { AuthBearerRefreshStrategy, BasicAuthProvider, BearerAuthFeatureType, BooleanArrayQueryField, CustomHeaderAuthProvider, DEFAULT_KEEP_UNUSED_FOR, DEFAULT_QUERY_PERSISTENCE_MAX_AGE, DEFAULT_QUERY_PERSISTENCE_MAX_ENTRIES, DEFAULT_QUERY_PERSISTENCE_VERSION, DEFAULT_QUERY_PERSISTENCE_WRITE_DELAY, DateArrayQueryField, DateQueryField, EMPTY_QUERY_DEVTOOLS_FAULT, EntityStore, IGNORED_FILTER_COUNT_FIELDS, INFINITY_QUERY_TOKEN, IS_QUERY_REQUEST, InfinityQuery, InfinityQueryDirective, InfinityQueryTriggerDirective, LegacyQuery, LegacyQueryCreator, MAX_QUERY_BATCH_TOMBSTONES, MAX_QUERY_BATCH_TOMBSTONE_BUCKETS, MAX_QUERY_DEVTOOLS_TOMBSTONES, MAX_UNUSED_ENTRIES, NumberArrayQueryField, ParseHttpErrorCodeToMessageDePipe, ParseHttpErrorCodeToMessageEnPipe, ParseHttpErrorCodeToTitleDePipe, ParseHttpErrorCodeToTitleEnPipe, QUERY_CLIENT_DEVTOOLS_TOKEN, QUERY_DEVTOOLS_FAULT_STATUSES, QUERY_DEVTOOLS_TOKEN_TTL_LIMIT, QUERY_PERSISTENCE_STORE_VERSION, QUERY_SYNC_PROTOCOL_VERSION, QUERY_VERSION, QueryClientFeatureType, QueryDevtoolsComponent, QueryDirective, QueryFeatureType, QueryField, QueryForm, QueryRuntimeErrorCode, QueryShortNamePipe, QueryStateType, QueryStore, SERVER_ERROR_KIND, SERVER_VIOLATION_ERROR_KIND, SPEED_BUFFER_TIME_IN_MS, SearchQueryField, SortQueryField, StringArrayQueryField, V2BearerAuthProvider, V2Query, V2QueryClient, V2QueryCreator, addQueryContainerHandling, addQueryDevtoolsAuthAccount, applyQueryDevtoolsOverrides, applyQueryDevtoolsTokenTtl, applyQueryFeatures, armAllQueryDevtoolsMocks, armQueryDevtoolsMock, armQueryDevtoolsOverrideTransfer, authExtractTokensResponseMissingAccessToken, authExtractTokensResponseMissingRefreshToken, authExtractTokensResponseNotObject, authProviderFeatureUsedMultipleTimes, booleanArrayQueryField, buildQueryCacheKey, buildQueryString, buildRoute, buildTimestampFromSeconds, canOverrideQueryDevtoolsTokenTtl, castQueryCreatorTypes, circularQueryDependency, circularQueryDependencyChecker, clearQueryDevtoolsArmedMocks, clearQueryDevtoolsAuthCredentials, clearQueryDevtoolsAuthSessions, clearQueryDevtoolsFaults, clearQueryDevtoolsMockStore, clearQueryDevtoolsOverrideStore, clearQueryDevtoolsStore, clearQueryDevtoolsTokenTtl, clearQueryDevtoolsTombstones, clearRestoredQueryDevtoolsOverrides, collectLeafPaths, collectQueryDevtoolsSchemaComponents, collectQueryFormLinks, computeQueryAuthHeader, computeQueryBody, computeQueryHeaders, computeQueryMethod, computeQueryQueryParams, consumeSuppressQueryStackDevtools, contentfulGqlLikePaginationAdapter, countUnresolvedQueryDevtoolsOverrides, createAuthGuard, createBaseQuery, createBaseQueryCreator, createBearerAuthProvider, createDefaultRetryFn, createDeleteQuery, createExecuteFn, createGetQuery, createGqlExecuteFn, createGqlMutationViaGet, createGqlMutationViaPost, createGqlQuery, createGqlQueryCreator, createGqlQueryViaGet, createGqlQueryViaPost, createHeadQuery, createHttpRequest, createIndexedDbQueryPersistenceAdapter, createInfinityQueryConfig, createLegacyQueryCreator, createNoopQueryPersistenceAdapter, createOptionsQuery, createPagedQueryStack, createPatchQuery, createPersistentAuthFeature, createPostQuery, createPutQuery, createQuery, createQueryBatch, createQueryClient, createQueryCollection, createQueryCollectionSignal, createQueryCollectionSubject, createQueryContext, createQueryCreator, createQueryDevtoolsFormLinks, createQueryDevtoolsFormLinksRecorder, createQueryDevtoolsOverrides, createQueryDevtoolsOverridesRecorder, createQueryDevtoolsStats, createQueryDevtoolsStatsRecorder, createQueryErrorResponse, createQueryFeature, createQueryInvalidationFilter, createQueryKeyLockManager, createQueryObject, createQueryPersistenceEngine, createQueryRepository, createQuerySnapshotFn, createQueryStack, createQuerySubmission, createQuerySyncEngine, createQuerySyncTransport, createSecureDeleteQuery, createSecureExecuteFactory, createSecureExecuteFn, createSecureGetQuery, createSecureGqlExecuteFn, createSecureGqlMutationViaGet, createSecureGqlMutationViaPost, createSecureGqlQuery, createSecureGqlQueryCreator, createSecureGqlQueryViaGet, createSecureGqlQueryViaPost, createSecureHeadQuery, createSecureOptionsQuery, createSecurePatchQuery, createSecurePostQuery, createSecurePutQuery, createSecureQuery, createSecureQueryCreator, createTrackingFeature, createWebSocketClient, currentQueryDevtoolsBatch, dateArrayQueryField, dateQueryField, decryptBearer, deepFreeze, def, defineQueryForm, deleteQueryDevtoolsMock, describeQueryDevtoolsFeatures, detectContentTypeHeader, detectPaginationShape, dynLikePaginationAdapter, effectComputed, emptyQueryArgs, ethletePaginationAdapter, executeUntilSettled, executeUntilSettled$, extractExpiresInSeconds, extractFormViolations, extractHtmlErrorMessage, extractQuery, fakePaginationAdapter, filterFailure, filterNull, filterQueryStates, filterSuccess, forEachHeader, forgetQueryDevtoolsAuthSession, forgetQueryDevtoolsAuthSessionsFor, formatQueryDevtoolsDuration, generateQueryDevtoolsNumberPreset, generateQueryDevtoolsSampleNumber, generateQueryDevtoolsStringPreset, getBearerAuthProviderName, getDefaultHeaders, getQueryClientName, getQueryFeatureUsage, getResponseUrl, ggLikePaginationAdapter, gql, gqlDataPropertyMissingInResponse, gqlErrorsInResponse, gqlTransformerFor, hasDefaultQueryRetry, hasHeader, hasQueryDevtoolsOverridesAtPath, htmlErrorPayload, htmlQueryErrorParser, ignoreAutoRefresh, initQueryDevtoolsAuthSessions, initQueryDevtoolsFaults, initQueryDevtoolsMocks, initQueryDevtoolsOverridePersistence, initQueryDevtoolsSettings, injectInfinityQueryResponseDelay, injectQueryContext, insertFrom, invalidBaseRouteError, invalidBodyError, invalidRouteError, invalidStateInsideSecureExecuteFactory, isAutoRefresh, isBasicAuthProvider, isBearerAuthProvider, isClassValidatorError, isCreateGqlQueryOptions, isCustomHeaderAuthProvider, isDateShapedLeaf, isEmptyString, isGqlQueryConfig, isHtmlErrorPayload, isIdempotentQueryMethod, isLegacyQuery, isNaN$1 as isNaN, isOverrideOp, isPageOutOfRangeError, isQuery, isQueryCollection, isQueryDevtoolsEnabled, isQueryDevtoolsFaultArmed, isQueryDevtoolsRepositoryLive, isQueryDevtoolsRequestInterceptionEnabled, isQueryErrorResponse, isQueryStateCancelled, isQueryStateFailure, isQueryStateLoading, isQueryStatePrepared, isQueryStateSuccess, isRequestError, isSymfonyError, isSymfonyFormViolationListError, isSymfonyListError, isSymfonyPagerfantaOutOfRangeError, isUnderInvalidatedUrl, legacyPrepareFallbackInjector, legacyPrepareWithoutInjectionContext, loadQueryDevtoolsSchema, loginQueryDevtoolsAuthAccount, logoutQueryDevtoolsAuthSession, mapToPaginated, mapViolationsToFormErrors, matchesQueryDevtoolsMockPattern, matchesQueryDevtoolsMockQuery, maybeExecute, measureQueryDevtoolsPayload, mergeHeaders, nestedEffect, noteQueryFormRead, noteQueryFormReads, numberArrayQueryField, pagedQueryStackNextPageCalledWithoutPreviousPage, pagedQueryStackPageBiggerThanTotalPages, pagedQueryStackPreviousPageCalledButAlreadyAtFirstPage, paginatedEntityValueUpdater, parseAllXhrResponseHeaders, parseHttpErrorCodeToMessageDe, parseHttpErrorCodeToMessageEn, parseHttpErrorCodeToTitleDe, parseHttpErrorCodeToTitleEn, parseQueryDevtoolsOverrideTransfer, parseQueryRoute, patchQueryDevtoolsTokenPayload, pathParamsMissingInRouteFunctionError, provideInfinityQueryResponseDelay, provideLegacyPrepareFallback, provideQueryClientForDevtools, provideQueryDevtools, queryArrayComputed, queryBatchAlreadyRunning, queryBatchWithArgsUsed, queryClientFeatureUsedMultipleTimes, queryComputed, queryComputedTillTruthy, queryCreatedInReactiveContext, queryDevtoolsAbout, queryDevtoolsAllowsLocalAuthSessions, queryDevtoolsApiEnvIds, queryDevtoolsApiEnvIsProduction, queryDevtoolsApiEnvScope, queryDevtoolsApiEnvValues, queryDevtoolsApiEnvs, queryDevtoolsArmedMocks, queryDevtoolsArmedMocksRestored, queryDevtoolsAuthAccountsFor, queryDevtoolsAuthActive, queryDevtoolsAuthFieldsFor, queryDevtoolsAuthOtherScopeCount, queryDevtoolsAuthProviders, queryDevtoolsAuthSessions, queryDevtoolsAuthSessionsFor, queryDevtoolsAuthTabLocal, queryDevtoolsEntries, queryDevtoolsFaults, queryDevtoolsFaultsRestored, queryDevtoolsFnDetail, queryDevtoolsMockId, queryDevtoolsMocks, queryDevtoolsOverridePersistence, queryDevtoolsRequestPath, queryDevtoolsResponseHistory, queryDevtoolsRestoredOverridesScope, queryDevtoolsSchemaNames, queryDevtoolsSchemaRoutes, queryDevtoolsSchemaState, queryDevtoolsSettings, queryDevtoolsStorage, queryDevtoolsTokenTtls, queryErrorMessage, queryErrorMessages, queryExecute, queryExecutedAfterDestroyMessage, queryFeatureUsedMultipleTimes, queryField, querySequence, querySequenceAlreadyRunning, queryStackWithArgsUsed, queryStackWithResponseUpdateUsed, queryStateAlreadyHasKeyError, queryStateCannotTransformError, queryStateDoesNotContainKeyError, queryStateErrorSignal, queryStateLoadingSignal, queryStateResponseSignal, queryStateSignal, readQueryDevtoolsAuthSeed, readQueryDevtoolsAuthSeedFor, readQueryDevtoolsStore, recordExecutionArgs, registerEthleteVersion, registerLiveQueryDevtoolsRepository, registerQueryDevtoolsAuthProvider, registerQueryDevtoolsEntry, registerQueryErrorParser, releaseQueryDevtoolsOverridePersistence, removeFrom, removeQueryDevtoolsAuthAccount, renameQueryDevtoolsAuthSession, request, resetExecuteState, resetPageOnError, resolveInvalidationUrl, resolveQueryDevtoolsFault, resolveQueryDevtoolsFaultForAttempt, resolveQueryDevtoolsMock, resolveQueryDevtoolsMockForAttempt, resolveQueryHeaders, restoredQueryDevtoolsOverrides, runDefaultQueryRetry, runInQueryDevtoolsBatch, runQueryErrorParsers, saveQueryDevtoolsMock, searchQueryField, seedQueryDevtoolsSchemaBody, seedQueryDevtoolsSchemaRoute, serializeBody, serializeQueryDevtoolsOverrideTransfer, setDefaultQueryRetryFn, setQueryDevtoolsApiEnv, setQueryDevtoolsApiEnvs, setQueryDevtoolsAppInfo, setQueryDevtoolsArmedMocksScope, setQueryDevtoolsAuthCredentials, setQueryDevtoolsAuthProviderRegistrar, setQueryDevtoolsAuthSeedReader, setQueryDevtoolsAuthTabLocal, setQueryDevtoolsFault, setQueryDevtoolsFaultResolver, setQueryDevtoolsFaultsScope, setQueryDevtoolsFormLinksFactory, setQueryDevtoolsMockResolver, setQueryDevtoolsOverridePersistence, setQueryDevtoolsOverridesFactory, setQueryDevtoolsOverridesScope, setQueryDevtoolsRegistrar, setQueryDevtoolsResponseHistory, setQueryDevtoolsSchemaLoader, setQueryDevtoolsSettings, setQueryDevtoolsStatsFactory, setQueryDevtoolsTokenPayloadPatcher, setQueryDevtoolsTokenTtl, setQueryDevtoolsUiMounted, setupQueryDependencies, setupQueryExecuteState, setupQueryState, shouldAutoExecuteGqlQuery, shouldAutoExecuteQuery, shouldCacheQuery, shouldRetryRequest, silenceMissingWithArgsFeatureErrorUsedButWithArgsPresent, skipPaginationPageParamCalculator, smartDuplicateArrayItem, snapshotQueryDevtoolsHandle, sortQueryField, splitQueryConfig, stringArrayQueryField, stringifyQueryRouteParts, sumQueryDevtoolsStats, suppressNextQueryStackDevtools, switchQueryCollectionState, switchQueryDevtoolsAuthSession, switchQueryState, symfonyQueryErrorParser, tableSortQueryField, takeUntilResponse, toQuerySignal, toQuerySubject, tokensNotAvailableInsideAuthAndExec, tombstoneOf, trackQueryDevtoolsAuthProvider, transformArrayResponse, transformExecStateToQueryState, transformGql, transformMethod, transformPaginatedResponse, transformToBoolean, transformToBooleanArray, transformToDate, transformToDateArray, transformToNumber, transformToNumberArray, transformToSort, transformToSortQueryParam, transformToString, transformToStringArray, transformToTableSort, transformToTableSortQueryParam, uncacheableRequestHasAllowCacheParam, uncacheableRequestHasCacheKeyParam, unwrapGqlResponseWithErrors, unwrapQuerySyncMessage, v2BuildQueryCacheKey, v2ExtractExpiresInSeconds, v2ShouldCacheQuery, v2ShouldRetryRequest, validateWithQuery, validateWithV2Query, withArgs, withArgsQueryFeatureMissingButRouteIsFunction, withAuthenticationQuery, withAutoRefresh, withAutoRefreshUsedInManualQuery, withAutoRefreshUsedOnUnsupportedHttpMethod, withBearerAuthMultiTabSync, withDefaultRetry, withErrorHandling, withEthleteApiErrors, withHtmlErrorParsing, withInactivityLogout, withLogging, withLongPolling, withLongPollingUsedOnUnsupportedHttpMethod, withLongPollingUsedWithPolling, withMultiTabSync, withPageResetOnError, withPersistentAuth, withPolling, withPollingUsedOnUnsupportedHttpMethod, withQueryDevtoolsOverridePersistence, withQueryPersistence, withRefreshQuery, withResponseUpdate, withSuccessHandling, withSymfonyErrors, withTokenExpirationWarning, withTokenRevocation, withTracking, wrapAsObservableSignal, wrapQuerySyncMessage, writeQueryDevtoolsStore };
|
|
19428
|
+
export { AuthBearerRefreshStrategy, BasicAuthProvider, BearerAuthFeatureType, BooleanArrayQueryField, CustomHeaderAuthProvider, DEFAULT_KEEP_UNUSED_FOR, DEFAULT_QUERY_PERSISTENCE_MAX_AGE, DEFAULT_QUERY_PERSISTENCE_MAX_ENTRIES, DEFAULT_QUERY_PERSISTENCE_VERSION, DEFAULT_QUERY_PERSISTENCE_WRITE_DELAY, DateArrayQueryField, DateQueryField, EntityStore, IGNORED_FILTER_COUNT_FIELDS, INFINITY_QUERY_TOKEN, IS_QUERY_REQUEST, InfinityQuery, InfinityQueryDirective, InfinityQueryTriggerDirective, LegacyQuery, LegacyQueryCreator, MAX_UNUSED_ENTRIES, NumberArrayQueryField, ParseHttpErrorCodeToMessageDePipe, ParseHttpErrorCodeToMessageEnPipe, ParseHttpErrorCodeToTitleDePipe, ParseHttpErrorCodeToTitleEnPipe, QUERY_CLIENT_DEVTOOLS_TOKEN, QUERY_PERSISTENCE_STORE_VERSION, QUERY_SYNC_PROTOCOL_VERSION, QUERY_VERSION, QueryClientFeatureType, QueryDevtoolsComponent, QueryDirective, QueryFeatureType, QueryField, QueryForm, QueryRuntimeErrorCode, QueryShortNamePipe, QueryStateType, QueryStore, SERVER_ERROR_KIND, SERVER_VIOLATION_ERROR_KIND, SPEED_BUFFER_TIME_IN_MS, SearchQueryField, SortQueryField, StringArrayQueryField, V2BearerAuthProvider, V2Query, V2QueryClient, V2QueryCreator, addQueryContainerHandling, applyQueryFeatures, authAccessTokenExpiryUnreadable, authExtractTokensResponseMissingAccessToken, authExtractTokensResponseMissingRefreshToken, authExtractTokensResponseNotObject, authProviderFeatureUsedMultipleTimes, booleanArrayQueryField, buildQueryCacheKey, buildQueryString, buildRoute, buildTimestampFromSeconds, castQueryCreatorTypes, circularQueryDependency, circularQueryDependencyChecker, clearQueryDevtoolsTokenTtl, computeQueryAuthHeader, computeQueryBody, computeQueryHeaders, computeQueryMethod, computeQueryQueryParams, contentfulGqlLikePaginationAdapter, createAuthGuard, createBaseQuery, createBaseQueryCreator, createBearerAuthProvider, createDefaultRetryFn, createDeleteQuery, createExecuteFn, createGetQuery, createGqlExecuteFn, createGqlMutationViaGet, createGqlMutationViaPost, createGqlQuery, createGqlQueryCreator, createGqlQueryViaGet, createGqlQueryViaPost, createHeadQuery, createHttpRequest, createIndexedDbQueryPersistenceAdapter, createInfinityQueryConfig, createLegacyQueryCreator, createNoopQueryPersistenceAdapter, createOptionsQuery, createPagedQueryStack, createPatchQuery, createPersistentAuthFeature, createPostQuery, createPutQuery, createQuery, createQueryBatch, createQueryClient, createQueryCollection, createQueryCollectionSignal, createQueryCollectionSubject, createQueryContext, createQueryCreator, createQueryErrorResponse, createQueryFeature, createQueryGroup, createQueryInvalidationFilter, createQueryKeyLockManager, createQueryObject, createQueryPersistenceEngine, createQueryRepository, createQuerySnapshotFn, createQueryStack, createQuerySubmission, createQuerySyncEngine, createQuerySyncTransport, createSecureDeleteQuery, createSecureExecuteFactory, createSecureExecuteFn, createSecureGetQuery, createSecureGqlExecuteFn, createSecureGqlMutationViaGet, createSecureGqlMutationViaPost, createSecureGqlQuery, createSecureGqlQueryCreator, createSecureGqlQueryViaGet, createSecureGqlQueryViaPost, createSecureHeadQuery, createSecureOptionsQuery, createSecurePatchQuery, createSecurePostQuery, createSecurePutQuery, createSecureQuery, createSecureQueryCreator, createTrackingFeature, createWebSocketClient, dateArrayQueryField, dateQueryField, decryptBearer, deepFreeze, def, defineQueryForm, detectContentTypeHeader, dynLikePaginationAdapter, effectComputed, emptyQueryArgs, ethletePaginationAdapter, executeUntilSettled, executeUntilSettled$, extractExpiresInSeconds, extractFormViolations, extractHtmlErrorMessage, extractQuery, fakePaginationAdapter, filterFailure, filterNull, filterQueryStates, filterSuccess, forEachHeader, getDefaultHeaders, getQueryFeatureUsage, getResponseUrl, ggLikePaginationAdapter, gql, gqlDataPropertyMissingInResponse, gqlErrorsInResponse, gqlTransformerFor, hasDefaultQueryRetry, hasHeader, htmlErrorPayload, htmlQueryErrorParser, ignoreAutoRefresh, injectInfinityQueryResponseDelay, injectQueryContext, insertFrom, invalidBaseRouteError, invalidBodyError, invalidRouteError, invalidStateInsideSecureExecuteFactory, invalidateOnSuccess, invalidatesUsedOnRead, isAutoRefresh, isBasicAuthProvider, isBearerAuthProvider, isClassValidatorError, isCreateGqlQueryOptions, isCustomHeaderAuthProvider, isEmptyString, isGqlQueryConfig, isHtmlErrorPayload, isIdempotentQueryMethod, isLegacyQuery, isNaN$1 as isNaN, isPageOutOfRangeError, isQuery, isQueryCollection, isQueryErrorResponse, isQueryStateCancelled, isQueryStateFailure, isQueryStateLoading, isQueryStatePrepared, isQueryStateSuccess, isRequestError, isSymfonyError, isSymfonyFormViolationListError, isSymfonyListError, isSymfonyPagerfantaOutOfRangeError, isUnderInvalidatedUrl, legacyPrepareFallbackInjector, legacyPrepareWithoutInjectionContext, mapToPaginated, mapViolationsToFormErrors, maybeExecute, mergeHeaders, nestedEffect, numberArrayQueryField, pagedQueryStackNextPageCalledWithoutPreviousPage, pagedQueryStackPageBiggerThanTotalPages, pagedQueryStackPreviousPageCalledButAlreadyAtFirstPage, paginatedEntityValueUpdater, parseAllXhrResponseHeaders, parseHttpErrorCodeToMessageDe, parseHttpErrorCodeToMessageEn, parseHttpErrorCodeToTitleDe, parseHttpErrorCodeToTitleEn, pathParamsMissingInRouteFunctionError, provideInfinityQueryResponseDelay, provideLegacyPrepareFallback, provideQueryClientForDevtools, provideQueryDevtools, queryArrayComputed, queryBatchAlreadyRunning, queryBatchWithArgsUsed, queryClientFeatureUsedMultipleTimes, queryComputed, queryComputedTillTruthy, queryCreatedInReactiveContext, queryErrorMessage, queryErrorMessages, queryExecute, queryExecutedAfterDestroyMessage, queryExecutedWhileParkedMessage, queryFeatureUsedMultipleTimes, queryField, querySequence, querySequenceAlreadyRunning, queryStackWithArgsUsed, queryStackWithResponseUpdateUsed, queryStateAlreadyHasKeyError, queryStateCannotTransformError, queryStateDoesNotContainKeyError, queryStateErrorSignal, queryStateLoadingSignal, queryStateResponseSignal, queryStateSignal, recordExecutionArgs, registerQueryErrorParser, removeFrom, request, resetExecuteState, resetPageOnError, resolveInvalidationUrl, resolveQueryHeaders, resolveQueryTags, runDefaultQueryRetry, runQueryErrorParsers, searchQueryField, serializeBody, setDefaultQueryRetryFn, setQueryDevtoolsTokenTtl, setQueryDevtoolsUiMounted, setupQueryDependencies, setupQueryExecuteState, setupQueryState, shouldAutoExecuteGqlQuery, shouldAutoExecuteQuery, shouldCacheQuery, shouldRetryRequest, silenceMissingWithArgsFeatureErrorUsedButWithArgsPresent, skipPaginationPageParamCalculator, skipParkedExecution, sortQueryField, splitQueryConfig, stringArrayQueryField, switchQueryCollectionState, switchQueryState, symfonyQueryErrorParser, tableSortQueryField, takeUntilResponse, toQuerySignal, toQuerySubject, tokensNotAvailableInsideAuthAndExec, transformArrayResponse, transformExecStateToQueryState, transformGql, transformMethod, transformPaginatedResponse, transformToBoolean, transformToBooleanArray, transformToDate, transformToDateArray, transformToNumber, transformToNumberArray, transformToSort, transformToSortQueryParam, transformToString, transformToStringArray, transformToTableSort, transformToTableSortQueryParam, uncacheableRequestHasAllowCacheParam, uncacheableRequestHasCacheKeyParam, unwrapGqlResponseWithErrors, unwrapQuerySyncMessage, v2BuildQueryCacheKey, v2ExtractExpiresInSeconds, v2ShouldCacheQuery, v2ShouldRetryRequest, validateWithQuery, validateWithV2Query, withArgs, withArgsQueryFeatureMissingButRouteIsFunction, withAuthenticationQuery, withAutoRefresh, withAutoRefreshUsedInManualQuery, withAutoRefreshUsedOnUnsupportedHttpMethod, withBearerAuthMultiTabSync, withDefaultRetry, withErrorHandling, withEthleteApiErrors, withHtmlErrorParsing, withInactivityLogout, withLogging, withLongPolling, withLongPollingUsedOnUnsupportedHttpMethod, withLongPollingUsedWithPolling, withMultiTabSync, withOptimisticUpdate, withOptimisticUpdateUsedOnRead, withPageResetOnError, withPersistentAuth, withPolling, withPollingUsedOnUnsupportedHttpMethod, withQueryPersistence, withRefreshQuery, withResponseUpdate, withSuccessHandling, withSymfonyErrors, withTokenExpirationWarning, withTokenRevocation, withTracking, wrapAsObservableSignal, wrapQuerySyncMessage, EMPTY_QUERY_DEVTOOLS_FAULT as ɵEMPTY_QUERY_DEVTOOLS_FAULT, MAX_QUERY_BATCH_TOMBSTONES as ɵMAX_QUERY_BATCH_TOMBSTONES, MAX_QUERY_BATCH_TOMBSTONE_BUCKETS as ɵMAX_QUERY_BATCH_TOMBSTONE_BUCKETS, MAX_QUERY_DEVTOOLS_TOMBSTONES as ɵMAX_QUERY_DEVTOOLS_TOMBSTONES, QUERY_DEVTOOLS_FAULT_STATUSES as ɵQUERY_DEVTOOLS_FAULT_STATUSES, QUERY_DEVTOOLS_TOKEN_TTL_LIMIT as ɵQUERY_DEVTOOLS_TOKEN_TTL_LIMIT, addQueryDevtoolsAuthAccount as ɵaddQueryDevtoolsAuthAccount, applyQueryDevtoolsOverrides as ɵapplyQueryDevtoolsOverrides, applyQueryDevtoolsTokenTtl as ɵapplyQueryDevtoolsTokenTtl, armAllQueryDevtoolsMocks as ɵarmAllQueryDevtoolsMocks, armQueryDevtoolsMock as ɵarmQueryDevtoolsMock, armQueryDevtoolsOverrideTransfer as ɵarmQueryDevtoolsOverrideTransfer, canOverrideQueryDevtoolsTokenTtl as ɵcanOverrideQueryDevtoolsTokenTtl, clearQueryDevtoolsArmedMocks as ɵclearQueryDevtoolsArmedMocks, clearQueryDevtoolsAuthCredentials as ɵclearQueryDevtoolsAuthCredentials, clearQueryDevtoolsAuthSessions as ɵclearQueryDevtoolsAuthSessions, clearQueryDevtoolsFaults as ɵclearQueryDevtoolsFaults, clearQueryDevtoolsMockStore as ɵclearQueryDevtoolsMockStore, clearQueryDevtoolsOverrideStore as ɵclearQueryDevtoolsOverrideStore, clearQueryDevtoolsStore as ɵclearQueryDevtoolsStore, clearQueryDevtoolsTombstones as ɵclearQueryDevtoolsTombstones, clearRestoredQueryDevtoolsOverrides as ɵclearRestoredQueryDevtoolsOverrides, collectLeafPaths as ɵcollectLeafPaths, collectQueryDevtoolsSchemaComponents as ɵcollectQueryDevtoolsSchemaComponents, countUnresolvedQueryDevtoolsOverrides as ɵcountUnresolvedQueryDevtoolsOverrides, createQueryDevtoolsOverrides as ɵcreateQueryDevtoolsOverrides, deleteQueryDevtoolsMock as ɵdeleteQueryDevtoolsMock, detectPaginationShape as ɵdetectPaginationShape, forgetQueryDevtoolsAuthSession as ɵforgetQueryDevtoolsAuthSession, forgetQueryDevtoolsAuthSessionsFor as ɵforgetQueryDevtoolsAuthSessionsFor, generateQueryDevtoolsNumberPreset as ɵgenerateQueryDevtoolsNumberPreset, generateQueryDevtoolsSampleNumber as ɵgenerateQueryDevtoolsSampleNumber, generateQueryDevtoolsStringPreset as ɵgenerateQueryDevtoolsStringPreset, hasQueryDevtoolsOverridesAtPath as ɵhasQueryDevtoolsOverridesAtPath, isDateShapedLeaf as ɵisDateShapedLeaf, isQueryDevtoolsEnabled as ɵisQueryDevtoolsEnabled, isQueryDevtoolsFaultArmed as ɵisQueryDevtoolsFaultArmed, isQueryDevtoolsRepositoryLive as ɵisQueryDevtoolsRepositoryLive, loadQueryDevtoolsSchema as ɵloadQueryDevtoolsSchema, loginQueryDevtoolsAuthAccount as ɵloginQueryDevtoolsAuthAccount, logoutQueryDevtoolsAuthSession as ɵlogoutQueryDevtoolsAuthSession, matchesQueryDevtoolsMockPattern as ɵmatchesQueryDevtoolsMockPattern, matchesQueryDevtoolsMockQuery as ɵmatchesQueryDevtoolsMockQuery, measureQueryDevtoolsPayload as ɵmeasureQueryDevtoolsPayload, parseQueryDevtoolsOverrideTransfer as ɵparseQueryDevtoolsOverrideTransfer, queryDevtoolsAbout as ɵqueryDevtoolsAbout, queryDevtoolsAllowsLocalAuthSessions as ɵqueryDevtoolsAllowsLocalAuthSessions, queryDevtoolsApiEnvIds as ɵqueryDevtoolsApiEnvIds, queryDevtoolsApiEnvIsProduction as ɵqueryDevtoolsApiEnvIsProduction, queryDevtoolsApiEnvScope as ɵqueryDevtoolsApiEnvScope, queryDevtoolsApiEnvValues as ɵqueryDevtoolsApiEnvValues, queryDevtoolsApiEnvs as ɵqueryDevtoolsApiEnvs, queryDevtoolsArmedMocks as ɵqueryDevtoolsArmedMocks, queryDevtoolsArmedMocksRestored as ɵqueryDevtoolsArmedMocksRestored, queryDevtoolsAuthAccountsFor as ɵqueryDevtoolsAuthAccountsFor, queryDevtoolsAuthActive as ɵqueryDevtoolsAuthActive, queryDevtoolsAuthFieldsFor as ɵqueryDevtoolsAuthFieldsFor, queryDevtoolsAuthOtherScopeCount as ɵqueryDevtoolsAuthOtherScopeCount, queryDevtoolsAuthSessions as ɵqueryDevtoolsAuthSessions, queryDevtoolsAuthSessionsFor as ɵqueryDevtoolsAuthSessionsFor, queryDevtoolsAuthTabLocal as ɵqueryDevtoolsAuthTabLocal, queryDevtoolsEntries as ɵqueryDevtoolsEntries, queryDevtoolsFaults as ɵqueryDevtoolsFaults, queryDevtoolsFaultsRestored as ɵqueryDevtoolsFaultsRestored, queryDevtoolsMockId as ɵqueryDevtoolsMockId, queryDevtoolsMocks as ɵqueryDevtoolsMocks, queryDevtoolsOverridePersistence as ɵqueryDevtoolsOverridePersistence, queryDevtoolsRequestPath as ɵqueryDevtoolsRequestPath, queryDevtoolsResponseHistory as ɵqueryDevtoolsResponseHistory, queryDevtoolsRestoredOverridesScope as ɵqueryDevtoolsRestoredOverridesScope, queryDevtoolsSchemaNames as ɵqueryDevtoolsSchemaNames, queryDevtoolsSchemaRoutes as ɵqueryDevtoolsSchemaRoutes, queryDevtoolsSchemaState as ɵqueryDevtoolsSchemaState, queryDevtoolsSettings as ɵqueryDevtoolsSettings, queryDevtoolsStorage as ɵqueryDevtoolsStorage, queryDevtoolsTokenTtls as ɵqueryDevtoolsTokenTtls, readQueryDevtoolsStore as ɵreadQueryDevtoolsStore, registerEthleteVersion as ɵregisterEthleteVersion, removeQueryDevtoolsAuthAccount as ɵremoveQueryDevtoolsAuthAccount, renameQueryDevtoolsAuthSession as ɵrenameQueryDevtoolsAuthSession, restoredQueryDevtoolsOverrides as ɵrestoredQueryDevtoolsOverrides, saveQueryDevtoolsMock as ɵsaveQueryDevtoolsMock, seedQueryDevtoolsSchemaBody as ɵseedQueryDevtoolsSchemaBody, seedQueryDevtoolsSchemaRoute as ɵseedQueryDevtoolsSchemaRoute, serializeQueryDevtoolsOverrideTransfer as ɵserializeQueryDevtoolsOverrideTransfer, setQueryDevtoolsApiEnv as ɵsetQueryDevtoolsApiEnv, setQueryDevtoolsAppInfo as ɵsetQueryDevtoolsAppInfo, setQueryDevtoolsArmedMocksScope as ɵsetQueryDevtoolsArmedMocksScope, setQueryDevtoolsAuthCredentials as ɵsetQueryDevtoolsAuthCredentials, setQueryDevtoolsAuthTabLocal as ɵsetQueryDevtoolsAuthTabLocal, setQueryDevtoolsFault as ɵsetQueryDevtoolsFault, setQueryDevtoolsFaultsScope as ɵsetQueryDevtoolsFaultsScope, setQueryDevtoolsOverridePersistence as ɵsetQueryDevtoolsOverridePersistence, setQueryDevtoolsOverridesScope as ɵsetQueryDevtoolsOverridesScope, setQueryDevtoolsSettings as ɵsetQueryDevtoolsSettings, smartDuplicateArrayItem as ɵsmartDuplicateArrayItem, snapshotQueryDevtoolsHandle as ɵsnapshotQueryDevtoolsHandle, stringifyQueryRouteParts as ɵstringifyQueryRouteParts, sumQueryDevtoolsStats as ɵsumQueryDevtoolsStats, switchQueryDevtoolsAuthSession as ɵswitchQueryDevtoolsAuthSession, tombstoneOf as ɵtombstoneOf, writeQueryDevtoolsStore as ɵwriteQueryDevtoolsStore };
|
|
18755
19429
|
//# sourceMappingURL=ethlete-query.mjs.map
|