@ethlete/query 6.0.0-next.53 → 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 +59 -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 +1853 -1126
- 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 +1031 -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;
|
|
@@ -907,244 +1486,25 @@ const formatQueryDevtoolsDuration = (ms) => {
|
|
|
907
1486
|
return round(ms / 1000, 's');
|
|
908
1487
|
if (ms < 3_600_000)
|
|
909
1488
|
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;
|
|
1126
|
-
};
|
|
1127
|
-
/**
|
|
1128
|
-
* Hands one auth provider to the devtools' session vault, which then keeps the live session's tokens in
|
|
1129
|
-
* step with the stored one. A no-op without `provideQueryDevtools()`.
|
|
1130
|
-
* @internal
|
|
1131
|
-
*/
|
|
1132
|
-
const registerQueryDevtoolsAuthProvider = (registration) => authProviderRegistrar?.(registration) ?? (() => undefined);
|
|
1133
|
-
let authSeedReader = null;
|
|
1489
|
+
return round(ms / 3_600_000, 'h');
|
|
1490
|
+
};
|
|
1134
1491
|
/**
|
|
1135
|
-
*
|
|
1136
|
-
*
|
|
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,12 +1625,12 @@ 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 = {
|
|
1188
1632
|
destroyRef: undefined, // Will be set after injector creation
|
|
1189
|
-
scopeDestroyRef: hostInjector.get(DestroyRef),
|
|
1633
|
+
scopeDestroyRef: options.queryConfig?.scopeDestroyRef ?? hostInjector.get(DestroyRef),
|
|
1190
1634
|
client: hostInjector.get(clientToken),
|
|
1191
1635
|
injector: undefined, // Will be set after injector creation
|
|
1192
1636
|
ngErrorHandler: hostInjector.get(ErrorHandler),
|
|
@@ -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;
|
|
@@ -1457,7 +1743,9 @@ const createQueryFeature = (config) => {
|
|
|
1457
1743
|
* The arguments are read within a computed function, so you can use reactive values.
|
|
1458
1744
|
*
|
|
1459
1745
|
* Return `null` to park the query: it is reset (`response()` and `executionState()` become `null`),
|
|
1460
|
-
* and polling and auto refresh pause until args are set again.
|
|
1746
|
+
* and polling and auto refresh pause until args are set again. A mutation that is still in flight
|
|
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.
|
|
1461
1749
|
*
|
|
1462
1750
|
* Changing arguments will automatically trigger a new execution of the query if it is eligible for auto execution (e.g. a GET request).
|
|
1463
1751
|
*/
|
|
@@ -1474,6 +1762,8 @@ const withArgs = (args) => {
|
|
|
1474
1762
|
nestedEffect(() => {
|
|
1475
1763
|
const currArgsNow = context.state.args();
|
|
1476
1764
|
if (currArgsNow === null) {
|
|
1765
|
+
if (!context.flags.shouldAutoExecuteMethod && context.state.loading() !== null)
|
|
1766
|
+
return;
|
|
1477
1767
|
untracked(() => {
|
|
1478
1768
|
if (context.state.subtle.request() !== null || context.state.loading() !== null)
|
|
1479
1769
|
context.execute.reset();
|
|
@@ -1481,6 +1771,8 @@ const withArgs = (args) => {
|
|
|
1481
1771
|
return;
|
|
1482
1772
|
}
|
|
1483
1773
|
untracked(() => {
|
|
1774
|
+
if (context.state.subtle.isExplicitArgs(currArgsNow))
|
|
1775
|
+
return;
|
|
1484
1776
|
const guard = context.state.subtle.autoExecuteGuard();
|
|
1485
1777
|
if (context.flags.shouldAutoExecute && (guard === null || guard()))
|
|
1486
1778
|
context.execute({ args: currArgsNow });
|
|
@@ -1590,9 +1882,9 @@ const withPolling = (options) => {
|
|
|
1590
1882
|
}, Math.max(0, firstDelay));
|
|
1591
1883
|
};
|
|
1592
1884
|
const remainingDelay = () => lastTickAt + currentInterval() - Date.now();
|
|
1593
|
-
//
|
|
1594
|
-
|
|
1595
|
-
|
|
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));
|
|
1596
1888
|
nestedEffect(() => {
|
|
1597
1889
|
const args = context.state.args();
|
|
1598
1890
|
untracked(() => {
|
|
@@ -1895,7 +2187,7 @@ const withResponseUpdate = (options) => {
|
|
|
1895
2187
|
if (response === null)
|
|
1896
2188
|
return;
|
|
1897
2189
|
untracked(() => {
|
|
1898
|
-
context.state.
|
|
2190
|
+
context.state.response.set(response);
|
|
1899
2191
|
});
|
|
1900
2192
|
}, { injector: context.deps.injector });
|
|
1901
2193
|
},
|
|
@@ -1960,6 +2252,62 @@ const withPageResetOnError = (options) => {
|
|
|
1960
2252
|
});
|
|
1961
2253
|
};
|
|
1962
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
|
+
|
|
1963
2311
|
const parsers = [];
|
|
1964
2312
|
/**
|
|
1965
2313
|
* Adds a parser to the pipeline `createQueryErrorResponse` runs, ahead of the built-in ladder.
|
|
@@ -2016,7 +2364,11 @@ const createDefaultRetryFn = (options = {}) => {
|
|
|
2016
2364
|
const jitter = clamp(options.jitter ?? DEFAULT_JITTER, 0, 1);
|
|
2017
2365
|
const retryableStatusCodes = options.retryableStatusCodes;
|
|
2018
2366
|
const retryNonIdempotent = options.retryNonIdempotent ?? false;
|
|
2019
|
-
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
|
+
};
|
|
2020
2372
|
const backoff = (retryCount) => {
|
|
2021
2373
|
const exponential = baseDelayMs * Math.pow(2, retryCount);
|
|
2022
2374
|
const spread = jitter === 0 ? 1 : 1 + (Math.random() * 2 - 1) * jitter;
|
|
@@ -2066,6 +2418,7 @@ const isQueryErrorResponse = (error) => {
|
|
|
2066
2418
|
/**
|
|
2067
2419
|
* Every message carried by an error, flattened into a plain list - one entry for a single error,
|
|
2068
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.
|
|
2069
2422
|
*
|
|
2070
2423
|
* The single/list split exists because that is how APIs answer; UI almost never wants to branch on
|
|
2071
2424
|
* it. Reach for this when rendering, and keep {@link QueryErrorResponse} for anything that needs
|
|
@@ -2081,7 +2434,9 @@ const isQueryErrorResponse = (error) => {
|
|
|
2081
2434
|
const queryErrorMessages = (error) => {
|
|
2082
2435
|
if (!error)
|
|
2083
2436
|
return [];
|
|
2084
|
-
|
|
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];
|
|
2085
2440
|
};
|
|
2086
2441
|
/**
|
|
2087
2442
|
* The first message of an error, or `null` when there is none - the single-line counterpart to
|
|
@@ -2210,14 +2565,24 @@ const setupQueryState = (options) => {
|
|
|
2210
2565
|
// set is overridden again the next time the source's dependencies change.
|
|
2211
2566
|
const argsSource = signal(() => null, /* @ts-ignore */
|
|
2212
2567
|
...(ngDevMode ? [{ debugName: "argsSource" }] : /* istanbul ignore next */ []));
|
|
2213
|
-
|
|
2214
|
-
...(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
|
+
} });
|
|
2215
2576
|
const hasArgsSource = signal(false, /* @ts-ignore */
|
|
2216
2577
|
...(ngDevMode ? [{ debugName: "hasArgsSource" }] : /* istanbul ignore next */ []));
|
|
2217
2578
|
const setArgsSource = (source) => {
|
|
2218
2579
|
argsSource.set(source);
|
|
2219
2580
|
hasArgsSource.set(true);
|
|
2220
2581
|
};
|
|
2582
|
+
const setExplicitArgs = (value) => {
|
|
2583
|
+
explicitArgs = value;
|
|
2584
|
+
args.set(value);
|
|
2585
|
+
};
|
|
2221
2586
|
const lastTimeExecutedAt = signal(null, /* @ts-ignore */
|
|
2222
2587
|
...(ngDevMode ? [{ debugName: "lastTimeExecutedAt" }] : /* istanbul ignore next */ []));
|
|
2223
2588
|
const lastTriggeredBy = signal(null, /* @ts-ignore */
|
|
@@ -2236,6 +2601,8 @@ const setupQueryState = (options) => {
|
|
|
2236
2601
|
...(ngDevMode ? [{ debugName: "defaultRunOptions" }] : /* istanbul ignore next */ []));
|
|
2237
2602
|
const autoExecuteGuard = signal(null, /* @ts-ignore */
|
|
2238
2603
|
...(ngDevMode ? [{ debugName: "autoExecuteGuard" }] : /* istanbul ignore next */ []));
|
|
2604
|
+
const beforeExecute = signal(null, /* @ts-ignore */
|
|
2605
|
+
...(ngDevMode ? [{ debugName: "beforeExecute" }] : /* istanbul ignore next */ []));
|
|
2239
2606
|
options.destroyRef?.onDestroy(() => {
|
|
2240
2607
|
requestEventsSubscription.unsubscribe();
|
|
2241
2608
|
requestEvents$.complete();
|
|
@@ -2306,8 +2673,11 @@ const setupQueryState = (options) => {
|
|
|
2306
2673
|
unbindRequestEvents,
|
|
2307
2674
|
defaultRunOptions,
|
|
2308
2675
|
autoExecuteGuard,
|
|
2676
|
+
beforeExecute,
|
|
2309
2677
|
setArgsSource,
|
|
2310
2678
|
hasArgsSource: hasArgsSource.asReadonly(),
|
|
2679
|
+
setExplicitArgs,
|
|
2680
|
+
isExplicitArgs: (value) => value !== null && value === explicitArgs,
|
|
2311
2681
|
devtoolsStats: options.devtoolsStats ?? null,
|
|
2312
2682
|
devtoolsFormLinks: options.devtoolsFormLinks ?? null,
|
|
2313
2683
|
},
|
|
@@ -2406,17 +2776,18 @@ const createQuerySnapshotFn = (options) => {
|
|
|
2406
2776
|
// snapshot has to settle itself - otherwise `executeUntilSettled` resolves with a snapshot still
|
|
2407
2777
|
// reporting the execution as loading.
|
|
2408
2778
|
unregisterScopeListener = context.deps.destroyRef.onDestroy(cancel);
|
|
2779
|
+
const watcher = createObservableSignalWatcher(context.deps.injector);
|
|
2409
2780
|
const snapshot = {
|
|
2410
|
-
args:
|
|
2411
|
-
response:
|
|
2412
|
-
latestHttpEvent:
|
|
2413
|
-
loading:
|
|
2414
|
-
error:
|
|
2415
|
-
lastTimeExecutedAt:
|
|
2416
|
-
triggeredBy:
|
|
2417
|
-
isAlive:
|
|
2418
|
-
id:
|
|
2419
|
-
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),
|
|
2420
2791
|
};
|
|
2421
2792
|
return snapshot;
|
|
2422
2793
|
});
|
|
@@ -2495,22 +2866,27 @@ const trackRefreshedExecutions = (options) => {
|
|
|
2495
2866
|
};
|
|
2496
2867
|
const createQueryObject = (options) => {
|
|
2497
2868
|
const { state, execute, deps } = options;
|
|
2498
|
-
const destroy = () =>
|
|
2869
|
+
const destroy = () => {
|
|
2870
|
+
if (!deps.injector.destroyed)
|
|
2871
|
+
deps.injector.destroy();
|
|
2872
|
+
};
|
|
2499
2873
|
const setResponse = (response) => state.response.set(response);
|
|
2500
2874
|
const setLoading = (loading) => state.loading.set(loading);
|
|
2501
2875
|
const setError = (error) => state.error.set(error);
|
|
2502
2876
|
const createSnapshot = createQuerySnapshotFn({ state, execute, deps });
|
|
2503
2877
|
// Pre-wrap all public signals once so both `query` and `roQuery` share the same instances.
|
|
2504
|
-
const
|
|
2505
|
-
const
|
|
2506
|
-
const
|
|
2507
|
-
const
|
|
2508
|
-
const
|
|
2509
|
-
const
|
|
2510
|
-
const
|
|
2511
|
-
const
|
|
2512
|
-
const
|
|
2513
|
-
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));
|
|
2514
2890
|
const asReadonly = () => {
|
|
2515
2891
|
const roQuery = {
|
|
2516
2892
|
args: wrappedArgs,
|
|
@@ -2522,6 +2898,7 @@ const createQueryObject = (options) => {
|
|
|
2522
2898
|
triggeredBy: wrappedTriggeredBy,
|
|
2523
2899
|
id: wrappedId,
|
|
2524
2900
|
createSnapshot,
|
|
2901
|
+
url: wrappedUrl,
|
|
2525
2902
|
executionState: wrappedExecutionState,
|
|
2526
2903
|
};
|
|
2527
2904
|
return roQuery;
|
|
@@ -2537,6 +2914,7 @@ const createQueryObject = (options) => {
|
|
|
2537
2914
|
triggeredBy: wrappedTriggeredBy,
|
|
2538
2915
|
id: wrappedId,
|
|
2539
2916
|
createSnapshot,
|
|
2917
|
+
url: wrappedUrl,
|
|
2540
2918
|
reset: execute.reset,
|
|
2541
2919
|
abort: execute.abort,
|
|
2542
2920
|
asReadonly,
|
|
@@ -2587,6 +2965,12 @@ const createBaseQuery = (options) => {
|
|
|
2587
2965
|
deps,
|
|
2588
2966
|
};
|
|
2589
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
|
+
}
|
|
2590
2974
|
maybeExecute({ execute, flags, state });
|
|
2591
2975
|
trackRefreshedExecutions({ state, deps });
|
|
2592
2976
|
const query = createQueryObject({ state, execute, deps });
|
|
@@ -2698,7 +3082,7 @@ const extractExpiresInSeconds = (headers) => {
|
|
|
2698
3082
|
if (cacheControl && /(?:^|,)\s*no-(?:cache|store)(?:\s|,|$)/i.test(cacheControl)) {
|
|
2699
3083
|
return null;
|
|
2700
3084
|
}
|
|
2701
|
-
const maxAgeMatch = cacheControl?.match(/(?:^|,)\s*(
|
|
3085
|
+
const maxAgeMatch = cacheControl?.match(/(?:^|,)\s*max-age=(\d+)/i) ?? cacheControl?.match(/(?:^|,)\s*s-maxage=(\d+)/i);
|
|
2702
3086
|
if (maxAgeMatch?.[1]) {
|
|
2703
3087
|
maxAge = parseInt(maxAgeMatch[1]);
|
|
2704
3088
|
}
|
|
@@ -2794,6 +3178,7 @@ const queryExecute = (options) => {
|
|
|
2794
3178
|
}
|
|
2795
3179
|
const defaultRunOptions = state.subtle.defaultRunOptions();
|
|
2796
3180
|
const runQueryOptions = defaultRunOptions || options.options ? { ...defaultRunOptions, ...options.options } : undefined;
|
|
3181
|
+
const onRequest = state.subtle.beforeExecute()?.(args);
|
|
2797
3182
|
const { key, request, executed } = deps.client.repository.request({
|
|
2798
3183
|
route: creatorInternals.route,
|
|
2799
3184
|
method: creatorInternals.method,
|
|
@@ -2807,25 +3192,44 @@ const queryExecute = (options) => {
|
|
|
2807
3192
|
isSecure,
|
|
2808
3193
|
isRefreshable,
|
|
2809
3194
|
silenceUncacheableAllowCacheError: queryConfig.silenceUncacheableAllowCacheError,
|
|
3195
|
+
tags: creator?.tags ? resolveQueryTags(creator.tags, args) : undefined,
|
|
2810
3196
|
});
|
|
2811
3197
|
executeState.previousKey.set(key);
|
|
2812
3198
|
state.lastTimeExecutedAt.set(Date.now());
|
|
2813
3199
|
state.lastTriggeredBy.set(runQueryOptions?.triggeredBy ?? null);
|
|
2814
3200
|
state.subtle.request.set(request);
|
|
3201
|
+
onRequest?.(request);
|
|
2815
3202
|
state.subtle.bindRequestEvents(request);
|
|
2816
3203
|
state.subtle.devtoolsStats?.recordExecution({ didRequest: executed, body: args?.body, url: request.url });
|
|
2817
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
|
+
};
|
|
2818
3216
|
/**
|
|
2819
3217
|
* Writes the args an execution runs with to `state.args`, so a later bare `execute()` re-sends them.
|
|
2820
|
-
* A `
|
|
2821
|
-
* 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.
|
|
2822
3220
|
*/
|
|
2823
3221
|
const recordExecutionArgs = (state, args, options) => {
|
|
2824
|
-
|
|
3222
|
+
const triggeredBy = options?.triggeredBy;
|
|
3223
|
+
if (triggeredBy === LONG_POLLING_TRIGGER)
|
|
3224
|
+
return;
|
|
3225
|
+
if (!state.subtle.hasArgsSource())
|
|
3226
|
+
return state.args.set(args);
|
|
3227
|
+
if (args === null || args === state.args() || (triggeredBy && FEATURE_TRIGGERS.includes(triggeredBy)))
|
|
2825
3228
|
return;
|
|
2826
|
-
state.
|
|
3229
|
+
state.subtle.setExplicitArgs(args);
|
|
2827
3230
|
};
|
|
2828
3231
|
const LONG_POLLING_TRIGGER = 'long-polling';
|
|
3232
|
+
const FEATURE_TRIGGERS = ['polling', 'auto-refresh'];
|
|
2829
3233
|
const CIRCULAR_DEPENDENCY_WINDOW_MS = 100;
|
|
2830
3234
|
const CIRCULAR_DEPENDENCY_MAX_REPEATS = 5;
|
|
2831
3235
|
/**
|
|
@@ -2862,6 +3266,8 @@ const createExecuteFn = (executeOptions) => {
|
|
|
2862
3266
|
const reset = () => untracked(() => resetExecuteState({ executeState, executeOptions }));
|
|
2863
3267
|
const exec = (executeArgs) => untracked(() => {
|
|
2864
3268
|
const { args = executeOptions.state.args(), options } = executeArgs ?? {};
|
|
3269
|
+
if (skipParkedExecution(executeOptions.state, args, executeOptions.creatorInternals.route))
|
|
3270
|
+
return;
|
|
2865
3271
|
circularChecker.check(args);
|
|
2866
3272
|
aborter.capture();
|
|
2867
3273
|
recordExecutionArgs(executeOptions.state, args, options);
|
|
@@ -2943,153 +3349,6 @@ const createBaseQueryCreator = (config) => {
|
|
|
2943
3349
|
return queryCreator;
|
|
2944
3350
|
};
|
|
2945
3351
|
|
|
2946
|
-
/**
|
|
2947
|
-
* Route building, query-string serialization and the token clock - shared by the current generation and
|
|
2948
|
-
* the legacy V2 client, which re-exports them under their original names.
|
|
2949
|
-
*/
|
|
2950
|
-
// codes 1-99 of the shared 0-999 `@ethlete/query` range - see `query-errors.ts` for the rest
|
|
2951
|
-
const RouteRuntimeErrorCode = {
|
|
2952
|
-
INVALID_BASE_ROUTE: 1,
|
|
2953
|
-
INVALID_ROUTE: 2,
|
|
2954
|
-
PATH_PARAMS_MISSING_IN_ROUTE_FUNCTION: 3,
|
|
2955
|
-
};
|
|
2956
|
-
const invalidBaseRouteError$1 = (data) => new RuntimeError(RouteRuntimeErrorCode.INVALID_BASE_ROUTE, 'The baseRoute must not end with a slash', data);
|
|
2957
|
-
const invalidRouteError$1 = (data) => new RuntimeError(RouteRuntimeErrorCode.INVALID_ROUTE, 'The route must start with a slash', data);
|
|
2958
|
-
const pathParamsMissingInRouteFunctionError$1 = (data) => new RuntimeError(RouteRuntimeErrorCode.PATH_PARAMS_MISSING_IN_ROUTE_FUNCTION, 'The route is a function but pathParams are missing', data);
|
|
2959
|
-
const isNaN$2 = (value) => typeof value === 'number' && Number.isNaN(value);
|
|
2960
|
-
const isEmptyString$1 = (value) => typeof value === 'string' && value.trim() === '';
|
|
2961
|
-
const buildQueryString$1 = (params, config) => {
|
|
2962
|
-
const objectNotation = config?.objectNotation ?? 'bracket';
|
|
2963
|
-
const writeArrayIndexes = config?.writeArrayIndexes ?? false;
|
|
2964
|
-
const ignoredValues = config?.ignoredValues ?? [undefined, null, Infinity, -Infinity];
|
|
2965
|
-
const ignoredValuesFns = config?.ignoredValuesFns ?? [isNaN$2, isEmptyString$1];
|
|
2966
|
-
const queryParams = [];
|
|
2967
|
-
function processValue(key, rawValue) {
|
|
2968
|
-
if (rawValue instanceof Date && Number.isNaN(rawValue.getTime()))
|
|
2969
|
-
return false;
|
|
2970
|
-
const value = rawValue instanceof Date ? rawValue.toISOString() : rawValue;
|
|
2971
|
-
if (config?.objectNotation === 'json-stringify') {
|
|
2972
|
-
if (value === undefined) {
|
|
2973
|
-
return false;
|
|
2974
|
-
}
|
|
2975
|
-
if (ignoredValues.includes(value)) {
|
|
2976
|
-
return false;
|
|
2977
|
-
}
|
|
2978
|
-
if (ignoredValuesFns.some((fn) => fn(value))) {
|
|
2979
|
-
return false;
|
|
2980
|
-
}
|
|
2981
|
-
const encodedKey = encodeURIComponent(key);
|
|
2982
|
-
const val = typeof value === 'object' ? JSON.stringify(value) : value;
|
|
2983
|
-
const encodedValue = encodeURIComponent(val);
|
|
2984
|
-
queryParams.push(`${encodedKey}=${encodedValue}`);
|
|
2985
|
-
return true;
|
|
2986
|
-
}
|
|
2987
|
-
else if (Array.isArray(value)) {
|
|
2988
|
-
let currentFilteredIndex = 0;
|
|
2989
|
-
let didAddAnyValue = false;
|
|
2990
|
-
for (const arrayValue of value) {
|
|
2991
|
-
const nestedKey = writeArrayIndexes ? `${key}[${currentFilteredIndex}]` : `${key}[]`;
|
|
2992
|
-
const didAddValue = processValue(nestedKey, arrayValue);
|
|
2993
|
-
if (didAddValue) {
|
|
2994
|
-
currentFilteredIndex++;
|
|
2995
|
-
didAddAnyValue = true;
|
|
2996
|
-
}
|
|
2997
|
-
}
|
|
2998
|
-
return didAddAnyValue;
|
|
2999
|
-
}
|
|
3000
|
-
else if (typeof value === 'object' && value !== null) {
|
|
3001
|
-
let didAddAnyValue = false;
|
|
3002
|
-
for (const [objKey, val] of Object.entries(value)) {
|
|
3003
|
-
const nestedKey = objectNotation === 'dot' ? `${key}.${objKey}` : `${key}[${objKey}]`;
|
|
3004
|
-
didAddAnyValue = processValue(nestedKey, val) || didAddAnyValue;
|
|
3005
|
-
}
|
|
3006
|
-
return didAddAnyValue;
|
|
3007
|
-
}
|
|
3008
|
-
else {
|
|
3009
|
-
if (ignoredValues.includes(value)) {
|
|
3010
|
-
return false;
|
|
3011
|
-
}
|
|
3012
|
-
if (ignoredValuesFns.some((fn) => fn(value))) {
|
|
3013
|
-
return false;
|
|
3014
|
-
}
|
|
3015
|
-
const encodedKey = encodeURIComponent(key);
|
|
3016
|
-
const encodedValue = encodeURIComponent(value);
|
|
3017
|
-
queryParams.push(`${encodedKey}=${encodedValue}`);
|
|
3018
|
-
return true;
|
|
3019
|
-
}
|
|
3020
|
-
}
|
|
3021
|
-
for (const [key, val] of Object.entries(params)) {
|
|
3022
|
-
processValue(key, val);
|
|
3023
|
-
}
|
|
3024
|
-
return queryParams.length ? queryParams.join('&') : null;
|
|
3025
|
-
};
|
|
3026
|
-
const isPlainObject = (value) => {
|
|
3027
|
-
if (!value || typeof value !== 'object')
|
|
3028
|
-
return false;
|
|
3029
|
-
const proto = Object.getPrototypeOf(value);
|
|
3030
|
-
return proto === Object.prototype || proto === null;
|
|
3031
|
-
};
|
|
3032
|
-
/** Copies query params with every plain object's keys sorted, so key order never reaches a cache key. */
|
|
3033
|
-
const sortQueryParamKeys = (value) => {
|
|
3034
|
-
if (Array.isArray(value))
|
|
3035
|
-
return value.map(sortQueryParamKeys);
|
|
3036
|
-
if (!isPlainObject(value))
|
|
3037
|
-
return value;
|
|
3038
|
-
return Object.fromEntries(Object.keys(value)
|
|
3039
|
-
.sort()
|
|
3040
|
-
.map((key) => [key, sortQueryParamKeys(value[key])]));
|
|
3041
|
-
};
|
|
3042
|
-
const buildRoute$1 = (options) => {
|
|
3043
|
-
if (options.base.endsWith('/')) {
|
|
3044
|
-
throw invalidBaseRouteError$1(options.base);
|
|
3045
|
-
}
|
|
3046
|
-
let route;
|
|
3047
|
-
if (typeof options.route === 'function') {
|
|
3048
|
-
if (!options.pathParams) {
|
|
3049
|
-
throw pathParamsMissingInRouteFunctionError$1(options.route({}));
|
|
3050
|
-
}
|
|
3051
|
-
route = options.route(options.pathParams);
|
|
3052
|
-
}
|
|
3053
|
-
else {
|
|
3054
|
-
route = options.route ?? null;
|
|
3055
|
-
}
|
|
3056
|
-
if (route && !route.startsWith('/')) {
|
|
3057
|
-
throw invalidRouteError$1(route);
|
|
3058
|
-
}
|
|
3059
|
-
if (options.queryParams) {
|
|
3060
|
-
const queryString = buildQueryString$1(options.queryParams, options.queryParamConfig);
|
|
3061
|
-
if (queryString) {
|
|
3062
|
-
route = route ? `${route}?${queryString}` : `/?${queryString}`;
|
|
3063
|
-
}
|
|
3064
|
-
}
|
|
3065
|
-
return `${options.base}${route ?? ''}`;
|
|
3066
|
-
};
|
|
3067
|
-
const buildTimestampFromSeconds$1 = (seconds) => {
|
|
3068
|
-
if (seconds === null) {
|
|
3069
|
-
return null;
|
|
3070
|
-
}
|
|
3071
|
-
return new Date(Date.now() + seconds * 1000).getTime();
|
|
3072
|
-
};
|
|
3073
|
-
/** Reads a bearer token's payload. */
|
|
3074
|
-
const decryptBearer$1 = (token) => {
|
|
3075
|
-
try {
|
|
3076
|
-
const base64Url = token.split('.')[1];
|
|
3077
|
-
if (!base64Url) {
|
|
3078
|
-
return null;
|
|
3079
|
-
}
|
|
3080
|
-
const base64 = base64Url.replace(/-/g, '+').replace(/_/g, '/');
|
|
3081
|
-
const jsonPayload = decodeURIComponent(atob(base64)
|
|
3082
|
-
.split('')
|
|
3083
|
-
.map((c) => '%' + ('00' + c.charCodeAt(0).toString(16)).slice(-2))
|
|
3084
|
-
.join(''));
|
|
3085
|
-
return JSON.parse(jsonPayload);
|
|
3086
|
-
}
|
|
3087
|
-
catch (error) {
|
|
3088
|
-
console.error('Invalid bearer token', error);
|
|
3089
|
-
return null;
|
|
3090
|
-
}
|
|
3091
|
-
};
|
|
3092
|
-
|
|
3093
3352
|
/**
|
|
3094
3353
|
* `true` on every request `@ethlete/query` sends through `HttpClient`, so an app interceptor can skip
|
|
3095
3354
|
* query requests: `if (req.context.get(IS_QUERY_REQUEST)) return next(req);`.
|
|
@@ -3443,11 +3702,18 @@ const createHttpRequest = (options) => {
|
|
|
3443
3702
|
retryState: retryState.asReadonly(),
|
|
3444
3703
|
lastDurationMs: lastDurationMs.asReadonly(),
|
|
3445
3704
|
abort,
|
|
3705
|
+
markStale: () => {
|
|
3706
|
+
if (!isStale())
|
|
3707
|
+
expiresIn.set(Date.now());
|
|
3708
|
+
},
|
|
3709
|
+
setResponse: (body) => response.set(body),
|
|
3446
3710
|
},
|
|
3447
3711
|
};
|
|
3448
3712
|
return httpRequest;
|
|
3449
3713
|
};
|
|
3450
3714
|
|
|
3715
|
+
const wrapAsObservableSignal = (source, defaultInjector) => createObservableSignalWatcher(defaultInjector).wrap(source);
|
|
3716
|
+
|
|
3451
3717
|
/* eslint-disable @typescript-eslint/no-explicit-any */
|
|
3452
3718
|
/** Transforms an array of arrays into a single array and filters out `null` values. */
|
|
3453
3719
|
const transformArrayResponse = (responses) => responses.filter((r) => !!r).flatMap((r) => r);
|
|
@@ -3459,7 +3725,7 @@ const createQueryStack = (options) => {
|
|
|
3459
3725
|
const lastQuery = newQueries[newQueries.length - 1] ?? oldQueries[oldQueries.length - 1] ?? null;
|
|
3460
3726
|
return { queries, lastQuery };
|
|
3461
3727
|
}, transform, argsKeyFn, deduplicateArgs, maxQueries = Infinity, removeStrategy = 'oldest', } = options ?? {};
|
|
3462
|
-
const injector =
|
|
3728
|
+
const injector = injectInQueryContext(Injector, 'query stack');
|
|
3463
3729
|
const queries = signal([], /* @ts-ignore */
|
|
3464
3730
|
...(ngDevMode ? [{ debugName: "queries" }] : /* istanbul ignore next */ []));
|
|
3465
3731
|
const lastQuery = signal(null, /* @ts-ignore */
|
|
@@ -3467,11 +3733,11 @@ const createQueryStack = (options) => {
|
|
|
3467
3733
|
const firstQuery = computed(() => queries()[0] ?? null, /* @ts-ignore */
|
|
3468
3734
|
...(ngDevMode ? [{ debugName: "firstQuery" }] : /* istanbul ignore next */ []));
|
|
3469
3735
|
const hasWithArgsFeature = features.some((f) => f.type === QueryFeatureType.WITH_ARGS);
|
|
3470
|
-
const
|
|
3736
|
+
const hasWithResponseUpdateFeature = features.some((f) => f.type === QueryFeatureType.WITH_RESPONSE_UPDATE);
|
|
3471
3737
|
if (hasWithArgsFeature) {
|
|
3472
3738
|
throw queryStackWithArgsUsed();
|
|
3473
3739
|
}
|
|
3474
|
-
if (
|
|
3740
|
+
if (hasWithResponseUpdateFeature) {
|
|
3475
3741
|
throw queryStackWithResponseUpdateUsed();
|
|
3476
3742
|
}
|
|
3477
3743
|
const runWithArgs = (args) => {
|
|
@@ -3724,6 +3990,7 @@ const createPagedQueryStack = (options) => {
|
|
|
3724
3990
|
const args = options.args(initialPage(), []);
|
|
3725
3991
|
untracked(() => {
|
|
3726
3992
|
stack.clear();
|
|
3993
|
+
pageDirection.set('next');
|
|
3727
3994
|
currentPageArgs.set(args);
|
|
3728
3995
|
loadedMinPage.set(initialPage());
|
|
3729
3996
|
loadedMaxPage.set(initialPage());
|
|
@@ -4499,7 +4766,8 @@ const createIndexedDbQueryPersistenceAdapter = (options) => {
|
|
|
4499
4766
|
*/
|
|
4500
4767
|
const createQueryBatch = (options) => {
|
|
4501
4768
|
const { queryCreator, args: mapArgs, stopOnError, allowCache, onItemSettled, features = [] } = options;
|
|
4502
|
-
const
|
|
4769
|
+
const requestedConcurrency = options.concurrency ?? 4;
|
|
4770
|
+
const concurrency = Number.isNaN(requestedConcurrency) ? 4 : Math.max(1, Math.floor(requestedConcurrency));
|
|
4503
4771
|
if (features.some((f) => f.type === QueryFeatureType.WITH_ARGS)) {
|
|
4504
4772
|
throw queryBatchWithArgsUsed();
|
|
4505
4773
|
}
|
|
@@ -4780,41 +5048,6 @@ const isQueryDevtoolsRepositoryLive = (repository) => {
|
|
|
4780
5048
|
return live$1.has(repository);
|
|
4781
5049
|
};
|
|
4782
5050
|
|
|
4783
|
-
/**
|
|
4784
|
-
* Resolves an {@link QueryInvalidationOptions.url} to the absolute form request URLs are built in, so
|
|
4785
|
-
* the comparison - and the message the other tabs receive - never depends on who resolves it.
|
|
4786
|
-
*/
|
|
4787
|
-
const resolveInvalidationUrl = (baseUrl, url) => {
|
|
4788
|
-
const absolute = url.startsWith('/') ? `${baseUrl}${url}` : url;
|
|
4789
|
-
// A trailing slash would put the boundary check below one character past where the URL actually
|
|
4790
|
-
// ends, which is the one way `/players/` could fail to match `/players/1`.
|
|
4791
|
-
return absolute.endsWith('/') ? absolute.slice(0, -1) : absolute;
|
|
4792
|
-
};
|
|
4793
|
-
/**
|
|
4794
|
-
* Whether a query URL is the invalidated one or sits below it. The check on what follows the prefix
|
|
4795
|
-
* is what keeps `/players` from matching `/players-archive`, which a `startsWith` alone would.
|
|
4796
|
-
*/
|
|
4797
|
-
const isUnderInvalidatedUrl = (queryUrl, invalidatedUrl) => {
|
|
4798
|
-
if (!queryUrl.startsWith(invalidatedUrl))
|
|
4799
|
-
return false;
|
|
4800
|
-
const rest = queryUrl.slice(invalidatedUrl.length);
|
|
4801
|
-
return rest === '' || rest.startsWith('/') || rest.startsWith('?') || rest.startsWith('#');
|
|
4802
|
-
};
|
|
4803
|
-
/**
|
|
4804
|
-
* Turns an invalidation into the filter the repository refreshes by, or `undefined` when nothing
|
|
4805
|
-
* narrows it - "everything in use" is the repository's own cheapest path.
|
|
4806
|
-
*/
|
|
4807
|
-
const createQueryInvalidationFilter = (options) => {
|
|
4808
|
-
const { url, filter } = options;
|
|
4809
|
-
if (!url && !filter)
|
|
4810
|
-
return undefined;
|
|
4811
|
-
return (request) => {
|
|
4812
|
-
if (url && !isUnderInvalidatedUrl(request.url, url))
|
|
4813
|
-
return false;
|
|
4814
|
-
return filter ? filter({ method: request.method, url: request.url }) : true;
|
|
4815
|
-
};
|
|
4816
|
-
};
|
|
4817
|
-
|
|
4818
5051
|
/**
|
|
4819
5052
|
* An error body can be an HTML page instead of JSON: a proxy's 502, a load balancer's maintenance page, a
|
|
4820
5053
|
* platform's "service temporarily unavailable". The markup is never worth showing, but the sentence inside it
|
|
@@ -4996,6 +5229,14 @@ const createQuerySyncEngine = (options) => {
|
|
|
4996
5229
|
// response sharing would just make a tab stop updating. Enforced here rather than documented.
|
|
4997
5230
|
const isPollingDedupeEnabled = (config.dedupePolling ?? true) && syncResponses && transport.isSupported && lockManager.isSupported;
|
|
4998
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
|
+
};
|
|
4999
5240
|
const broadcast = (event) => {
|
|
5000
5241
|
if (event.type !== 'request-success' || !event.isMultiTabSyncEnabled)
|
|
5001
5242
|
return;
|
|
@@ -5017,7 +5258,12 @@ const createQuerySyncEngine = (options) => {
|
|
|
5017
5258
|
// worth telling the other tabs about - and an uncacheable read has no shared key to send anyway.
|
|
5018
5259
|
if (!refreshOnMutation || shouldCacheQuery(event.request.method))
|
|
5019
5260
|
return;
|
|
5020
|
-
transport.post({
|
|
5261
|
+
transport.post({
|
|
5262
|
+
type: 'mutation',
|
|
5263
|
+
method: event.request.method,
|
|
5264
|
+
url: event.request.url,
|
|
5265
|
+
batch: currentOutgoingBatch(),
|
|
5266
|
+
});
|
|
5021
5267
|
};
|
|
5022
5268
|
const apply = (message) => {
|
|
5023
5269
|
if (message.type === 'response') {
|
|
@@ -5029,23 +5275,20 @@ const createQuerySyncEngine = (options) => {
|
|
|
5029
5275
|
// An invalidation is something the other tab's app code asked for explicitly, so unlike the
|
|
5030
5276
|
// mutation heuristic it is not something `refreshOnMutation: false` opts out of.
|
|
5031
5277
|
if (message.type === 'invalidate') {
|
|
5032
|
-
repository.refreshInUse(createQueryInvalidationFilter({ url: message.url }), {
|
|
5278
|
+
repository.refreshInUse(createQueryInvalidationFilter({ url: message.url, tag: message.tag }), {
|
|
5033
5279
|
type: 'invalidation',
|
|
5034
5280
|
url: message.url,
|
|
5035
5281
|
otherTab: true,
|
|
5036
|
-
|
|
5282
|
+
...(message.tag && { tag: message.tag }),
|
|
5283
|
+
}, message.batch);
|
|
5037
5284
|
return;
|
|
5038
5285
|
}
|
|
5039
5286
|
if (!refreshOnMutation)
|
|
5040
5287
|
return;
|
|
5041
5288
|
const mutation = { method: message.method, url: message.url };
|
|
5042
|
-
repository.refreshInUse(mutationFilter ? (request) => mutationFilter(mutation, request) : undefined, {
|
|
5043
|
-
type: 'mutation',
|
|
5044
|
-
url: message.url,
|
|
5045
|
-
otherTab: true,
|
|
5046
|
-
});
|
|
5289
|
+
repository.refreshInUse(mutationFilter ? (request) => mutationFilter(mutation, request) : undefined, { type: 'mutation', url: message.url, otherTab: true }, message.batch);
|
|
5047
5290
|
};
|
|
5048
|
-
const postInvalidation = (url) => transport.post({ type: 'invalidate', url });
|
|
5291
|
+
const postInvalidation = (url, tag) => transport.post({ type: 'invalidate', url, ...(tag && { tag }), batch: currentOutgoingBatch() });
|
|
5049
5292
|
const eventSubscription = repository.events$.subscribe(broadcast);
|
|
5050
5293
|
const unlisten = transport.listen(apply);
|
|
5051
5294
|
const destroy = () => {
|
|
@@ -5098,11 +5341,13 @@ const unwrapQuerySyncMessage = (data) => {
|
|
|
5098
5341
|
expiresAt: typeof expiresAt === 'number' ? expiresAt : null,
|
|
5099
5342
|
};
|
|
5100
5343
|
}
|
|
5344
|
+
const batch = typeof raw['batch'] === 'string' ? { batch: raw['batch'] } : {};
|
|
5101
5345
|
if (type === 'mutation' && typeof method === 'string' && typeof url === 'string') {
|
|
5102
|
-
return { type: 'mutation', method: method, url };
|
|
5346
|
+
return { type: 'mutation', method: method, url, ...batch };
|
|
5103
5347
|
}
|
|
5104
5348
|
if (type === 'invalidate' && (typeof url === 'string' || url === null)) {
|
|
5105
|
-
|
|
5349
|
+
const tag = raw['tag'];
|
|
5350
|
+
return { type: 'invalidate', url, tag: typeof tag === 'string' ? tag : null, ...batch };
|
|
5106
5351
|
}
|
|
5107
5352
|
return null;
|
|
5108
5353
|
};
|
|
@@ -5364,6 +5609,16 @@ const withEthleteApiErrors = (options) => () => {
|
|
|
5364
5609
|
};
|
|
5365
5610
|
};
|
|
5366
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
|
+
};
|
|
5367
5622
|
const generateUuid = () => randomId();
|
|
5368
5623
|
/** @see CreateQueryClientConfigOptions.keepUnusedFor */
|
|
5369
5624
|
const DEFAULT_KEEP_UNUSED_FOR = 300_000;
|
|
@@ -5415,8 +5670,17 @@ const createQueryRepository = (config) => {
|
|
|
5415
5670
|
queryParamConfig: config.queryString,
|
|
5416
5671
|
})
|
|
5417
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('_');
|
|
5418
5682
|
const cacheKey = shouldCache
|
|
5419
|
-
? buildQueryCacheKey(`${options.key ? options.key + '_' : ''}${keyRoute}`, {
|
|
5683
|
+
? buildQueryCacheKey(`${wireKey ? wireKey + '_' : ''}${options.key ? options.key + '_' : ''}${keyRoute}`, {
|
|
5420
5684
|
body: args?.body,
|
|
5421
5685
|
queryParams: args?.queryParams,
|
|
5422
5686
|
pathParams: args?.pathParams,
|
|
@@ -5427,6 +5691,7 @@ const createQueryRepository = (config) => {
|
|
|
5427
5691
|
const keepUnusedFor = resolveKeepUnusedFor(creatorOptions, shouldCache, runQueryOptions?.keepUnusedFor);
|
|
5428
5692
|
const isMultiTabSyncEnabled = creatorOptions?.multiTabSync !== false;
|
|
5429
5693
|
const isSecure = options.isSecure ?? false;
|
|
5694
|
+
const tags = options.tags ?? [];
|
|
5430
5695
|
// A secure response needs an explicit `persistence: true` to reach the disk: leaving an
|
|
5431
5696
|
// authenticated user's data there is a decision per endpoint, not a default. Everything else
|
|
5432
5697
|
// persists unless it opted out.
|
|
@@ -5451,6 +5716,7 @@ const createQueryRepository = (config) => {
|
|
|
5451
5716
|
isMultiTabSyncEnabled,
|
|
5452
5717
|
isPersistEnabled,
|
|
5453
5718
|
keepUnusedFor,
|
|
5719
|
+
tags,
|
|
5454
5720
|
});
|
|
5455
5721
|
const executed = !runQueryOptions?.allowCache || cacheEntry.request.isStale()
|
|
5456
5722
|
? cacheEntry.request.execute({ allowCache: runQueryOptions?.allowCache })
|
|
@@ -5483,6 +5749,7 @@ const createQueryRepository = (config) => {
|
|
|
5483
5749
|
isMultiTabSyncEnabled,
|
|
5484
5750
|
isPersistEnabled,
|
|
5485
5751
|
keepUnusedFor,
|
|
5752
|
+
tags,
|
|
5486
5753
|
});
|
|
5487
5754
|
// Announced after the entry is bound and its request is on its way, because the one thing that acts
|
|
5488
5755
|
// on it - hydration from the persisted store - is only ever allowed to fill a gap this request has
|
|
@@ -5544,6 +5811,7 @@ const createQueryRepository = (config) => {
|
|
|
5544
5811
|
cacheEntry.isPersistEnabled = bindings.some((binding) => binding.isPersistEnabled);
|
|
5545
5812
|
cacheEntry.isMultiTabSyncEnabled = bindings.every((binding) => binding.isMultiTabSyncEnabled);
|
|
5546
5813
|
cacheEntry.keepUnusedFor = Math.min(...bindings.map((binding) => binding.keepUnusedFor));
|
|
5814
|
+
cacheEntry.tags = bindings.flatMap((binding) => binding.tags);
|
|
5547
5815
|
};
|
|
5548
5816
|
const unbind = (key, consumerDestroyRef) => {
|
|
5549
5817
|
if (key === null)
|
|
@@ -5586,10 +5854,30 @@ const createQueryRepository = (config) => {
|
|
|
5586
5854
|
bumpCacheVersion();
|
|
5587
5855
|
eventsSubject.next({ type: 'unbind-all-secure' });
|
|
5588
5856
|
};
|
|
5589
|
-
|
|
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) => {
|
|
5590
5875
|
const refreshed = [];
|
|
5876
|
+
const marksUnusedStale = cause?.type === 'invalidation';
|
|
5877
|
+
const restartedInBatch = resolveRestartedBatch(cause, batch);
|
|
5591
5878
|
for (const cacheEntry of cache.values()) {
|
|
5592
|
-
|
|
5879
|
+
const isUnused = cacheEntry.consumers.size === 0;
|
|
5880
|
+
if (isUnused && !marksUnusedStale)
|
|
5593
5881
|
continue;
|
|
5594
5882
|
// Re-firing a mutation would be a side effect nobody asked for, so only reads are refreshed:
|
|
5595
5883
|
// a cacheable method, or a GQL query transported via POST. Opting into the cache alone does not
|
|
@@ -5597,8 +5885,15 @@ const createQueryRepository = (config) => {
|
|
|
5597
5885
|
// end the session.
|
|
5598
5886
|
if (!cacheEntry.isRefreshable)
|
|
5599
5887
|
continue;
|
|
5600
|
-
if (filter && !filter(cacheEntry.request))
|
|
5888
|
+
if (filter && !filter(cacheEntry.request, cacheEntry.tags))
|
|
5601
5889
|
continue;
|
|
5890
|
+
if (isUnused) {
|
|
5891
|
+
cacheEntry.request.subtle.markStale();
|
|
5892
|
+
continue;
|
|
5893
|
+
}
|
|
5894
|
+
if (restartedInBatch?.has(cacheEntry.request))
|
|
5895
|
+
continue;
|
|
5896
|
+
restartedInBatch?.add(cacheEntry.request);
|
|
5602
5897
|
refreshed.push(cacheEntry.request);
|
|
5603
5898
|
cacheEntry.request.execute({ force: true });
|
|
5604
5899
|
}
|
|
@@ -5629,7 +5924,7 @@ const createQueryRepository = (config) => {
|
|
|
5629
5924
|
return true;
|
|
5630
5925
|
};
|
|
5631
5926
|
const bind = (options) => {
|
|
5632
|
-
const { key, consumerDestroyRef, request, isSecure, isCached, isRefreshable, isMultiTabSyncEnabled, isPersistEnabled, keepUnusedFor, } = options;
|
|
5927
|
+
const { key, consumerDestroyRef, request, isSecure, isCached, isRefreshable, isMultiTabSyncEnabled, isPersistEnabled, keepUnusedFor, tags, } = options;
|
|
5633
5928
|
const cacheEntry = cache.get(key);
|
|
5634
5929
|
if (cacheEntry && isCached) {
|
|
5635
5930
|
const existingBinding = cacheEntry.consumers.get(consumerDestroyRef);
|
|
@@ -5639,6 +5934,7 @@ const createQueryRepository = (config) => {
|
|
|
5639
5934
|
existingBinding.isMultiTabSyncEnabled = isMultiTabSyncEnabled;
|
|
5640
5935
|
existingBinding.isPersistEnabled = isPersistEnabled;
|
|
5641
5936
|
existingBinding.keepUnusedFor = keepUnusedFor;
|
|
5937
|
+
existingBinding.tags = tags;
|
|
5642
5938
|
}
|
|
5643
5939
|
else {
|
|
5644
5940
|
cacheEntry.consumers.set(consumerDestroyRef, {
|
|
@@ -5648,6 +5944,7 @@ const createQueryRepository = (config) => {
|
|
|
5648
5944
|
isMultiTabSyncEnabled,
|
|
5649
5945
|
isPersistEnabled,
|
|
5650
5946
|
keepUnusedFor,
|
|
5947
|
+
tags,
|
|
5651
5948
|
});
|
|
5652
5949
|
}
|
|
5653
5950
|
mergeConsumerPolicies(cacheEntry);
|
|
@@ -5663,6 +5960,7 @@ const createQueryRepository = (config) => {
|
|
|
5663
5960
|
isMultiTabSyncEnabled,
|
|
5664
5961
|
isPersistEnabled,
|
|
5665
5962
|
keepUnusedFor,
|
|
5963
|
+
tags,
|
|
5666
5964
|
},
|
|
5667
5965
|
],
|
|
5668
5966
|
]);
|
|
@@ -5709,6 +6007,7 @@ const createQueryRepository = (config) => {
|
|
|
5709
6007
|
isRefreshable,
|
|
5710
6008
|
isMultiTabSyncEnabled,
|
|
5711
6009
|
isPersistEnabled,
|
|
6010
|
+
tags,
|
|
5712
6011
|
});
|
|
5713
6012
|
}
|
|
5714
6013
|
bumpCacheVersion();
|
|
@@ -5718,6 +6017,8 @@ const createQueryRepository = (config) => {
|
|
|
5718
6017
|
isSecure: entry.isSecure,
|
|
5719
6018
|
consumerCount: entry.consumers.size,
|
|
5720
6019
|
isUnused: entry.consumers.size === 0,
|
|
6020
|
+
isRefreshable: entry.isRefreshable,
|
|
6021
|
+
tags: entry.tags,
|
|
5721
6022
|
request: entry.request,
|
|
5722
6023
|
}));
|
|
5723
6024
|
const evict = (key, cause = 'manual') => {
|
|
@@ -5744,7 +6045,21 @@ const createQueryRepository = (config) => {
|
|
|
5744
6045
|
return repository;
|
|
5745
6046
|
};
|
|
5746
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
|
+
};
|
|
5747
6060
|
const createQueryClient = (options) => defineRootProvider(() => {
|
|
6061
|
+
if (options.baseUrl.endsWith('/'))
|
|
6062
|
+
throw invalidBaseRouteError$1(options.baseUrl);
|
|
5748
6063
|
const httpClient = inject(HttpClient);
|
|
5749
6064
|
const ngErrorHandler = inject(ErrorHandler);
|
|
5750
6065
|
const injector = inject(Injector);
|
|
@@ -5762,6 +6077,8 @@ const createQueryClient = (options) => defineRootProvider(() => {
|
|
|
5762
6077
|
dependencies: { httpClient, ngErrorHandler, injector },
|
|
5763
6078
|
});
|
|
5764
6079
|
const destroyRef = inject(DestroyRef);
|
|
6080
|
+
if (isDevMode())
|
|
6081
|
+
warnOnDuplicateClientName(options.name, destroyRef);
|
|
5765
6082
|
destroyRef.onDestroy(() => {
|
|
5766
6083
|
for (const entry of repository.subtle.cacheEntries()) {
|
|
5767
6084
|
if (entry.isUnused)
|
|
@@ -5801,15 +6118,17 @@ const createQueryClient = (options) => defineRootProvider(() => {
|
|
|
5801
6118
|
refreshQueriesInUse: () => repository.refreshInUse(),
|
|
5802
6119
|
invalidateQueries: (invalidation) => {
|
|
5803
6120
|
const url = invalidation?.url ? resolveInvalidationUrl(options.baseUrl, invalidation.url) : null;
|
|
5804
|
-
|
|
6121
|
+
const tag = invalidation?.tag ?? null;
|
|
6122
|
+
repository.refreshInUse(createQueryInvalidationFilter({ url, tag, filter: invalidation?.filter }), {
|
|
5805
6123
|
type: 'invalidation',
|
|
5806
6124
|
url,
|
|
5807
6125
|
otherTab: false,
|
|
6126
|
+
...(tag && { tag }),
|
|
5808
6127
|
});
|
|
5809
6128
|
// The resolved URL is what travels: the other tabs are the same client, so they would
|
|
5810
6129
|
// resolve it identically, and a message that needs no interpretation cannot drift.
|
|
5811
6130
|
if (invalidation?.otherTabs ?? true)
|
|
5812
|
-
sync?.postInvalidation(url);
|
|
6131
|
+
sync?.postInvalidation(url, tag);
|
|
5813
6132
|
},
|
|
5814
6133
|
clearPersistedQueries: () => persistenceEngine?.clear() ?? Promise.resolve(),
|
|
5815
6134
|
whenPersistenceReady: persistenceEngine?.whenReady ?? Promise.resolve(),
|
|
@@ -5953,6 +6272,8 @@ const createSecureExecuteFactory = (options) => {
|
|
|
5953
6272
|
args: executeArgs?.args ?? options.state.args(),
|
|
5954
6273
|
options: executeArgs?.options,
|
|
5955
6274
|
};
|
|
6275
|
+
if (skipParkedExecution(options.state, execArgsWithDefaults.args, options.route))
|
|
6276
|
+
return;
|
|
5956
6277
|
circularChecker.check(execArgsWithDefaults.args);
|
|
5957
6278
|
hasExecuted = true;
|
|
5958
6279
|
lastExecuteArgs = execArgsWithDefaults;
|
|
@@ -6042,6 +6363,7 @@ const createSecureExecuteFn = (executeOptions) => {
|
|
|
6042
6363
|
authProvider,
|
|
6043
6364
|
deps: executeOptions.deps,
|
|
6044
6365
|
state: executeOptions.state,
|
|
6366
|
+
route: executeOptions.creatorInternals.route,
|
|
6045
6367
|
autoExecutes: shouldAutoExecuteQuery(executeOptions.creatorInternals.method) && !executeOptions.queryConfig.onlyManualExecution,
|
|
6046
6368
|
transformAuthAndExec: (executeArgs, executeState) => {
|
|
6047
6369
|
queryExecute({
|
|
@@ -6104,6 +6426,181 @@ const createPatchQuery = /* @__PURE__ */ createQueryTemplate('PATCH');
|
|
|
6104
6426
|
/** A query creator that creates a secure PATCH query (requires authentication) */
|
|
6105
6427
|
const createSecurePatchQuery = /* @__PURE__ */ createSecureQueryTemplate('PATCH');
|
|
6106
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
|
+
|
|
6107
6604
|
const isAbortedSnapshot = (snapshot) => snapshot.latestHttpEvent()?.type === 'cancel' && snapshot.error() === null;
|
|
6108
6605
|
const executeAndSettle$ = (query, executeArgs, emitAborted) => defer(() => {
|
|
6109
6606
|
query.execute(executeArgs);
|
|
@@ -6743,16 +7240,18 @@ const createBearerAuthProviderImpl = (config) => {
|
|
|
6743
7240
|
const refreshToken = signal(null, /* @ts-ignore */
|
|
6744
7241
|
...(ngDevMode ? [{ debugName: "refreshToken" }] : /* istanbul ignore next */ []));
|
|
6745
7242
|
const afterTokenRefresh$ = new Subject();
|
|
7243
|
+
const expiresInPropertyName = signal('exp', /* @ts-ignore */
|
|
7244
|
+
...(ngDevMode ? [{ debugName: "expiresInPropertyName" }] : /* istanbul ignore next */ []));
|
|
6746
7245
|
const bearerData = computed(() => {
|
|
6747
7246
|
const token = accessToken();
|
|
6748
7247
|
if (!token)
|
|
6749
7248
|
return null;
|
|
6750
7249
|
try {
|
|
6751
|
-
const decoded = config.bearerDecryptFn
|
|
7250
|
+
const decoded = config.bearerDecryptFn ? config.bearerDecryptFn(token) : decryptBearer$1(token);
|
|
6752
7251
|
return patchQueryDevtoolsTokenPayload({
|
|
6753
7252
|
payload: decoded,
|
|
6754
7253
|
providerName: config.name,
|
|
6755
|
-
expiresInPropertyName:
|
|
7254
|
+
expiresInPropertyName: expiresInPropertyName(),
|
|
6756
7255
|
});
|
|
6757
7256
|
}
|
|
6758
7257
|
catch (error) {
|
|
@@ -6885,6 +7384,7 @@ const createBearerAuthProviderImpl = (config) => {
|
|
|
6885
7384
|
reportAccessTokenExpiry: (isExpired) => {
|
|
6886
7385
|
readAccessTokenExpiry = isExpired;
|
|
6887
7386
|
},
|
|
7387
|
+
reportExpiresInPropertyName: (claim) => expiresInPropertyName.set(claim),
|
|
6888
7388
|
};
|
|
6889
7389
|
for (const builder of config.queries) {
|
|
6890
7390
|
builder.setup?.(querySetupContext);
|
|
@@ -7075,6 +7575,7 @@ const withRefreshQuery = (key, config) => {
|
|
|
7075
7575
|
const buildArgs = (refreshToken) => (config.buildArgs?.(refreshToken) ?? { body: { token: refreshToken } });
|
|
7076
7576
|
const setup = (context) => {
|
|
7077
7577
|
const expiresInPropertyName = config.expiresInPropertyName ?? 'exp';
|
|
7578
|
+
context.reportExpiresInPropertyName(expiresInPropertyName);
|
|
7078
7579
|
const minRefreshInterval = config.minRefreshInterval ?? 30000; // 30 seconds default
|
|
7079
7580
|
const refreshIfExpired = config.refreshIfExpired ?? true;
|
|
7080
7581
|
const destroyRef = inject(DestroyRef);
|
|
@@ -7259,48 +7760,51 @@ const withRefreshQuery = (key, config) => {
|
|
|
7259
7760
|
case 'notLeader':
|
|
7260
7761
|
return Math.min(Math.max(msUntilAccessTokenIsStale() ?? minRefreshInterval, rescheduleDelayMs), maxTimerDelayMs);
|
|
7261
7762
|
}
|
|
7262
|
-
};
|
|
7263
|
-
|
|
7264
|
-
|
|
7265
|
-
|
|
7266
|
-
|
|
7267
|
-
|
|
7763
|
+
};
|
|
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);
|
|
7268
7782
|
}
|
|
7269
|
-
|
|
7270
|
-
const minBufferMs = config.refreshStrategy?.minBufferMs ?? 60000; // 1 minute
|
|
7271
|
-
const maxBufferMs = config.refreshStrategy?.maxBufferMs ?? 600000; // 10 minutes
|
|
7272
|
-
const calculatedBuffer = tokenLifetimeMs * (1 - percentage);
|
|
7273
|
-
return Math.max(minBufferMs, Math.min(maxBufferMs, calculatedBuffer));
|
|
7783
|
+
return times;
|
|
7274
7784
|
};
|
|
7275
7785
|
/** When this token expires, as a timestamp, or `null` if the expiry cannot be read off it. */
|
|
7276
|
-
const tokenExpiresAt = (token) =>
|
|
7277
|
-
|
|
7278
|
-
|
|
7279
|
-
|
|
7280
|
-
|
|
7281
|
-
|
|
7282
|
-
|
|
7283
|
-
|
|
7284
|
-
|
|
7285
|
-
if (typeof expiresIn !== 'number') {
|
|
7286
|
-
if (isDevMode()) {
|
|
7287
|
-
console.warn(`Token does not contain valid ${expiresInPropertyName} property for auto-refresh`);
|
|
7288
|
-
}
|
|
7289
|
-
return null;
|
|
7290
|
-
}
|
|
7291
|
-
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;
|
|
7292
7795
|
}
|
|
7293
|
-
|
|
7294
|
-
|
|
7796
|
+
if (measuredLifetime?.token !== token) {
|
|
7797
|
+
measuredLifetime = { token, lifetimeMs: times.expiresAtMs - Date.now() };
|
|
7295
7798
|
}
|
|
7799
|
+
return measuredLifetime.lifetimeMs;
|
|
7296
7800
|
};
|
|
7297
7801
|
/** When this access token is due to be refreshed, or `null` if it cannot be scheduled at all. */
|
|
7298
7802
|
const scheduledRefreshDelay = (token) => {
|
|
7299
|
-
const
|
|
7300
|
-
if (
|
|
7803
|
+
const times = tokenTimes(token);
|
|
7804
|
+
if (times === null)
|
|
7301
7805
|
return null;
|
|
7302
|
-
const
|
|
7303
|
-
if (
|
|
7806
|
+
const remainingMs = times.expiresAtMs - Date.now();
|
|
7807
|
+
if (remainingMs <= 0) {
|
|
7304
7808
|
if (!refreshIfExpired)
|
|
7305
7809
|
return null;
|
|
7306
7810
|
if (isDevMode()) {
|
|
@@ -7308,7 +7812,7 @@ const withRefreshQuery = (key, config) => {
|
|
|
7308
7812
|
}
|
|
7309
7813
|
return 0;
|
|
7310
7814
|
}
|
|
7311
|
-
return Math.max(
|
|
7815
|
+
return Math.max(remainingMs - refreshBufferMs(config.refreshStrategy, tokenLifetimeMs(token, times)), 0);
|
|
7312
7816
|
};
|
|
7313
7817
|
/**
|
|
7314
7818
|
* How long this tab can still work with the access token it holds, or `null` without a readable
|
|
@@ -7909,40 +8413,59 @@ const buildPersistentAuthFeature = (config, context) => {
|
|
|
7909
8413
|
*/
|
|
7910
8414
|
const createPersistentAuthFeature = (config, context) => runInInjectionContext(context.injector, () => buildPersistentAuthFeature(config, context));
|
|
7911
8415
|
|
|
8416
|
+
const RENEWAL_GRACE_MS = 10_000;
|
|
7912
8417
|
const withTokenExpirationWarning = (config = {}) => {
|
|
7913
8418
|
return (context) => {
|
|
7914
8419
|
const warningThreshold = config.warningThreshold ?? 5 * 60 * 1000;
|
|
7915
8420
|
const checkInterval = config.checkInterval ?? 1000;
|
|
7916
8421
|
const refreshBuilder = context.queryBuilders.find((builder) => builder._type === 'tokenRefreshQuery');
|
|
7917
|
-
const
|
|
7918
|
-
|
|
7919
|
-
|
|
7920
|
-
const
|
|
7921
|
-
const
|
|
7922
|
-
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)
|
|
7923
8428
|
return null;
|
|
7924
|
-
const
|
|
7925
|
-
|
|
8429
|
+
const times = readAccessTokenTimes({
|
|
8430
|
+
token,
|
|
8431
|
+
decoded: context.bearerData(),
|
|
8432
|
+
claim: expiresInPropertyName,
|
|
8433
|
+
providerName: context.name,
|
|
8434
|
+
});
|
|
8435
|
+
if (!times)
|
|
7926
8436
|
return null;
|
|
7927
|
-
|
|
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;
|
|
7928
8449
|
}, /* @ts-ignore */
|
|
7929
8450
|
...(ngDevMode ? [{ debugName: "expiresAt" }] : /* istanbul ignore next */ []));
|
|
7930
|
-
const expiresIn
|
|
8451
|
+
const expiresIn = toSignal(toObservable(tokenTimes).pipe(switchMap((times) => times
|
|
7931
8452
|
? timer(0, checkInterval).pipe(map(() => {
|
|
7932
|
-
const
|
|
7933
|
-
if (!expiry)
|
|
7934
|
-
return null;
|
|
7935
|
-
const msUntilExpiry = expiry.getTime() - Date.now();
|
|
8453
|
+
const msUntilExpiry = times.expiresAtMs - Date.now();
|
|
7936
8454
|
return msUntilExpiry > 0 ? msUntilExpiry : null;
|
|
7937
8455
|
}))
|
|
7938
|
-
: of(null)));
|
|
7939
|
-
const isExpiringSoon
|
|
7940
|
-
|
|
8456
|
+
: of(null))), { initialValue: null });
|
|
8457
|
+
const isExpiringSoon = computed(() => {
|
|
8458
|
+
const msUntilExpiry = expiresIn();
|
|
8459
|
+
const times = tokenTimes();
|
|
8460
|
+
if (msUntilExpiry === null || !times || msUntilExpiry > warningThreshold)
|
|
7941
8461
|
return false;
|
|
7942
|
-
|
|
7943
|
-
|
|
7944
|
-
|
|
7945
|
-
|
|
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 */ []));
|
|
7946
8469
|
const instance = {
|
|
7947
8470
|
expiresAt,
|
|
7948
8471
|
expiresIn,
|
|
@@ -7995,10 +8518,11 @@ const withTokenRevocation = (config) => {
|
|
|
7995
8518
|
...(ngDevMode ? [{ debugName: "target" }] : /* istanbul ignore next */ []));
|
|
7996
8519
|
const dropped = signal(false, /* @ts-ignore */
|
|
7997
8520
|
...(ngDevMode ? [{ debugName: "dropped" }] : /* istanbul ignore next */ []));
|
|
7998
|
-
const
|
|
8521
|
+
const watcher = createObservableSignalWatcher(context.injector);
|
|
8522
|
+
const follow = (read) => watcher.wrap(computed(() => {
|
|
7999
8523
|
const snapshot = target();
|
|
8000
8524
|
return snapshot ? read(snapshot) : null;
|
|
8001
|
-
})
|
|
8525
|
+
}));
|
|
8002
8526
|
const snapshot = {
|
|
8003
8527
|
args: follow((s) => s.args()),
|
|
8004
8528
|
response: follow((s) => s.response()),
|
|
@@ -8009,7 +8533,7 @@ const withTokenRevocation = (config) => {
|
|
|
8009
8533
|
triggeredBy: follow((s) => s.triggeredBy()),
|
|
8010
8534
|
id: follow((s) => s.id()),
|
|
8011
8535
|
executionState: follow((s) => s.executionState()),
|
|
8012
|
-
isAlive:
|
|
8536
|
+
isAlive: watcher.wrap(computed(() => target()?.isAlive() ?? !(dropped() || queueDropped()))),
|
|
8013
8537
|
};
|
|
8014
8538
|
const queued = {
|
|
8015
8539
|
tokens,
|
|
@@ -8146,19 +8670,18 @@ const buildTrackingFeature = (context, config) => {
|
|
|
8146
8670
|
}
|
|
8147
8671
|
}
|
|
8148
8672
|
};
|
|
8149
|
-
|
|
8150
|
-
|
|
8151
|
-
|
|
8152
|
-
|
|
8153
|
-
|
|
8154
|
-
|
|
8155
|
-
|
|
8156
|
-
|
|
8157
|
-
|
|
8158
|
-
|
|
8159
|
-
|
|
8160
|
-
|
|
8161
|
-
};
|
|
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
|
+
});
|
|
8162
8685
|
const pendingForwardedMessages = [];
|
|
8163
8686
|
const forwardingChannel = context.leaderElection && typeof BroadcastChannel !== 'undefined'
|
|
8164
8687
|
? new BroadcastChannel(`ethlete-auth-tracking:${context.name}`)
|
|
@@ -8292,6 +8815,10 @@ const buildTrackingFeature = (context, config) => {
|
|
|
8292
8815
|
*/
|
|
8293
8816
|
const createTrackingFeature = (context, config) => runInInjectionContext(context.injector, () => buildTrackingFeature(context, config));
|
|
8294
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
|
+
|
|
8295
8822
|
const ethlete = {};
|
|
8296
8823
|
let app = null;
|
|
8297
8824
|
/** What `window.ethlete` exposes - the same object {@link queryDevtoolsAbout} returns. */
|
|
@@ -9315,12 +9842,23 @@ const sanitizeSession = (value) => {
|
|
|
9315
9842
|
savedAt: typeof session.savedAt === 'number' ? session.savedAt : 0,
|
|
9316
9843
|
};
|
|
9317
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
|
+
};
|
|
9318
9856
|
const sanitize = (value) => ({
|
|
9319
9857
|
sessions: Array.isArray(value?.sessions)
|
|
9320
9858
|
? value.sessions.map(sanitizeSession).filter((session) => session !== null)
|
|
9321
9859
|
: [],
|
|
9322
9860
|
credentials: typeof value?.credentials === 'object' && value.credentials !== null ? value.credentials : {},
|
|
9323
|
-
accounts: Array.isArray(value?.accounts) ? value.accounts : [],
|
|
9861
|
+
accounts: Array.isArray(value?.accounts) ? value.accounts.filter(isLocalAccount) : [],
|
|
9324
9862
|
});
|
|
9325
9863
|
/**
|
|
9326
9864
|
* The vault is one key every tab writes, so a tab holding a stale copy of it would drop whatever another
|
|
@@ -9993,246 +10531,39 @@ const resolveQueryDevtoolsFaultForAttempt = (target) => {
|
|
|
9993
10531
|
const noteQueryFormReads = (formId, source) => {
|
|
9994
10532
|
const memoized = computed(() => source(), /* @ts-ignore */
|
|
9995
10533
|
...(ngDevMode ? [{ debugName: "memoized" }] : /* istanbul ignore next */ []));
|
|
9996
|
-
// The read must be noted per read, not per recomputation of the value: the second query to build
|
|
9997
|
-
// args reads a memoized value, and a note that only fires on a recomputation never reaches its
|
|
9998
|
-
// collection window. Carrying the memo's signal node over keeps this function a real `Signal`.
|
|
9999
|
-
const read = () => {
|
|
10000
|
-
noteQueryFormRead(formId);
|
|
10001
|
-
return memoized();
|
|
10002
|
-
};
|
|
10003
|
-
return Object.assign(read, { [SIGNAL]: memoized[SIGNAL] });
|
|
10004
|
-
};
|
|
10005
|
-
/**
|
|
10006
|
-
* Records which query forms feed one query's args, so the devtools can answer "which query does this
|
|
10007
|
-
* form drive?" from the reads themselves rather than from a naming convention.
|
|
10008
|
-
* @internal
|
|
10009
|
-
*/
|
|
10010
|
-
const createQueryDevtoolsFormLinks = () => {
|
|
10011
|
-
const ids = signal([], /* @ts-ignore */
|
|
10012
|
-
...(ngDevMode ? [{ debugName: "ids" }] : /* istanbul ignore next */ []));
|
|
10013
|
-
return {
|
|
10014
|
-
ids: ids.asReadonly(),
|
|
10015
|
-
track: (build) => {
|
|
10016
|
-
const collected = collectQueryFormLinks(build);
|
|
10017
|
-
// Args are built inside a reactive computation, which may not write to a signal - `untracked`
|
|
10018
|
-
// lifts that restriction, and skipping the write unless something is new keeps the panel from
|
|
10019
|
-
// being notified on every evaluation.
|
|
10020
|
-
untracked(() => {
|
|
10021
|
-
const known = ids();
|
|
10022
|
-
const added = collected.ids.filter((id) => !known.includes(id));
|
|
10023
|
-
if (added.length)
|
|
10024
|
-
ids.set([...known, ...added]);
|
|
10025
|
-
});
|
|
10026
|
-
return collected.value;
|
|
10027
|
-
},
|
|
10028
|
-
};
|
|
10029
|
-
};
|
|
10030
|
-
|
|
10031
|
-
/**
|
|
10032
|
-
* The designed library. Which of them is **armed** is a separate store under a separate scope - see
|
|
10033
|
-
* {@link queryDevtoolsArmedMocks}.
|
|
10034
|
-
*/
|
|
10035
|
-
const STORAGE_KEY$1 = 'ethlete:query:devtools:mocks:v1';
|
|
10036
|
-
const ARMED_STORAGE_KEY = 'ethlete:query:devtools:mocks:armed:v1';
|
|
10037
|
-
const mocks = /* @__PURE__ */ signal([], /* @ts-ignore */
|
|
10038
|
-
...(ngDevMode ? [{ debugName: "mocks" }] : /* istanbul ignore next */ []));
|
|
10039
|
-
const armed = /* @__PURE__ */ signal(/* @__PURE__ */ new Set(), /* @ts-ignore */
|
|
10040
|
-
...(ngDevMode ? [{ debugName: "armed" }] : /* istanbul ignore next */ []));
|
|
10041
|
-
const armedRestored = /* @__PURE__ */ signal(false, /* @ts-ignore */
|
|
10042
|
-
...(ngDevMode ? [{ debugName: "armedRestored" }] : /* istanbul ignore next */ []));
|
|
10043
|
-
const scope$1 = () => queryDevtoolsSettings().mocks;
|
|
10044
|
-
const armedScope = () => queryDevtoolsSettings().armedMocks;
|
|
10045
|
-
/** The designed mocks, armed or not. Persisted, because losing an hour of authoring is unacceptable. */
|
|
10046
|
-
const queryDevtoolsMocks = /* @__PURE__ */ mocks.asReadonly();
|
|
10047
|
-
/**
|
|
10048
|
-
* The ids currently being served. Not kept across page loads unless the `armedMocks` setting asks for it -
|
|
10049
|
-
* an app that silently serves designed data tomorrow morning is worse than losing which mocks were armed.
|
|
10050
|
-
*/
|
|
10051
|
-
const queryDevtoolsArmedMocks = /* @__PURE__ */ armed.asReadonly();
|
|
10052
|
-
/**
|
|
10053
|
-
* Whether what is armed right now came back from a previous page load rather than being armed by hand.
|
|
10054
|
-
* The panel's armed bar says so, because mocks nobody remembers arming look exactly like a broken API.
|
|
10055
|
-
*/
|
|
10056
|
-
const queryDevtoolsArmedMocksRestored = /* @__PURE__ */ armedRestored.asReadonly();
|
|
10057
|
-
/**
|
|
10058
|
-
* The identity of a designed mock: one per client, method, route **and** declared query. The query is part
|
|
10059
|
-
* of it so `/posts?page=1` and `/posts?page=2` can hold different bodies.
|
|
10060
|
-
*/
|
|
10061
|
-
const queryDevtoolsMockId = (parts) => `${parts.clientName}|${parts.method}|${parts.pattern}${parts.query ? `?${parts.query}` : ''}`;
|
|
10062
|
-
const write = () => {
|
|
10063
|
-
if (scope$1() === 'none') {
|
|
10064
|
-
clearQueryDevtoolsStore(STORAGE_KEY$1);
|
|
10065
|
-
return;
|
|
10066
|
-
}
|
|
10067
|
-
writeQueryDevtoolsStore(scope$1(), STORAGE_KEY$1, mocks());
|
|
10068
|
-
};
|
|
10069
|
-
const writeArmed = () => {
|
|
10070
|
-
if (armedScope() === 'none') {
|
|
10071
|
-
clearQueryDevtoolsStore(ARMED_STORAGE_KEY);
|
|
10072
|
-
return;
|
|
10073
|
-
}
|
|
10074
|
-
writeQueryDevtoolsStore(armedScope(), ARMED_STORAGE_KEY, [...armed()]);
|
|
10075
|
-
};
|
|
10076
|
-
/** Every change made by hand goes through here, so none of them can leave the store or the bar behind. */
|
|
10077
|
-
const setArmed = (next) => {
|
|
10078
|
-
armed.set(next);
|
|
10079
|
-
armedRestored.set(false);
|
|
10080
|
-
writeArmed();
|
|
10081
|
-
};
|
|
10082
|
-
const isMock = (value) => {
|
|
10083
|
-
const mock = value;
|
|
10084
|
-
return (!!mock &&
|
|
10085
|
-
typeof mock.id === 'string' &&
|
|
10086
|
-
typeof mock.clientName === 'string' &&
|
|
10087
|
-
typeof mock.method === 'string' &&
|
|
10088
|
-
typeof mock.pattern === 'string' &&
|
|
10089
|
-
typeof mock.status === 'number');
|
|
10090
|
-
};
|
|
10091
|
-
/**
|
|
10092
|
-
* Reads the designed library, so a mock can be armed without authoring it again. Called by
|
|
10093
|
-
* `provideQueryDevtools()` after the settings it takes its scope from; nothing else may call it.
|
|
10094
|
-
* @internal
|
|
10095
|
-
*/
|
|
10096
|
-
const initQueryDevtoolsMocks = () => {
|
|
10097
|
-
const stored = readQueryDevtoolsStore(scope$1(), STORAGE_KEY$1);
|
|
10098
|
-
// `query` was added after the first version of the store, so an entry written before it still loads.
|
|
10099
|
-
mocks.set(Array.isArray(stored) ? stored.filter(isMock).map((mock) => ({ ...mock, query: mock.query ?? '' })) : []);
|
|
10100
|
-
const kept = readQueryDevtoolsStore(armedScope(), ARMED_STORAGE_KEY);
|
|
10101
|
-
const library = new Set(mocks().map((mock) => mock.id));
|
|
10102
|
-
// An id the library no longer holds would arm nothing, so it is dropped rather than kept waiting for a
|
|
10103
|
-
// mock that may never be designed again.
|
|
10104
|
-
const inherited = Array.isArray(kept)
|
|
10105
|
-
? kept.filter((id) => typeof id === 'string' && library.has(id))
|
|
10106
|
-
: [];
|
|
10107
|
-
armed.set(new Set(inherited));
|
|
10108
|
-
armedRestored.set(inherited.length > 0);
|
|
10109
|
-
};
|
|
10110
|
-
/**
|
|
10111
|
-
* The path of a request URL relative to the client's `baseUrl` (base path included), without its query
|
|
10112
|
-
* string - what a pattern is matched against. A URL outside `baseUrl`, or no `baseUrl` at all, falls
|
|
10113
|
-
* back to the path without its origin.
|
|
10114
|
-
*/
|
|
10115
|
-
const queryDevtoolsRequestPath = (url, baseUrl) => {
|
|
10116
|
-
const withoutQuery = url.split('?')[0] ?? '';
|
|
10117
|
-
const base = baseUrl?.replace(/\/+$/, '');
|
|
10118
|
-
if (base && withoutQuery.startsWith(base)) {
|
|
10119
|
-
const rest = withoutQuery.slice(base.length);
|
|
10120
|
-
if (!rest)
|
|
10121
|
-
return '/';
|
|
10122
|
-
if (rest.startsWith('/'))
|
|
10123
|
-
return rest;
|
|
10124
|
-
}
|
|
10125
|
-
const schemeEnd = withoutQuery.indexOf('://');
|
|
10126
|
-
if (schemeEnd === -1)
|
|
10127
|
-
return withoutQuery;
|
|
10128
|
-
const pathStart = withoutQuery.indexOf('/', schemeEnd + 3);
|
|
10129
|
-
return pathStart === -1 ? '/' : withoutQuery.slice(pathStart);
|
|
10130
|
-
};
|
|
10131
|
-
const segmentsOf$1 = (path) => path.split('/').filter(Boolean);
|
|
10132
|
-
/** The query string of a request URL, without the `?`. */
|
|
10133
|
-
const queryOf = (url) => url.split('?')[1] ?? '';
|
|
10134
|
-
/**
|
|
10135
|
-
* Whether a request carries every query parameter a mock declares. Extra parameters are ignored - a mock
|
|
10136
|
-
* says what the request must ask for, not everything it may.
|
|
10137
|
-
*/
|
|
10138
|
-
const matchesQueryDevtoolsMockQuery = (declared, url) => {
|
|
10139
|
-
if (!declared)
|
|
10140
|
-
return true;
|
|
10141
|
-
const asked = new URLSearchParams(queryOf(url));
|
|
10142
|
-
return [...new URLSearchParams(declared)].every(([key, value]) => asked.get(key) === value);
|
|
10143
|
-
};
|
|
10144
|
-
/**
|
|
10145
|
-
* Whether a request path is the route a pattern describes. A `:param` segment matches any single
|
|
10146
|
-
* non-empty segment, which is the whole point: a mock is armed for a route, not for the one URL a query
|
|
10147
|
-
* happened to build.
|
|
10148
|
-
*/
|
|
10149
|
-
const matchesQueryDevtoolsMockPattern = (pattern, path) => {
|
|
10150
|
-
const patternSegments = segmentsOf$1(pattern);
|
|
10151
|
-
const pathSegments = segmentsOf$1(path);
|
|
10152
|
-
if (patternSegments.length !== pathSegments.length)
|
|
10153
|
-
return false;
|
|
10154
|
-
return patternSegments.every((segment, index) => segment.startsWith(':') || segment === pathSegments[index]);
|
|
10155
|
-
};
|
|
10156
|
-
/**
|
|
10157
|
-
* Adds a designed mock, or replaces the one already under its id. Arming is untouched: re-designing a
|
|
10158
|
-
* body while it is being served changes what the next request gets.
|
|
10159
|
-
*/
|
|
10160
|
-
const saveQueryDevtoolsMock = (mock) => {
|
|
10161
|
-
mocks.update((current) => {
|
|
10162
|
-
const index = current.findIndex((entry) => entry.id === mock.id);
|
|
10163
|
-
if (index === -1)
|
|
10164
|
-
return [...current, mock];
|
|
10165
|
-
return current.map((entry) => (entry.id === mock.id ? mock : entry));
|
|
10166
|
-
});
|
|
10167
|
-
write();
|
|
10168
|
-
};
|
|
10169
|
-
/** Removes a designed mock, disarming it on the way out. */
|
|
10170
|
-
const deleteQueryDevtoolsMock = (id) => {
|
|
10171
|
-
mocks.update((current) => current.filter((entry) => entry.id !== id));
|
|
10172
|
-
armQueryDevtoolsMock(id, false);
|
|
10173
|
-
write();
|
|
10174
|
-
};
|
|
10175
|
-
/** Starts or stops serving one designed mock. Arming a mock that is not in the library does nothing. */
|
|
10176
|
-
const armQueryDevtoolsMock = (id, isArmed) => {
|
|
10177
|
-
if (isArmed && !mocks().some((mock) => mock.id === id))
|
|
10178
|
-
return;
|
|
10179
|
-
const next = new Set(armed());
|
|
10180
|
-
if (isArmed)
|
|
10181
|
-
next.add(id);
|
|
10182
|
-
else
|
|
10183
|
-
next.delete(id);
|
|
10184
|
-
setArmed(next);
|
|
10185
|
-
};
|
|
10186
|
-
/** Starts serving every mock in the library - the counterpart of {@link clearQueryDevtoolsArmedMocks}. */
|
|
10187
|
-
const armAllQueryDevtoolsMocks = () => setArmed(new Set(mocks().map((mock) => mock.id)));
|
|
10188
|
-
/** Stops serving every mock, leaving the library alone - the armed bar's one-click way out. */
|
|
10189
|
-
const clearQueryDevtoolsArmedMocks = () => setArmed(new Set());
|
|
10190
|
-
/**
|
|
10191
|
-
* Where the armed set is kept. Switching to a scope that keeps it stores whatever is armed right now, so
|
|
10192
|
-
* the choice reads as "keep these"; switching to `none` empties the store and leaves this page serving
|
|
10193
|
-
* exactly what it was already serving.
|
|
10194
|
-
*/
|
|
10195
|
-
const setQueryDevtoolsArmedMocksScope = (next) => {
|
|
10196
|
-
if (next === armedScope())
|
|
10197
|
-
return;
|
|
10198
|
-
// Off both stores first: whichever the previous scope was, its copy must not outlive the change.
|
|
10199
|
-
clearQueryDevtoolsStore(ARMED_STORAGE_KEY);
|
|
10200
|
-
setQueryDevtoolsSettings({ armedMocks: next });
|
|
10201
|
-
writeArmed();
|
|
10202
|
-
};
|
|
10203
|
-
/** Empties the designed library and disarms everything - the panel's "Reset devtools". */
|
|
10204
|
-
const clearQueryDevtoolsMockStore = () => {
|
|
10205
|
-
mocks.set([]);
|
|
10206
|
-
armed.set(new Set());
|
|
10207
|
-
armedRestored.set(false);
|
|
10208
|
-
clearQueryDevtoolsStore(STORAGE_KEY$1);
|
|
10209
|
-
clearQueryDevtoolsStore(ARMED_STORAGE_KEY);
|
|
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] });
|
|
10210
10542
|
};
|
|
10211
|
-
const declaredParamCount = (mock) => (mock.query ? [...new URLSearchParams(mock.query)].length : 0);
|
|
10212
10543
|
/**
|
|
10213
|
-
*
|
|
10214
|
-
*
|
|
10215
|
-
*
|
|
10216
|
-
* 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.
|
|
10217
10546
|
* @internal
|
|
10218
10547
|
*/
|
|
10219
|
-
const
|
|
10220
|
-
const ids =
|
|
10221
|
-
|
|
10222
|
-
|
|
10223
|
-
|
|
10224
|
-
|
|
10225
|
-
|
|
10226
|
-
|
|
10227
|
-
|
|
10228
|
-
|
|
10229
|
-
|
|
10230
|
-
|
|
10231
|
-
|
|
10232
|
-
|
|
10233
|
-
|
|
10234
|
-
|
|
10235
|
-
|
|
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
|
+
};
|
|
10236
10567
|
};
|
|
10237
10568
|
|
|
10238
10569
|
const isPathAtOrBelow = (path, root) => path.length >= root.length && root.every((step, index) => step === path[index]);
|
|
@@ -10469,19 +10800,19 @@ const resizePagination = (value, shape, mode, amount) => {
|
|
|
10469
10800
|
}
|
|
10470
10801
|
return result;
|
|
10471
10802
|
};
|
|
10472
|
-
const STRING_PRESETS = {
|
|
10803
|
+
const STRING_PRESETS$1 = {
|
|
10473
10804
|
short: 'Ab',
|
|
10474
10805
|
long: /* @__PURE__ */ 'Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt. '.repeat(4),
|
|
10475
10806
|
longWord: 'Rindfleischetikettierungsüberwachungsaufgabenübertragungsgesetz',
|
|
10476
10807
|
unicode: 'مرحبا بالعالم 👋 日本語テスト 🎉 Ñoño',
|
|
10477
10808
|
};
|
|
10478
|
-
const stringPresetValue = (preset, custom) => custom ?? (preset === 'custom' ? '' : STRING_PRESETS[preset]);
|
|
10479
|
-
const NUMBER_PRESETS = {
|
|
10809
|
+
const stringPresetValue = (preset, custom) => custom ?? (preset === 'custom' ? '' : STRING_PRESETS$1[preset]);
|
|
10810
|
+
const NUMBER_PRESETS$1 = {
|
|
10480
10811
|
zero: 0,
|
|
10481
10812
|
negative: -1,
|
|
10482
10813
|
huge: Number.MAX_SAFE_INTEGER,
|
|
10483
10814
|
};
|
|
10484
|
-
const numberPresetValue = (preset, custom) => custom ?? (preset === 'custom' ? 0 : NUMBER_PRESETS[preset]);
|
|
10815
|
+
const numberPresetValue = (preset, custom) => custom ?? (preset === 'custom' ? 0 : NUMBER_PRESETS$1[preset]);
|
|
10485
10816
|
const pickRandom = (values) => values[Math.floor(Math.random() * values.length)];
|
|
10486
10817
|
const randomIntBetween = (min, max) => min + Math.floor(Math.random() * (max - min + 1));
|
|
10487
10818
|
const randomHex = (length) => {
|
|
@@ -10697,6 +11028,30 @@ const OP_TYPES = /* @__PURE__ */ new Set([
|
|
|
10697
11028
|
'reset',
|
|
10698
11029
|
]);
|
|
10699
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
|
+
};
|
|
10700
11055
|
/**
|
|
10701
11056
|
* Whether a value parsed from outside - a paste, a stored set - is an op this build can replay.
|
|
10702
11057
|
* @internal
|
|
@@ -10705,7 +11060,7 @@ const isOverrideOp = (value) => {
|
|
|
10705
11060
|
if (!value || typeof value !== 'object' || Array.isArray(value))
|
|
10706
11061
|
return false;
|
|
10707
11062
|
const op = value;
|
|
10708
|
-
return typeof op
|
|
11063
|
+
return (typeof op['type'] === 'string' && OP_TYPES.has(op['type']) && isJsonPath(op['path']) && hasReplayableFields(op));
|
|
10709
11064
|
};
|
|
10710
11065
|
/**
|
|
10711
11066
|
* Serializes the ops armed on one query into clipboard text. `reset` never reaches a recorder's list, so
|
|
@@ -10771,12 +11126,12 @@ const armQueryDevtoolsOverrideTransfer = (recorder, ops) => {
|
|
|
10771
11126
|
* debugging the wrong thing. `local` is offered in Settings for the dev who has a reason, and the
|
|
10772
11127
|
* restored-overrides bar names the scope whatever it is.
|
|
10773
11128
|
*/
|
|
10774
|
-
const STORAGE_KEY = 'ethlete:query:devtools:overrides:v1';
|
|
11129
|
+
const STORAGE_KEY$1 = 'ethlete:query:devtools:overrides:v1';
|
|
10775
11130
|
const restored = /* @__PURE__ */ signal([], /* @ts-ignore */
|
|
10776
11131
|
...(ngDevMode ? [{ debugName: "restored" }] : /* istanbul ignore next */ []));
|
|
10777
11132
|
const restoredScope = /* @__PURE__ */ signal(null, /* @ts-ignore */
|
|
10778
11133
|
...(ngDevMode ? [{ debugName: "restoredScope" }] : /* istanbul ignore next */ []));
|
|
10779
|
-
const scope = () => queryDevtoolsSettings().overrides;
|
|
11134
|
+
const scope$1 = () => queryDevtoolsSettings().overrides;
|
|
10780
11135
|
/** Where "Keep across reloads" puts them back when it is switched on again. */
|
|
10781
11136
|
let lastPersistentScope = 'session';
|
|
10782
11137
|
/** Every recorder that currently holds at least one op, so switching persistence on can capture them. */
|
|
@@ -10787,7 +11142,7 @@ const carriedOver = /* @__PURE__ */ new Map();
|
|
|
10787
11142
|
const replayed = /* @__PURE__ */ new Set();
|
|
10788
11143
|
let ops = {};
|
|
10789
11144
|
const readStore = () => {
|
|
10790
|
-
const parsed = readQueryDevtoolsStore(scope(), STORAGE_KEY);
|
|
11145
|
+
const parsed = readQueryDevtoolsStore(scope$1(), STORAGE_KEY$1);
|
|
10791
11146
|
const stored = parsed?.ops;
|
|
10792
11147
|
const usable = !!stored && typeof stored === 'object' && !Array.isArray(stored);
|
|
10793
11148
|
const valid = {};
|
|
@@ -10799,11 +11154,11 @@ const readStore = () => {
|
|
|
10799
11154
|
return { enabled: parsed?.enabled === true, ops: valid };
|
|
10800
11155
|
};
|
|
10801
11156
|
const writeStore = () => {
|
|
10802
|
-
if (scope() === 'none') {
|
|
10803
|
-
clearQueryDevtoolsStore(STORAGE_KEY);
|
|
11157
|
+
if (scope$1() === 'none') {
|
|
11158
|
+
clearQueryDevtoolsStore(STORAGE_KEY$1);
|
|
10804
11159
|
return;
|
|
10805
11160
|
}
|
|
10806
|
-
writeQueryDevtoolsStore(scope(), STORAGE_KEY, { enabled: true, ops });
|
|
11161
|
+
writeQueryDevtoolsStore(scope$1(), STORAGE_KEY$1, { enabled: true, ops });
|
|
10807
11162
|
};
|
|
10808
11163
|
const publishRestored = () => restored.set([...carriedOver].map(([id, count]) => ({ id, count, armed: replayed.has(id) })));
|
|
10809
11164
|
const forgetCarriedOver = () => {
|
|
@@ -10836,7 +11191,7 @@ const initQueryDevtoolsOverridePersistence = () => {
|
|
|
10836
11191
|
if (list.length)
|
|
10837
11192
|
carriedOver.set(id, list.length);
|
|
10838
11193
|
}
|
|
10839
|
-
const current = scope();
|
|
11194
|
+
const current = scope$1();
|
|
10840
11195
|
if (current !== 'none')
|
|
10841
11196
|
lastPersistentScope = current;
|
|
10842
11197
|
restoredScope.set(carriedOver.size ? current : null);
|
|
@@ -10846,7 +11201,7 @@ const initQueryDevtoolsOverridePersistence = () => {
|
|
|
10846
11201
|
* Whether armed response overrides are stored and replayed on the next page load - that is, whether
|
|
10847
11202
|
* their scope is anything other than `none`. Off by default.
|
|
10848
11203
|
*/
|
|
10849
|
-
const queryDevtoolsOverridePersistence = /* @__PURE__ */ computed(() => scope() !== 'none', /* @ts-ignore */
|
|
11204
|
+
const queryDevtoolsOverridePersistence = /* @__PURE__ */ computed(() => scope$1() !== 'none', /* @ts-ignore */
|
|
10850
11205
|
...(ngDevMode ? [{ debugName: "queryDevtoolsOverridePersistence" }] : /* istanbul ignore next */ []));
|
|
10851
11206
|
/**
|
|
10852
11207
|
* Where armed response overrides are kept. Switching to a scope that keeps them captures whatever is
|
|
@@ -10855,11 +11210,11 @@ const queryDevtoolsOverridePersistence = /* @__PURE__ */ computed(() => scope()
|
|
|
10855
11210
|
* op exactly where it is for the rest of this page.
|
|
10856
11211
|
*/
|
|
10857
11212
|
const setQueryDevtoolsOverridesScope = (next) => {
|
|
10858
|
-
const previous = scope();
|
|
11213
|
+
const previous = scope$1();
|
|
10859
11214
|
if (next === previous)
|
|
10860
11215
|
return;
|
|
10861
11216
|
// Off both stores first: whichever the previous scope was, its copy must not outlive the change.
|
|
10862
|
-
clearQueryDevtoolsStore(STORAGE_KEY);
|
|
11217
|
+
clearQueryDevtoolsStore(STORAGE_KEY$1);
|
|
10863
11218
|
setQueryDevtoolsSettings({ overrides: next });
|
|
10864
11219
|
if (next === 'none') {
|
|
10865
11220
|
ops = {};
|
|
@@ -10878,106 +11233,309 @@ const setQueryDevtoolsOverridesScope = (next) => {
|
|
|
10878
11233
|
writeStore();
|
|
10879
11234
|
};
|
|
10880
11235
|
/**
|
|
10881
|
-
* Turns persistence on or off - the panel's "Keep across reloads" toggle. On restores the last scope
|
|
10882
|
-
* 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
|
|
11396
|
+
*/
|
|
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);
|
|
11410
|
+
};
|
|
11411
|
+
/**
|
|
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.
|
|
11415
|
+
*/
|
|
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);
|
|
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] ?? '';
|
|
11435
|
+
/**
|
|
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.
|
|
10883
11438
|
*/
|
|
10884
|
-
const
|
|
10885
|
-
|
|
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));
|
|
10886
11444
|
};
|
|
10887
11445
|
/**
|
|
10888
|
-
*
|
|
10889
|
-
*
|
|
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.
|
|
10890
11449
|
*/
|
|
10891
|
-
const
|
|
10892
|
-
|
|
10893
|
-
|
|
10894
|
-
|
|
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]);
|
|
10895
11456
|
};
|
|
10896
11457
|
/**
|
|
10897
|
-
*
|
|
10898
|
-
*
|
|
10899
|
-
* lied to looks exactly like an app that is broken.
|
|
10900
|
-
*/
|
|
10901
|
-
const restoredQueryDevtoolsOverrides =
|
|
10902
|
-
/* @__PURE__ */ restored.asReadonly();
|
|
10903
|
-
/**
|
|
10904
|
-
* The scope those ops came back from, or `null` when this page inherited none. `local` is the one worth
|
|
10905
|
-
* naming in the banner: it means they outlived closing the tab, not just a reload.
|
|
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.
|
|
10906
11460
|
*/
|
|
10907
|
-
const
|
|
10908
|
-
|
|
10909
|
-
|
|
10910
|
-
|
|
10911
|
-
|
|
10912
|
-
|
|
10913
|
-
|
|
10914
|
-
|
|
10915
|
-
|
|
10916
|
-
|
|
10917
|
-
|
|
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();
|
|
10918
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))
|
|
11479
|
+
return;
|
|
11480
|
+
const next = new Set(armed());
|
|
11481
|
+
if (isArmed)
|
|
11482
|
+
next.add(id);
|
|
11483
|
+
else
|
|
11484
|
+
next.delete(id);
|
|
11485
|
+
setArmed(next);
|
|
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());
|
|
10919
11491
|
/**
|
|
10920
|
-
*
|
|
10921
|
-
*
|
|
10922
|
-
*
|
|
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.
|
|
10923
11495
|
*/
|
|
10924
|
-
const
|
|
10925
|
-
|
|
10926
|
-
if (scope() !== 'none')
|
|
11496
|
+
const setQueryDevtoolsArmedMocksScope = (next) => {
|
|
11497
|
+
if (next === armedScope())
|
|
10927
11498
|
return;
|
|
10928
|
-
|
|
10929
|
-
|
|
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);
|
|
10930
11511
|
};
|
|
11512
|
+
const declaredParamCount = (mock) => (mock.query ? [...new URLSearchParams(mock.query)].length : 0);
|
|
10931
11513
|
/**
|
|
10932
|
-
*
|
|
10933
|
-
*
|
|
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.
|
|
10934
11516
|
*
|
|
10935
|
-
*
|
|
10936
|
-
* same order re-arm correctly; an id the store holds that nobody claims is reported by
|
|
10937
|
-
* {@link restoredQueryDevtoolsOverrides} rather than guessed at.
|
|
11517
|
+
* Installed as the mock resolver by `provideQueryDevtools()`; nothing else may call it.
|
|
10938
11518
|
* @internal
|
|
10939
11519
|
*/
|
|
10940
|
-
const
|
|
10941
|
-
const
|
|
10942
|
-
if (
|
|
10943
|
-
|
|
10944
|
-
|
|
10945
|
-
|
|
10946
|
-
|
|
10947
|
-
|
|
10948
|
-
|
|
10949
|
-
|
|
10950
|
-
|
|
10951
|
-
|
|
10952
|
-
|
|
10953
|
-
|
|
10954
|
-
|
|
10955
|
-
|
|
10956
|
-
|
|
10957
|
-
};
|
|
10958
|
-
return {
|
|
10959
|
-
...recorder,
|
|
10960
|
-
arm: (op) => {
|
|
10961
|
-
recorder.arm(op);
|
|
10962
|
-
sync();
|
|
10963
|
-
},
|
|
10964
|
-
clear: (opId) => {
|
|
10965
|
-
recorder.clear(opId);
|
|
10966
|
-
sync();
|
|
10967
|
-
},
|
|
10968
|
-
clearAll: () => {
|
|
10969
|
-
recorder.clearAll();
|
|
10970
|
-
if (replayed.delete(id))
|
|
10971
|
-
publishRestored();
|
|
10972
|
-
sync();
|
|
10973
|
-
},
|
|
10974
|
-
};
|
|
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 };
|
|
10975
11537
|
};
|
|
10976
11538
|
|
|
10977
|
-
// Generated from package.json by tools/scripts/generate-version.js - do not edit.
|
|
10978
|
-
/** The version of `@ethlete/query` this build was cut from. */
|
|
10979
|
-
const QUERY_VERSION = '6.0.0-next.53';
|
|
10980
|
-
|
|
10981
11539
|
/** How deep a schema is expanded before a nested branch is given up on. */
|
|
10982
11540
|
const MAX_DEPTH = 12;
|
|
10983
11541
|
/** How many elements are generated for an array, whatever its `minItems` says. */
|
|
@@ -11196,9 +11754,7 @@ const stringSample = (node, schema, ctx) => {
|
|
|
11196
11754
|
if (ctx.style === 'stress')
|
|
11197
11755
|
return fitLength(generateQueryDevtoolsStringPreset(pickStressStringPreset()), schema);
|
|
11198
11756
|
const title = schema['title'];
|
|
11199
|
-
|
|
11200
|
-
return title;
|
|
11201
|
-
return node.key ?? 'string';
|
|
11757
|
+
return fitLength(typeof title === 'string' && title ? title : (node.key ?? 'string'), schema);
|
|
11202
11758
|
};
|
|
11203
11759
|
const boundsOf = (schema) => {
|
|
11204
11760
|
const exclusiveMin = finiteNumber(schema['exclusiveMinimum']);
|
|
@@ -11218,9 +11774,9 @@ const clampToBounds = (value, bounds) => {
|
|
|
11218
11774
|
const numberSample = (schema, ctx) => {
|
|
11219
11775
|
const bounds = boundsOf(schema);
|
|
11220
11776
|
if (ctx.style === 'placeholder')
|
|
11221
|
-
return bounds.min ?? 0;
|
|
11777
|
+
return clampToBounds(bounds.min ?? 0, bounds);
|
|
11222
11778
|
if (ctx.style === 'realistic') {
|
|
11223
|
-
return generateQueryDevtoolsSampleNumber({ ...bounds, fractional: typeOf(schema) === 'number' });
|
|
11779
|
+
return clampToBounds(generateQueryDevtoolsSampleNumber({ ...bounds, fractional: typeOf(schema) === 'number' }), bounds);
|
|
11224
11780
|
}
|
|
11225
11781
|
const allowsNegative = bounds.min === null || bounds.min < 0;
|
|
11226
11782
|
return clampToBounds(generateQueryDevtoolsNumberPreset(allowsNegative && Math.random() < 0.5 ? 'negative' : 'huge'), bounds);
|
|
@@ -11661,7 +12217,8 @@ const queryDevtoolsResponseHistory = () => queryDevtoolsSettings().responseHisto
|
|
|
11661
12217
|
* @internal
|
|
11662
12218
|
*/
|
|
11663
12219
|
const setQueryDevtoolsResponseHistory = (count) => {
|
|
11664
|
-
providedResponseHistory =
|
|
12220
|
+
providedResponseHistory =
|
|
12221
|
+
count === undefined || Number.isNaN(count) ? DEFAULT_RESPONSE_HISTORY : Math.max(1, Math.floor(count));
|
|
11665
12222
|
};
|
|
11666
12223
|
/** The index of the newest run still in flight, or -1 when none is. */
|
|
11667
12224
|
const lastPendingRunIndex = (runs) => {
|
|
@@ -12347,7 +12904,7 @@ const provideQueryDevtools = (options) => {
|
|
|
12347
12904
|
return makeEnvironmentProviders([settleThePillsOnFirstRender()]);
|
|
12348
12905
|
};
|
|
12349
12906
|
|
|
12350
|
-
const getOpName = /\b(?:query|mutation)\s+([\w-]+)(?:\s*\([^)]*\))
|
|
12907
|
+
const getOpName = /\b(?:query|mutation)\s+([\w-]+)(?:\s*\([^)]*\))?(?:\s*@\w+(?:\s*\([^)]*\))?)*\s*\{/;
|
|
12351
12908
|
const STRING_LITERAL = '"""[\\s\\S]*?"""|"(?:[^"\\\\\\n]|\\\\.)*"';
|
|
12352
12909
|
const commentOutsideString = /* @__PURE__ */ new RegExp(`(${STRING_LITERAL})|#[^\\n\\r]*`, 'g');
|
|
12353
12910
|
const whitespaceOutsideString = /* @__PURE__ */ new RegExp(`(${STRING_LITERAL})|\\s+`, 'g');
|
|
@@ -12355,6 +12912,19 @@ const minifyGql = (document) => document
|
|
|
12355
12912
|
.replace(commentOutsideString, (_match, literal) => literal ?? '')
|
|
12356
12913
|
.replace(whitespaceOutsideString, (_match, literal) => literal ?? ' ')
|
|
12357
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
|
+
};
|
|
12358
12928
|
/**
|
|
12359
12929
|
* Builds the GraphQL-over-HTTP payload of a document. `variables` comes back as a JSON string for
|
|
12360
12930
|
* the `GET` transport (the default), where the payload travels as query parameters, and as the map
|
|
@@ -12362,7 +12932,7 @@ const minifyGql = (document) => document
|
|
|
12362
12932
|
*/
|
|
12363
12933
|
const transformGql = (str) => {
|
|
12364
12934
|
const normalizedStr = Array.isArray(str) ? str.join('') : str;
|
|
12365
|
-
const operationName = getOpName.exec(normalizedStr.replace(commentOutsideString, (_match, literal) => (literal ? '""' : '')))?.[1];
|
|
12935
|
+
const operationName = getOpName.exec(topLevelOnly(normalizedStr.replace(commentOutsideString, (_match, literal) => (literal ? '""' : ''))))?.[1];
|
|
12366
12936
|
let minified;
|
|
12367
12937
|
return (variables, transport = 'GET') => {
|
|
12368
12938
|
const data = { query: isDevMode() ? normalizedStr : (minified ??= minifyGql(normalizedStr)) };
|
|
@@ -12403,9 +12973,11 @@ const createGqlExecuteFn = (executeOptions) => {
|
|
|
12403
12973
|
const executeState = setupQueryExecuteState();
|
|
12404
12974
|
const circularChecker = circularQueryDependencyChecker();
|
|
12405
12975
|
const aborter = createQueryExecutionAborter(executeOptions.state);
|
|
12406
|
-
const reset = () => resetExecuteState({ executeState, executeOptions });
|
|
12407
|
-
const exec = (executeArgs) => {
|
|
12976
|
+
const reset = () => untracked(() => resetExecuteState({ executeState, executeOptions }));
|
|
12977
|
+
const exec = (executeArgs) => untracked(() => {
|
|
12408
12978
|
const { args = executeOptions.state.args(), options } = executeArgs ?? {};
|
|
12979
|
+
if (skipParkedExecution(executeOptions.state, args, executeOptions.creator?.route))
|
|
12980
|
+
return;
|
|
12409
12981
|
circularChecker.check(args);
|
|
12410
12982
|
let gqlParams = gqlTransformerFor(executeOptions.creatorInternals)(args?.variables, executeOptions.creatorInternals.transport);
|
|
12411
12983
|
if (args?.queryParams && executeOptions.creatorInternals.transport === 'GET') {
|
|
@@ -12445,7 +13017,7 @@ const createGqlExecuteFn = (executeOptions) => {
|
|
|
12445
13017
|
options,
|
|
12446
13018
|
isRefreshable: executeOptions.creatorInternals.method === 'QUERY',
|
|
12447
13019
|
});
|
|
12448
|
-
};
|
|
13020
|
+
});
|
|
12449
13021
|
exec['reset'] = reset;
|
|
12450
13022
|
exec['abort'] = () => aborter.abort();
|
|
12451
13023
|
exec['aborted$'] = aborter.aborted$;
|
|
@@ -12495,6 +13067,7 @@ const createSecureGqlExecuteFn = (executeOptions) => {
|
|
|
12495
13067
|
authProvider,
|
|
12496
13068
|
deps: executeOptions.deps,
|
|
12497
13069
|
state: executeOptions.state,
|
|
13070
|
+
route: executeOptions.creator?.route,
|
|
12498
13071
|
autoExecutes: shouldAutoExecuteGqlQuery(executeOptions.creatorInternals.method) &&
|
|
12499
13072
|
!executeOptions.queryConfig.onlyManualExecution,
|
|
12500
13073
|
transformAuthAndExec: (executeArgs, executeState) => {
|
|
@@ -13059,6 +13632,15 @@ const request = (config) => {
|
|
|
13059
13632
|
});
|
|
13060
13633
|
};
|
|
13061
13634
|
|
|
13635
|
+
/**
|
|
13636
|
+
* Destroying a legacy query tears down the underlying query's injector, which cancels its request - so
|
|
13637
|
+
* a superseded or released query that was not aborted (a mutation the server may already have accepted)
|
|
13638
|
+
* is torn down only once it has settled. `state$` completes with the query, so this ends either way.
|
|
13639
|
+
*/
|
|
13640
|
+
const destroyOnceSettled = (query) => {
|
|
13641
|
+
query.state$.pipe(takeUntilResponse()).subscribe({ complete: () => query.destroy() });
|
|
13642
|
+
};
|
|
13643
|
+
|
|
13062
13644
|
let nextQueryContainerOwnerId = 0;
|
|
13063
13645
|
const queryContainerOwnerIds = /* @__PURE__ */ new WeakMap();
|
|
13064
13646
|
const queryContainerOwnerId = (injector) => {
|
|
@@ -13070,14 +13652,6 @@ const queryContainerOwnerId = (injector) => {
|
|
|
13070
13652
|
queryContainerOwnerIds.set(injector, id);
|
|
13071
13653
|
return id;
|
|
13072
13654
|
};
|
|
13073
|
-
/**
|
|
13074
|
-
* Destroying a legacy query tears down the underlying query's injector, which cancels its request - so
|
|
13075
|
-
* a superseded query that was not aborted (a mutation the server may already have accepted) is torn
|
|
13076
|
-
* down only once it has settled. `state$` completes with the query, so this ends either way.
|
|
13077
|
-
*/
|
|
13078
|
-
const destroyOnceSettled = (query) => {
|
|
13079
|
-
query.state$.pipe(takeUntilResponse()).subscribe({ complete: () => query.destroy() });
|
|
13080
|
-
};
|
|
13081
13655
|
/**
|
|
13082
13656
|
* @deprecated Part of the legacy (v2) query system. Migrate to the current query API - see https://ethlete-sdk-docs.web.app/query/migrating-from-v2, and run `nx g @ethlete/query:migrate-to-query-v3` to rewrite the mechanical parts. Intent to remove in v7.
|
|
13083
13657
|
*/
|
|
@@ -13153,7 +13727,14 @@ const addQueryContainerHandling = (obs, valueFn, config) => {
|
|
|
13153
13727
|
if ((q?.canBeCached && abortOnDestroy === undefined) || abortOnDestroy) {
|
|
13154
13728
|
q?.abort();
|
|
13155
13729
|
}
|
|
13156
|
-
q
|
|
13730
|
+
const legacyQuery = q;
|
|
13731
|
+
if (!legacyQuery?.destroy)
|
|
13732
|
+
return;
|
|
13733
|
+
if (isQueryStateLoading(q?.rawState)) {
|
|
13734
|
+
destroyOnceSettled(legacyQuery);
|
|
13735
|
+
return;
|
|
13736
|
+
}
|
|
13737
|
+
legacyQuery.destroy();
|
|
13157
13738
|
};
|
|
13158
13739
|
if (isQuery(query)) {
|
|
13159
13740
|
handleQuery(query);
|
|
@@ -13918,6 +14499,10 @@ class LegacyQuery {
|
|
|
13918
14499
|
// NG0205 ("Injector has already been destroyed") from inside the view's cleanup phase.
|
|
13919
14500
|
this.newQuery.subtle.destroyRef.onDestroy(() => this.teardown());
|
|
13920
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
|
+
}
|
|
13921
14506
|
get rawState() {
|
|
13922
14507
|
return this.toQueryState(this.newQuery.executionState());
|
|
13923
14508
|
}
|
|
@@ -14176,9 +14761,10 @@ const createInertQuery = () => {
|
|
|
14176
14761
|
...base,
|
|
14177
14762
|
execute: noop,
|
|
14178
14763
|
createSnapshot,
|
|
14764
|
+
url: inertSignal(null),
|
|
14179
14765
|
reset: noop,
|
|
14180
14766
|
abort: () => false,
|
|
14181
|
-
asReadonly: () => ({ ...base, createSnapshot }),
|
|
14767
|
+
asReadonly: () => ({ ...base, createSnapshot, url: inertSignal(null) }),
|
|
14182
14768
|
subtle: {
|
|
14183
14769
|
destroy: noop,
|
|
14184
14770
|
setResponse: noop,
|
|
@@ -14204,6 +14790,35 @@ const isInjectorUsable = (injector) => {
|
|
|
14204
14790
|
return false;
|
|
14205
14791
|
}
|
|
14206
14792
|
};
|
|
14793
|
+
/**
|
|
14794
|
+
* The scope ends when its owner dies with no request in flight, when that request settles, or when the
|
|
14795
|
+
* owner's nearest environment injector dies - a destroyed injector does not destroy the environment
|
|
14796
|
+
* injectors made with `createEnvironmentInjector` below it.
|
|
14797
|
+
*/
|
|
14798
|
+
const createSettleScope = (owner) => {
|
|
14799
|
+
const anchor = owner.get(EnvironmentInjector);
|
|
14800
|
+
const scope = createEnvironmentInjector([], anchor);
|
|
14801
|
+
const end = () => {
|
|
14802
|
+
if (!scope.destroyed)
|
|
14803
|
+
scope.destroy();
|
|
14804
|
+
};
|
|
14805
|
+
const bind = (query, queryInjector) => {
|
|
14806
|
+
const stopOnAnchorDestroy = anchor.get(DestroyRef).onDestroy(end);
|
|
14807
|
+
const stopOnOwnerDestroy = owner.get(DestroyRef).onDestroy(() => {
|
|
14808
|
+
if (isQueryStateLoading(untracked(() => query.rawState))) {
|
|
14809
|
+
query.state$.pipe(takeUntilResponse()).subscribe({ complete: end });
|
|
14810
|
+
return;
|
|
14811
|
+
}
|
|
14812
|
+
end();
|
|
14813
|
+
});
|
|
14814
|
+
queryInjector.get(DestroyRef).onDestroy(() => {
|
|
14815
|
+
stopOnAnchorDestroy();
|
|
14816
|
+
stopOnOwnerDestroy();
|
|
14817
|
+
end();
|
|
14818
|
+
});
|
|
14819
|
+
};
|
|
14820
|
+
return { destroyRef: scope.get(DestroyRef), bind };
|
|
14821
|
+
};
|
|
14207
14822
|
/**
|
|
14208
14823
|
* Angular's NG0203, thrown by `inject()` outside an injection context. Matched on the code rather than
|
|
14209
14824
|
* the message: production builds strip the message, and NG0205 ("Injector has already been destroyed")
|
|
@@ -14278,14 +14893,17 @@ class LegacyQueryCreator {
|
|
|
14278
14893
|
}
|
|
14279
14894
|
return runInInjectionContext(injector, () => {
|
|
14280
14895
|
return untracked(() => {
|
|
14896
|
+
const settleScope = this.canBeCached ? null : createSettleScope(injector);
|
|
14281
14897
|
const newQuery = this.options.creator({
|
|
14282
14898
|
onlyManualExecution: true,
|
|
14283
14899
|
injector,
|
|
14900
|
+
...(settleScope ? { scopeDestroyRef: settleScope.destroyRef } : {}),
|
|
14284
14901
|
silenceMissingWithArgsFeatureError: true,
|
|
14285
14902
|
// `LegacyQuery.execute()` forwards v2's `skipCache` as `allowCache` for every method, cacheable or not.
|
|
14286
14903
|
silenceUncacheableAllowCacheError: true,
|
|
14287
14904
|
});
|
|
14288
14905
|
const legacyQuery = new LegacyQuery(newQuery, queryArgs, this.options.entity, false, this.canBeCached);
|
|
14906
|
+
settleScope?.bind(legacyQuery, newQuery.subtle.injector);
|
|
14289
14907
|
if (args?.config?.destroyOnResponse) {
|
|
14290
14908
|
// Owned by the query's own injector, not the caller's. Call sites are told to pass an injector
|
|
14291
14909
|
// that outlives them, so they hand over a root or environment one - and an effect that only
|
|
@@ -15490,9 +16108,16 @@ class InfinityQuery {
|
|
|
15490
16108
|
destroyQueries() {
|
|
15491
16109
|
for (const query of this.queries$.value) {
|
|
15492
16110
|
query.stopPolling();
|
|
15493
|
-
query.
|
|
15494
|
-
|
|
16111
|
+
if (query.canBeCached)
|
|
16112
|
+
query.abort();
|
|
16113
|
+
if (!('destroy' in query) || typeof query.destroy !== 'function')
|
|
16114
|
+
continue;
|
|
16115
|
+
if (!query.canBeCached && isQueryStateLoading(query.rawState)) {
|
|
16116
|
+
destroyOnceSettled(query);
|
|
16117
|
+
}
|
|
16118
|
+
else {
|
|
15495
16119
|
query.destroy();
|
|
16120
|
+
}
|
|
15496
16121
|
}
|
|
15497
16122
|
}
|
|
15498
16123
|
prepareArgs(config, page) {
|
|
@@ -15818,7 +16443,8 @@ class QueryDirective {
|
|
|
15818
16443
|
this.queryState = queryStateSignal(this.query);
|
|
15819
16444
|
inject(ViewContainerRef).createEmbeddedView(inject(TemplateRef), this.viewContext);
|
|
15820
16445
|
effect(() => {
|
|
15821
|
-
const
|
|
16446
|
+
const rawQuery = this.query();
|
|
16447
|
+
const query = extractQuery(rawQuery);
|
|
15822
16448
|
untracked(() => {
|
|
15823
16449
|
if (!query) {
|
|
15824
16450
|
this.viewContext.$implicit = null;
|
|
@@ -15834,7 +16460,7 @@ class QueryDirective {
|
|
|
15834
16460
|
if (isQueryStatePrepared(query.rawState) || isQueryStateCancelled(query.rawState)) {
|
|
15835
16461
|
query.execute();
|
|
15836
16462
|
}
|
|
15837
|
-
this.viewContext.scope = isQueryCollection(
|
|
16463
|
+
this.viewContext.scope = isQueryCollection(rawQuery) ? rawQuery.type : null;
|
|
15838
16464
|
this.viewContext.query = query;
|
|
15839
16465
|
});
|
|
15840
16466
|
});
|
|
@@ -16904,13 +17530,13 @@ const transformToStringArray = (value) => {
|
|
|
16904
17530
|
};
|
|
16905
17531
|
const transformToNumber = (value) => {
|
|
16906
17532
|
if (typeof value === 'number') {
|
|
16907
|
-
return value;
|
|
17533
|
+
return Number.isFinite(value) ? value : null;
|
|
16908
17534
|
}
|
|
16909
17535
|
if (typeof value === 'string') {
|
|
16910
17536
|
if (!value.trim())
|
|
16911
17537
|
return null;
|
|
16912
17538
|
const number = Number(value);
|
|
16913
|
-
return Number.
|
|
17539
|
+
return Number.isFinite(number) ? number : null;
|
|
16914
17540
|
}
|
|
16915
17541
|
return null;
|
|
16916
17542
|
};
|
|
@@ -16946,7 +17572,7 @@ const transformToBooleanArray = (value) => {
|
|
|
16946
17572
|
const DATE_ONLY_PATTERN = /^(\d{4})-(\d{2})-(\d{2})$/;
|
|
16947
17573
|
const transformToDate = (value) => {
|
|
16948
17574
|
if (value instanceof Date) {
|
|
16949
|
-
return value;
|
|
17575
|
+
return Number.isNaN(value.getTime()) ? null : value;
|
|
16950
17576
|
}
|
|
16951
17577
|
if (typeof value === 'string') {
|
|
16952
17578
|
const date = new Date(value);
|
|
@@ -16973,7 +17599,9 @@ const transformToDateArray = (value) => {
|
|
|
16973
17599
|
};
|
|
16974
17600
|
const transformToSort = (value) => {
|
|
16975
17601
|
if (typeof value === 'string') {
|
|
16976
|
-
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);
|
|
16977
17605
|
if (!active) {
|
|
16978
17606
|
return null;
|
|
16979
17607
|
}
|
|
@@ -17676,6 +18304,8 @@ const MAX_RESET_PASSES = 10;
|
|
|
17676
18304
|
const changedKeysBetween = (previous, current) => Object.keys(current).filter((key) => !equal(previous?.[key], current[key]));
|
|
17677
18305
|
/** Reads a URL string into the type of the field's default; `null` means the default's type cannot hold it. */
|
|
17678
18306
|
const autoCoerce = (raw, defaultValue) => {
|
|
18307
|
+
if (Array.isArray(raw))
|
|
18308
|
+
return Array.isArray(defaultValue) ? raw : null;
|
|
17679
18309
|
if (typeof raw !== 'string')
|
|
17680
18310
|
return raw;
|
|
17681
18311
|
if (Array.isArray(defaultValue))
|
|
@@ -17703,10 +18333,13 @@ const resolveDebounce = (fieldDefs, changedKeys, live) => {
|
|
|
17703
18333
|
}
|
|
17704
18334
|
return Math.min(...times);
|
|
17705
18335
|
};
|
|
18336
|
+
const isUnserializable = (value) => value === undefined ||
|
|
18337
|
+
(typeof value === 'number' && Number.isNaN(value)) ||
|
|
18338
|
+
(value instanceof Date && Number.isNaN(value.getTime()));
|
|
17706
18339
|
/**
|
|
17707
18340
|
* A value that cannot be told apart from the default once serialized - a cleared text (`''`), an emptied list
|
|
17708
|
-
* (`[]`) or one whose `valueToQueryParam` yields nothing - commits as the
|
|
17709
|
-
* 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.
|
|
17710
18343
|
*/
|
|
17711
18344
|
const normalizeLive = (fieldDefs, rawLive, defaults) => {
|
|
17712
18345
|
const live = { ...rawLive };
|
|
@@ -17717,7 +18350,7 @@ const normalizeLive = (fieldDefs, rawLive, defaults) => {
|
|
|
17717
18350
|
const serialized = def.valueToQueryParam?.(value);
|
|
17718
18351
|
const serializedToNothing = !!def.valueToQueryParam && (serialized === null || serialized === undefined);
|
|
17719
18352
|
const clearedNullable = defaults[key] === null && (value === '' || (Array.isArray(value) && value.length === 0));
|
|
17720
|
-
if (serializedToNothing || clearedNullable) {
|
|
18353
|
+
if (serializedToNothing || clearedNullable || isUnserializable(value)) {
|
|
17721
18354
|
live[key] = defaults[key];
|
|
17722
18355
|
}
|
|
17723
18356
|
}
|
|
@@ -18208,6 +18841,13 @@ const defineQueryForm = (config) => {
|
|
|
18208
18841
|
}
|
|
18209
18842
|
observeOptions = options;
|
|
18210
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
|
+
}
|
|
18211
18851
|
changesSeenAtObserve = untracked(queryParamChanges);
|
|
18212
18852
|
if (options?.persistence) {
|
|
18213
18853
|
restoreFromStorage(options.persistence, route.snapshot.queryParams);
|
|
@@ -18334,6 +18974,11 @@ const normalizeConfig = (config) => {
|
|
|
18334
18974
|
isResetBy: isResetBy === undefined ? undefined : Array.isArray(isResetBy) ? isResetBy : [isResetBy],
|
|
18335
18975
|
};
|
|
18336
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
|
+
};
|
|
18337
18982
|
function queryField(config) {
|
|
18338
18983
|
return {
|
|
18339
18984
|
defaultValue: null,
|
|
@@ -18410,6 +19055,7 @@ function dateQueryField(config) {
|
|
|
18410
19055
|
return {
|
|
18411
19056
|
defaultValue: null,
|
|
18412
19057
|
queryParamToValue: transformToDate,
|
|
19058
|
+
valueToQueryParam: dateToQueryParam,
|
|
18413
19059
|
...normalizeConfig(rest),
|
|
18414
19060
|
};
|
|
18415
19061
|
}
|
|
@@ -18417,6 +19063,11 @@ function dateQueryField(config) {
|
|
|
18417
19063
|
const dateArrayQueryField = (config) => ({
|
|
18418
19064
|
defaultValue: null,
|
|
18419
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
|
+
},
|
|
18420
19071
|
...normalizeConfig(config),
|
|
18421
19072
|
});
|
|
18422
19073
|
|
|
@@ -18425,6 +19076,7 @@ const WebSocketRuntimeErrorCode = {
|
|
|
18425
19076
|
// Web Socket Client
|
|
18426
19077
|
ROOM_NOT_JOINED: 1000,
|
|
18427
19078
|
MESSAGE_MALFORMED: 1001,
|
|
19079
|
+
AUTH_AND_AUTH_PROVIDER: 1002,
|
|
18428
19080
|
};
|
|
18429
19081
|
const roomNotJoined = (room) => {
|
|
18430
19082
|
return new RuntimeError(WebSocketRuntimeErrorCode.ROOM_NOT_JOINED, `Tried leaving the room "${room}" but it has not been joined.`);
|
|
@@ -18432,6 +19084,9 @@ const roomNotJoined = (room) => {
|
|
|
18432
19084
|
const messageMalformed = () => {
|
|
18433
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 }.');
|
|
18434
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
|
+
};
|
|
18435
19090
|
|
|
18436
19091
|
const MAX_DEVTOOLS_MESSAGES = 100;
|
|
18437
19092
|
const RECONNECT_BASE_DELAY = 1000;
|
|
@@ -18441,7 +19096,10 @@ const createWebSocketClient = (options) => {
|
|
|
18441
19096
|
return defineRootProvider(() => {
|
|
18442
19097
|
if (isPlatformServer(inject(PLATFORM_ID)))
|
|
18443
19098
|
return createServerWebSocketClient();
|
|
18444
|
-
|
|
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;
|
|
18445
19103
|
const socket = options.io(options.url, {
|
|
18446
19104
|
withCredentials: options.withCredentials ?? true,
|
|
18447
19105
|
autoConnect: false,
|
|
@@ -18449,6 +19107,7 @@ const createWebSocketClient = (options) => {
|
|
|
18449
19107
|
auth: auth ? (cb) => cb(typeof auth === 'function' ? auth() : auth) : undefined,
|
|
18450
19108
|
});
|
|
18451
19109
|
const rooms = new Map();
|
|
19110
|
+
const endedRoomHolders = new Map();
|
|
18452
19111
|
const joinsDeliveredThisConnection = new Set();
|
|
18453
19112
|
const joinsDeliveredToClosedConnection = new Set();
|
|
18454
19113
|
const isConnected = signal(false, /* @ts-ignore */
|
|
@@ -18479,50 +19138,54 @@ const createWebSocketClient = (options) => {
|
|
|
18479
19138
|
direction: 'out',
|
|
18480
19139
|
});
|
|
18481
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
|
+
};
|
|
18482
19170
|
const joinRoom = (room) => {
|
|
18483
|
-
|
|
18484
|
-
|
|
18485
|
-
|
|
18486
|
-
// effect joins the new room without ever leaving the old one.
|
|
18487
|
-
const pre = previousSignalValue(computed(() => roomFn()));
|
|
19171
|
+
if (typeof room === 'string')
|
|
19172
|
+
return joinStaticRoom(room);
|
|
19173
|
+
const roomFn = room;
|
|
18488
19174
|
const roomData = signal(null, /* @ts-ignore */
|
|
18489
19175
|
...(ngDevMode ? [{ debugName: "roomData" }] : /* istanbul ignore next */ []));
|
|
18490
19176
|
let joinedRoomName = null;
|
|
18491
|
-
const join = (name) => {
|
|
18492
|
-
const existingRoom = rooms.get(name);
|
|
18493
|
-
if (existingRoom) {
|
|
18494
|
-
existingRoom.joinCount++;
|
|
18495
|
-
return existingRoom;
|
|
18496
|
-
}
|
|
18497
|
-
emit({ event: 'join-room', data: name, room: name });
|
|
18498
|
-
joinsDeliveredToClosedConnection.delete(name);
|
|
18499
|
-
const messages = new Subject();
|
|
18500
|
-
const newRoom = {
|
|
18501
|
-
latestMessage: signal(null),
|
|
18502
|
-
messages,
|
|
18503
|
-
messages$: messages.asObservable(),
|
|
18504
|
-
joinCount: 1,
|
|
18505
|
-
};
|
|
18506
|
-
rooms.set(name, newRoom);
|
|
18507
|
-
syncDevtoolsRooms();
|
|
18508
|
-
return newRoom;
|
|
18509
|
-
};
|
|
18510
19177
|
effect(() => {
|
|
18511
|
-
const current = roomFn();
|
|
19178
|
+
const current = roomFn() || null;
|
|
18512
19179
|
untracked(() => {
|
|
18513
|
-
|
|
18514
|
-
if (previous === current)
|
|
19180
|
+
if (current === joinedRoomName)
|
|
18515
19181
|
return;
|
|
18516
|
-
if (
|
|
18517
|
-
leaveRoom(
|
|
19182
|
+
if (joinedRoomName) {
|
|
19183
|
+
leaveRoom(joinedRoomName);
|
|
18518
19184
|
joinedRoomName = null;
|
|
18519
19185
|
}
|
|
18520
19186
|
if (current) {
|
|
18521
|
-
|
|
18522
|
-
|
|
18523
|
-
joinedRoomName = current;
|
|
18524
|
-
roomData.set(joinedRoom);
|
|
18525
|
-
}
|
|
19187
|
+
joinedRoomName = current;
|
|
19188
|
+
roomData.set(join(current));
|
|
18526
19189
|
}
|
|
18527
19190
|
else {
|
|
18528
19191
|
roomData.set(null);
|
|
@@ -18539,6 +19202,14 @@ const createWebSocketClient = (options) => {
|
|
|
18539
19202
|
return roomData.asReadonly();
|
|
18540
19203
|
};
|
|
18541
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
|
+
}
|
|
18542
19213
|
const joinedRoom = rooms.get(room);
|
|
18543
19214
|
if (!joinedRoom) {
|
|
18544
19215
|
if (isDevMode())
|
|
@@ -18559,9 +19230,10 @@ const createWebSocketClient = (options) => {
|
|
|
18559
19230
|
let reconnectTimer = null;
|
|
18560
19231
|
let connectedAt = null;
|
|
18561
19232
|
let destroyed = false;
|
|
19233
|
+
let suspended = false;
|
|
18562
19234
|
/** socket.io stops reconnecting after a server disconnect or a rejected handshake, so the client takes over. */
|
|
18563
19235
|
const scheduleReconnect = () => {
|
|
18564
|
-
if (reconnectTimer !== null || destroyed)
|
|
19236
|
+
if (reconnectTimer !== null || destroyed || suspended)
|
|
18565
19237
|
return;
|
|
18566
19238
|
const delay = Math.min(RECONNECT_BASE_DELAY * 2 ** reconnectAttempt, RECONNECT_MAX_DELAY);
|
|
18567
19239
|
reconnectAttempt++;
|
|
@@ -18651,7 +19323,62 @@ const createWebSocketClient = (options) => {
|
|
|
18651
19323
|
});
|
|
18652
19324
|
setupWebSocketConnectionListener();
|
|
18653
19325
|
setupWebSocketListener();
|
|
18654
|
-
|
|
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
|
+
}
|
|
18655
19382
|
const client = {
|
|
18656
19383
|
joinRoom,
|
|
18657
19384
|
isConnected: isConnected.asReadonly(),
|
|
@@ -18698,5 +19425,5 @@ const createServerWebSocketClient = () => {
|
|
|
18698
19425
|
* Generated bundle index. Do not edit.
|
|
18699
19426
|
*/
|
|
18700
19427
|
|
|
18701
|
-
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 };
|
|
18702
19429
|
//# sourceMappingURL=ethlete-query.mjs.map
|