@ethlete/query 6.0.0-next.54 → 6.0.0-next.56
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 +61 -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 +42 -21
- package/fesm2022/ethlete-query-testing.mjs.map +1 -1
- package/fesm2022/ethlete-query.mjs +2059 -1289
- 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 +1092 -815
|
@@ -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, or removes it with `null`. Called by `provideQueryDevtools()` and its test reset only.
|
|
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;
|
|
@@ -879,272 +1458,53 @@ const createAuthGuard = (providerRef, config) => {
|
|
|
879
1458
|
return readReturnUrl(router, router.url, param);
|
|
880
1459
|
},
|
|
881
1460
|
navigateAfterLogin: () => {
|
|
882
|
-
const router = inject(Router);
|
|
883
|
-
return defer(() => {
|
|
884
|
-
const returnUrl = readReturnUrl(router, router.url, param);
|
|
885
|
-
return from(router.navigateByUrl(returnUrl ?? resolveTarget(router, config.defaultUrl ?? DEFAULT_URL)));
|
|
886
|
-
});
|
|
887
|
-
},
|
|
888
|
-
};
|
|
889
|
-
};
|
|
890
|
-
|
|
891
|
-
/**
|
|
892
|
-
* Turns the features applied at creation into their devtools representation.
|
|
893
|
-
* @internal
|
|
894
|
-
*/
|
|
895
|
-
const describeQueryDevtoolsFeatures = (features) => features?.map((feature) => ({ type: feature.type, details: feature.devtools?.() ?? [] })) ?? [];
|
|
896
|
-
/**
|
|
897
|
-
* Renders a millisecond duration the way feature details spell one out - `500ms`, `1.5s`, `15m`.
|
|
898
|
-
* @internal
|
|
899
|
-
*/
|
|
900
|
-
const formatQueryDevtoolsDuration = (ms) => {
|
|
901
|
-
if (!Number.isFinite(ms))
|
|
902
|
-
return String(ms);
|
|
903
|
-
if (ms < 1000)
|
|
904
|
-
return `${ms}ms`;
|
|
905
|
-
const round = (value, unit) => `${Number(value.toFixed(1))}${unit}`;
|
|
906
|
-
if (ms < 60_000)
|
|
907
|
-
return round(ms / 1000, 's');
|
|
908
|
-
if (ms < 3_600_000)
|
|
909
|
-
return round(ms / 60_000, 'm');
|
|
910
|
-
return round(ms / 3_600_000, 'h');
|
|
911
|
-
};
|
|
912
|
-
/**
|
|
913
|
-
* The name of a function passed as `options.<key>`, or `null` when it is an inline lambda. An inline
|
|
914
|
-
* `{ handler: (e) => … }` inherits the name `handler` from the key it is assigned to, which tells the
|
|
915
|
-
* reader nothing - a reference to a declared function does.
|
|
916
|
-
*/
|
|
917
|
-
const queryDevtoolsFnName = (fn, key) => {
|
|
918
|
-
const name = typeof fn === 'function' ? fn.name.replace(/^bound /, '') : '';
|
|
919
|
-
return !name || name === key ? null : name;
|
|
920
|
-
};
|
|
921
|
-
/**
|
|
922
|
-
* A detail for the function passed as `options.<key>`, omitted when it is an inline lambda.
|
|
923
|
-
* @internal
|
|
924
|
-
*/
|
|
925
|
-
const queryDevtoolsFnDetail = (fn, key) => {
|
|
926
|
-
const name = queryDevtoolsFnName(fn, key);
|
|
927
|
-
return name ? [{ label: key, value: name }] : [];
|
|
928
|
-
};
|
|
929
|
-
|
|
930
|
-
const noop$2 = () => undefined;
|
|
931
|
-
let registrar = null;
|
|
932
|
-
/**
|
|
933
|
-
* Installs the real registry. Called by `provideQueryDevtools()`; nothing else may call it.
|
|
934
|
-
* @internal
|
|
935
|
-
*/
|
|
936
|
-
const setQueryDevtoolsRegistrar = (fn) => {
|
|
937
|
-
registrar = fn;
|
|
938
|
-
};
|
|
939
|
-
/**
|
|
940
|
-
* Whether `provideQueryDevtools()` has been called on this page. When `false` every instrumentation
|
|
941
|
-
* site is a no-op, which is what a devtools UI gates itself on so a build that omits the provider
|
|
942
|
-
* renders nothing.
|
|
943
|
-
*
|
|
944
|
-
* It is answered by the `provideQueryDevtools()` call itself, which runs while the providers array is
|
|
945
|
-
* built - so it is already settled by the time an application bootstraps and needs no signal.
|
|
946
|
-
*/
|
|
947
|
-
const isQueryDevtoolsEnabled = () => registrar !== null;
|
|
948
|
-
/**
|
|
949
|
-
* Registers an entry with the devtools registry. Returns an unregister callback. A no-op (returning
|
|
950
|
-
* a no-op callback) unless {@link provideQueryDevtools} has been called.
|
|
951
|
-
* @internal
|
|
952
|
-
*/
|
|
953
|
-
const registerQueryDevtoolsEntry = (entry) => registrar?.(entry) ?? noop$2;
|
|
954
|
-
let statsFactory = null;
|
|
955
|
-
/**
|
|
956
|
-
* Installs the stats recorder factory. Called by `provideQueryDevtools()`; nothing else may call it.
|
|
957
|
-
* @internal
|
|
958
|
-
*/
|
|
959
|
-
const setQueryDevtoolsStatsFactory = (fn) => {
|
|
960
|
-
statsFactory = fn;
|
|
961
|
-
};
|
|
962
|
-
/**
|
|
963
|
-
* A stats recorder for one entry, or `null` unless {@link provideQueryDevtools} has been called.
|
|
964
|
-
*
|
|
965
|
-
* Instrumentation sites must go through this instead of importing `createQueryDevtoolsStats`: a static
|
|
966
|
-
* import pins the whole stats module (~1.4 kB) into every bundle that touches a query, and a runtime
|
|
967
|
-
* `isQueryDevtoolsEnabled()` guard at the call site does not undo that. Reached only from the provider,
|
|
968
|
-
* it is dead code in an app without devtools.
|
|
969
|
-
* @internal
|
|
970
|
-
*/
|
|
971
|
-
const createQueryDevtoolsStatsRecorder = () => statsFactory?.() ?? null;
|
|
972
|
-
let formLinksFactory = null;
|
|
973
|
-
/**
|
|
974
|
-
* Installs the form-links recorder factory. Called by `provideQueryDevtools()`; nothing else may call it.
|
|
975
|
-
* @internal
|
|
976
|
-
*/
|
|
977
|
-
const setQueryDevtoolsFormLinksFactory = (fn) => {
|
|
978
|
-
formLinksFactory = fn;
|
|
979
|
-
};
|
|
980
|
-
/**
|
|
981
|
-
* A form-links recorder for one query, or `null` unless {@link provideQueryDevtools} has been called.
|
|
982
|
-
* Reached only from the provider for the same reason {@link createQueryDevtoolsStatsRecorder} is.
|
|
983
|
-
* @internal
|
|
984
|
-
*/
|
|
985
|
-
const createQueryDevtoolsFormLinksRecorder = () => formLinksFactory?.() ?? null;
|
|
986
|
-
let overridesFactory = null;
|
|
987
|
-
/**
|
|
988
|
-
* Installs the overrides recorder factory. Called by `provideQueryDevtools()`; nothing else may call it.
|
|
989
|
-
* @internal
|
|
990
|
-
*/
|
|
991
|
-
const setQueryDevtoolsOverridesFactory = (fn) => {
|
|
992
|
-
overridesFactory = fn;
|
|
993
|
-
};
|
|
994
|
-
/**
|
|
995
|
-
* An overrides recorder for one query, or `null` unless {@link provideQueryDevtools} has been called.
|
|
996
|
-
* Reached only from the provider for the same reason {@link createQueryDevtoolsStatsRecorder} is.
|
|
997
|
-
* @internal
|
|
998
|
-
*/
|
|
999
|
-
const createQueryDevtoolsOverridesRecorder = () => overridesFactory?.() ?? null;
|
|
1000
|
-
/** The ids of the query forms read by the args build currently running, or `null` outside of one. */
|
|
1001
|
-
let formLinkSink = null;
|
|
1002
|
-
/**
|
|
1003
|
-
* Runs `build` and reports which query forms it read. Nested builds are supported - the outer one
|
|
1004
|
-
* resumes collecting once the inner one is done.
|
|
1005
|
-
* @internal
|
|
1006
|
-
*/
|
|
1007
|
-
const collectQueryFormLinks = (build) => {
|
|
1008
|
-
const outer = formLinkSink;
|
|
1009
|
-
const sink = new Set();
|
|
1010
|
-
formLinkSink = sink;
|
|
1011
|
-
try {
|
|
1012
|
-
return { value: build(), ids: [...sink] };
|
|
1013
|
-
}
|
|
1014
|
-
finally {
|
|
1015
|
-
formLinkSink = outer;
|
|
1016
|
-
}
|
|
1017
|
-
};
|
|
1018
|
-
/**
|
|
1019
|
-
* Records that a query form's committed value was read. The form calls this from its `value` signal, so
|
|
1020
|
-
* a read that happens while a query builds its args is what links the two - no naming convention, and
|
|
1021
|
-
* nothing to keep in sync when the args change.
|
|
1022
|
-
* @internal
|
|
1023
|
-
*/
|
|
1024
|
-
const noteQueryFormRead = (id) => {
|
|
1025
|
-
formLinkSink?.add(id);
|
|
1026
|
-
};
|
|
1027
|
-
let faultResolver = null;
|
|
1028
|
-
let mockResolver = null;
|
|
1029
|
-
/**
|
|
1030
|
-
* Installs the fault resolver. Called by `provideQueryDevtools()`; nothing else may call it.
|
|
1031
|
-
* @internal
|
|
1032
|
-
*/
|
|
1033
|
-
const setQueryDevtoolsFaultResolver = (fn) => {
|
|
1034
|
-
faultResolver = fn;
|
|
1035
|
-
};
|
|
1036
|
-
/**
|
|
1037
|
-
* Installs the mock resolver. Called by `provideQueryDevtools()`; nothing else may call it.
|
|
1038
|
-
* @internal
|
|
1039
|
-
*/
|
|
1040
|
-
const setQueryDevtoolsMockResolver = (fn) => {
|
|
1041
|
-
mockResolver = fn;
|
|
1042
|
-
};
|
|
1043
|
-
/**
|
|
1044
|
-
* Whether the devtools can do anything to a request at all - inject a fault, or serve a mock instead of
|
|
1045
|
-
* sending it. Checked before building the wrapper that resolves both per attempt, so a request in an app
|
|
1046
|
-
* without devtools keeps the pipeline it always had.
|
|
1047
|
-
* @internal
|
|
1048
|
-
*/
|
|
1049
|
-
const isQueryDevtoolsRequestInterceptionEnabled = () => faultResolver !== null || mockResolver !== null;
|
|
1050
|
-
/**
|
|
1051
|
-
* The mock to serve one upcoming attempt instead of sending it, or `null` to let it through.
|
|
1052
|
-
* @internal
|
|
1053
|
-
*/
|
|
1054
|
-
const resolveQueryDevtoolsMock = (target) => mockResolver?.(target) ?? null;
|
|
1055
|
-
/**
|
|
1056
|
-
* Resolves the fault to apply to one upcoming attempt, or `null` when nothing is armed. Consumes a
|
|
1057
|
-
* `failNext` budget, so call it exactly once per attempt.
|
|
1058
|
-
* @internal
|
|
1059
|
-
*/
|
|
1060
|
-
const resolveQueryDevtoolsFault = (target) => faultResolver?.(target) ?? null;
|
|
1061
|
-
let tokenPayloadPatcher = null;
|
|
1062
|
-
/**
|
|
1063
|
-
* Installs the access-token payload patcher. Called by `provideQueryDevtools()`; nothing else may call it.
|
|
1064
|
-
* @internal
|
|
1065
|
-
*/
|
|
1066
|
-
const setQueryDevtoolsTokenPayloadPatcher = (fn) => {
|
|
1067
|
-
tokenPayloadPatcher = fn;
|
|
1068
|
-
};
|
|
1069
|
-
/**
|
|
1070
|
-
* A decoded access token as the devtools want it seen, which today means with an overridden lifetime
|
|
1071
|
-
* applied. Returns the payload as decoded unless `provideQueryDevtools()` installed a patcher.
|
|
1072
|
-
*
|
|
1073
|
-
* Auth code must reach the override through this rather than importing the registry it lives in: a
|
|
1074
|
-
* static import would pin that module into every bundle that builds an auth provider, and a runtime
|
|
1075
|
-
* guard at the call site does not undo that.
|
|
1076
|
-
* @internal
|
|
1077
|
-
*/
|
|
1078
|
-
const patchQueryDevtoolsTokenPayload = (options) => (tokenPayloadPatcher ? tokenPayloadPatcher(options) : options.payload);
|
|
1079
|
-
/** The batch item being created right now, or `null` outside one. */
|
|
1080
|
-
let batchOwner = null;
|
|
1081
|
-
/**
|
|
1082
|
-
* Runs `create` with every query it creates attributed to `owner`'s batch and item. The attribution has
|
|
1083
|
-
* to be ambient: a batch's item query is built by the query creator the caller handed it, which takes no
|
|
1084
|
-
* devtools argument - so there is nothing to thread the owner through. Nested runs restore the outer one.
|
|
1085
|
-
* @internal
|
|
1086
|
-
*/
|
|
1087
|
-
const runInQueryDevtoolsBatch = (owner, create) => {
|
|
1088
|
-
const outer = batchOwner;
|
|
1089
|
-
batchOwner = owner;
|
|
1090
|
-
try {
|
|
1091
|
-
return create();
|
|
1092
|
-
}
|
|
1093
|
-
finally {
|
|
1094
|
-
batchOwner = outer;
|
|
1095
|
-
}
|
|
1096
|
-
};
|
|
1097
|
-
/** The batch item a query being created belongs to, or `null` for one created outside a batch. @internal */
|
|
1098
|
-
const currentQueryDevtoolsBatch = () => batchOwner;
|
|
1099
|
-
let suppressStackRegistration = false;
|
|
1100
|
-
/**
|
|
1101
|
-
* Suppresses devtools registration for the next {@link createQueryStack} call. Used by
|
|
1102
|
-
* `createPagedQueryStack` so its backing stack is not registered as a separate `query-stack` entry.
|
|
1103
|
-
* @internal
|
|
1104
|
-
*/
|
|
1105
|
-
const suppressNextQueryStackDevtools = () => {
|
|
1106
|
-
suppressStackRegistration = true;
|
|
1461
|
+
const router = inject(Router);
|
|
1462
|
+
return defer(() => {
|
|
1463
|
+
const returnUrl = readReturnUrl(router, router.url, param);
|
|
1464
|
+
return from(router.navigateByUrl(returnUrl ?? resolveTarget(router, config.defaultUrl ?? DEFAULT_URL)));
|
|
1465
|
+
});
|
|
1466
|
+
},
|
|
1467
|
+
};
|
|
1107
1468
|
};
|
|
1469
|
+
|
|
1108
1470
|
/**
|
|
1109
|
-
*
|
|
1110
|
-
* devtools are disabled) so a suppression request never leaks into a later stack.
|
|
1471
|
+
* Turns the features applied at creation into their devtools representation.
|
|
1111
1472
|
* @internal
|
|
1112
1473
|
*/
|
|
1113
|
-
const
|
|
1114
|
-
const value = suppressStackRegistration;
|
|
1115
|
-
suppressStackRegistration = false;
|
|
1116
|
-
return value;
|
|
1117
|
-
};
|
|
1118
|
-
let authProviderRegistrar = null;
|
|
1474
|
+
const describeQueryDevtoolsFeatures = (features) => features?.map((feature) => ({ type: feature.type, details: feature.devtools?.() ?? [] })) ?? [];
|
|
1119
1475
|
/**
|
|
1120
|
-
*
|
|
1121
|
-
* call it.
|
|
1476
|
+
* Renders a millisecond duration the way feature details spell one out - `500ms`, `1.5s`, `15m`.
|
|
1122
1477
|
* @internal
|
|
1123
1478
|
*/
|
|
1124
|
-
const
|
|
1125
|
-
|
|
1479
|
+
const formatQueryDevtoolsDuration = (ms) => {
|
|
1480
|
+
if (!Number.isFinite(ms))
|
|
1481
|
+
return String(ms);
|
|
1482
|
+
if (ms < 1000)
|
|
1483
|
+
return `${ms}ms`;
|
|
1484
|
+
const round = (value, unit) => `${Number(value.toFixed(1))}${unit}`;
|
|
1485
|
+
if (ms < 60_000)
|
|
1486
|
+
return round(ms / 1000, 's');
|
|
1487
|
+
if (ms < 3_600_000)
|
|
1488
|
+
return round(ms / 60_000, 'm');
|
|
1489
|
+
return round(ms / 3_600_000, 'h');
|
|
1126
1490
|
};
|
|
1127
1491
|
/**
|
|
1128
|
-
*
|
|
1129
|
-
*
|
|
1130
|
-
*
|
|
1131
|
-
*/
|
|
1132
|
-
const registerQueryDevtoolsAuthProvider = (registration) => authProviderRegistrar?.(registration) ?? (() => undefined);
|
|
1133
|
-
let authSeedReader = null;
|
|
1134
|
-
/**
|
|
1135
|
-
* Installs the tab-local session reader. Called by `provideQueryDevtools()`; nothing else may call it.
|
|
1136
|
-
* @internal
|
|
1492
|
+
* The name of a function passed as `options.<key>`, or `null` when it is an inline lambda. An inline
|
|
1493
|
+
* `{ handler: (e) => … }` inherits the name `handler` from the key it is assigned to, which tells the
|
|
1494
|
+
* reader nothing - a reference to a declared function does.
|
|
1137
1495
|
*/
|
|
1138
|
-
const
|
|
1139
|
-
|
|
1496
|
+
const queryDevtoolsFnName = (fn, key) => {
|
|
1497
|
+
const name = typeof fn === 'function' ? fn.name.replace(/^bound /, '') : '';
|
|
1498
|
+
return !name || name === key ? null : name;
|
|
1140
1499
|
};
|
|
1141
1500
|
/**
|
|
1142
|
-
*
|
|
1143
|
-
* provider is built, before its features run: the tokens have to be in place before `withPersistentAuth`
|
|
1144
|
-
* decides whether to spend its cookie.
|
|
1501
|
+
* A detail for the function passed as `options.<key>`, omitted when it is an inline lambda.
|
|
1145
1502
|
* @internal
|
|
1146
1503
|
*/
|
|
1147
|
-
const
|
|
1504
|
+
const queryDevtoolsFnDetail = (fn, key) => {
|
|
1505
|
+
const name = queryDevtoolsFnName(fn, key);
|
|
1506
|
+
return name ? [{ label: key, value: name }] : [];
|
|
1507
|
+
};
|
|
1148
1508
|
|
|
1149
1509
|
/**
|
|
1150
1510
|
* Whether a create-query options object came from the gql layer - only gql's `creatorInternals` carries
|
|
@@ -1153,21 +1513,105 @@ const readQueryDevtoolsAuthSeed = (providerName) => authSeedReader?.(providerNam
|
|
|
1153
1513
|
*/
|
|
1154
1514
|
const isCreateGqlQueryOptions = (options) => 'transport' in options.creatorInternals;
|
|
1155
1515
|
|
|
1156
|
-
const
|
|
1157
|
-
const
|
|
1158
|
-
const
|
|
1159
|
-
const
|
|
1160
|
-
|
|
1161
|
-
|
|
1516
|
+
const createObservableSignalWatcher = (injector) => {
|
|
1517
|
+
const destroyRef = injector.get(DestroyRef);
|
|
1518
|
+
const watched = [];
|
|
1519
|
+
const registrations = signal(0, /* @ts-ignore */
|
|
1520
|
+
...(ngDevMode ? [{ debugName: "registrations" }] : /* istanbul ignore next */ []));
|
|
1521
|
+
let watching = false;
|
|
1522
|
+
let destroyed = false;
|
|
1523
|
+
const push = (entry) => {
|
|
1524
|
+
if (entry.subject.closed || entry.subject.isStopped)
|
|
1525
|
+
return;
|
|
1526
|
+
let value;
|
|
1527
|
+
try {
|
|
1528
|
+
value = entry.source();
|
|
1529
|
+
}
|
|
1530
|
+
catch (error) {
|
|
1531
|
+
untracked(() => entry.subject.error(error));
|
|
1532
|
+
return;
|
|
1533
|
+
}
|
|
1534
|
+
if (entry.hasValue && Object.is(value, entry.last))
|
|
1535
|
+
return;
|
|
1536
|
+
entry.hasValue = true;
|
|
1537
|
+
entry.last = value;
|
|
1538
|
+
untracked(() => entry.subject.next(value));
|
|
1539
|
+
};
|
|
1540
|
+
const startWatching = () => {
|
|
1541
|
+
if (watching)
|
|
1542
|
+
return;
|
|
1543
|
+
watching = true;
|
|
1544
|
+
effect(() => {
|
|
1545
|
+
registrations();
|
|
1546
|
+
for (const entry of watched)
|
|
1547
|
+
push(entry);
|
|
1548
|
+
}, { injector });
|
|
1549
|
+
destroyRef.onDestroy(() => {
|
|
1550
|
+
destroyed = true;
|
|
1551
|
+
for (const entry of watched)
|
|
1552
|
+
entry.subject.complete();
|
|
1553
|
+
});
|
|
1554
|
+
};
|
|
1555
|
+
const watch = (source) => {
|
|
1556
|
+
const entry = { source, subject: new ReplaySubject(1), hasValue: false, last: undefined };
|
|
1557
|
+
if (destroyed || destroyRef.destroyed) {
|
|
1558
|
+
push(entry);
|
|
1559
|
+
entry.subject.complete();
|
|
1560
|
+
return entry;
|
|
1561
|
+
}
|
|
1562
|
+
startWatching();
|
|
1563
|
+
watched.push(entry);
|
|
1564
|
+
push(entry);
|
|
1565
|
+
registrations.update((count) => count + 1);
|
|
1566
|
+
return entry;
|
|
1567
|
+
};
|
|
1568
|
+
const wrap = (source) => {
|
|
1569
|
+
let default$ = null;
|
|
1570
|
+
const overrides = new WeakMap();
|
|
1571
|
+
const getDefault = () => {
|
|
1572
|
+
if (default$)
|
|
1573
|
+
return default$;
|
|
1574
|
+
const entry = untracked(() => watch(source));
|
|
1575
|
+
default$ = new Observable((subscriber) => {
|
|
1576
|
+
untracked(() => push(entry));
|
|
1577
|
+
return entry.subject.subscribe(subscriber);
|
|
1578
|
+
});
|
|
1162
1579
|
return default$;
|
|
1163
|
-
|
|
1164
|
-
|
|
1165
|
-
|
|
1166
|
-
|
|
1167
|
-
|
|
1168
|
-
|
|
1580
|
+
};
|
|
1581
|
+
const asObservable = (options) => {
|
|
1582
|
+
const overrideInjector = options?.injector;
|
|
1583
|
+
if (!overrideInjector)
|
|
1584
|
+
return getDefault();
|
|
1585
|
+
const existing = overrides.get(overrideInjector);
|
|
1586
|
+
if (existing)
|
|
1587
|
+
return existing;
|
|
1588
|
+
const override$ = getDefault().pipe(takeUntilDestroyed(overrideInjector.get(DestroyRef)));
|
|
1589
|
+
overrides.set(overrideInjector, override$);
|
|
1590
|
+
return override$;
|
|
1591
|
+
};
|
|
1592
|
+
return Object.assign(computed(() => source()), { asObservable });
|
|
1169
1593
|
};
|
|
1170
|
-
return
|
|
1594
|
+
return { wrap };
|
|
1595
|
+
};
|
|
1596
|
+
|
|
1597
|
+
// Angular has no public check for an injection context; `inject()` outside one throws NG0203 (`code: -203`).
|
|
1598
|
+
const MISSING_INJECTION_CONTEXT = -203;
|
|
1599
|
+
const queryCreatedOutsideInjectionContext = (kind) => {
|
|
1600
|
+
const fix = kind === 'query'
|
|
1601
|
+
? 'or pass an injector as the first creator argument: getPost({ injector: this.injector }, withArgs(...))'
|
|
1602
|
+
: 'or wrap the call in runInInjectionContext(this.injector, () => ...)';
|
|
1603
|
+
return new RuntimeError(QueryRuntimeErrorCode.QUERY_CREATED_OUTSIDE_INJECTION_CONTEXT, `A ${kind} was created outside an injection context (in ngOnInit, an event handler or a timer). Create it in a field initializer or constructor, ${fix}.`);
|
|
1604
|
+
};
|
|
1605
|
+
const injectInQueryContext = (token, kind) => {
|
|
1606
|
+
try {
|
|
1607
|
+
return inject(token);
|
|
1608
|
+
}
|
|
1609
|
+
catch (error) {
|
|
1610
|
+
if (error?.code === MISSING_INJECTION_CONTEXT) {
|
|
1611
|
+
throw queryCreatedOutsideInjectionContext(kind);
|
|
1612
|
+
}
|
|
1613
|
+
throw error;
|
|
1614
|
+
}
|
|
1171
1615
|
};
|
|
1172
1616
|
|
|
1173
1617
|
const QUERY_CONTEXT_TOKEN = new InjectionToken('QUERY_CONTEXT');
|
|
@@ -1181,7 +1625,7 @@ const injectQueryContext = () => inject(QUERY_CONTEXT_TOKEN);
|
|
|
1181
1625
|
|
|
1182
1626
|
/** @internal */
|
|
1183
1627
|
const setupQueryDependencies = (options) => {
|
|
1184
|
-
const hostInjector = options.queryConfig?.injector ??
|
|
1628
|
+
const hostInjector = options.queryConfig?.injector ?? injectInQueryContext(Injector, 'query');
|
|
1185
1629
|
const environmentInjector = options.queryConfig?.injector?.get(EnvironmentInjector) ?? hostInjector.get(EnvironmentInjector);
|
|
1186
1630
|
const clientToken = options.client.token;
|
|
1187
1631
|
const dependencies = {
|
|
@@ -1223,165 +1667,6 @@ const setupQueryDependencies = (options) => {
|
|
|
1223
1667
|
return dependencies;
|
|
1224
1668
|
};
|
|
1225
1669
|
|
|
1226
|
-
// codes 0-999
|
|
1227
|
-
const QueryRuntimeErrorCode = {
|
|
1228
|
-
// Query
|
|
1229
|
-
QUERY_FEATURE_USED_MULTIPLE_TIMES: 0,
|
|
1230
|
-
QUERY_CREATED_IN_REACTIVE_CONTEXT: 1,
|
|
1231
|
-
// Query features
|
|
1232
|
-
WITH_ARGS_QUERY_FEATURE_MISSING_BUT_ROUTE_IS_FUNCTION: 100,
|
|
1233
|
-
WITH_POLLING_USED_ON_UNSUPPORTED_HTTP_METHOD: 101,
|
|
1234
|
-
WITH_AUTO_REFRESH_USED_ON_UNSUPPORTED_HTTP_METHOD: 102,
|
|
1235
|
-
WITH_AUTO_REFRESH_USED_IN_MANUAL_QUERY: 103,
|
|
1236
|
-
SILENCE_MISSING_WITH_ARGS_FEATURE_ERROR_USED_BUT_WITH_ARGS_PRESENT: 104,
|
|
1237
|
-
WITH_LONG_POLLING_USED_ON_UNSUPPORTED_HTTP_METHOD: 105,
|
|
1238
|
-
WITH_LONG_POLLING_USED_WITH_POLLING: 106,
|
|
1239
|
-
// Auth provider
|
|
1240
|
-
AUTH_EXTRACT_TOKENS_RESPONSE_NOT_OBJECT: 200,
|
|
1241
|
-
AUTH_EXTRACT_TOKENS_RESPONSE_MISSING_ACCESS_TOKEN: 201,
|
|
1242
|
-
AUTH_EXTRACT_TOKENS_RESPONSE_MISSING_REFRESH_TOKEN: 202,
|
|
1243
|
-
AUTH_PROVIDER_FEATURE_USED_MULTIPLE_TIMES: 203,
|
|
1244
|
-
// Query client
|
|
1245
|
-
QUERY_CLIENT_FEATURE_USED_MULTIPLE_TIMES: 250,
|
|
1246
|
-
// Query Repository
|
|
1247
|
-
UNCACHEABLE_REQUEST_HAS_CACHE_KEY_PARAM: 300,
|
|
1248
|
-
UNCACHEABLE_REQUEST_HAS_ALLOW_CACHE_PARAM: 301,
|
|
1249
|
-
// Paged Query Stack
|
|
1250
|
-
PAGED_QUERY_STACK_PAGE_BIGGER_THAN_TOTAL_PAGES: 400,
|
|
1251
|
-
PAGED_QUERY_STACK_NEXT_PAGE_CALLED_WITHOUT_PREVIOUS_PAGE: 401,
|
|
1252
|
-
PAGED_QUERY_STACK_PREVIOUS_PAGE_CALLED_BUT_ALREADY_AT_FIRST_PAGE: 402,
|
|
1253
|
-
// Query Stack
|
|
1254
|
-
QUERY_STACK_WITH_ARGS_USED: 500,
|
|
1255
|
-
QUERY_STACK_WITH_RESPONSE_UPDATE_USED: 501,
|
|
1256
|
-
// GQL
|
|
1257
|
-
GQL_DATA_PROPERTY_MISSING_IN_RESPONSE: 600,
|
|
1258
|
-
GQL_ERRORS_IN_RESPONSE: 601,
|
|
1259
|
-
// Secure Execute
|
|
1260
|
-
TOKENS_NOT_AVAILABLE_INSIDE_AUTH_AND_EXEC: 700,
|
|
1261
|
-
INVALID_STATE_INSIDE_SECURE_EXECUTE_FACTORY: 701,
|
|
1262
|
-
// Query Execution
|
|
1263
|
-
CIRCULAR_QUERY_DEPENDENCY: 800,
|
|
1264
|
-
// Query Sequence
|
|
1265
|
-
QUERY_SEQUENCE_ALREADY_RUNNING: 900,
|
|
1266
|
-
// Query Batch
|
|
1267
|
-
QUERY_BATCH_ALREADY_RUNNING: 910,
|
|
1268
|
-
QUERY_BATCH_WITH_ARGS_USED: 911,
|
|
1269
|
-
// Legacy interop
|
|
1270
|
-
LEGACY_PREPARE_WITHOUT_INJECTION_CONTEXT: 950,
|
|
1271
|
-
LEGACY_QUERY_STATE_ALREADY_HAS_KEY: 951,
|
|
1272
|
-
LEGACY_QUERY_STATE_DOES_NOT_CONTAIN_KEY: 952,
|
|
1273
|
-
LEGACY_QUERY_STATE_CANNOT_TRANSFORM: 953,
|
|
1274
|
-
LEGACY_INVALID_BODY: 954,
|
|
1275
|
-
};
|
|
1276
|
-
const queryFeatureUsedMultipleTimes = (type) => {
|
|
1277
|
-
return new RuntimeError(QueryRuntimeErrorCode.QUERY_FEATURE_USED_MULTIPLE_TIMES, `The query feature "${type}()" was used multiple times.`);
|
|
1278
|
-
};
|
|
1279
|
-
const queryCreatedInReactiveContext = () => {
|
|
1280
|
-
return new RuntimeError(QueryRuntimeErrorCode.QUERY_CREATED_IN_REACTIVE_CONTEXT, `A query was created inside a reactive context (a computed, an effect or a template). Every re-run would create another query that lives until its injector is destroyed. Create the query in a field initializer or constructor and change its args instead.`);
|
|
1281
|
-
};
|
|
1282
|
-
const withArgsQueryFeatureMissingButRouteIsFunction = () => {
|
|
1283
|
-
return new RuntimeError(QueryRuntimeErrorCode.WITH_ARGS_QUERY_FEATURE_MISSING_BUT_ROUTE_IS_FUNCTION, `This queries route is a function, but a "withArgs()" feature is missing. Mutations need one too: create the query with "withArgs(() => ({ body: form.value() }))", then call ".execute()". The "silenceMissingWithArgsFeatureError" config is an escape hatch for args that only exist at call time.`);
|
|
1284
|
-
};
|
|
1285
|
-
const withPollingUsedOnUnsupportedHttpMethod = (method) => {
|
|
1286
|
-
return new RuntimeError(QueryRuntimeErrorCode.WITH_POLLING_USED_ON_UNSUPPORTED_HTTP_METHOD, `This is a "${method}" request, "withPolling()" is only supported for GET, HEAD, OPTIONS requests and GQL queries.`);
|
|
1287
|
-
};
|
|
1288
|
-
const withAutoRefreshUsedOnUnsupportedHttpMethod = (method) => {
|
|
1289
|
-
return new RuntimeError(QueryRuntimeErrorCode.WITH_AUTO_REFRESH_USED_ON_UNSUPPORTED_HTTP_METHOD, `This is a "${method}" request, "withAutoRefresh()" is only supported for GET, HEAD, OPTIONS requests and GQL queries.`);
|
|
1290
|
-
};
|
|
1291
|
-
const withAutoRefreshUsedInManualQuery = () => {
|
|
1292
|
-
return new RuntimeError(QueryRuntimeErrorCode.WITH_AUTO_REFRESH_USED_IN_MANUAL_QUERY, `"withAutoRefresh()" has been used inside a query that has "onlyManualExecution" set to true.` +
|
|
1293
|
-
` If this is intentional, set "ignoreOnlyManualExecution" to true inside the auto refresh config.`);
|
|
1294
|
-
};
|
|
1295
|
-
const withLongPollingUsedOnUnsupportedHttpMethod = (method) => {
|
|
1296
|
-
return new RuntimeError(QueryRuntimeErrorCode.WITH_LONG_POLLING_USED_ON_UNSUPPORTED_HTTP_METHOD, `This is a "${method}" request, "withLongPolling()" is only supported for GET, HEAD, OPTIONS requests and GQL queries.`);
|
|
1297
|
-
};
|
|
1298
|
-
const withLongPollingUsedWithPolling = () => {
|
|
1299
|
-
return new RuntimeError(QueryRuntimeErrorCode.WITH_LONG_POLLING_USED_WITH_POLLING, `"withLongPolling()" and "withPolling()" cannot be used on the same query - both would drive its re-execution.` +
|
|
1300
|
-
` Keep the long polling feature and drop the interval, or the other way around.`);
|
|
1301
|
-
};
|
|
1302
|
-
const silenceMissingWithArgsFeatureErrorUsedButWithArgsPresent = () => {
|
|
1303
|
-
return new RuntimeError(QueryRuntimeErrorCode.SILENCE_MISSING_WITH_ARGS_FEATURE_ERROR_USED_BUT_WITH_ARGS_PRESENT, `The "silenceMissingWithArgsFeatureError" config is set to true, but a "withArgs()" feature is present.`);
|
|
1304
|
-
};
|
|
1305
|
-
const authExtractTokensResponseNotObject = () => {
|
|
1306
|
-
return new RuntimeError(QueryRuntimeErrorCode.AUTH_EXTRACT_TOKENS_RESPONSE_NOT_OBJECT, `The auth token extractor expects the response to be an object.`);
|
|
1307
|
-
};
|
|
1308
|
-
const authExtractTokensResponseMissingAccessToken = () => {
|
|
1309
|
-
return new RuntimeError(QueryRuntimeErrorCode.AUTH_EXTRACT_TOKENS_RESPONSE_MISSING_ACCESS_TOKEN, `The auth token extractor expects the response to contain a string "accessToken" property.`);
|
|
1310
|
-
};
|
|
1311
|
-
const authExtractTokensResponseMissingRefreshToken = () => {
|
|
1312
|
-
return new RuntimeError(QueryRuntimeErrorCode.AUTH_EXTRACT_TOKENS_RESPONSE_MISSING_REFRESH_TOKEN, `The auth token extractor expects the response to contain a string "refreshToken" property.`);
|
|
1313
|
-
};
|
|
1314
|
-
const authProviderFeatureUsedMultipleTimes = (type) => {
|
|
1315
|
-
return new RuntimeError(QueryRuntimeErrorCode.AUTH_PROVIDER_FEATURE_USED_MULTIPLE_TIMES, `Bearer auth feature "${type}" was used multiple times.`);
|
|
1316
|
-
};
|
|
1317
|
-
const queryClientFeatureUsedMultipleTimes = (type) => {
|
|
1318
|
-
return new RuntimeError(QueryRuntimeErrorCode.QUERY_CLIENT_FEATURE_USED_MULTIPLE_TIMES, `Query client feature "${type}" was used multiple times.`);
|
|
1319
|
-
};
|
|
1320
|
-
const uncacheableRequestHasCacheKeyParam = (key) => {
|
|
1321
|
-
return new RuntimeError(QueryRuntimeErrorCode.UNCACHEABLE_REQUEST_HAS_CACHE_KEY_PARAM, `This request is uncacheable, but a cache key was provided: ${key}. Please remove it.`);
|
|
1322
|
-
};
|
|
1323
|
-
const uncacheableRequestHasAllowCacheParam = () => {
|
|
1324
|
-
return new RuntimeError(QueryRuntimeErrorCode.UNCACHEABLE_REQUEST_HAS_ALLOW_CACHE_PARAM, `This request is uncacheable, but allowCache is set to true. Please remove it.`);
|
|
1325
|
-
};
|
|
1326
|
-
const pagedQueryStackPageBiggerThanTotalPages = (page, totalPages) => {
|
|
1327
|
-
return new RuntimeError(QueryRuntimeErrorCode.PAGED_QUERY_STACK_PAGE_BIGGER_THAN_TOTAL_PAGES, `The page "${page}" is bigger than the total pages "${totalPages}".`);
|
|
1328
|
-
};
|
|
1329
|
-
const pagedQueryStackNextPageCalledWithoutPreviousPage = () => {
|
|
1330
|
-
return new RuntimeError(QueryRuntimeErrorCode.PAGED_QUERY_STACK_NEXT_PAGE_CALLED_WITHOUT_PREVIOUS_PAGE, `fetchNextPage() has been called but the current page is not yet loaded. Please call it after the previous page has been loaded.`);
|
|
1331
|
-
};
|
|
1332
|
-
const pagedQueryStackPreviousPageCalledButAlreadyAtFirstPage = () => {
|
|
1333
|
-
return new RuntimeError(QueryRuntimeErrorCode.PAGED_QUERY_STACK_PREVIOUS_PAGE_CALLED_BUT_ALREADY_AT_FIRST_PAGE, `fetchPreviousPage() has been called but the current page is already the first page.`);
|
|
1334
|
-
};
|
|
1335
|
-
const queryStackWithArgsUsed = () => {
|
|
1336
|
-
return new RuntimeError(QueryRuntimeErrorCode.QUERY_STACK_WITH_ARGS_USED, `withArgs() has been used in a query stack or a paged query stack. This is not supported.`);
|
|
1337
|
-
};
|
|
1338
|
-
const queryStackWithResponseUpdateUsed = () => {
|
|
1339
|
-
return new RuntimeError(QueryRuntimeErrorCode.QUERY_STACK_WITH_RESPONSE_UPDATE_USED, `withResponseUpdate() has been used in a query stack or a paged query stack. This is not supported.`);
|
|
1340
|
-
};
|
|
1341
|
-
const gqlDataPropertyMissingInResponse = () => {
|
|
1342
|
-
return new RuntimeError(QueryRuntimeErrorCode.GQL_DATA_PROPERTY_MISSING_IN_RESPONSE, `The GraphQL response is missing the required "data" property. Please add a custom transformResponse param to the query creator to handle this response format.`);
|
|
1343
|
-
};
|
|
1344
|
-
const gqlErrorsInResponse = (errors) => {
|
|
1345
|
-
return new HttpErrorResponse({
|
|
1346
|
-
error: errors,
|
|
1347
|
-
status: 0,
|
|
1348
|
-
statusText: new RuntimeError(QueryRuntimeErrorCode.GQL_ERRORS_IN_RESPONSE, `The GraphQL response carries errors and no data. The server's errors are in raw.error.`).message,
|
|
1349
|
-
});
|
|
1350
|
-
};
|
|
1351
|
-
const tokensNotAvailableInsideAuthAndExec = () => {
|
|
1352
|
-
return new RuntimeError(QueryRuntimeErrorCode.TOKENS_NOT_AVAILABLE_INSIDE_AUTH_AND_EXEC, `Tokens are not available inside authAndExec.`);
|
|
1353
|
-
};
|
|
1354
|
-
const invalidStateInsideSecureExecuteFactory = () => {
|
|
1355
|
-
return new RuntimeError(QueryRuntimeErrorCode.INVALID_STATE_INSIDE_SECURE_EXECUTE_FACTORY, `An invalid state occurred inside the secure execute factory.`);
|
|
1356
|
-
};
|
|
1357
|
-
const circularQueryDependency = () => {
|
|
1358
|
-
return new RuntimeError(QueryRuntimeErrorCode.CIRCULAR_QUERY_DEPENDENCY, `The same query was executed with identical args more than 5 times in a row, less than 100ms apart. This is usually a sign of a circular dependency.`);
|
|
1359
|
-
};
|
|
1360
|
-
const queryExecutedAfterDestroyMessage = (route) => {
|
|
1361
|
-
const target = typeof route === 'string' ? `The query for "${route}"` : 'A query with a function route';
|
|
1362
|
-
return `${target} was executed after the scope that created it was destroyed. The call was ignored - drop the reference to the query when its component or injector goes away.`;
|
|
1363
|
-
};
|
|
1364
|
-
const legacyPrepareWithoutInjectionContext = (creatorName, method = 'prepare') => {
|
|
1365
|
-
const label = creatorName ? `"${creatorName}"` : 'A legacy query creator';
|
|
1366
|
-
const example = method === 'prepare'
|
|
1367
|
-
? ` search = (term: string) => myQuery.prepare({ queryParams: { term }, injector: this.injector });`
|
|
1368
|
-
: ` container = myQuery.${method}(null, { injector: this.injector });`;
|
|
1369
|
-
return new RuntimeError(QueryRuntimeErrorCode.LEGACY_PREPARE_WITHOUT_INJECTION_CONTEXT, `${label}.${method}() was called outside of an injection context. Unlike the v2 client, the legacy interop layer needs an injector to build the underlying query.\n\n` +
|
|
1370
|
-
`Capture one where a context does exist (field initializer, constructor) and pass it along:\n\n` +
|
|
1371
|
-
` private injector = inject(Injector);\n` +
|
|
1372
|
-
`${example}\n\n` +
|
|
1373
|
-
`Prefer an injector that outlives the call site. A component injector passed from a callback that can still fire after the component is gone leaves prepare() with a destroyed injector, which returns an inert query instead of executing.`);
|
|
1374
|
-
};
|
|
1375
|
-
const querySequenceAlreadyRunning = () => {
|
|
1376
|
-
return new RuntimeError(QueryRuntimeErrorCode.QUERY_SEQUENCE_ALREADY_RUNNING, `run() was called on a query sequence that is already running. Wait for the current run to settle before starting another.`);
|
|
1377
|
-
};
|
|
1378
|
-
const queryBatchAlreadyRunning = () => {
|
|
1379
|
-
return new RuntimeError(QueryRuntimeErrorCode.QUERY_BATCH_ALREADY_RUNNING, `run() or retryFailed() was called on a query batch that is already running. Await the current run, or call cancel() first.`);
|
|
1380
|
-
};
|
|
1381
|
-
const queryBatchWithArgsUsed = () => {
|
|
1382
|
-
return new RuntimeError(QueryRuntimeErrorCode.QUERY_BATCH_WITH_ARGS_USED, `The "withArgs()" feature cannot be used inside a query batch. Each item's args come from the batch's "args" option.`);
|
|
1383
|
-
};
|
|
1384
|
-
|
|
1385
1670
|
const isClassValidatorError = (error) => {
|
|
1386
1671
|
return (typeof error === 'object' &&
|
|
1387
1672
|
error !== null &&
|
|
@@ -1448,6 +1733,7 @@ const QueryFeatureType = {
|
|
|
1448
1733
|
WITH_AUTO_REFRESH: 'WITH_AUTO_REFRESH',
|
|
1449
1734
|
WITH_RESPONSE_UPDATE: 'WITH_RESPONSE_UPDATE',
|
|
1450
1735
|
WITH_PAGE_RESET_ON_ERROR: 'WITH_PAGE_RESET_ON_ERROR',
|
|
1736
|
+
WITH_OPTIMISTIC_UPDATE: 'WITH_OPTIMISTIC_UPDATE',
|
|
1451
1737
|
};
|
|
1452
1738
|
const createQueryFeature = (config) => {
|
|
1453
1739
|
return config;
|
|
@@ -1458,7 +1744,8 @@ const createQueryFeature = (config) => {
|
|
|
1458
1744
|
*
|
|
1459
1745
|
* Return `null` to park the query: it is reset (`response()` and `executionState()` become `null`),
|
|
1460
1746
|
* and polling and auto refresh pause until args are set again. A mutation that is still in flight
|
|
1461
|
-
* finishes first and the query parks once it has settled.
|
|
1747
|
+
* finishes first and the query parks once it has settled. `execute({ args })` runs and keeps other args
|
|
1748
|
+
* until this function produces new non-null ones.
|
|
1462
1749
|
*
|
|
1463
1750
|
* Changing arguments will automatically trigger a new execution of the query if it is eligible for auto execution (e.g. a GET request).
|
|
1464
1751
|
*/
|
|
@@ -1484,6 +1771,8 @@ const withArgs = (args) => {
|
|
|
1484
1771
|
return;
|
|
1485
1772
|
}
|
|
1486
1773
|
untracked(() => {
|
|
1774
|
+
if (context.state.subtle.isExplicitArgs(currArgsNow))
|
|
1775
|
+
return;
|
|
1487
1776
|
const guard = context.state.subtle.autoExecuteGuard();
|
|
1488
1777
|
if (context.flags.shouldAutoExecute && (guard === null || guard()))
|
|
1489
1778
|
context.execute({ args: currArgsNow });
|
|
@@ -1593,9 +1882,9 @@ const withPolling = (options) => {
|
|
|
1593
1882
|
}, Math.max(0, firstDelay));
|
|
1594
1883
|
};
|
|
1595
1884
|
const remainingDelay = () => lastTickAt + currentInterval() - Date.now();
|
|
1596
|
-
//
|
|
1597
|
-
|
|
1598
|
-
|
|
1885
|
+
// Args an `execute({ args })` recorded already went out with that execution.
|
|
1886
|
+
const wasJustExecutedWith = (args) => context.state.subtle.isExplicitArgs(args) ||
|
|
1887
|
+
(!context.state.subtle.hasArgsSource() && args !== null && equal(context.state.subtle.request()?.args, args));
|
|
1599
1888
|
nestedEffect(() => {
|
|
1600
1889
|
const args = context.state.args();
|
|
1601
1890
|
untracked(() => {
|
|
@@ -1898,7 +2187,7 @@ const withResponseUpdate = (options) => {
|
|
|
1898
2187
|
if (response === null)
|
|
1899
2188
|
return;
|
|
1900
2189
|
untracked(() => {
|
|
1901
|
-
context.state.
|
|
2190
|
+
context.state.response.set(response);
|
|
1902
2191
|
});
|
|
1903
2192
|
}, { injector: context.deps.injector });
|
|
1904
2193
|
},
|
|
@@ -1963,6 +2252,62 @@ const withPageResetOnError = (options) => {
|
|
|
1963
2252
|
});
|
|
1964
2253
|
};
|
|
1965
2254
|
|
|
2255
|
+
/**
|
|
2256
|
+
* Resolves an {@link QueryInvalidationOptions.url} to the absolute form request URLs are built in, so
|
|
2257
|
+
* the comparison - and the message the other tabs receive - never depends on who resolves it.
|
|
2258
|
+
*/
|
|
2259
|
+
const resolveInvalidationUrl = (baseUrl, url) => {
|
|
2260
|
+
const absolute = url.startsWith('/') ? `${baseUrl}${url}` : url;
|
|
2261
|
+
// A trailing slash would put the boundary check below one character past where the URL actually
|
|
2262
|
+
// ends, which is the one way `/players/` could fail to match `/players/1`.
|
|
2263
|
+
return absolute.endsWith('/') ? absolute.slice(0, -1) : absolute;
|
|
2264
|
+
};
|
|
2265
|
+
/**
|
|
2266
|
+
* Whether a query URL is the invalidated one or sits below it. The check on what follows the prefix
|
|
2267
|
+
* is what keeps `/players` from matching `/players-archive`, which a `startsWith` alone would.
|
|
2268
|
+
*/
|
|
2269
|
+
const isUnderInvalidatedUrl = (queryUrl, invalidatedUrl) => {
|
|
2270
|
+
if (!queryUrl.startsWith(invalidatedUrl))
|
|
2271
|
+
return false;
|
|
2272
|
+
const rest = queryUrl.slice(invalidatedUrl.length);
|
|
2273
|
+
return rest === '' || rest.startsWith('/') || rest.startsWith('?') || rest.startsWith('#');
|
|
2274
|
+
};
|
|
2275
|
+
/**
|
|
2276
|
+
* Turns an invalidation into the filter the repository refreshes by, or `undefined` when nothing
|
|
2277
|
+
* narrows it - "everything in use" is the repository's own cheapest path.
|
|
2278
|
+
*/
|
|
2279
|
+
const createQueryInvalidationFilter = (options) => {
|
|
2280
|
+
const { url, tag, filter } = options;
|
|
2281
|
+
if (!url && !tag && !filter)
|
|
2282
|
+
return undefined;
|
|
2283
|
+
return (request, tags) => {
|
|
2284
|
+
if (url && !isUnderInvalidatedUrl(request.url, url))
|
|
2285
|
+
return false;
|
|
2286
|
+
if (tag && !tags?.includes(tag))
|
|
2287
|
+
return false;
|
|
2288
|
+
return filter ? filter({ method: request.method, url: request.url }) : true;
|
|
2289
|
+
};
|
|
2290
|
+
};
|
|
2291
|
+
/** @internal */
|
|
2292
|
+
const resolveQueryTags = (tags, args) => (typeof tags === 'function' ? tags({ args: args ?? {} }) : tags);
|
|
2293
|
+
/** @internal */
|
|
2294
|
+
const invalidateOnSuccess = (options) => {
|
|
2295
|
+
const { invalidates, state, deps } = options;
|
|
2296
|
+
const subscription = state.events$.subscribe((event) => {
|
|
2297
|
+
if (event.type !== HttpEventType.Response)
|
|
2298
|
+
return;
|
|
2299
|
+
const targets = typeof invalidates === 'function'
|
|
2300
|
+
? invalidates({
|
|
2301
|
+
args: state.subtle.request()?.args ?? {},
|
|
2302
|
+
response: state.response(),
|
|
2303
|
+
})
|
|
2304
|
+
: invalidates;
|
|
2305
|
+
for (const target of targets)
|
|
2306
|
+
deps.client.invalidateQueries(target);
|
|
2307
|
+
});
|
|
2308
|
+
deps.destroyRef.onDestroy(() => subscription.unsubscribe());
|
|
2309
|
+
};
|
|
2310
|
+
|
|
1966
2311
|
const parsers = [];
|
|
1967
2312
|
/**
|
|
1968
2313
|
* Adds a parser to the pipeline `createQueryErrorResponse` runs, ahead of the built-in ladder.
|
|
@@ -2019,7 +2364,11 @@ const createDefaultRetryFn = (options = {}) => {
|
|
|
2019
2364
|
const jitter = clamp(options.jitter ?? DEFAULT_JITTER, 0, 1);
|
|
2020
2365
|
const retryableStatusCodes = options.retryableStatusCodes;
|
|
2021
2366
|
const retryNonIdempotent = options.retryNonIdempotent ?? false;
|
|
2022
|
-
const isRetryableStatus = (status) =>
|
|
2367
|
+
const isRetryableStatus = (status) => {
|
|
2368
|
+
if (typeof retryableStatusCodes === 'function')
|
|
2369
|
+
return retryableStatusCodes(status, isRetryableByDefault(status));
|
|
2370
|
+
return retryableStatusCodes ? retryableStatusCodes.includes(status) : isRetryableByDefault(status);
|
|
2371
|
+
};
|
|
2023
2372
|
const backoff = (retryCount) => {
|
|
2024
2373
|
const exponential = baseDelayMs * Math.pow(2, retryCount);
|
|
2025
2374
|
const spread = jitter === 0 ? 1 : 1 + (Math.random() * 2 - 1) * jitter;
|
|
@@ -2069,6 +2418,7 @@ const isQueryErrorResponse = (error) => {
|
|
|
2069
2418
|
/**
|
|
2070
2419
|
* Every message carried by an error, flattened into a plain list - one entry for a single error,
|
|
2071
2420
|
* one per violation for a list (a form response with several field violations), empty for `null`.
|
|
2421
|
+
* Blank messages are dropped; an error left with none yields the raw response's message instead.
|
|
2072
2422
|
*
|
|
2073
2423
|
* The single/list split exists because that is how APIs answer; UI almost never wants to branch on
|
|
2074
2424
|
* it. Reach for this when rendering, and keep {@link QueryErrorResponse} for anything that needs
|
|
@@ -2084,7 +2434,9 @@ const isQueryErrorResponse = (error) => {
|
|
|
2084
2434
|
const queryErrorMessages = (error) => {
|
|
2085
2435
|
if (!error)
|
|
2086
2436
|
return [];
|
|
2087
|
-
|
|
2437
|
+
const items = error.isList ? error.errors : [error.error];
|
|
2438
|
+
const messages = items.map((item) => item.message).filter((message) => message.trim());
|
|
2439
|
+
return messages.length ? messages : [error.raw.message];
|
|
2088
2440
|
};
|
|
2089
2441
|
/**
|
|
2090
2442
|
* The first message of an error, or `null` when there is none - the single-line counterpart to
|
|
@@ -2213,14 +2565,24 @@ const setupQueryState = (options) => {
|
|
|
2213
2565
|
// set is overridden again the next time the source's dependencies change.
|
|
2214
2566
|
const argsSource = signal(() => null, /* @ts-ignore */
|
|
2215
2567
|
...(ngDevMode ? [{ debugName: "argsSource" }] : /* istanbul ignore next */ []));
|
|
2216
|
-
|
|
2217
|
-
...(ngDevMode ?
|
|
2568
|
+
let explicitArgs = null;
|
|
2569
|
+
const args = linkedSignal({ ...(ngDevMode ? { debugName: "args" } : /* istanbul ignore next */ {}), source: () => argsSource()(),
|
|
2570
|
+
computation: (next, previous) => {
|
|
2571
|
+
if (next === null && explicitArgs !== null && previous)
|
|
2572
|
+
return previous.value;
|
|
2573
|
+
explicitArgs = null;
|
|
2574
|
+
return next;
|
|
2575
|
+
} });
|
|
2218
2576
|
const hasArgsSource = signal(false, /* @ts-ignore */
|
|
2219
2577
|
...(ngDevMode ? [{ debugName: "hasArgsSource" }] : /* istanbul ignore next */ []));
|
|
2220
2578
|
const setArgsSource = (source) => {
|
|
2221
2579
|
argsSource.set(source);
|
|
2222
2580
|
hasArgsSource.set(true);
|
|
2223
2581
|
};
|
|
2582
|
+
const setExplicitArgs = (value) => {
|
|
2583
|
+
explicitArgs = value;
|
|
2584
|
+
args.set(value);
|
|
2585
|
+
};
|
|
2224
2586
|
const lastTimeExecutedAt = signal(null, /* @ts-ignore */
|
|
2225
2587
|
...(ngDevMode ? [{ debugName: "lastTimeExecutedAt" }] : /* istanbul ignore next */ []));
|
|
2226
2588
|
const lastTriggeredBy = signal(null, /* @ts-ignore */
|
|
@@ -2239,6 +2601,8 @@ const setupQueryState = (options) => {
|
|
|
2239
2601
|
...(ngDevMode ? [{ debugName: "defaultRunOptions" }] : /* istanbul ignore next */ []));
|
|
2240
2602
|
const autoExecuteGuard = signal(null, /* @ts-ignore */
|
|
2241
2603
|
...(ngDevMode ? [{ debugName: "autoExecuteGuard" }] : /* istanbul ignore next */ []));
|
|
2604
|
+
const beforeExecute = signal(null, /* @ts-ignore */
|
|
2605
|
+
...(ngDevMode ? [{ debugName: "beforeExecute" }] : /* istanbul ignore next */ []));
|
|
2242
2606
|
options.destroyRef?.onDestroy(() => {
|
|
2243
2607
|
requestEventsSubscription.unsubscribe();
|
|
2244
2608
|
requestEvents$.complete();
|
|
@@ -2309,8 +2673,11 @@ const setupQueryState = (options) => {
|
|
|
2309
2673
|
unbindRequestEvents,
|
|
2310
2674
|
defaultRunOptions,
|
|
2311
2675
|
autoExecuteGuard,
|
|
2676
|
+
beforeExecute,
|
|
2312
2677
|
setArgsSource,
|
|
2313
2678
|
hasArgsSource: hasArgsSource.asReadonly(),
|
|
2679
|
+
setExplicitArgs,
|
|
2680
|
+
isExplicitArgs: (value) => value !== null && value === explicitArgs,
|
|
2314
2681
|
devtoolsStats: options.devtoolsStats ?? null,
|
|
2315
2682
|
devtoolsFormLinks: options.devtoolsFormLinks ?? null,
|
|
2316
2683
|
},
|
|
@@ -2336,6 +2703,19 @@ const createDestroyedSnapshot = (state, execute) => {
|
|
|
2336
2703
|
executionState: frozenObservableSignal({ type: 'failure', error, hasCachedResponse: false }),
|
|
2337
2704
|
};
|
|
2338
2705
|
};
|
|
2706
|
+
/** @internal */
|
|
2707
|
+
const createSkippedSnapshot = (query) => ({
|
|
2708
|
+
args: frozenObservableSignal(query.args()),
|
|
2709
|
+
response: frozenObservableSignal(null),
|
|
2710
|
+
latestHttpEvent: frozenObservableSignal(CANCEL_EVENT),
|
|
2711
|
+
loading: frozenObservableSignal(null),
|
|
2712
|
+
error: frozenObservableSignal(null),
|
|
2713
|
+
lastTimeExecutedAt: frozenObservableSignal(query.lastTimeExecutedAt()),
|
|
2714
|
+
triggeredBy: frozenObservableSignal(query.triggeredBy()),
|
|
2715
|
+
isAlive: frozenObservableSignal(false),
|
|
2716
|
+
id: frozenObservableSignal(query.id()),
|
|
2717
|
+
executionState: frozenObservableSignal(null),
|
|
2718
|
+
});
|
|
2339
2719
|
const createQuerySnapshotFn = (options) => {
|
|
2340
2720
|
const { state } = options;
|
|
2341
2721
|
const context = injectQueryContext();
|
|
@@ -2409,17 +2789,18 @@ const createQuerySnapshotFn = (options) => {
|
|
|
2409
2789
|
// snapshot has to settle itself - otherwise `executeUntilSettled` resolves with a snapshot still
|
|
2410
2790
|
// reporting the execution as loading.
|
|
2411
2791
|
unregisterScopeListener = context.deps.destroyRef.onDestroy(cancel);
|
|
2792
|
+
const watcher = createObservableSignalWatcher(context.deps.injector);
|
|
2412
2793
|
const snapshot = {
|
|
2413
|
-
args:
|
|
2414
|
-
response:
|
|
2415
|
-
latestHttpEvent:
|
|
2416
|
-
loading:
|
|
2417
|
-
error:
|
|
2418
|
-
lastTimeExecutedAt:
|
|
2419
|
-
triggeredBy:
|
|
2420
|
-
isAlive:
|
|
2421
|
-
id:
|
|
2422
|
-
executionState:
|
|
2794
|
+
args: watcher.wrap(snapshotState.args.asReadonly()),
|
|
2795
|
+
response: watcher.wrap(snapshotState.response),
|
|
2796
|
+
latestHttpEvent: watcher.wrap(snapshotState.latestHttpEvent.asReadonly()),
|
|
2797
|
+
loading: watcher.wrap(snapshotState.loading.asReadonly()),
|
|
2798
|
+
error: watcher.wrap(snapshotState.error.asReadonly()),
|
|
2799
|
+
lastTimeExecutedAt: watcher.wrap(snapshotState.lastTimeExecutedAt.asReadonly()),
|
|
2800
|
+
triggeredBy: watcher.wrap(snapshotState.lastTriggeredBy.asReadonly()),
|
|
2801
|
+
isAlive: watcher.wrap(isAlive.asReadonly()),
|
|
2802
|
+
id: watcher.wrap(options.execute.currentRepositoryKey),
|
|
2803
|
+
executionState: watcher.wrap(snapshotState.executionState),
|
|
2423
2804
|
};
|
|
2424
2805
|
return snapshot;
|
|
2425
2806
|
});
|
|
@@ -2507,16 +2888,18 @@ const createQueryObject = (options) => {
|
|
|
2507
2888
|
const setError = (error) => state.error.set(error);
|
|
2508
2889
|
const createSnapshot = createQuerySnapshotFn({ state, execute, deps });
|
|
2509
2890
|
// Pre-wrap all public signals once so both `query` and `roQuery` share the same instances.
|
|
2510
|
-
const
|
|
2511
|
-
const
|
|
2512
|
-
const
|
|
2513
|
-
const
|
|
2514
|
-
const
|
|
2515
|
-
const
|
|
2516
|
-
const
|
|
2517
|
-
const
|
|
2518
|
-
const
|
|
2519
|
-
const
|
|
2891
|
+
const watcher = createObservableSignalWatcher(deps.injector);
|
|
2892
|
+
const wrappedArgs = watcher.wrap(state.args.asReadonly());
|
|
2893
|
+
const wrappedResponse = watcher.wrap(state.response);
|
|
2894
|
+
const wrappedLatestHttpEvent = watcher.wrap(state.latestHttpEvent.asReadonly());
|
|
2895
|
+
const wrappedLoading = watcher.wrap(state.loading.asReadonly());
|
|
2896
|
+
const wrappedError = watcher.wrap(state.error.asReadonly());
|
|
2897
|
+
const wrappedLastTimeExecutedAt = watcher.wrap(state.lastTimeExecutedAt.asReadonly());
|
|
2898
|
+
const wrappedTriggeredBy = watcher.wrap(state.lastTriggeredBy.asReadonly());
|
|
2899
|
+
const wrappedId = watcher.wrap(execute.currentRepositoryKey);
|
|
2900
|
+
const wrappedExecutionState = watcher.wrap(state.executionState);
|
|
2901
|
+
const wrappedRequest = watcher.wrap(state.subtle.request.asReadonly());
|
|
2902
|
+
const wrappedUrl = watcher.wrap(computed(() => state.subtle.request()?.url ?? null));
|
|
2520
2903
|
const asReadonly = () => {
|
|
2521
2904
|
const roQuery = {
|
|
2522
2905
|
args: wrappedArgs,
|
|
@@ -2528,6 +2911,7 @@ const createQueryObject = (options) => {
|
|
|
2528
2911
|
triggeredBy: wrappedTriggeredBy,
|
|
2529
2912
|
id: wrappedId,
|
|
2530
2913
|
createSnapshot,
|
|
2914
|
+
url: wrappedUrl,
|
|
2531
2915
|
executionState: wrappedExecutionState,
|
|
2532
2916
|
};
|
|
2533
2917
|
return roQuery;
|
|
@@ -2543,6 +2927,7 @@ const createQueryObject = (options) => {
|
|
|
2543
2927
|
triggeredBy: wrappedTriggeredBy,
|
|
2544
2928
|
id: wrappedId,
|
|
2545
2929
|
createSnapshot,
|
|
2930
|
+
url: wrappedUrl,
|
|
2546
2931
|
reset: execute.reset,
|
|
2547
2932
|
abort: execute.abort,
|
|
2548
2933
|
asReadonly,
|
|
@@ -2593,6 +2978,12 @@ const createBaseQuery = (options) => {
|
|
|
2593
2978
|
deps,
|
|
2594
2979
|
};
|
|
2595
2980
|
applyQueryFeatures(options.features, featureFnContext);
|
|
2981
|
+
const invalidates = options.creator?.invalidates;
|
|
2982
|
+
if (invalidates) {
|
|
2983
|
+
if (flags.shouldAutoExecuteMethod)
|
|
2984
|
+
throw invalidatesUsedOnRead(flags.method);
|
|
2985
|
+
invalidateOnSuccess({ invalidates, state, deps });
|
|
2986
|
+
}
|
|
2596
2987
|
maybeExecute({ execute, flags, state });
|
|
2597
2988
|
trackRefreshedExecutions({ state, deps });
|
|
2598
2989
|
const query = createQueryObject({ state, execute, deps });
|
|
@@ -2704,7 +3095,7 @@ const extractExpiresInSeconds = (headers) => {
|
|
|
2704
3095
|
if (cacheControl && /(?:^|,)\s*no-(?:cache|store)(?:\s|,|$)/i.test(cacheControl)) {
|
|
2705
3096
|
return null;
|
|
2706
3097
|
}
|
|
2707
|
-
const maxAgeMatch = cacheControl?.match(/(?:^|,)\s*(
|
|
3098
|
+
const maxAgeMatch = cacheControl?.match(/(?:^|,)\s*max-age=(\d+)/i) ?? cacheControl?.match(/(?:^|,)\s*s-maxage=(\d+)/i);
|
|
2708
3099
|
if (maxAgeMatch?.[1]) {
|
|
2709
3100
|
maxAge = parseInt(maxAgeMatch[1]);
|
|
2710
3101
|
}
|
|
@@ -2800,38 +3191,66 @@ const queryExecute = (options) => {
|
|
|
2800
3191
|
}
|
|
2801
3192
|
const defaultRunOptions = state.subtle.defaultRunOptions();
|
|
2802
3193
|
const runQueryOptions = defaultRunOptions || options.options ? { ...defaultRunOptions, ...options.options } : undefined;
|
|
2803
|
-
const
|
|
2804
|
-
|
|
2805
|
-
|
|
2806
|
-
|
|
2807
|
-
|
|
2808
|
-
|
|
2809
|
-
|
|
2810
|
-
|
|
2811
|
-
|
|
2812
|
-
|
|
2813
|
-
|
|
2814
|
-
|
|
2815
|
-
|
|
2816
|
-
|
|
3194
|
+
const onRequest = state.subtle.beforeExecute()?.(args);
|
|
3195
|
+
let requestResult;
|
|
3196
|
+
try {
|
|
3197
|
+
requestResult = deps.client.repository.request({
|
|
3198
|
+
route: creatorInternals.route,
|
|
3199
|
+
method: creatorInternals.method,
|
|
3200
|
+
args,
|
|
3201
|
+
creatorOptions: creator,
|
|
3202
|
+
retryFn: creator?.retryFn,
|
|
3203
|
+
consumerDestroyRef: deps.destroyRef,
|
|
3204
|
+
key: queryConfig.key,
|
|
3205
|
+
previousKey: executeState.previousKey(),
|
|
3206
|
+
runQueryOptions,
|
|
3207
|
+
isSecure,
|
|
3208
|
+
isRefreshable,
|
|
3209
|
+
silenceUncacheableAllowCacheError: queryConfig.silenceUncacheableAllowCacheError,
|
|
3210
|
+
tags: creator?.tags ? resolveQueryTags(creator.tags, args) : undefined,
|
|
3211
|
+
});
|
|
3212
|
+
}
|
|
3213
|
+
catch (error) {
|
|
3214
|
+
onRequest?.(null);
|
|
3215
|
+
throw error;
|
|
3216
|
+
}
|
|
3217
|
+
const { key, request, executed } = requestResult;
|
|
2817
3218
|
executeState.previousKey.set(key);
|
|
2818
3219
|
state.lastTimeExecutedAt.set(Date.now());
|
|
2819
3220
|
state.lastTriggeredBy.set(runQueryOptions?.triggeredBy ?? null);
|
|
2820
3221
|
state.subtle.request.set(request);
|
|
3222
|
+
onRequest?.(request);
|
|
2821
3223
|
state.subtle.bindRequestEvents(request);
|
|
2822
3224
|
state.subtle.devtoolsStats?.recordExecution({ didRequest: executed, body: args?.body, url: request.url });
|
|
2823
3225
|
};
|
|
3226
|
+
/**
|
|
3227
|
+
* A query over a `withArgs` source that returns `null` is parked. Running it would send `null` args, so the
|
|
3228
|
+
* call is dropped with a dev-mode warning.
|
|
3229
|
+
*/
|
|
3230
|
+
const skipParkedExecution = (state, args, route) => {
|
|
3231
|
+
if ((args !== null && args !== undefined) || !state.subtle.hasArgsSource())
|
|
3232
|
+
return false;
|
|
3233
|
+
if (isDevMode())
|
|
3234
|
+
console.warn(queryExecutedWhileParkedMessage(route));
|
|
3235
|
+
return true;
|
|
3236
|
+
};
|
|
2824
3237
|
/**
|
|
2825
3238
|
* Writes the args an execution runs with to `state.args`, so a later bare `execute()` re-sends them.
|
|
2826
|
-
* A `
|
|
2827
|
-
* args
|
|
3239
|
+
* A `withLongPolling` round must not look like a new args value, so it is not written. Over a `withArgs`
|
|
3240
|
+
* source only the caller's own non-null args are written, never what a feature re-sends.
|
|
2828
3241
|
*/
|
|
2829
3242
|
const recordExecutionArgs = (state, args, options) => {
|
|
2830
|
-
|
|
3243
|
+
const triggeredBy = options?.triggeredBy;
|
|
3244
|
+
if (triggeredBy === LONG_POLLING_TRIGGER)
|
|
3245
|
+
return;
|
|
3246
|
+
if (!state.subtle.hasArgsSource())
|
|
3247
|
+
return state.args.set(args);
|
|
3248
|
+
if (args === null || args === state.args() || (triggeredBy && FEATURE_TRIGGERS.includes(triggeredBy)))
|
|
2831
3249
|
return;
|
|
2832
|
-
state.
|
|
3250
|
+
state.subtle.setExplicitArgs(args);
|
|
2833
3251
|
};
|
|
2834
3252
|
const LONG_POLLING_TRIGGER = 'long-polling';
|
|
3253
|
+
const FEATURE_TRIGGERS = ['polling', 'auto-refresh'];
|
|
2835
3254
|
const CIRCULAR_DEPENDENCY_WINDOW_MS = 100;
|
|
2836
3255
|
const CIRCULAR_DEPENDENCY_MAX_REPEATS = 5;
|
|
2837
3256
|
/**
|
|
@@ -2841,259 +3260,115 @@ const CIRCULAR_DEPENDENCY_MAX_REPEATS = 5;
|
|
|
2841
3260
|
*/
|
|
2842
3261
|
const circularQueryDependencyChecker = () => {
|
|
2843
3262
|
const recent = new Map();
|
|
2844
|
-
const check = (args) => {
|
|
2845
|
-
const now = Date.now();
|
|
2846
|
-
const signature = buildQueryCacheKey(JSON.stringify([args?.pathParams ?? null, args?.queryParams ?? null]), args ?? undefined);
|
|
2847
|
-
for (const [key, entry] of recent) {
|
|
2848
|
-
if (now - entry.lastTs >= CIRCULAR_DEPENDENCY_WINDOW_MS)
|
|
2849
|
-
recent.delete(key);
|
|
2850
|
-
}
|
|
2851
|
-
const entry = recent.get(signature);
|
|
2852
|
-
const repeats = entry ? entry.repeats + 1 : 1;
|
|
2853
|
-
if (repeats > CIRCULAR_DEPENDENCY_MAX_REPEATS) {
|
|
2854
|
-
recent.delete(signature);
|
|
2855
|
-
throw circularQueryDependency();
|
|
2856
|
-
}
|
|
2857
|
-
recent.set(signature, { lastTs: now, repeats });
|
|
2858
|
-
};
|
|
2859
|
-
return {
|
|
2860
|
-
check,
|
|
2861
|
-
};
|
|
2862
|
-
};
|
|
2863
|
-
|
|
2864
|
-
const createExecuteFn = (executeOptions) => {
|
|
2865
|
-
const executeState = setupQueryExecuteState();
|
|
2866
|
-
const circularChecker = circularQueryDependencyChecker();
|
|
2867
|
-
const aborter = createQueryExecutionAborter(executeOptions.state);
|
|
2868
|
-
const reset = () => untracked(() => resetExecuteState({ executeState, executeOptions }));
|
|
2869
|
-
const exec = (executeArgs) => untracked(() => {
|
|
2870
|
-
const { args = executeOptions.state.args(), options } = executeArgs ?? {};
|
|
2871
|
-
circularChecker.check(args);
|
|
2872
|
-
aborter.capture();
|
|
2873
|
-
recordExecutionArgs(executeOptions.state, args, options);
|
|
2874
|
-
queryExecute({ executeOptions, executeState, args, options });
|
|
2875
|
-
});
|
|
2876
|
-
exec['reset'] = reset;
|
|
2877
|
-
exec['abort'] = () => aborter.abort();
|
|
2878
|
-
exec['aborted$'] = aborter.aborted$;
|
|
2879
|
-
exec['currentRepositoryKey'] = executeState.previousKey.asReadonly();
|
|
2880
|
-
return exec;
|
|
2881
|
-
};
|
|
2882
|
-
|
|
2883
|
-
/* eslint-disable @typescript-eslint/no-explicit-any */
|
|
2884
|
-
/**
|
|
2885
|
-
* The empty request-args object, typed to satisfy `RequestArgs<TArgs>` for a query that takes no
|
|
2886
|
-
* arguments - its `QueryArgs` is just `{ response: T }`, so `RequestArgs` resolves to `{}`.
|
|
2887
|
-
*
|
|
2888
|
-
* Only needed in generic code, where `TArgs` is still a type parameter and TS therefore cannot see
|
|
2889
|
-
* that `{}` is the whole of it. Against a concrete query, omit the argument instead: both
|
|
2890
|
-
* `query.execute()` and the auth registry's `execute()` already take none.
|
|
2891
|
-
*
|
|
2892
|
-
* @example
|
|
2893
|
-
* const runAnything = <TArgs extends QueryArgs>(query: Query<TArgs>) =>
|
|
2894
|
-
* query.execute({ args: emptyQueryArgs<TArgs>() });
|
|
2895
|
-
*/
|
|
2896
|
-
const emptyQueryArgs = () => ({});
|
|
2897
|
-
const createQuery = (options) => createBaseQuery({
|
|
2898
|
-
creator: options.creator,
|
|
2899
|
-
creatorInternals: options.creatorInternals,
|
|
2900
|
-
features: options.features,
|
|
2901
|
-
queryConfig: options.queryConfig,
|
|
2902
|
-
executeFactory: createExecuteFn,
|
|
2903
|
-
});
|
|
2904
|
-
|
|
2905
|
-
const splitQueryConfig = (args) => {
|
|
2906
|
-
let queryConfig = {};
|
|
2907
|
-
let features = [];
|
|
2908
|
-
const first = args[0];
|
|
2909
|
-
if (first) {
|
|
2910
|
-
if ('type' in first) {
|
|
2911
|
-
features = args;
|
|
2912
|
-
}
|
|
2913
|
-
else {
|
|
2914
|
-
[queryConfig, ...features] = args;
|
|
2915
|
-
}
|
|
2916
|
-
}
|
|
2917
|
-
return { features, queryConfig };
|
|
2918
|
-
};
|
|
2919
|
-
const createQueryCreator = (options, internals) => createBaseQueryCreator({
|
|
2920
|
-
options,
|
|
2921
|
-
internals,
|
|
2922
|
-
queryFactory: createQuery,
|
|
2923
|
-
});
|
|
2924
|
-
|
|
2925
|
-
const createBaseQueryCreator = (config) => {
|
|
2926
|
-
function queryCreator(...args) {
|
|
2927
|
-
if (getActiveConsumer() !== null)
|
|
2928
|
-
throw queryCreatedInReactiveContext();
|
|
2929
|
-
const { features, queryConfig } = splitQueryConfig(args);
|
|
2930
|
-
return config.queryFactory({
|
|
2931
|
-
creator: config.options,
|
|
2932
|
-
creatorInternals: config.internals,
|
|
2933
|
-
features,
|
|
2934
|
-
queryConfig,
|
|
2935
|
-
});
|
|
2936
|
-
}
|
|
2937
|
-
// Add clone method to create a new creator with merged options
|
|
2938
|
-
// Cast to match QueryCreator's expected signature while preserving TOptions internally
|
|
2939
|
-
queryCreator.clone = (additionalOptions) => {
|
|
2940
|
-
return createBaseQueryCreator({
|
|
2941
|
-
...config,
|
|
2942
|
-
options: { ...config.options, ...additionalOptions },
|
|
2943
|
-
});
|
|
2944
|
-
};
|
|
2945
|
-
queryCreator.subtle = {
|
|
2946
|
-
creatorInternals: config.internals,
|
|
2947
|
-
creatorOptions: config.options,
|
|
2948
|
-
};
|
|
2949
|
-
return queryCreator;
|
|
2950
|
-
};
|
|
2951
|
-
|
|
2952
|
-
/**
|
|
2953
|
-
* Route building, query-string serialization and the token clock - shared by the current generation and
|
|
2954
|
-
* the legacy V2 client, which re-exports them under their original names.
|
|
2955
|
-
*/
|
|
2956
|
-
// codes 1-99 of the shared 0-999 `@ethlete/query` range - see `query-errors.ts` for the rest
|
|
2957
|
-
const RouteRuntimeErrorCode = {
|
|
2958
|
-
INVALID_BASE_ROUTE: 1,
|
|
2959
|
-
INVALID_ROUTE: 2,
|
|
2960
|
-
PATH_PARAMS_MISSING_IN_ROUTE_FUNCTION: 3,
|
|
2961
|
-
};
|
|
2962
|
-
const invalidBaseRouteError$1 = (data) => new RuntimeError(RouteRuntimeErrorCode.INVALID_BASE_ROUTE, 'The baseRoute must not end with a slash', data);
|
|
2963
|
-
const invalidRouteError$1 = (data) => new RuntimeError(RouteRuntimeErrorCode.INVALID_ROUTE, 'The route must start with a slash', data);
|
|
2964
|
-
const pathParamsMissingInRouteFunctionError$1 = (data) => new RuntimeError(RouteRuntimeErrorCode.PATH_PARAMS_MISSING_IN_ROUTE_FUNCTION, 'The route is a function but pathParams are missing', data);
|
|
2965
|
-
const isNaN$2 = (value) => typeof value === 'number' && Number.isNaN(value);
|
|
2966
|
-
const isEmptyString$1 = (value) => typeof value === 'string' && value.trim() === '';
|
|
2967
|
-
const buildQueryString$1 = (params, config) => {
|
|
2968
|
-
const objectNotation = config?.objectNotation ?? 'bracket';
|
|
2969
|
-
const writeArrayIndexes = config?.writeArrayIndexes ?? false;
|
|
2970
|
-
const ignoredValues = config?.ignoredValues ?? [undefined, null, Infinity, -Infinity];
|
|
2971
|
-
const ignoredValuesFns = config?.ignoredValuesFns ?? [isNaN$2, isEmptyString$1];
|
|
2972
|
-
const queryParams = [];
|
|
2973
|
-
function processValue(key, rawValue) {
|
|
2974
|
-
if (rawValue instanceof Date && Number.isNaN(rawValue.getTime()))
|
|
2975
|
-
return false;
|
|
2976
|
-
const value = rawValue instanceof Date ? rawValue.toISOString() : rawValue;
|
|
2977
|
-
if (config?.objectNotation === 'json-stringify') {
|
|
2978
|
-
if (value === undefined) {
|
|
2979
|
-
return false;
|
|
2980
|
-
}
|
|
2981
|
-
if (ignoredValues.includes(value)) {
|
|
2982
|
-
return false;
|
|
2983
|
-
}
|
|
2984
|
-
if (ignoredValuesFns.some((fn) => fn(value))) {
|
|
2985
|
-
return false;
|
|
2986
|
-
}
|
|
2987
|
-
const encodedKey = encodeURIComponent(key);
|
|
2988
|
-
const val = typeof value === 'object' ? JSON.stringify(value) : value;
|
|
2989
|
-
const encodedValue = encodeURIComponent(val);
|
|
2990
|
-
queryParams.push(`${encodedKey}=${encodedValue}`);
|
|
2991
|
-
return true;
|
|
2992
|
-
}
|
|
2993
|
-
else if (Array.isArray(value)) {
|
|
2994
|
-
let currentFilteredIndex = 0;
|
|
2995
|
-
let didAddAnyValue = false;
|
|
2996
|
-
for (const arrayValue of value) {
|
|
2997
|
-
const nestedKey = writeArrayIndexes ? `${key}[${currentFilteredIndex}]` : `${key}[]`;
|
|
2998
|
-
const didAddValue = processValue(nestedKey, arrayValue);
|
|
2999
|
-
if (didAddValue) {
|
|
3000
|
-
currentFilteredIndex++;
|
|
3001
|
-
didAddAnyValue = true;
|
|
3002
|
-
}
|
|
3003
|
-
}
|
|
3004
|
-
return didAddAnyValue;
|
|
3005
|
-
}
|
|
3006
|
-
else if (typeof value === 'object' && value !== null) {
|
|
3007
|
-
let didAddAnyValue = false;
|
|
3008
|
-
for (const [objKey, val] of Object.entries(value)) {
|
|
3009
|
-
const nestedKey = objectNotation === 'dot' ? `${key}.${objKey}` : `${key}[${objKey}]`;
|
|
3010
|
-
didAddAnyValue = processValue(nestedKey, val) || didAddAnyValue;
|
|
3011
|
-
}
|
|
3012
|
-
return didAddAnyValue;
|
|
3013
|
-
}
|
|
3014
|
-
else {
|
|
3015
|
-
if (ignoredValues.includes(value)) {
|
|
3016
|
-
return false;
|
|
3017
|
-
}
|
|
3018
|
-
if (ignoredValuesFns.some((fn) => fn(value))) {
|
|
3019
|
-
return false;
|
|
3020
|
-
}
|
|
3021
|
-
const encodedKey = encodeURIComponent(key);
|
|
3022
|
-
const encodedValue = encodeURIComponent(value);
|
|
3023
|
-
queryParams.push(`${encodedKey}=${encodedValue}`);
|
|
3024
|
-
return true;
|
|
3025
|
-
}
|
|
3026
|
-
}
|
|
3027
|
-
for (const [key, val] of Object.entries(params)) {
|
|
3028
|
-
processValue(key, val);
|
|
3029
|
-
}
|
|
3030
|
-
return queryParams.length ? queryParams.join('&') : null;
|
|
3031
|
-
};
|
|
3032
|
-
const isPlainObject = (value) => {
|
|
3033
|
-
if (!value || typeof value !== 'object')
|
|
3034
|
-
return false;
|
|
3035
|
-
const proto = Object.getPrototypeOf(value);
|
|
3036
|
-
return proto === Object.prototype || proto === null;
|
|
3037
|
-
};
|
|
3038
|
-
/** Copies query params with every plain object's keys sorted, so key order never reaches a cache key. */
|
|
3039
|
-
const sortQueryParamKeys = (value) => {
|
|
3040
|
-
if (Array.isArray(value))
|
|
3041
|
-
return value.map(sortQueryParamKeys);
|
|
3042
|
-
if (!isPlainObject(value))
|
|
3043
|
-
return value;
|
|
3044
|
-
return Object.fromEntries(Object.keys(value)
|
|
3045
|
-
.sort()
|
|
3046
|
-
.map((key) => [key, sortQueryParamKeys(value[key])]));
|
|
3047
|
-
};
|
|
3048
|
-
const buildRoute$1 = (options) => {
|
|
3049
|
-
if (options.base.endsWith('/')) {
|
|
3050
|
-
throw invalidBaseRouteError$1(options.base);
|
|
3051
|
-
}
|
|
3052
|
-
let route;
|
|
3053
|
-
if (typeof options.route === 'function') {
|
|
3054
|
-
if (!options.pathParams) {
|
|
3055
|
-
throw pathParamsMissingInRouteFunctionError$1(options.route({}));
|
|
3263
|
+
const check = (args) => {
|
|
3264
|
+
const now = Date.now();
|
|
3265
|
+
const signature = buildQueryCacheKey(JSON.stringify([args?.pathParams ?? null, args?.queryParams ?? null]), args ?? undefined);
|
|
3266
|
+
for (const [key, entry] of recent) {
|
|
3267
|
+
if (now - entry.lastTs >= CIRCULAR_DEPENDENCY_WINDOW_MS)
|
|
3268
|
+
recent.delete(key);
|
|
3056
3269
|
}
|
|
3057
|
-
|
|
3058
|
-
|
|
3059
|
-
|
|
3060
|
-
|
|
3061
|
-
|
|
3062
|
-
if (route && !route.startsWith('/')) {
|
|
3063
|
-
throw invalidRouteError$1(route);
|
|
3064
|
-
}
|
|
3065
|
-
if (options.queryParams) {
|
|
3066
|
-
const queryString = buildQueryString$1(options.queryParams, options.queryParamConfig);
|
|
3067
|
-
if (queryString) {
|
|
3068
|
-
route = route ? `${route}?${queryString}` : `/?${queryString}`;
|
|
3270
|
+
const entry = recent.get(signature);
|
|
3271
|
+
const repeats = entry ? entry.repeats + 1 : 1;
|
|
3272
|
+
if (repeats > CIRCULAR_DEPENDENCY_MAX_REPEATS) {
|
|
3273
|
+
recent.delete(signature);
|
|
3274
|
+
throw circularQueryDependency();
|
|
3069
3275
|
}
|
|
3070
|
-
|
|
3071
|
-
|
|
3276
|
+
recent.set(signature, { lastTs: now, repeats });
|
|
3277
|
+
};
|
|
3278
|
+
return {
|
|
3279
|
+
check,
|
|
3280
|
+
};
|
|
3072
3281
|
};
|
|
3073
|
-
|
|
3074
|
-
|
|
3075
|
-
|
|
3076
|
-
|
|
3077
|
-
|
|
3282
|
+
|
|
3283
|
+
const createExecuteFn = (executeOptions) => {
|
|
3284
|
+
const executeState = setupQueryExecuteState();
|
|
3285
|
+
const circularChecker = circularQueryDependencyChecker();
|
|
3286
|
+
const aborter = createQueryExecutionAborter(executeOptions.state);
|
|
3287
|
+
const reset = () => untracked(() => resetExecuteState({ executeState, executeOptions }));
|
|
3288
|
+
const exec = (executeArgs) => untracked(() => {
|
|
3289
|
+
const { args = executeOptions.state.args(), options } = executeArgs ?? {};
|
|
3290
|
+
if (skipParkedExecution(executeOptions.state, args, executeOptions.creatorInternals.route))
|
|
3291
|
+
return false;
|
|
3292
|
+
circularChecker.check(args);
|
|
3293
|
+
aborter.capture();
|
|
3294
|
+
recordExecutionArgs(executeOptions.state, args, options);
|
|
3295
|
+
queryExecute({ executeOptions, executeState, args, options });
|
|
3296
|
+
return true;
|
|
3297
|
+
});
|
|
3298
|
+
exec['reset'] = reset;
|
|
3299
|
+
exec['abort'] = () => aborter.abort();
|
|
3300
|
+
exec['aborted$'] = aborter.aborted$;
|
|
3301
|
+
exec['currentRepositoryKey'] = executeState.previousKey.asReadonly();
|
|
3302
|
+
return exec;
|
|
3078
3303
|
};
|
|
3079
|
-
|
|
3080
|
-
|
|
3081
|
-
|
|
3082
|
-
|
|
3083
|
-
|
|
3084
|
-
|
|
3304
|
+
|
|
3305
|
+
/* eslint-disable @typescript-eslint/no-explicit-any */
|
|
3306
|
+
/**
|
|
3307
|
+
* The empty request-args object, typed to satisfy `RequestArgs<TArgs>` for a query that takes no
|
|
3308
|
+
* arguments - its `QueryArgs` is just `{ response: T }`, so `RequestArgs` resolves to `{}`.
|
|
3309
|
+
*
|
|
3310
|
+
* Only needed in generic code, where `TArgs` is still a type parameter and TS therefore cannot see
|
|
3311
|
+
* that `{}` is the whole of it. Against a concrete query, omit the argument instead: both
|
|
3312
|
+
* `query.execute()` and the auth registry's `execute()` already take none.
|
|
3313
|
+
*
|
|
3314
|
+
* @example
|
|
3315
|
+
* const runAnything = <TArgs extends QueryArgs>(query: Query<TArgs>) =>
|
|
3316
|
+
* query.execute({ args: emptyQueryArgs<TArgs>() });
|
|
3317
|
+
*/
|
|
3318
|
+
const emptyQueryArgs = () => ({});
|
|
3319
|
+
const createQuery = (options) => createBaseQuery({
|
|
3320
|
+
creator: options.creator,
|
|
3321
|
+
creatorInternals: options.creatorInternals,
|
|
3322
|
+
features: options.features,
|
|
3323
|
+
queryConfig: options.queryConfig,
|
|
3324
|
+
executeFactory: createExecuteFn,
|
|
3325
|
+
});
|
|
3326
|
+
|
|
3327
|
+
const splitQueryConfig = (args) => {
|
|
3328
|
+
let queryConfig = {};
|
|
3329
|
+
let features = [];
|
|
3330
|
+
const first = args[0];
|
|
3331
|
+
if (first) {
|
|
3332
|
+
if ('type' in first) {
|
|
3333
|
+
features = args;
|
|
3334
|
+
}
|
|
3335
|
+
else {
|
|
3336
|
+
[queryConfig, ...features] = args;
|
|
3085
3337
|
}
|
|
3086
|
-
const base64 = base64Url.replace(/-/g, '+').replace(/_/g, '/');
|
|
3087
|
-
const jsonPayload = decodeURIComponent(atob(base64)
|
|
3088
|
-
.split('')
|
|
3089
|
-
.map((c) => '%' + ('00' + c.charCodeAt(0).toString(16)).slice(-2))
|
|
3090
|
-
.join(''));
|
|
3091
|
-
return JSON.parse(jsonPayload);
|
|
3092
3338
|
}
|
|
3093
|
-
|
|
3094
|
-
|
|
3095
|
-
|
|
3339
|
+
return { features, queryConfig };
|
|
3340
|
+
};
|
|
3341
|
+
const createQueryCreator = (options, internals) => createBaseQueryCreator({
|
|
3342
|
+
options,
|
|
3343
|
+
internals,
|
|
3344
|
+
queryFactory: createQuery,
|
|
3345
|
+
});
|
|
3346
|
+
|
|
3347
|
+
const createBaseQueryCreator = (config) => {
|
|
3348
|
+
function queryCreator(...args) {
|
|
3349
|
+
if (getActiveConsumer() !== null)
|
|
3350
|
+
throw queryCreatedInReactiveContext();
|
|
3351
|
+
const { features, queryConfig } = splitQueryConfig(args);
|
|
3352
|
+
return config.queryFactory({
|
|
3353
|
+
creator: config.options,
|
|
3354
|
+
creatorInternals: config.internals,
|
|
3355
|
+
features,
|
|
3356
|
+
queryConfig,
|
|
3357
|
+
});
|
|
3096
3358
|
}
|
|
3359
|
+
// Add clone method to create a new creator with merged options
|
|
3360
|
+
// Cast to match QueryCreator's expected signature while preserving TOptions internally
|
|
3361
|
+
queryCreator.clone = (additionalOptions) => {
|
|
3362
|
+
return createBaseQueryCreator({
|
|
3363
|
+
...config,
|
|
3364
|
+
options: { ...config.options, ...additionalOptions },
|
|
3365
|
+
});
|
|
3366
|
+
};
|
|
3367
|
+
queryCreator.subtle = {
|
|
3368
|
+
creatorInternals: config.internals,
|
|
3369
|
+
creatorOptions: config.options,
|
|
3370
|
+
};
|
|
3371
|
+
return queryCreator;
|
|
3097
3372
|
};
|
|
3098
3373
|
|
|
3099
3374
|
/**
|
|
@@ -3449,11 +3724,18 @@ const createHttpRequest = (options) => {
|
|
|
3449
3724
|
retryState: retryState.asReadonly(),
|
|
3450
3725
|
lastDurationMs: lastDurationMs.asReadonly(),
|
|
3451
3726
|
abort,
|
|
3727
|
+
markStale: () => {
|
|
3728
|
+
if (!isStale())
|
|
3729
|
+
expiresIn.set(Date.now());
|
|
3730
|
+
},
|
|
3731
|
+
setResponse: (body) => response.set(body),
|
|
3452
3732
|
},
|
|
3453
3733
|
};
|
|
3454
3734
|
return httpRequest;
|
|
3455
3735
|
};
|
|
3456
3736
|
|
|
3737
|
+
const wrapAsObservableSignal = (source, defaultInjector) => createObservableSignalWatcher(defaultInjector).wrap(source);
|
|
3738
|
+
|
|
3457
3739
|
/* eslint-disable @typescript-eslint/no-explicit-any */
|
|
3458
3740
|
/** Transforms an array of arrays into a single array and filters out `null` values. */
|
|
3459
3741
|
const transformArrayResponse = (responses) => responses.filter((r) => !!r).flatMap((r) => r);
|
|
@@ -3465,7 +3747,7 @@ const createQueryStack = (options) => {
|
|
|
3465
3747
|
const lastQuery = newQueries[newQueries.length - 1] ?? oldQueries[oldQueries.length - 1] ?? null;
|
|
3466
3748
|
return { queries, lastQuery };
|
|
3467
3749
|
}, transform, argsKeyFn, deduplicateArgs, maxQueries = Infinity, removeStrategy = 'oldest', } = options ?? {};
|
|
3468
|
-
const injector =
|
|
3750
|
+
const injector = injectInQueryContext(Injector, 'query stack');
|
|
3469
3751
|
const queries = signal([], /* @ts-ignore */
|
|
3470
3752
|
...(ngDevMode ? [{ debugName: "queries" }] : /* istanbul ignore next */ []));
|
|
3471
3753
|
const lastQuery = signal(null, /* @ts-ignore */
|
|
@@ -3473,11 +3755,11 @@ const createQueryStack = (options) => {
|
|
|
3473
3755
|
const firstQuery = computed(() => queries()[0] ?? null, /* @ts-ignore */
|
|
3474
3756
|
...(ngDevMode ? [{ debugName: "firstQuery" }] : /* istanbul ignore next */ []));
|
|
3475
3757
|
const hasWithArgsFeature = features.some((f) => f.type === QueryFeatureType.WITH_ARGS);
|
|
3476
|
-
const
|
|
3758
|
+
const hasWithResponseUpdateFeature = features.some((f) => f.type === QueryFeatureType.WITH_RESPONSE_UPDATE);
|
|
3477
3759
|
if (hasWithArgsFeature) {
|
|
3478
3760
|
throw queryStackWithArgsUsed();
|
|
3479
3761
|
}
|
|
3480
|
-
if (
|
|
3762
|
+
if (hasWithResponseUpdateFeature) {
|
|
3481
3763
|
throw queryStackWithResponseUpdateUsed();
|
|
3482
3764
|
}
|
|
3483
3765
|
const runWithArgs = (args) => {
|
|
@@ -3730,6 +4012,7 @@ const createPagedQueryStack = (options) => {
|
|
|
3730
4012
|
const args = options.args(initialPage(), []);
|
|
3731
4013
|
untracked(() => {
|
|
3732
4014
|
stack.clear();
|
|
4015
|
+
pageDirection.set('next');
|
|
3733
4016
|
currentPageArgs.set(args);
|
|
3734
4017
|
loadedMinPage.set(initialPage());
|
|
3735
4018
|
loadedMaxPage.set(initialPage());
|
|
@@ -4505,7 +4788,8 @@ const createIndexedDbQueryPersistenceAdapter = (options) => {
|
|
|
4505
4788
|
*/
|
|
4506
4789
|
const createQueryBatch = (options) => {
|
|
4507
4790
|
const { queryCreator, args: mapArgs, stopOnError, allowCache, onItemSettled, features = [] } = options;
|
|
4508
|
-
const
|
|
4791
|
+
const requestedConcurrency = options.concurrency ?? 4;
|
|
4792
|
+
const concurrency = Number.isNaN(requestedConcurrency) ? 4 : Math.max(1, Math.floor(requestedConcurrency));
|
|
4509
4793
|
if (features.some((f) => f.type === QueryFeatureType.WITH_ARGS)) {
|
|
4510
4794
|
throw queryBatchWithArgsUsed();
|
|
4511
4795
|
}
|
|
@@ -4786,41 +5070,6 @@ const isQueryDevtoolsRepositoryLive = (repository) => {
|
|
|
4786
5070
|
return live$1.has(repository);
|
|
4787
5071
|
};
|
|
4788
5072
|
|
|
4789
|
-
/**
|
|
4790
|
-
* Resolves an {@link QueryInvalidationOptions.url} to the absolute form request URLs are built in, so
|
|
4791
|
-
* the comparison - and the message the other tabs receive - never depends on who resolves it.
|
|
4792
|
-
*/
|
|
4793
|
-
const resolveInvalidationUrl = (baseUrl, url) => {
|
|
4794
|
-
const absolute = url.startsWith('/') ? `${baseUrl}${url}` : url;
|
|
4795
|
-
// A trailing slash would put the boundary check below one character past where the URL actually
|
|
4796
|
-
// ends, which is the one way `/players/` could fail to match `/players/1`.
|
|
4797
|
-
return absolute.endsWith('/') ? absolute.slice(0, -1) : absolute;
|
|
4798
|
-
};
|
|
4799
|
-
/**
|
|
4800
|
-
* Whether a query URL is the invalidated one or sits below it. The check on what follows the prefix
|
|
4801
|
-
* is what keeps `/players` from matching `/players-archive`, which a `startsWith` alone would.
|
|
4802
|
-
*/
|
|
4803
|
-
const isUnderInvalidatedUrl = (queryUrl, invalidatedUrl) => {
|
|
4804
|
-
if (!queryUrl.startsWith(invalidatedUrl))
|
|
4805
|
-
return false;
|
|
4806
|
-
const rest = queryUrl.slice(invalidatedUrl.length);
|
|
4807
|
-
return rest === '' || rest.startsWith('/') || rest.startsWith('?') || rest.startsWith('#');
|
|
4808
|
-
};
|
|
4809
|
-
/**
|
|
4810
|
-
* Turns an invalidation into the filter the repository refreshes by, or `undefined` when nothing
|
|
4811
|
-
* narrows it - "everything in use" is the repository's own cheapest path.
|
|
4812
|
-
*/
|
|
4813
|
-
const createQueryInvalidationFilter = (options) => {
|
|
4814
|
-
const { url, filter } = options;
|
|
4815
|
-
if (!url && !filter)
|
|
4816
|
-
return undefined;
|
|
4817
|
-
return (request) => {
|
|
4818
|
-
if (url && !isUnderInvalidatedUrl(request.url, url))
|
|
4819
|
-
return false;
|
|
4820
|
-
return filter ? filter({ method: request.method, url: request.url }) : true;
|
|
4821
|
-
};
|
|
4822
|
-
};
|
|
4823
|
-
|
|
4824
5073
|
/**
|
|
4825
5074
|
* An error body can be an HTML page instead of JSON: a proxy's 502, a load balancer's maintenance page, a
|
|
4826
5075
|
* platform's "service temporarily unavailable". The markup is never worth showing, but the sentence inside it
|
|
@@ -5002,6 +5251,14 @@ const createQuerySyncEngine = (options) => {
|
|
|
5002
5251
|
// response sharing would just make a tab stop updating. Enforced here rather than documented.
|
|
5003
5252
|
const isPollingDedupeEnabled = (config.dedupePolling ?? true) && syncResponses && transport.isSupported && lockManager.isSupported;
|
|
5004
5253
|
const mutationFilter = typeof refreshOnMutation === 'object' ? refreshOnMutation.filter : null;
|
|
5254
|
+
let outgoingBatch = null;
|
|
5255
|
+
const currentOutgoingBatch = () => {
|
|
5256
|
+
if (outgoingBatch === null) {
|
|
5257
|
+
outgoingBatch = randomId();
|
|
5258
|
+
queueMicrotask(() => (outgoingBatch = null));
|
|
5259
|
+
}
|
|
5260
|
+
return outgoingBatch;
|
|
5261
|
+
};
|
|
5005
5262
|
const broadcast = (event) => {
|
|
5006
5263
|
if (event.type !== 'request-success' || !event.isMultiTabSyncEnabled)
|
|
5007
5264
|
return;
|
|
@@ -5023,7 +5280,12 @@ const createQuerySyncEngine = (options) => {
|
|
|
5023
5280
|
// worth telling the other tabs about - and an uncacheable read has no shared key to send anyway.
|
|
5024
5281
|
if (!refreshOnMutation || shouldCacheQuery(event.request.method))
|
|
5025
5282
|
return;
|
|
5026
|
-
transport.post({
|
|
5283
|
+
transport.post({
|
|
5284
|
+
type: 'mutation',
|
|
5285
|
+
method: event.request.method,
|
|
5286
|
+
url: event.request.url,
|
|
5287
|
+
batch: currentOutgoingBatch(),
|
|
5288
|
+
});
|
|
5027
5289
|
};
|
|
5028
5290
|
const apply = (message) => {
|
|
5029
5291
|
if (message.type === 'response') {
|
|
@@ -5035,23 +5297,20 @@ const createQuerySyncEngine = (options) => {
|
|
|
5035
5297
|
// An invalidation is something the other tab's app code asked for explicitly, so unlike the
|
|
5036
5298
|
// mutation heuristic it is not something `refreshOnMutation: false` opts out of.
|
|
5037
5299
|
if (message.type === 'invalidate') {
|
|
5038
|
-
repository.refreshInUse(createQueryInvalidationFilter({ url: message.url }), {
|
|
5300
|
+
repository.refreshInUse(createQueryInvalidationFilter({ url: message.url, tag: message.tag }), {
|
|
5039
5301
|
type: 'invalidation',
|
|
5040
5302
|
url: message.url,
|
|
5041
5303
|
otherTab: true,
|
|
5042
|
-
|
|
5304
|
+
...(message.tag && { tag: message.tag }),
|
|
5305
|
+
}, message.batch);
|
|
5043
5306
|
return;
|
|
5044
5307
|
}
|
|
5045
5308
|
if (!refreshOnMutation)
|
|
5046
5309
|
return;
|
|
5047
5310
|
const mutation = { method: message.method, url: message.url };
|
|
5048
|
-
repository.refreshInUse(mutationFilter ? (request) => mutationFilter(mutation, request) : undefined, {
|
|
5049
|
-
type: 'mutation',
|
|
5050
|
-
url: message.url,
|
|
5051
|
-
otherTab: true,
|
|
5052
|
-
});
|
|
5311
|
+
repository.refreshInUse(mutationFilter ? (request) => mutationFilter(mutation, request) : undefined, { type: 'mutation', url: message.url, otherTab: true }, message.batch);
|
|
5053
5312
|
};
|
|
5054
|
-
const postInvalidation = (url) => transport.post({ type: 'invalidate', url });
|
|
5313
|
+
const postInvalidation = (url, tag) => transport.post({ type: 'invalidate', url, ...(tag && { tag }), batch: currentOutgoingBatch() });
|
|
5055
5314
|
const eventSubscription = repository.events$.subscribe(broadcast);
|
|
5056
5315
|
const unlisten = transport.listen(apply);
|
|
5057
5316
|
const destroy = () => {
|
|
@@ -5104,11 +5363,13 @@ const unwrapQuerySyncMessage = (data) => {
|
|
|
5104
5363
|
expiresAt: typeof expiresAt === 'number' ? expiresAt : null,
|
|
5105
5364
|
};
|
|
5106
5365
|
}
|
|
5366
|
+
const batch = typeof raw['batch'] === 'string' ? { batch: raw['batch'] } : {};
|
|
5107
5367
|
if (type === 'mutation' && typeof method === 'string' && typeof url === 'string') {
|
|
5108
|
-
return { type: 'mutation', method: method, url };
|
|
5368
|
+
return { type: 'mutation', method: method, url, ...batch };
|
|
5109
5369
|
}
|
|
5110
5370
|
if (type === 'invalidate' && (typeof url === 'string' || url === null)) {
|
|
5111
|
-
|
|
5371
|
+
const tag = raw['tag'];
|
|
5372
|
+
return { type: 'invalidate', url, tag: typeof tag === 'string' ? tag : null, ...batch };
|
|
5112
5373
|
}
|
|
5113
5374
|
return null;
|
|
5114
5375
|
};
|
|
@@ -5370,6 +5631,16 @@ const withEthleteApiErrors = (options) => () => {
|
|
|
5370
5631
|
};
|
|
5371
5632
|
};
|
|
5372
5633
|
|
|
5634
|
+
const functionIds = /* @__PURE__ */ new WeakMap();
|
|
5635
|
+
let nextFunctionId = 1;
|
|
5636
|
+
const functionId = (fn) => {
|
|
5637
|
+
let id = functionIds.get(fn);
|
|
5638
|
+
if (id === undefined) {
|
|
5639
|
+
id = nextFunctionId++;
|
|
5640
|
+
functionIds.set(fn, id);
|
|
5641
|
+
}
|
|
5642
|
+
return id;
|
|
5643
|
+
};
|
|
5373
5644
|
const generateUuid = () => randomId();
|
|
5374
5645
|
/** @see CreateQueryClientConfigOptions.keepUnusedFor */
|
|
5375
5646
|
const DEFAULT_KEEP_UNUSED_FOR = 300_000;
|
|
@@ -5379,6 +5650,7 @@ const DEFAULT_KEEP_UNUSED_FOR = 300_000;
|
|
|
5379
5650
|
* `unbind`) must never grow unbounded - the least recently orphaned entries are dropped first.
|
|
5380
5651
|
*/
|
|
5381
5652
|
const MAX_UNUSED_ENTRIES = 50;
|
|
5653
|
+
const MAX_TIMEOUT_DELAY = 2 ** 31 - 1;
|
|
5382
5654
|
const createQueryRepository = (config) => {
|
|
5383
5655
|
const cache = new Map();
|
|
5384
5656
|
const eventsSubject = new Subject();
|
|
@@ -5421,8 +5693,17 @@ const createQueryRepository = (config) => {
|
|
|
5421
5693
|
queryParamConfig: config.queryString,
|
|
5422
5694
|
})
|
|
5423
5695
|
: route;
|
|
5696
|
+
const wireKey = [
|
|
5697
|
+
creatorOptions?.responseType && creatorOptions.responseType !== 'json' ? creatorOptions.responseType : '',
|
|
5698
|
+
creatorOptions?.withCredentials ? 'credentials' : '',
|
|
5699
|
+
creatorOptions?.reportErrors === false ? 'silent' : '',
|
|
5700
|
+
creatorOptions?.reportProgress ? 'progress' : '',
|
|
5701
|
+
options.retryFn ? `retry${functionId(options.retryFn)}` : '',
|
|
5702
|
+
]
|
|
5703
|
+
.filter(Boolean)
|
|
5704
|
+
.join('_');
|
|
5424
5705
|
const cacheKey = shouldCache
|
|
5425
|
-
? buildQueryCacheKey(`${options.key ? options.key + '_' : ''}${keyRoute}`, {
|
|
5706
|
+
? buildQueryCacheKey(`${wireKey ? wireKey + '_' : ''}${options.key ? options.key + '_' : ''}${keyRoute}`, {
|
|
5426
5707
|
body: args?.body,
|
|
5427
5708
|
queryParams: args?.queryParams,
|
|
5428
5709
|
pathParams: args?.pathParams,
|
|
@@ -5433,6 +5714,7 @@ const createQueryRepository = (config) => {
|
|
|
5433
5714
|
const keepUnusedFor = resolveKeepUnusedFor(creatorOptions, shouldCache, runQueryOptions?.keepUnusedFor);
|
|
5434
5715
|
const isMultiTabSyncEnabled = creatorOptions?.multiTabSync !== false;
|
|
5435
5716
|
const isSecure = options.isSecure ?? false;
|
|
5717
|
+
const tags = options.tags ?? [];
|
|
5436
5718
|
// A secure response needs an explicit `persistence: true` to reach the disk: leaving an
|
|
5437
5719
|
// authenticated user's data there is a decision per endpoint, not a default. Everything else
|
|
5438
5720
|
// persists unless it opted out.
|
|
@@ -5457,6 +5739,7 @@ const createQueryRepository = (config) => {
|
|
|
5457
5739
|
isMultiTabSyncEnabled,
|
|
5458
5740
|
isPersistEnabled,
|
|
5459
5741
|
keepUnusedFor,
|
|
5742
|
+
tags,
|
|
5460
5743
|
});
|
|
5461
5744
|
const executed = !runQueryOptions?.allowCache || cacheEntry.request.isStale()
|
|
5462
5745
|
? cacheEntry.request.execute({ allowCache: runQueryOptions?.allowCache })
|
|
@@ -5489,6 +5772,7 @@ const createQueryRepository = (config) => {
|
|
|
5489
5772
|
isMultiTabSyncEnabled,
|
|
5490
5773
|
isPersistEnabled,
|
|
5491
5774
|
keepUnusedFor,
|
|
5775
|
+
tags,
|
|
5492
5776
|
});
|
|
5493
5777
|
// Announced after the entry is bound and its request is on its way, because the one thing that acts
|
|
5494
5778
|
// on it - hydration from the persisted store - is only ever allowed to fill a gap this request has
|
|
@@ -5510,8 +5794,6 @@ const createQueryRepository = (config) => {
|
|
|
5510
5794
|
eventsSubject.next({ type: 'entry-destroyed', key, isSecure: cacheEntry.isSecure, method, url, cause });
|
|
5511
5795
|
};
|
|
5512
5796
|
const cancelEviction = (cacheEntry) => {
|
|
5513
|
-
if (cacheEntry.evictTimer === undefined)
|
|
5514
|
-
return;
|
|
5515
5797
|
clearTimeout(cacheEntry.evictTimer);
|
|
5516
5798
|
cacheEntry.evictTimer = undefined;
|
|
5517
5799
|
cacheEntry.unusedSince = undefined;
|
|
@@ -5529,7 +5811,9 @@ const createQueryRepository = (config) => {
|
|
|
5529
5811
|
const retain = (key, cacheEntry) => {
|
|
5530
5812
|
clearTimeout(cacheEntry.evictTimer);
|
|
5531
5813
|
cacheEntry.unusedSince = Date.now();
|
|
5532
|
-
cacheEntry.evictTimer =
|
|
5814
|
+
cacheEntry.evictTimer = Number.isFinite(cacheEntry.keepUnusedFor)
|
|
5815
|
+
? setTimeout(() => evict(key, 'expired'), Math.min(cacheEntry.keepUnusedFor, MAX_TIMEOUT_DELAY))
|
|
5816
|
+
: undefined;
|
|
5533
5817
|
enforceUnusedEntryLimit();
|
|
5534
5818
|
};
|
|
5535
5819
|
/**
|
|
@@ -5550,6 +5834,7 @@ const createQueryRepository = (config) => {
|
|
|
5550
5834
|
cacheEntry.isPersistEnabled = bindings.some((binding) => binding.isPersistEnabled);
|
|
5551
5835
|
cacheEntry.isMultiTabSyncEnabled = bindings.every((binding) => binding.isMultiTabSyncEnabled);
|
|
5552
5836
|
cacheEntry.keepUnusedFor = Math.min(...bindings.map((binding) => binding.keepUnusedFor));
|
|
5837
|
+
cacheEntry.tags = bindings.flatMap((binding) => binding.tags);
|
|
5553
5838
|
};
|
|
5554
5839
|
const unbind = (key, consumerDestroyRef) => {
|
|
5555
5840
|
if (key === null)
|
|
@@ -5592,10 +5877,30 @@ const createQueryRepository = (config) => {
|
|
|
5592
5877
|
bumpCacheVersion();
|
|
5593
5878
|
eventsSubject.next({ type: 'unbind-all-secure' });
|
|
5594
5879
|
};
|
|
5595
|
-
|
|
5880
|
+
let restartedBatch = null;
|
|
5881
|
+
let turnBatchKey = null;
|
|
5882
|
+
const currentTurnBatchKey = () => {
|
|
5883
|
+
if (turnBatchKey === null) {
|
|
5884
|
+
turnBatchKey = generateUuid();
|
|
5885
|
+
queueMicrotask(() => (turnBatchKey = null));
|
|
5886
|
+
}
|
|
5887
|
+
return turnBatchKey;
|
|
5888
|
+
};
|
|
5889
|
+
const resolveRestartedBatch = (cause, batch) => {
|
|
5890
|
+
if (!cause || cause.type === 'refresh')
|
|
5891
|
+
return null;
|
|
5892
|
+
const key = batch ?? currentTurnBatchKey();
|
|
5893
|
+
if (restartedBatch?.key !== key)
|
|
5894
|
+
restartedBatch = { key, requests: new WeakSet() };
|
|
5895
|
+
return restartedBatch.requests;
|
|
5896
|
+
};
|
|
5897
|
+
const refreshInUse = (filter, cause, batch) => {
|
|
5596
5898
|
const refreshed = [];
|
|
5899
|
+
const marksUnusedStale = cause?.type === 'invalidation';
|
|
5900
|
+
const restartedInBatch = resolveRestartedBatch(cause, batch);
|
|
5597
5901
|
for (const cacheEntry of cache.values()) {
|
|
5598
|
-
|
|
5902
|
+
const isUnused = cacheEntry.consumers.size === 0;
|
|
5903
|
+
if (isUnused && !marksUnusedStale)
|
|
5599
5904
|
continue;
|
|
5600
5905
|
// Re-firing a mutation would be a side effect nobody asked for, so only reads are refreshed:
|
|
5601
5906
|
// a cacheable method, or a GQL query transported via POST. Opting into the cache alone does not
|
|
@@ -5603,8 +5908,15 @@ const createQueryRepository = (config) => {
|
|
|
5603
5908
|
// end the session.
|
|
5604
5909
|
if (!cacheEntry.isRefreshable)
|
|
5605
5910
|
continue;
|
|
5606
|
-
if (filter && !filter(cacheEntry.request))
|
|
5911
|
+
if (filter && !filter(cacheEntry.request, cacheEntry.tags))
|
|
5607
5912
|
continue;
|
|
5913
|
+
if (isUnused) {
|
|
5914
|
+
cacheEntry.request.subtle.markStale();
|
|
5915
|
+
continue;
|
|
5916
|
+
}
|
|
5917
|
+
if (restartedInBatch?.has(cacheEntry.request))
|
|
5918
|
+
continue;
|
|
5919
|
+
restartedInBatch?.add(cacheEntry.request);
|
|
5608
5920
|
refreshed.push(cacheEntry.request);
|
|
5609
5921
|
cacheEntry.request.execute({ force: true });
|
|
5610
5922
|
}
|
|
@@ -5635,7 +5947,7 @@ const createQueryRepository = (config) => {
|
|
|
5635
5947
|
return true;
|
|
5636
5948
|
};
|
|
5637
5949
|
const bind = (options) => {
|
|
5638
|
-
const { key, consumerDestroyRef, request, isSecure, isCached, isRefreshable, isMultiTabSyncEnabled, isPersistEnabled, keepUnusedFor, } = options;
|
|
5950
|
+
const { key, consumerDestroyRef, request, isSecure, isCached, isRefreshable, isMultiTabSyncEnabled, isPersistEnabled, keepUnusedFor, tags, } = options;
|
|
5639
5951
|
const cacheEntry = cache.get(key);
|
|
5640
5952
|
if (cacheEntry && isCached) {
|
|
5641
5953
|
const existingBinding = cacheEntry.consumers.get(consumerDestroyRef);
|
|
@@ -5645,6 +5957,7 @@ const createQueryRepository = (config) => {
|
|
|
5645
5957
|
existingBinding.isMultiTabSyncEnabled = isMultiTabSyncEnabled;
|
|
5646
5958
|
existingBinding.isPersistEnabled = isPersistEnabled;
|
|
5647
5959
|
existingBinding.keepUnusedFor = keepUnusedFor;
|
|
5960
|
+
existingBinding.tags = tags;
|
|
5648
5961
|
}
|
|
5649
5962
|
else {
|
|
5650
5963
|
cacheEntry.consumers.set(consumerDestroyRef, {
|
|
@@ -5654,6 +5967,7 @@ const createQueryRepository = (config) => {
|
|
|
5654
5967
|
isMultiTabSyncEnabled,
|
|
5655
5968
|
isPersistEnabled,
|
|
5656
5969
|
keepUnusedFor,
|
|
5970
|
+
tags,
|
|
5657
5971
|
});
|
|
5658
5972
|
}
|
|
5659
5973
|
mergeConsumerPolicies(cacheEntry);
|
|
@@ -5669,6 +5983,7 @@ const createQueryRepository = (config) => {
|
|
|
5669
5983
|
isMultiTabSyncEnabled,
|
|
5670
5984
|
isPersistEnabled,
|
|
5671
5985
|
keepUnusedFor,
|
|
5986
|
+
tags,
|
|
5672
5987
|
},
|
|
5673
5988
|
],
|
|
5674
5989
|
]);
|
|
@@ -5715,6 +6030,7 @@ const createQueryRepository = (config) => {
|
|
|
5715
6030
|
isRefreshable,
|
|
5716
6031
|
isMultiTabSyncEnabled,
|
|
5717
6032
|
isPersistEnabled,
|
|
6033
|
+
tags,
|
|
5718
6034
|
});
|
|
5719
6035
|
}
|
|
5720
6036
|
bumpCacheVersion();
|
|
@@ -5724,6 +6040,8 @@ const createQueryRepository = (config) => {
|
|
|
5724
6040
|
isSecure: entry.isSecure,
|
|
5725
6041
|
consumerCount: entry.consumers.size,
|
|
5726
6042
|
isUnused: entry.consumers.size === 0,
|
|
6043
|
+
isRefreshable: entry.isRefreshable,
|
|
6044
|
+
tags: entry.tags,
|
|
5727
6045
|
request: entry.request,
|
|
5728
6046
|
}));
|
|
5729
6047
|
const evict = (key, cause = 'manual') => {
|
|
@@ -5750,7 +6068,21 @@ const createQueryRepository = (config) => {
|
|
|
5750
6068
|
return repository;
|
|
5751
6069
|
};
|
|
5752
6070
|
|
|
6071
|
+
const clientNamesByInjector = /* @__PURE__ */ new WeakMap();
|
|
6072
|
+
const warnOnDuplicateClientName = (name, destroyRef) => {
|
|
6073
|
+
const environmentInjector = inject(EnvironmentInjector);
|
|
6074
|
+
const names = clientNamesByInjector.get(environmentInjector) ?? new Set();
|
|
6075
|
+
clientNamesByInjector.set(environmentInjector, names);
|
|
6076
|
+
if (names.has(name)) {
|
|
6077
|
+
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.`);
|
|
6078
|
+
return;
|
|
6079
|
+
}
|
|
6080
|
+
names.add(name);
|
|
6081
|
+
destroyRef.onDestroy(() => names.delete(name));
|
|
6082
|
+
};
|
|
5753
6083
|
const createQueryClient = (options) => defineRootProvider(() => {
|
|
6084
|
+
if (options.baseUrl.endsWith('/'))
|
|
6085
|
+
throw invalidBaseRouteError$1(options.baseUrl);
|
|
5754
6086
|
const httpClient = inject(HttpClient);
|
|
5755
6087
|
const ngErrorHandler = inject(ErrorHandler);
|
|
5756
6088
|
const injector = inject(Injector);
|
|
@@ -5768,6 +6100,8 @@ const createQueryClient = (options) => defineRootProvider(() => {
|
|
|
5768
6100
|
dependencies: { httpClient, ngErrorHandler, injector },
|
|
5769
6101
|
});
|
|
5770
6102
|
const destroyRef = inject(DestroyRef);
|
|
6103
|
+
if (isDevMode())
|
|
6104
|
+
warnOnDuplicateClientName(options.name, destroyRef);
|
|
5771
6105
|
destroyRef.onDestroy(() => {
|
|
5772
6106
|
for (const entry of repository.subtle.cacheEntries()) {
|
|
5773
6107
|
if (entry.isUnused)
|
|
@@ -5807,15 +6141,17 @@ const createQueryClient = (options) => defineRootProvider(() => {
|
|
|
5807
6141
|
refreshQueriesInUse: () => repository.refreshInUse(),
|
|
5808
6142
|
invalidateQueries: (invalidation) => {
|
|
5809
6143
|
const url = invalidation?.url ? resolveInvalidationUrl(options.baseUrl, invalidation.url) : null;
|
|
5810
|
-
|
|
6144
|
+
const tag = invalidation?.tag ?? null;
|
|
6145
|
+
repository.refreshInUse(createQueryInvalidationFilter({ url, tag, filter: invalidation?.filter }), {
|
|
5811
6146
|
type: 'invalidation',
|
|
5812
6147
|
url,
|
|
5813
6148
|
otherTab: false,
|
|
6149
|
+
...(tag && { tag }),
|
|
5814
6150
|
});
|
|
5815
6151
|
// The resolved URL is what travels: the other tabs are the same client, so they would
|
|
5816
6152
|
// resolve it identically, and a message that needs no interpretation cannot drift.
|
|
5817
6153
|
if (invalidation?.otherTabs ?? true)
|
|
5818
|
-
sync?.postInvalidation(url);
|
|
6154
|
+
sync?.postInvalidation(url, tag);
|
|
5819
6155
|
},
|
|
5820
6156
|
clearPersistedQueries: () => persistenceEngine?.clear() ?? Promise.resolve(),
|
|
5821
6157
|
whenPersistenceReady: persistenceEngine?.whenReady ?? Promise.resolve(),
|
|
@@ -6034,7 +6370,12 @@ const createSecureExecuteFactory = (options) => {
|
|
|
6034
6370
|
throw invalidStateInsideSecureExecuteFactory();
|
|
6035
6371
|
}
|
|
6036
6372
|
};
|
|
6037
|
-
const untrackedExec = (executeArgs) => untracked(() =>
|
|
6373
|
+
const untrackedExec = (executeArgs) => untracked(() => {
|
|
6374
|
+
if (skipParkedExecution(options.state, executeArgs?.args ?? options.state.args(), options.route))
|
|
6375
|
+
return false;
|
|
6376
|
+
exec(executeArgs);
|
|
6377
|
+
return true;
|
|
6378
|
+
});
|
|
6038
6379
|
untrackedExec['reset'] = () => untracked(reset);
|
|
6039
6380
|
untrackedExec['abort'] = () => aborter.abort(cancelPending);
|
|
6040
6381
|
untrackedExec['aborted$'] = aborter.aborted$;
|
|
@@ -6048,6 +6389,7 @@ const createSecureExecuteFn = (executeOptions) => {
|
|
|
6048
6389
|
authProvider,
|
|
6049
6390
|
deps: executeOptions.deps,
|
|
6050
6391
|
state: executeOptions.state,
|
|
6392
|
+
route: executeOptions.creatorInternals.route,
|
|
6051
6393
|
autoExecutes: shouldAutoExecuteQuery(executeOptions.creatorInternals.method) && !executeOptions.queryConfig.onlyManualExecution,
|
|
6052
6394
|
transformAuthAndExec: (executeArgs, executeState) => {
|
|
6053
6395
|
queryExecute({
|
|
@@ -6110,10 +6452,189 @@ const createPatchQuery = /* @__PURE__ */ createQueryTemplate('PATCH');
|
|
|
6110
6452
|
/** A query creator that creates a secure PATCH query (requires authentication) */
|
|
6111
6453
|
const createSecurePatchQuery = /* @__PURE__ */ createSecureQueryTemplate('PATCH');
|
|
6112
6454
|
|
|
6455
|
+
/**
|
|
6456
|
+
* Groups a fixed set of queries of which the one executed last wins. The group fits `etQueryButton` and
|
|
6457
|
+
* `et-query-error`'s `[query]`, which retries the member that ran last. Call it in an injection context.
|
|
6458
|
+
*
|
|
6459
|
+
* @example
|
|
6460
|
+
* readonly actions = createQueryGroup({
|
|
6461
|
+
* accept: postAcceptInvitation(withArgs(() => ({ pathParams: { id: this.id() } }))),
|
|
6462
|
+
* decline: postDeclineInvitation(withArgs(() => ({ pathParams: { id: this.id() } }))),
|
|
6463
|
+
* });
|
|
6464
|
+
*
|
|
6465
|
+
* accept() {
|
|
6466
|
+
* this.actions.members.accept.execute();
|
|
6467
|
+
* }
|
|
6468
|
+
*/
|
|
6469
|
+
const createQueryGroup = (members) => {
|
|
6470
|
+
const executions = new Subject();
|
|
6471
|
+
const latestMember = signal(null, /* @ts-ignore */
|
|
6472
|
+
...(ngDevMode ? [{ debugName: "latestMember" }] : /* istanbul ignore next */ []));
|
|
6473
|
+
injectInQueryContext(DestroyRef, 'query group').onDestroy(() => executions.complete());
|
|
6474
|
+
const wrap = (key, query) => {
|
|
6475
|
+
const member = {
|
|
6476
|
+
key,
|
|
6477
|
+
query,
|
|
6478
|
+
execute: (executeArgs) => {
|
|
6479
|
+
const ran = query.execute(executeArgs);
|
|
6480
|
+
if (ran === false)
|
|
6481
|
+
return false;
|
|
6482
|
+
latestMember.set(member);
|
|
6483
|
+
executions.next(member);
|
|
6484
|
+
return true;
|
|
6485
|
+
},
|
|
6486
|
+
};
|
|
6487
|
+
return { ...query, execute: member.execute };
|
|
6488
|
+
};
|
|
6489
|
+
const wrappedMembers = Object.fromEntries(Object.entries(members).map(([key, query]) => [key, wrap(key, query)]));
|
|
6490
|
+
const queries = Object.values(members);
|
|
6491
|
+
const loading = computed(() => latestMember()?.query.loading() ??
|
|
6492
|
+
queries.map((query) => query.loading()).find((state) => state !== null) ??
|
|
6493
|
+
null, /* @ts-ignore */
|
|
6494
|
+
...(ngDevMode ? [{ debugName: "loading" }] : /* istanbul ignore next */ []));
|
|
6495
|
+
const error = computed(() => latestMember()?.query.error() ?? null, /* @ts-ignore */
|
|
6496
|
+
...(ngDevMode ? [{ debugName: "error" }] : /* istanbul ignore next */ []));
|
|
6497
|
+
const latest = computed(() => {
|
|
6498
|
+
const member = latestMember();
|
|
6499
|
+
return member ? { key: member.key, response: member.query.response() } : null;
|
|
6500
|
+
}, /* @ts-ignore */
|
|
6501
|
+
...(ngDevMode ? [{ debugName: "latest" }] : /* istanbul ignore next */ []));
|
|
6502
|
+
const execute = (executeArgs) => untracked(latestMember)?.execute(executeArgs);
|
|
6503
|
+
const succeeded$ = executions.pipe(mergeMap(({ key, query }) => {
|
|
6504
|
+
const snapshot = query.createSnapshot();
|
|
6505
|
+
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 })));
|
|
6506
|
+
}), share());
|
|
6507
|
+
return { members: wrappedMembers, loading, error, latest, execute, succeeded$ };
|
|
6508
|
+
};
|
|
6509
|
+
|
|
6510
|
+
const optimisticLayers = /* @__PURE__ */ new WeakMap();
|
|
6511
|
+
const liveLayer = (request) => {
|
|
6512
|
+
const layer = optimisticLayers.get(request);
|
|
6513
|
+
if (layer && layer.written === request.response())
|
|
6514
|
+
return layer;
|
|
6515
|
+
optimisticLayers.delete(request);
|
|
6516
|
+
return null;
|
|
6517
|
+
};
|
|
6518
|
+
const rewrite = (request, layer) => {
|
|
6519
|
+
layer.written = layer.ops.reduce((current, op) => op.apply(current) ?? current, layer.base);
|
|
6520
|
+
request.subtle.setResponse(layer.written);
|
|
6521
|
+
if (layer.ops.every((op) => op.settled))
|
|
6522
|
+
optimisticLayers.delete(request);
|
|
6523
|
+
};
|
|
6524
|
+
const applyOp$1 = (request, op) => {
|
|
6525
|
+
const current = request.response();
|
|
6526
|
+
if (current === null)
|
|
6527
|
+
return false;
|
|
6528
|
+
const layer = liveLayer(request) ?? { base: current, written: current, ops: [] };
|
|
6529
|
+
const next = op.apply(layer.written);
|
|
6530
|
+
if (next === null)
|
|
6531
|
+
return false;
|
|
6532
|
+
layer.ops.push(op);
|
|
6533
|
+
layer.written = next;
|
|
6534
|
+
optimisticLayers.set(request, layer);
|
|
6535
|
+
request.subtle.setResponse(next);
|
|
6536
|
+
return true;
|
|
6537
|
+
};
|
|
6538
|
+
const settleOp = (request, op, confirmed) => {
|
|
6539
|
+
const layer = liveLayer(request);
|
|
6540
|
+
const index = layer?.ops.indexOf(op) ?? -1;
|
|
6541
|
+
if (!layer || index === -1)
|
|
6542
|
+
return;
|
|
6543
|
+
if (confirmed) {
|
|
6544
|
+
op.apply = confirmed;
|
|
6545
|
+
op.settled = true;
|
|
6546
|
+
}
|
|
6547
|
+
else {
|
|
6548
|
+
layer.ops.splice(index, 1);
|
|
6549
|
+
}
|
|
6550
|
+
rewrite(request, layer);
|
|
6551
|
+
};
|
|
6552
|
+
const findTargetRequests = (deps, targets) => {
|
|
6553
|
+
const filters = targets.map((target) => createQueryInvalidationFilter('url' in target
|
|
6554
|
+
? { url: resolveInvalidationUrl(deps.client.baseUrl, target.url) }
|
|
6555
|
+
: { url: null, tag: target.tag }));
|
|
6556
|
+
return deps.client.repository.subtle
|
|
6557
|
+
.cacheEntries()
|
|
6558
|
+
.filter((entry) => entry.isRefreshable && filters.some((filter) => filter?.(entry.request, entry.tags)))
|
|
6559
|
+
.map((entry) => entry.request);
|
|
6560
|
+
};
|
|
6561
|
+
/**
|
|
6562
|
+
* Writes the expected result of a mutation into the cached responses of the reads `target` matches before
|
|
6563
|
+
* the request leaves, and rolls those writes back if it fails or is cancelled. Pair it with the creator's
|
|
6564
|
+
* `invalidates`, whose refetch corrects a wrong guess. The writes stay in this tab; the invalidation reaches
|
|
6565
|
+
* the others.
|
|
6566
|
+
*
|
|
6567
|
+
* A rollback leaves an entry alone that a refetch or another tab wrote in the meantime; other optimistic
|
|
6568
|
+
* updates of the entry still pending are re-applied on top of what it held before.
|
|
6569
|
+
*
|
|
6570
|
+
* @example
|
|
6571
|
+
* patchOpportunityPerson(
|
|
6572
|
+
* withArgs(() => ({ pathParams: { uuid, peopleUuid }, body })),
|
|
6573
|
+
* withOptimisticUpdate({
|
|
6574
|
+
* read: getOpportunity,
|
|
6575
|
+
* target: ({ args }) => ({ tag: `opportunity:${args.pathParams.uuid}` }),
|
|
6576
|
+
* update: ({ current, args }) => ({ ...current, people: toggle(current.people, args.pathParams.peopleUuid) }),
|
|
6577
|
+
* }),
|
|
6578
|
+
* );
|
|
6579
|
+
*
|
|
6580
|
+
* @throws If the query is a read (e.g. a GET request)
|
|
6581
|
+
*/
|
|
6582
|
+
const withOptimisticUpdate = (options) => {
|
|
6583
|
+
const update = options.update;
|
|
6584
|
+
return createQueryFeature({
|
|
6585
|
+
type: QueryFeatureType.WITH_OPTIMISTIC_UPDATE,
|
|
6586
|
+
devtools: () => queryDevtoolsFnDetail(options.update, 'update'),
|
|
6587
|
+
fn: (context) => {
|
|
6588
|
+
if (context.flags.shouldAutoExecuteMethod)
|
|
6589
|
+
throw withOptimisticUpdateUsedOnRead(context.flags.method);
|
|
6590
|
+
context.state.subtle.beforeExecute.set((executeArgs) => {
|
|
6591
|
+
const args = executeArgs ?? {};
|
|
6592
|
+
const target = options.target({ args });
|
|
6593
|
+
const targets = Array.isArray(target) ? target : [target];
|
|
6594
|
+
const optimistic = (current) => update({ current, args });
|
|
6595
|
+
const applied = findTargetRequests(context.deps, targets)
|
|
6596
|
+
.map((request) => ({ request, op: { apply: optimistic, settled: false } }))
|
|
6597
|
+
.filter(({ request, op }) => applyOp$1(request, op));
|
|
6598
|
+
if (!applied.length)
|
|
6599
|
+
return null;
|
|
6600
|
+
const settle = (confirmed) => {
|
|
6601
|
+
for (const { request, op } of applied)
|
|
6602
|
+
settleOp(request, op, confirmed);
|
|
6603
|
+
};
|
|
6604
|
+
return (request) => {
|
|
6605
|
+
if (!request?.loading())
|
|
6606
|
+
return settle(null);
|
|
6607
|
+
let settled = false;
|
|
6608
|
+
const subscription = request.events$.subscribe({
|
|
6609
|
+
next: (event) => {
|
|
6610
|
+
if (event.type === HttpEventType.Response) {
|
|
6611
|
+
const response = context.state.subtle.request() === request ? context.state.response() : null;
|
|
6612
|
+
settled = true;
|
|
6613
|
+
settle((current) => update({ current, args, response }) ?? optimistic(current));
|
|
6614
|
+
}
|
|
6615
|
+
else if (event.type === 'error' || event.type === 'cancel') {
|
|
6616
|
+
settled = true;
|
|
6617
|
+
settle(null);
|
|
6618
|
+
}
|
|
6619
|
+
if (settled)
|
|
6620
|
+
subscription.unsubscribe();
|
|
6621
|
+
},
|
|
6622
|
+
complete: () => {
|
|
6623
|
+
if (!settled)
|
|
6624
|
+
settle(null);
|
|
6625
|
+
},
|
|
6626
|
+
});
|
|
6627
|
+
};
|
|
6628
|
+
});
|
|
6629
|
+
},
|
|
6630
|
+
});
|
|
6631
|
+
};
|
|
6632
|
+
|
|
6113
6633
|
const isAbortedSnapshot = (snapshot) => snapshot.latestHttpEvent()?.type === 'cancel' && snapshot.error() === null;
|
|
6114
6634
|
const executeAndSettle$ = (query, executeArgs, emitAborted) => defer(() => {
|
|
6115
|
-
query.execute(executeArgs);
|
|
6116
|
-
|
|
6635
|
+
const ran = query.execute(executeArgs);
|
|
6636
|
+
// `false` means a parked query dropped the call: nothing runs, so a live snapshot would never settle.
|
|
6637
|
+
const snapshot = ran === false ? createSkippedSnapshot(query) : query.createSnapshot();
|
|
6117
6638
|
return snapshot.isAlive.asObservable().pipe(filter((isAlive) => !isAlive), take(1),
|
|
6118
6639
|
// The stream completes without emitting when the query's injector is torn down mid-execution.
|
|
6119
6640
|
defaultIfEmpty(false), map(() => snapshot), filter((settled) => emitAborted || !untracked(() => isAbortedSnapshot(settled))), finalize(() => untracked(() => {
|
|
@@ -6128,7 +6649,7 @@ const executeAndSettle$ = (query, executeArgs, emitAborted) => defer(() => {
|
|
|
6128
6649
|
*
|
|
6129
6650
|
* Unsubscribing before it settles aborts the execution like `query.abort()`, the way unsubscribing
|
|
6130
6651
|
* from `HttpClient` cancels its request. An execution stopped by `query.abort()` completes the stream
|
|
6131
|
-
* without a value.
|
|
6652
|
+
* without a value, and so does a call a parked query (a `withArgs` source returning `null`) drops.
|
|
6132
6653
|
*
|
|
6133
6654
|
* A cancelled execution settles too - the entry was evicted, unbound by a logout, or the scope that
|
|
6134
6655
|
* owns the query was destroyed. The snapshot then reports the execution as a failure whose error
|
|
@@ -6140,8 +6661,8 @@ const executeUntilSettled$ = (query, executeArgs) => executeAndSettle$(query, ex
|
|
|
6140
6661
|
* signal-forms `submit()` action mapping server violations onto the form via
|
|
6141
6662
|
* `mapViolationsToFormErrors`. Executes immediately. Prefer {@link executeUntilSettled$} in RxJS code.
|
|
6142
6663
|
*
|
|
6143
|
-
* An execution stopped by `query.abort()
|
|
6144
|
-
* `null` `executionState()` and `latestHttpEvent()` `{ type: 'cancel' }`.
|
|
6664
|
+
* An execution stopped by `query.abort()`, or dropped by a parked query, resolves with a snapshot that
|
|
6665
|
+
* has no response, no error, a `null` `executionState()` and `latestHttpEvent()` `{ type: 'cancel' }`.
|
|
6145
6666
|
*/
|
|
6146
6667
|
const executeUntilSettled = (query, executeArgs) => firstValueFrom(executeAndSettle$(query, executeArgs, true));
|
|
6147
6668
|
|
|
@@ -6575,6 +7096,17 @@ const defaultExtractTokens = (response) => {
|
|
|
6575
7096
|
}
|
|
6576
7097
|
return { accessToken: response['accessToken'], refreshToken: response['refreshToken'] };
|
|
6577
7098
|
};
|
|
7099
|
+
const subjectOf = (token) => {
|
|
7100
|
+
const payload = decryptBearer$1(token);
|
|
7101
|
+
return typeof payload?.sub === 'string' || typeof payload?.sub === 'number' ? payload.sub : null;
|
|
7102
|
+
};
|
|
7103
|
+
const isDifferentUser = (options) => {
|
|
7104
|
+
const previous = subjectOf(options.previous);
|
|
7105
|
+
const next = subjectOf(options.next);
|
|
7106
|
+
if (previous !== null && next !== null)
|
|
7107
|
+
return previous !== next;
|
|
7108
|
+
return options.source === 'session';
|
|
7109
|
+
};
|
|
6578
7110
|
const deriveExecutionStateType = (builder, triggeredBy) => {
|
|
6579
7111
|
if (triggeredBy === 'persistent-auth')
|
|
6580
7112
|
return 'autoLogin';
|
|
@@ -6623,7 +7155,7 @@ const setupBearerQueryRegistry = (builders, context) => {
|
|
|
6623
7155
|
const response = outcome.response;
|
|
6624
7156
|
try {
|
|
6625
7157
|
const tokens = extractTokens(response);
|
|
6626
|
-
applyTokens(tokens.accessToken, tokens.refreshToken);
|
|
7158
|
+
applyTokens(tokens.accessToken, tokens.refreshToken, type === 'tokenRefresh' ? 'rotation' : 'session');
|
|
6627
7159
|
executionState.set({ type, state: 'success', response });
|
|
6628
7160
|
}
|
|
6629
7161
|
catch (extractError) {
|
|
@@ -6749,16 +7281,18 @@ const createBearerAuthProviderImpl = (config) => {
|
|
|
6749
7281
|
const refreshToken = signal(null, /* @ts-ignore */
|
|
6750
7282
|
...(ngDevMode ? [{ debugName: "refreshToken" }] : /* istanbul ignore next */ []));
|
|
6751
7283
|
const afterTokenRefresh$ = new Subject();
|
|
7284
|
+
const expiresInPropertyName = signal('exp', /* @ts-ignore */
|
|
7285
|
+
...(ngDevMode ? [{ debugName: "expiresInPropertyName" }] : /* istanbul ignore next */ []));
|
|
6752
7286
|
const bearerData = computed(() => {
|
|
6753
7287
|
const token = accessToken();
|
|
6754
7288
|
if (!token)
|
|
6755
7289
|
return null;
|
|
6756
7290
|
try {
|
|
6757
|
-
const decoded = config.bearerDecryptFn
|
|
7291
|
+
const decoded = config.bearerDecryptFn ? config.bearerDecryptFn(token) : decryptBearer$1(token);
|
|
6758
7292
|
return patchQueryDevtoolsTokenPayload({
|
|
6759
7293
|
payload: decoded,
|
|
6760
7294
|
providerName: config.name,
|
|
6761
|
-
expiresInPropertyName:
|
|
7295
|
+
expiresInPropertyName: expiresInPropertyName(),
|
|
6762
7296
|
});
|
|
6763
7297
|
}
|
|
6764
7298
|
catch (error) {
|
|
@@ -6783,11 +7317,22 @@ const createBearerAuthProviderImpl = (config) => {
|
|
|
6783
7317
|
...(ngDevMode ? [{ debugName: "sessionEndCause" }] : /* istanbul ignore next */ []));
|
|
6784
7318
|
const sessionEnd = signal(null, /* @ts-ignore */
|
|
6785
7319
|
...(ngDevMode ? [{ debugName: "sessionEnd" }] : /* istanbul ignore next */ []));
|
|
6786
|
-
const
|
|
7320
|
+
const sessionId = signal(0, /* @ts-ignore */
|
|
7321
|
+
...(ngDevMode ? [{ debugName: "sessionId" }] : /* istanbul ignore next */ []));
|
|
7322
|
+
const applyTokens = (access, refresh, source = 'rotation') => {
|
|
7323
|
+
const previousAccess = untracked(accessToken);
|
|
7324
|
+
const startsSession = previousAccess === null || isDifferentUser({ previous: previousAccess, next: access, source });
|
|
6787
7325
|
accessToken.set(access);
|
|
6788
7326
|
refreshToken.set(refresh);
|
|
6789
7327
|
sessionStatus.set('authenticated');
|
|
6790
7328
|
sessionEndCause.set(null);
|
|
7329
|
+
if (startsSession)
|
|
7330
|
+
sessionId.update((id) => id + 1);
|
|
7331
|
+
// After the new tokens are in force: unbindAllSecure re-arms every auto-executing secure query, which
|
|
7332
|
+
// would otherwise run again on the previous user's token.
|
|
7333
|
+
if (previousAccess !== null && startsSession) {
|
|
7334
|
+
untracked(() => queryClient.repository.unbindAllSecure());
|
|
7335
|
+
}
|
|
6791
7336
|
// Emit on the next microtask so that synchronous reactive work triggered by applying
|
|
6792
7337
|
// the tokens (signals/effects) has a chance to settle before subscribers react.
|
|
6793
7338
|
// This avoids rare races where a subscriber reacting to the emission would run
|
|
@@ -6802,7 +7347,7 @@ const createBearerAuthProviderImpl = (config) => {
|
|
|
6802
7347
|
// auto-login or a refresh still out with an older token can only report a failure that no longer
|
|
6803
7348
|
// means anything - or apply the pair it comes back with over this one.
|
|
6804
7349
|
invalidateTokenIssuingExecutions();
|
|
6805
|
-
applyTokens(access, refresh);
|
|
7350
|
+
applyTokens(access, refresh, 'session');
|
|
6806
7351
|
executionState.set({ type: 'tokenSeed', state: 'success' });
|
|
6807
7352
|
};
|
|
6808
7353
|
const { queries, hasTokenIssuingExecutionInFlight, invalidateTokenIssuingExecutions } = setupBearerQueryRegistry(config.queries, {
|
|
@@ -6891,6 +7436,7 @@ const createBearerAuthProviderImpl = (config) => {
|
|
|
6891
7436
|
reportAccessTokenExpiry: (isExpired) => {
|
|
6892
7437
|
readAccessTokenExpiry = isExpired;
|
|
6893
7438
|
},
|
|
7439
|
+
reportExpiresInPropertyName: (claim) => expiresInPropertyName.set(claim),
|
|
6894
7440
|
};
|
|
6895
7441
|
for (const builder of config.queries) {
|
|
6896
7442
|
builder.setup?.(querySetupContext);
|
|
@@ -6949,6 +7495,7 @@ const createBearerAuthProviderImpl = (config) => {
|
|
|
6949
7495
|
executionState: executionState.asReadonly(),
|
|
6950
7496
|
sessionStatus: sessionStatus.asReadonly(),
|
|
6951
7497
|
sessionEndCause: sessionEndCause.asReadonly(),
|
|
7498
|
+
sessionId: sessionId.asReadonly(),
|
|
6952
7499
|
session: computed(() => ({
|
|
6953
7500
|
status: sessionStatus(),
|
|
6954
7501
|
endCause: sessionEndCause(),
|
|
@@ -7081,6 +7628,7 @@ const withRefreshQuery = (key, config) => {
|
|
|
7081
7628
|
const buildArgs = (refreshToken) => (config.buildArgs?.(refreshToken) ?? { body: { token: refreshToken } });
|
|
7082
7629
|
const setup = (context) => {
|
|
7083
7630
|
const expiresInPropertyName = config.expiresInPropertyName ?? 'exp';
|
|
7631
|
+
context.reportExpiresInPropertyName(expiresInPropertyName);
|
|
7084
7632
|
const minRefreshInterval = config.minRefreshInterval ?? 30000; // 30 seconds default
|
|
7085
7633
|
const refreshIfExpired = config.refreshIfExpired ?? true;
|
|
7086
7634
|
const destroyRef = inject(DestroyRef);
|
|
@@ -7265,48 +7813,51 @@ const withRefreshQuery = (key, config) => {
|
|
|
7265
7813
|
case 'notLeader':
|
|
7266
7814
|
return Math.min(Math.max(msUntilAccessTokenIsStale() ?? minRefreshInterval, rescheduleDelayMs), maxTimerDelayMs);
|
|
7267
7815
|
}
|
|
7268
|
-
};
|
|
7269
|
-
|
|
7270
|
-
|
|
7271
|
-
|
|
7272
|
-
|
|
7273
|
-
|
|
7816
|
+
};
|
|
7817
|
+
let expiryWarnedFor = null;
|
|
7818
|
+
const tokenTimes = (token) => {
|
|
7819
|
+
let decoded;
|
|
7820
|
+
try {
|
|
7821
|
+
decoded = context.bearerDecryptFn ? context.bearerDecryptFn(token) : decryptBearer$1(token);
|
|
7822
|
+
}
|
|
7823
|
+
catch {
|
|
7824
|
+
decoded = null;
|
|
7825
|
+
}
|
|
7826
|
+
const times = readAccessTokenTimes({
|
|
7827
|
+
token,
|
|
7828
|
+
decoded,
|
|
7829
|
+
claim: expiresInPropertyName,
|
|
7830
|
+
providerName: context.name,
|
|
7831
|
+
});
|
|
7832
|
+
if (!times && isDevMode() && expiryWarnedFor !== token) {
|
|
7833
|
+
expiryWarnedFor = token;
|
|
7834
|
+
console.warn(authAccessTokenExpiryUnreadable(expiresInPropertyName).message);
|
|
7274
7835
|
}
|
|
7275
|
-
|
|
7276
|
-
const minBufferMs = config.refreshStrategy?.minBufferMs ?? 60000; // 1 minute
|
|
7277
|
-
const maxBufferMs = config.refreshStrategy?.maxBufferMs ?? 600000; // 10 minutes
|
|
7278
|
-
const calculatedBuffer = tokenLifetimeMs * (1 - percentage);
|
|
7279
|
-
return Math.max(minBufferMs, Math.min(maxBufferMs, calculatedBuffer));
|
|
7836
|
+
return times;
|
|
7280
7837
|
};
|
|
7281
7838
|
/** When this token expires, as a timestamp, or `null` if the expiry cannot be read off it. */
|
|
7282
|
-
const tokenExpiresAt = (token) =>
|
|
7283
|
-
|
|
7284
|
-
|
|
7285
|
-
|
|
7286
|
-
|
|
7287
|
-
|
|
7288
|
-
|
|
7289
|
-
|
|
7290
|
-
|
|
7291
|
-
if (typeof expiresIn !== 'number') {
|
|
7292
|
-
if (isDevMode()) {
|
|
7293
|
-
console.warn(`Token does not contain valid ${expiresInPropertyName} property for auto-refresh`);
|
|
7294
|
-
}
|
|
7295
|
-
return null;
|
|
7296
|
-
}
|
|
7297
|
-
return expiresIn * 1000;
|
|
7839
|
+
const tokenExpiresAt = (token) => tokenTimes(token)?.expiresAtMs ?? null;
|
|
7840
|
+
let measuredLifetime = null;
|
|
7841
|
+
/**
|
|
7842
|
+
* The whole lifetime of a token, not what is left of it: a schedule recomputed later (the tab came
|
|
7843
|
+
* back to the foreground) must land at the same instant as the first one.
|
|
7844
|
+
*/
|
|
7845
|
+
const tokenLifetimeMs = (token, times) => {
|
|
7846
|
+
if (times.issuedAtMs !== null && times.expiresAtMs > times.issuedAtMs) {
|
|
7847
|
+
return times.expiresAtMs - times.issuedAtMs;
|
|
7298
7848
|
}
|
|
7299
|
-
|
|
7300
|
-
|
|
7849
|
+
if (measuredLifetime?.token !== token) {
|
|
7850
|
+
measuredLifetime = { token, lifetimeMs: times.expiresAtMs - Date.now() };
|
|
7301
7851
|
}
|
|
7852
|
+
return measuredLifetime.lifetimeMs;
|
|
7302
7853
|
};
|
|
7303
7854
|
/** When this access token is due to be refreshed, or `null` if it cannot be scheduled at all. */
|
|
7304
7855
|
const scheduledRefreshDelay = (token) => {
|
|
7305
|
-
const
|
|
7306
|
-
if (
|
|
7856
|
+
const times = tokenTimes(token);
|
|
7857
|
+
if (times === null)
|
|
7307
7858
|
return null;
|
|
7308
|
-
const
|
|
7309
|
-
if (
|
|
7859
|
+
const remainingMs = times.expiresAtMs - Date.now();
|
|
7860
|
+
if (remainingMs <= 0) {
|
|
7310
7861
|
if (!refreshIfExpired)
|
|
7311
7862
|
return null;
|
|
7312
7863
|
if (isDevMode()) {
|
|
@@ -7314,7 +7865,7 @@ const withRefreshQuery = (key, config) => {
|
|
|
7314
7865
|
}
|
|
7315
7866
|
return 0;
|
|
7316
7867
|
}
|
|
7317
|
-
return Math.max(
|
|
7868
|
+
return Math.max(remainingMs - refreshBufferMs(config.refreshStrategy, tokenLifetimeMs(token, times)), 0);
|
|
7318
7869
|
};
|
|
7319
7870
|
/**
|
|
7320
7871
|
* How long this tab can still work with the access token it holds, or `null` without a readable
|
|
@@ -7915,40 +8466,59 @@ const buildPersistentAuthFeature = (config, context) => {
|
|
|
7915
8466
|
*/
|
|
7916
8467
|
const createPersistentAuthFeature = (config, context) => runInInjectionContext(context.injector, () => buildPersistentAuthFeature(config, context));
|
|
7917
8468
|
|
|
8469
|
+
const RENEWAL_GRACE_MS = 10_000;
|
|
7918
8470
|
const withTokenExpirationWarning = (config = {}) => {
|
|
7919
8471
|
return (context) => {
|
|
7920
8472
|
const warningThreshold = config.warningThreshold ?? 5 * 60 * 1000;
|
|
7921
8473
|
const checkInterval = config.checkInterval ?? 1000;
|
|
7922
8474
|
const refreshBuilder = context.queryBuilders.find((builder) => builder._type === 'tokenRefreshQuery');
|
|
7923
|
-
const
|
|
7924
|
-
|
|
7925
|
-
|
|
7926
|
-
const
|
|
7927
|
-
const
|
|
7928
|
-
if (!
|
|
8475
|
+
const refreshConfig = refreshBuilder?._type === 'tokenRefreshQuery' ? refreshBuilder.config : null;
|
|
8476
|
+
const expiresInPropertyName = config.expiresInPropertyName ?? refreshConfig?.expiresInPropertyName ?? 'exp';
|
|
8477
|
+
let measuredLifetime = null;
|
|
8478
|
+
const tokenTimes = computed(() => {
|
|
8479
|
+
const token = context.accessToken();
|
|
8480
|
+
if (!token)
|
|
7929
8481
|
return null;
|
|
7930
|
-
const
|
|
7931
|
-
|
|
8482
|
+
const times = readAccessTokenTimes({
|
|
8483
|
+
token,
|
|
8484
|
+
decoded: context.bearerData(),
|
|
8485
|
+
claim: expiresInPropertyName,
|
|
8486
|
+
providerName: context.name,
|
|
8487
|
+
});
|
|
8488
|
+
if (!times)
|
|
7932
8489
|
return null;
|
|
7933
|
-
|
|
8490
|
+
if (times.issuedAtMs !== null && times.expiresAtMs > times.issuedAtMs) {
|
|
8491
|
+
return { ...times, lifetimeMs: times.expiresAtMs - times.issuedAtMs };
|
|
8492
|
+
}
|
|
8493
|
+
if (measuredLifetime?.token !== token) {
|
|
8494
|
+
measuredLifetime = { token, lifetimeMs: times.expiresAtMs - Date.now() };
|
|
8495
|
+
}
|
|
8496
|
+
return { ...times, lifetimeMs: measuredLifetime.lifetimeMs };
|
|
8497
|
+
}, /* @ts-ignore */
|
|
8498
|
+
...(ngDevMode ? [{ debugName: "tokenTimes" }] : /* istanbul ignore next */ []));
|
|
8499
|
+
const expiresAt = computed(() => {
|
|
8500
|
+
const times = tokenTimes();
|
|
8501
|
+
return times ? new Date(times.expiresAtMs) : null;
|
|
7934
8502
|
}, /* @ts-ignore */
|
|
7935
8503
|
...(ngDevMode ? [{ debugName: "expiresAt" }] : /* istanbul ignore next */ []));
|
|
7936
|
-
const expiresIn
|
|
8504
|
+
const expiresIn = toSignal(toObservable(tokenTimes).pipe(switchMap((times) => times
|
|
7937
8505
|
? timer(0, checkInterval).pipe(map(() => {
|
|
7938
|
-
const
|
|
7939
|
-
if (!expiry)
|
|
7940
|
-
return null;
|
|
7941
|
-
const msUntilExpiry = expiry.getTime() - Date.now();
|
|
8506
|
+
const msUntilExpiry = times.expiresAtMs - Date.now();
|
|
7942
8507
|
return msUntilExpiry > 0 ? msUntilExpiry : null;
|
|
7943
8508
|
}))
|
|
7944
|
-
: of(null)));
|
|
7945
|
-
const isExpiringSoon
|
|
7946
|
-
|
|
8509
|
+
: of(null))), { initialValue: null });
|
|
8510
|
+
const isExpiringSoon = computed(() => {
|
|
8511
|
+
const msUntilExpiry = expiresIn();
|
|
8512
|
+
const times = tokenTimes();
|
|
8513
|
+
if (msUntilExpiry === null || !times || msUntilExpiry > warningThreshold)
|
|
7947
8514
|
return false;
|
|
7948
|
-
|
|
7949
|
-
|
|
7950
|
-
|
|
7951
|
-
|
|
8515
|
+
if (!refreshConfig || !context.refreshToken())
|
|
8516
|
+
return true;
|
|
8517
|
+
const bufferMs = refreshBufferMs(refreshConfig.refreshStrategy, times.lifetimeMs);
|
|
8518
|
+
const msOverdue = bufferMs - msUntilExpiry;
|
|
8519
|
+
return msOverdue >= Math.min(RENEWAL_GRACE_MS, bufferMs / 2);
|
|
8520
|
+
}, /* @ts-ignore */
|
|
8521
|
+
...(ngDevMode ? [{ debugName: "isExpiringSoon" }] : /* istanbul ignore next */ []));
|
|
7952
8522
|
const instance = {
|
|
7953
8523
|
expiresAt,
|
|
7954
8524
|
expiresIn,
|
|
@@ -8001,10 +8571,11 @@ const withTokenRevocation = (config) => {
|
|
|
8001
8571
|
...(ngDevMode ? [{ debugName: "target" }] : /* istanbul ignore next */ []));
|
|
8002
8572
|
const dropped = signal(false, /* @ts-ignore */
|
|
8003
8573
|
...(ngDevMode ? [{ debugName: "dropped" }] : /* istanbul ignore next */ []));
|
|
8004
|
-
const
|
|
8574
|
+
const watcher = createObservableSignalWatcher(context.injector);
|
|
8575
|
+
const follow = (read) => watcher.wrap(computed(() => {
|
|
8005
8576
|
const snapshot = target();
|
|
8006
8577
|
return snapshot ? read(snapshot) : null;
|
|
8007
|
-
})
|
|
8578
|
+
}));
|
|
8008
8579
|
const snapshot = {
|
|
8009
8580
|
args: follow((s) => s.args()),
|
|
8010
8581
|
response: follow((s) => s.response()),
|
|
@@ -8015,7 +8586,7 @@ const withTokenRevocation = (config) => {
|
|
|
8015
8586
|
triggeredBy: follow((s) => s.triggeredBy()),
|
|
8016
8587
|
id: follow((s) => s.id()),
|
|
8017
8588
|
executionState: follow((s) => s.executionState()),
|
|
8018
|
-
isAlive:
|
|
8589
|
+
isAlive: watcher.wrap(computed(() => target()?.isAlive() ?? !(dropped() || queueDropped()))),
|
|
8019
8590
|
};
|
|
8020
8591
|
const queued = {
|
|
8021
8592
|
tokens,
|
|
@@ -8152,19 +8723,18 @@ const buildTrackingFeature = (context, config) => {
|
|
|
8152
8723
|
}
|
|
8153
8724
|
}
|
|
8154
8725
|
};
|
|
8155
|
-
|
|
8156
|
-
|
|
8157
|
-
|
|
8158
|
-
|
|
8159
|
-
|
|
8160
|
-
|
|
8161
|
-
|
|
8162
|
-
|
|
8163
|
-
|
|
8164
|
-
|
|
8165
|
-
|
|
8166
|
-
|
|
8167
|
-
};
|
|
8726
|
+
// Handlers run outside the reactive context of the effects that fire them, so one that ends the
|
|
8727
|
+
// session or creates a query does not throw NG0602.
|
|
8728
|
+
const fireHandlers = (event, data) => untracked(() => {
|
|
8729
|
+
handlers.get(event)?.forEach((handler) => {
|
|
8730
|
+
try {
|
|
8731
|
+
handler(data);
|
|
8732
|
+
}
|
|
8733
|
+
catch (error) {
|
|
8734
|
+
console.error(`Error in tracking event handler for "${event}":`, error);
|
|
8735
|
+
}
|
|
8736
|
+
});
|
|
8737
|
+
});
|
|
8168
8738
|
const pendingForwardedMessages = [];
|
|
8169
8739
|
const forwardingChannel = context.leaderElection && typeof BroadcastChannel !== 'undefined'
|
|
8170
8740
|
? new BroadcastChannel(`ethlete-auth-tracking:${context.name}`)
|
|
@@ -8298,6 +8868,10 @@ const buildTrackingFeature = (context, config) => {
|
|
|
8298
8868
|
*/
|
|
8299
8869
|
const createTrackingFeature = (context, config) => runInInjectionContext(context.injector, () => buildTrackingFeature(context, config));
|
|
8300
8870
|
|
|
8871
|
+
// Generated from package.json by tools/scripts/generate-version.js - do not edit.
|
|
8872
|
+
/** The version of `@ethlete/query` this build was cut from. */
|
|
8873
|
+
const QUERY_VERSION = '6.0.0-next.56';
|
|
8874
|
+
|
|
8301
8875
|
const ethlete = {};
|
|
8302
8876
|
let app = null;
|
|
8303
8877
|
/** What `window.ethlete` exposes - the same object {@link queryDevtoolsAbout} returns. */
|
|
@@ -9321,12 +9895,23 @@ const sanitizeSession = (value) => {
|
|
|
9321
9895
|
savedAt: typeof session.savedAt === 'number' ? session.savedAt : 0,
|
|
9322
9896
|
};
|
|
9323
9897
|
};
|
|
9898
|
+
const isLocalAccount = (value) => {
|
|
9899
|
+
const account = value;
|
|
9900
|
+
return (typeof account === 'object' &&
|
|
9901
|
+
account !== null &&
|
|
9902
|
+
typeof account.id === 'string' &&
|
|
9903
|
+
typeof account.provider === 'string' &&
|
|
9904
|
+
typeof account.label === 'string' &&
|
|
9905
|
+
typeof account.loginQuery === 'string' &&
|
|
9906
|
+
typeof account.scope === 'string' &&
|
|
9907
|
+
(account.fields === undefined || Array.isArray(account.fields)));
|
|
9908
|
+
};
|
|
9324
9909
|
const sanitize = (value) => ({
|
|
9325
9910
|
sessions: Array.isArray(value?.sessions)
|
|
9326
9911
|
? value.sessions.map(sanitizeSession).filter((session) => session !== null)
|
|
9327
9912
|
: [],
|
|
9328
9913
|
credentials: typeof value?.credentials === 'object' && value.credentials !== null ? value.credentials : {},
|
|
9329
|
-
accounts: Array.isArray(value?.accounts) ? value.accounts : [],
|
|
9914
|
+
accounts: Array.isArray(value?.accounts) ? value.accounts.filter(isLocalAccount) : [],
|
|
9330
9915
|
});
|
|
9331
9916
|
/**
|
|
9332
9917
|
* The vault is one key every tab writes, so a tab holding a stale copy of it would drop whatever another
|
|
@@ -9998,247 +10583,40 @@ const resolveQueryDevtoolsFaultForAttempt = (target) => {
|
|
|
9998
10583
|
*/
|
|
9999
10584
|
const noteQueryFormReads = (formId, source) => {
|
|
10000
10585
|
const memoized = computed(() => source(), /* @ts-ignore */
|
|
10001
|
-
...(ngDevMode ? [{ debugName: "memoized" }] : /* istanbul ignore next */ []));
|
|
10002
|
-
// The read must be noted per read, not per recomputation of the value: the second query to build
|
|
10003
|
-
// args reads a memoized value, and a note that only fires on a recomputation never reaches its
|
|
10004
|
-
// collection window. Carrying the memo's signal node over keeps this function a real `Signal`.
|
|
10005
|
-
const read = () => {
|
|
10006
|
-
noteQueryFormRead(formId);
|
|
10007
|
-
return memoized();
|
|
10008
|
-
};
|
|
10009
|
-
return Object.assign(read, { [SIGNAL]: memoized[SIGNAL] });
|
|
10010
|
-
};
|
|
10011
|
-
/**
|
|
10012
|
-
* Records which query forms feed one query's args, so the devtools can answer "which query does this
|
|
10013
|
-
* form drive?" from the reads themselves rather than from a naming convention.
|
|
10014
|
-
* @internal
|
|
10015
|
-
*/
|
|
10016
|
-
const createQueryDevtoolsFormLinks = () => {
|
|
10017
|
-
const ids = signal([], /* @ts-ignore */
|
|
10018
|
-
...(ngDevMode ? [{ debugName: "ids" }] : /* istanbul ignore next */ []));
|
|
10019
|
-
return {
|
|
10020
|
-
ids: ids.asReadonly(),
|
|
10021
|
-
track: (build) => {
|
|
10022
|
-
const collected = collectQueryFormLinks(build);
|
|
10023
|
-
// Args are built inside a reactive computation, which may not write to a signal - `untracked`
|
|
10024
|
-
// lifts that restriction, and skipping the write unless something is new keeps the panel from
|
|
10025
|
-
// being notified on every evaluation.
|
|
10026
|
-
untracked(() => {
|
|
10027
|
-
const known = ids();
|
|
10028
|
-
const added = collected.ids.filter((id) => !known.includes(id));
|
|
10029
|
-
if (added.length)
|
|
10030
|
-
ids.set([...known, ...added]);
|
|
10031
|
-
});
|
|
10032
|
-
return collected.value;
|
|
10033
|
-
},
|
|
10034
|
-
};
|
|
10035
|
-
};
|
|
10036
|
-
|
|
10037
|
-
/**
|
|
10038
|
-
* The designed library. Which of them is **armed** is a separate store under a separate scope - see
|
|
10039
|
-
* {@link queryDevtoolsArmedMocks}.
|
|
10040
|
-
*/
|
|
10041
|
-
const STORAGE_KEY$1 = 'ethlete:query:devtools:mocks:v1';
|
|
10042
|
-
const ARMED_STORAGE_KEY = 'ethlete:query:devtools:mocks:armed:v1';
|
|
10043
|
-
const mocks = /* @__PURE__ */ signal([], /* @ts-ignore */
|
|
10044
|
-
...(ngDevMode ? [{ debugName: "mocks" }] : /* istanbul ignore next */ []));
|
|
10045
|
-
const armed = /* @__PURE__ */ signal(/* @__PURE__ */ new Set(), /* @ts-ignore */
|
|
10046
|
-
...(ngDevMode ? [{ debugName: "armed" }] : /* istanbul ignore next */ []));
|
|
10047
|
-
const armedRestored = /* @__PURE__ */ signal(false, /* @ts-ignore */
|
|
10048
|
-
...(ngDevMode ? [{ debugName: "armedRestored" }] : /* istanbul ignore next */ []));
|
|
10049
|
-
const scope$1 = () => queryDevtoolsSettings().mocks;
|
|
10050
|
-
const armedScope = () => queryDevtoolsSettings().armedMocks;
|
|
10051
|
-
/** The designed mocks, armed or not. Persisted, because losing an hour of authoring is unacceptable. */
|
|
10052
|
-
const queryDevtoolsMocks = /* @__PURE__ */ mocks.asReadonly();
|
|
10053
|
-
/**
|
|
10054
|
-
* The ids currently being served. Not kept across page loads unless the `armedMocks` setting asks for it -
|
|
10055
|
-
* an app that silently serves designed data tomorrow morning is worse than losing which mocks were armed.
|
|
10056
|
-
*/
|
|
10057
|
-
const queryDevtoolsArmedMocks = /* @__PURE__ */ armed.asReadonly();
|
|
10058
|
-
/**
|
|
10059
|
-
* Whether what is armed right now came back from a previous page load rather than being armed by hand.
|
|
10060
|
-
* The panel's armed bar says so, because mocks nobody remembers arming look exactly like a broken API.
|
|
10061
|
-
*/
|
|
10062
|
-
const queryDevtoolsArmedMocksRestored = /* @__PURE__ */ armedRestored.asReadonly();
|
|
10063
|
-
/**
|
|
10064
|
-
* The identity of a designed mock: one per client, method, route **and** declared query. The query is part
|
|
10065
|
-
* of it so `/posts?page=1` and `/posts?page=2` can hold different bodies.
|
|
10066
|
-
*/
|
|
10067
|
-
const queryDevtoolsMockId = (parts) => `${parts.clientName}|${parts.method}|${parts.pattern}${parts.query ? `?${parts.query}` : ''}`;
|
|
10068
|
-
const write = () => {
|
|
10069
|
-
if (scope$1() === 'none') {
|
|
10070
|
-
clearQueryDevtoolsStore(STORAGE_KEY$1);
|
|
10071
|
-
return;
|
|
10072
|
-
}
|
|
10073
|
-
writeQueryDevtoolsStore(scope$1(), STORAGE_KEY$1, mocks());
|
|
10074
|
-
};
|
|
10075
|
-
const writeArmed = () => {
|
|
10076
|
-
if (armedScope() === 'none') {
|
|
10077
|
-
clearQueryDevtoolsStore(ARMED_STORAGE_KEY);
|
|
10078
|
-
return;
|
|
10079
|
-
}
|
|
10080
|
-
writeQueryDevtoolsStore(armedScope(), ARMED_STORAGE_KEY, [...armed()]);
|
|
10081
|
-
};
|
|
10082
|
-
/** Every change made by hand goes through here, so none of them can leave the store or the bar behind. */
|
|
10083
|
-
const setArmed = (next) => {
|
|
10084
|
-
armed.set(next);
|
|
10085
|
-
armedRestored.set(false);
|
|
10086
|
-
writeArmed();
|
|
10087
|
-
};
|
|
10088
|
-
const isMock = (value) => {
|
|
10089
|
-
const mock = value;
|
|
10090
|
-
return (!!mock &&
|
|
10091
|
-
typeof mock.id === 'string' &&
|
|
10092
|
-
typeof mock.clientName === 'string' &&
|
|
10093
|
-
typeof mock.method === 'string' &&
|
|
10094
|
-
typeof mock.pattern === 'string' &&
|
|
10095
|
-
typeof mock.status === 'number');
|
|
10096
|
-
};
|
|
10097
|
-
/**
|
|
10098
|
-
* Reads the designed library, so a mock can be armed without authoring it again. Called by
|
|
10099
|
-
* `provideQueryDevtools()` after the settings it takes its scope from; nothing else may call it.
|
|
10100
|
-
* @internal
|
|
10101
|
-
*/
|
|
10102
|
-
const initQueryDevtoolsMocks = () => {
|
|
10103
|
-
const stored = readQueryDevtoolsStore(scope$1(), STORAGE_KEY$1);
|
|
10104
|
-
// `query` was added after the first version of the store, so an entry written before it still loads.
|
|
10105
|
-
mocks.set(Array.isArray(stored) ? stored.filter(isMock).map((mock) => ({ ...mock, query: mock.query ?? '' })) : []);
|
|
10106
|
-
const kept = readQueryDevtoolsStore(armedScope(), ARMED_STORAGE_KEY);
|
|
10107
|
-
const library = new Set(mocks().map((mock) => mock.id));
|
|
10108
|
-
// An id the library no longer holds would arm nothing, so it is dropped rather than kept waiting for a
|
|
10109
|
-
// mock that may never be designed again.
|
|
10110
|
-
const inherited = Array.isArray(kept)
|
|
10111
|
-
? kept.filter((id) => typeof id === 'string' && library.has(id))
|
|
10112
|
-
: [];
|
|
10113
|
-
armed.set(new Set(inherited));
|
|
10114
|
-
armedRestored.set(inherited.length > 0);
|
|
10115
|
-
};
|
|
10116
|
-
/**
|
|
10117
|
-
* The path of a request URL relative to the client's `baseUrl` (base path included), without its query
|
|
10118
|
-
* string - what a pattern is matched against. A URL outside `baseUrl`, or no `baseUrl` at all, falls
|
|
10119
|
-
* back to the path without its origin.
|
|
10120
|
-
*/
|
|
10121
|
-
const queryDevtoolsRequestPath = (url, baseUrl) => {
|
|
10122
|
-
const withoutQuery = url.split('?')[0] ?? '';
|
|
10123
|
-
const base = baseUrl?.replace(/\/+$/, '');
|
|
10124
|
-
if (base && withoutQuery.startsWith(base)) {
|
|
10125
|
-
const rest = withoutQuery.slice(base.length);
|
|
10126
|
-
if (!rest)
|
|
10127
|
-
return '/';
|
|
10128
|
-
if (rest.startsWith('/'))
|
|
10129
|
-
return rest;
|
|
10130
|
-
}
|
|
10131
|
-
const schemeEnd = withoutQuery.indexOf('://');
|
|
10132
|
-
if (schemeEnd === -1)
|
|
10133
|
-
return withoutQuery;
|
|
10134
|
-
const pathStart = withoutQuery.indexOf('/', schemeEnd + 3);
|
|
10135
|
-
return pathStart === -1 ? '/' : withoutQuery.slice(pathStart);
|
|
10136
|
-
};
|
|
10137
|
-
const segmentsOf$1 = (path) => path.split('/').filter(Boolean);
|
|
10138
|
-
/** The query string of a request URL, without the `?`. */
|
|
10139
|
-
const queryOf = (url) => url.split('?')[1] ?? '';
|
|
10140
|
-
/**
|
|
10141
|
-
* Whether a request carries every query parameter a mock declares. Extra parameters are ignored - a mock
|
|
10142
|
-
* says what the request must ask for, not everything it may.
|
|
10143
|
-
*/
|
|
10144
|
-
const matchesQueryDevtoolsMockQuery = (declared, url) => {
|
|
10145
|
-
if (!declared)
|
|
10146
|
-
return true;
|
|
10147
|
-
const asked = new URLSearchParams(queryOf(url));
|
|
10148
|
-
return [...new URLSearchParams(declared)].every(([key, value]) => asked.get(key) === value);
|
|
10149
|
-
};
|
|
10150
|
-
/**
|
|
10151
|
-
* Whether a request path is the route a pattern describes. A `:param` segment matches any single
|
|
10152
|
-
* non-empty segment, which is the whole point: a mock is armed for a route, not for the one URL a query
|
|
10153
|
-
* happened to build.
|
|
10154
|
-
*/
|
|
10155
|
-
const matchesQueryDevtoolsMockPattern = (pattern, path) => {
|
|
10156
|
-
const patternSegments = segmentsOf$1(pattern);
|
|
10157
|
-
const pathSegments = segmentsOf$1(path);
|
|
10158
|
-
if (patternSegments.length !== pathSegments.length)
|
|
10159
|
-
return false;
|
|
10160
|
-
return patternSegments.every((segment, index) => segment.startsWith(':') || segment === pathSegments[index]);
|
|
10161
|
-
};
|
|
10162
|
-
/**
|
|
10163
|
-
* Adds a designed mock, or replaces the one already under its id. Arming is untouched: re-designing a
|
|
10164
|
-
* body while it is being served changes what the next request gets.
|
|
10165
|
-
*/
|
|
10166
|
-
const saveQueryDevtoolsMock = (mock) => {
|
|
10167
|
-
mocks.update((current) => {
|
|
10168
|
-
const index = current.findIndex((entry) => entry.id === mock.id);
|
|
10169
|
-
if (index === -1)
|
|
10170
|
-
return [...current, mock];
|
|
10171
|
-
return current.map((entry) => (entry.id === mock.id ? mock : entry));
|
|
10172
|
-
});
|
|
10173
|
-
write();
|
|
10174
|
-
};
|
|
10175
|
-
/** Removes a designed mock, disarming it on the way out. */
|
|
10176
|
-
const deleteQueryDevtoolsMock = (id) => {
|
|
10177
|
-
mocks.update((current) => current.filter((entry) => entry.id !== id));
|
|
10178
|
-
armQueryDevtoolsMock(id, false);
|
|
10179
|
-
write();
|
|
10180
|
-
};
|
|
10181
|
-
/** Starts or stops serving one designed mock. Arming a mock that is not in the library does nothing. */
|
|
10182
|
-
const armQueryDevtoolsMock = (id, isArmed) => {
|
|
10183
|
-
if (isArmed && !mocks().some((mock) => mock.id === id))
|
|
10184
|
-
return;
|
|
10185
|
-
const next = new Set(armed());
|
|
10186
|
-
if (isArmed)
|
|
10187
|
-
next.add(id);
|
|
10188
|
-
else
|
|
10189
|
-
next.delete(id);
|
|
10190
|
-
setArmed(next);
|
|
10191
|
-
};
|
|
10192
|
-
/** Starts serving every mock in the library - the counterpart of {@link clearQueryDevtoolsArmedMocks}. */
|
|
10193
|
-
const armAllQueryDevtoolsMocks = () => setArmed(new Set(mocks().map((mock) => mock.id)));
|
|
10194
|
-
/** Stops serving every mock, leaving the library alone - the armed bar's one-click way out. */
|
|
10195
|
-
const clearQueryDevtoolsArmedMocks = () => setArmed(new Set());
|
|
10196
|
-
/**
|
|
10197
|
-
* Where the armed set is kept. Switching to a scope that keeps it stores whatever is armed right now, so
|
|
10198
|
-
* the choice reads as "keep these"; switching to `none` empties the store and leaves this page serving
|
|
10199
|
-
* exactly what it was already serving.
|
|
10200
|
-
*/
|
|
10201
|
-
const setQueryDevtoolsArmedMocksScope = (next) => {
|
|
10202
|
-
if (next === armedScope())
|
|
10203
|
-
return;
|
|
10204
|
-
// Off both stores first: whichever the previous scope was, its copy must not outlive the change.
|
|
10205
|
-
clearQueryDevtoolsStore(ARMED_STORAGE_KEY);
|
|
10206
|
-
setQueryDevtoolsSettings({ armedMocks: next });
|
|
10207
|
-
writeArmed();
|
|
10208
|
-
};
|
|
10209
|
-
/** Empties the designed library and disarms everything - the panel's "Reset devtools". */
|
|
10210
|
-
const clearQueryDevtoolsMockStore = () => {
|
|
10211
|
-
mocks.set([]);
|
|
10212
|
-
armed.set(new Set());
|
|
10213
|
-
armedRestored.set(false);
|
|
10214
|
-
clearQueryDevtoolsStore(STORAGE_KEY$1);
|
|
10215
|
-
clearQueryDevtoolsStore(ARMED_STORAGE_KEY);
|
|
10586
|
+
...(ngDevMode ? [{ debugName: "memoized" }] : /* istanbul ignore next */ []));
|
|
10587
|
+
// The read must be noted per read, not per recomputation of the value: the second query to build
|
|
10588
|
+
// args reads a memoized value, and a note that only fires on a recomputation never reaches its
|
|
10589
|
+
// collection window. Carrying the memo's signal node over keeps this function a real `Signal`.
|
|
10590
|
+
const read = () => {
|
|
10591
|
+
noteQueryFormRead(formId);
|
|
10592
|
+
return memoized();
|
|
10593
|
+
};
|
|
10594
|
+
return Object.assign(read, { [SIGNAL]: memoized[SIGNAL] });
|
|
10216
10595
|
};
|
|
10217
|
-
const declaredParamCount = (mock) => (mock.query ? [...new URLSearchParams(mock.query)].length : 0);
|
|
10218
10596
|
/**
|
|
10219
|
-
*
|
|
10220
|
-
*
|
|
10221
|
-
*
|
|
10222
|
-
* Installed as the mock resolver by `provideQueryDevtools()`; nothing else may call it.
|
|
10597
|
+
* Records which query forms feed one query's args, so the devtools can answer "which query does this
|
|
10598
|
+
* form drive?" from the reads themselves rather than from a naming convention.
|
|
10223
10599
|
* @internal
|
|
10224
10600
|
*/
|
|
10225
|
-
const
|
|
10226
|
-
const ids =
|
|
10227
|
-
|
|
10228
|
-
|
|
10229
|
-
|
|
10230
|
-
|
|
10231
|
-
|
|
10232
|
-
|
|
10233
|
-
|
|
10234
|
-
|
|
10235
|
-
|
|
10236
|
-
|
|
10237
|
-
|
|
10238
|
-
|
|
10239
|
-
|
|
10240
|
-
|
|
10241
|
-
|
|
10601
|
+
const createQueryDevtoolsFormLinks = () => {
|
|
10602
|
+
const ids = signal([], /* @ts-ignore */
|
|
10603
|
+
...(ngDevMode ? [{ debugName: "ids" }] : /* istanbul ignore next */ []));
|
|
10604
|
+
return {
|
|
10605
|
+
ids: ids.asReadonly(),
|
|
10606
|
+
track: (build) => {
|
|
10607
|
+
const collected = collectQueryFormLinks(build);
|
|
10608
|
+
// Args are built inside a reactive computation, which may not write to a signal - `untracked`
|
|
10609
|
+
// lifts that restriction, and skipping the write unless something is new keeps the panel from
|
|
10610
|
+
// being notified on every evaluation.
|
|
10611
|
+
untracked(() => {
|
|
10612
|
+
const known = ids();
|
|
10613
|
+
const added = collected.ids.filter((id) => !known.includes(id));
|
|
10614
|
+
if (added.length)
|
|
10615
|
+
ids.set([...known, ...added]);
|
|
10616
|
+
});
|
|
10617
|
+
return collected.value;
|
|
10618
|
+
},
|
|
10619
|
+
};
|
|
10242
10620
|
};
|
|
10243
10621
|
|
|
10244
10622
|
const isPathAtOrBelow = (path, root) => path.length >= root.length && root.every((step, index) => step === path[index]);
|
|
@@ -10475,19 +10853,19 @@ const resizePagination = (value, shape, mode, amount) => {
|
|
|
10475
10853
|
}
|
|
10476
10854
|
return result;
|
|
10477
10855
|
};
|
|
10478
|
-
const STRING_PRESETS = {
|
|
10856
|
+
const STRING_PRESETS$1 = {
|
|
10479
10857
|
short: 'Ab',
|
|
10480
10858
|
long: /* @__PURE__ */ 'Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt. '.repeat(4),
|
|
10481
10859
|
longWord: 'Rindfleischetikettierungsüberwachungsaufgabenübertragungsgesetz',
|
|
10482
10860
|
unicode: 'مرحبا بالعالم 👋 日本語テスト 🎉 Ñoño',
|
|
10483
10861
|
};
|
|
10484
|
-
const stringPresetValue = (preset, custom) => custom ?? (preset === 'custom' ? '' : STRING_PRESETS[preset]);
|
|
10485
|
-
const NUMBER_PRESETS = {
|
|
10862
|
+
const stringPresetValue = (preset, custom) => custom ?? (preset === 'custom' ? '' : STRING_PRESETS$1[preset]);
|
|
10863
|
+
const NUMBER_PRESETS$1 = {
|
|
10486
10864
|
zero: 0,
|
|
10487
10865
|
negative: -1,
|
|
10488
10866
|
huge: Number.MAX_SAFE_INTEGER,
|
|
10489
10867
|
};
|
|
10490
|
-
const numberPresetValue = (preset, custom) => custom ?? (preset === 'custom' ? 0 : NUMBER_PRESETS[preset]);
|
|
10868
|
+
const numberPresetValue = (preset, custom) => custom ?? (preset === 'custom' ? 0 : NUMBER_PRESETS$1[preset]);
|
|
10491
10869
|
const pickRandom = (values) => values[Math.floor(Math.random() * values.length)];
|
|
10492
10870
|
const randomIntBetween = (min, max) => min + Math.floor(Math.random() * (max - min + 1));
|
|
10493
10871
|
const randomHex = (length) => {
|
|
@@ -10703,6 +11081,30 @@ const OP_TYPES = /* @__PURE__ */ new Set([
|
|
|
10703
11081
|
'reset',
|
|
10704
11082
|
]);
|
|
10705
11083
|
const isJsonPath = (value) => Array.isArray(value) && value.every((step) => typeof step === 'string' || typeof step === 'number');
|
|
11084
|
+
const STRING_PRESETS = ['short', 'long', 'longWord', 'unicode', 'custom'];
|
|
11085
|
+
const NUMBER_PRESETS = ['zero', 'negative', 'huge', 'custom'];
|
|
11086
|
+
const DATE_PRESETS = ['now', 'plusDay', 'minusDay', 'farFuture', 'farPast', 'invalid'];
|
|
11087
|
+
const isIndex = (value) => Number.isInteger(value) && value >= 0;
|
|
11088
|
+
const hasReplayableFields = (op) => {
|
|
11089
|
+
switch (op['type']) {
|
|
11090
|
+
case 'set':
|
|
11091
|
+
return 'value' in op;
|
|
11092
|
+
case 'stringPreset':
|
|
11093
|
+
return STRING_PRESETS.includes(op['preset']) && (op['custom'] === undefined || typeof op['custom'] === 'string');
|
|
11094
|
+
case 'numberPreset':
|
|
11095
|
+
return NUMBER_PRESETS.includes(op['preset']) && (op['custom'] === undefined || Number.isFinite(op['custom']));
|
|
11096
|
+
case 'datePreset':
|
|
11097
|
+
return DATE_PRESETS.includes(op['preset']);
|
|
11098
|
+
case 'duplicateArrayItem':
|
|
11099
|
+
return isIndex(op['index']);
|
|
11100
|
+
case 'pasteArrayItem':
|
|
11101
|
+
return 'value' in op && (op['index'] === undefined || isIndex(op['index']));
|
|
11102
|
+
case 'paginationResize':
|
|
11103
|
+
return (op['mode'] === 'shrink' || op['mode'] === 'extend') && isIndex(op['amount']);
|
|
11104
|
+
default:
|
|
11105
|
+
return true;
|
|
11106
|
+
}
|
|
11107
|
+
};
|
|
10706
11108
|
/**
|
|
10707
11109
|
* Whether a value parsed from outside - a paste, a stored set - is an op this build can replay.
|
|
10708
11110
|
* @internal
|
|
@@ -10711,7 +11113,7 @@ const isOverrideOp = (value) => {
|
|
|
10711
11113
|
if (!value || typeof value !== 'object' || Array.isArray(value))
|
|
10712
11114
|
return false;
|
|
10713
11115
|
const op = value;
|
|
10714
|
-
return typeof op
|
|
11116
|
+
return (typeof op['type'] === 'string' && OP_TYPES.has(op['type']) && isJsonPath(op['path']) && hasReplayableFields(op));
|
|
10715
11117
|
};
|
|
10716
11118
|
/**
|
|
10717
11119
|
* Serializes the ops armed on one query into clipboard text. `reset` never reaches a recorder's list, so
|
|
@@ -10777,12 +11179,12 @@ const armQueryDevtoolsOverrideTransfer = (recorder, ops) => {
|
|
|
10777
11179
|
* debugging the wrong thing. `local` is offered in Settings for the dev who has a reason, and the
|
|
10778
11180
|
* restored-overrides bar names the scope whatever it is.
|
|
10779
11181
|
*/
|
|
10780
|
-
const STORAGE_KEY = 'ethlete:query:devtools:overrides:v1';
|
|
11182
|
+
const STORAGE_KEY$1 = 'ethlete:query:devtools:overrides:v1';
|
|
10781
11183
|
const restored = /* @__PURE__ */ signal([], /* @ts-ignore */
|
|
10782
11184
|
...(ngDevMode ? [{ debugName: "restored" }] : /* istanbul ignore next */ []));
|
|
10783
11185
|
const restoredScope = /* @__PURE__ */ signal(null, /* @ts-ignore */
|
|
10784
11186
|
...(ngDevMode ? [{ debugName: "restoredScope" }] : /* istanbul ignore next */ []));
|
|
10785
|
-
const scope = () => queryDevtoolsSettings().overrides;
|
|
11187
|
+
const scope$1 = () => queryDevtoolsSettings().overrides;
|
|
10786
11188
|
/** Where "Keep across reloads" puts them back when it is switched on again. */
|
|
10787
11189
|
let lastPersistentScope = 'session';
|
|
10788
11190
|
/** Every recorder that currently holds at least one op, so switching persistence on can capture them. */
|
|
@@ -10793,7 +11195,7 @@ const carriedOver = /* @__PURE__ */ new Map();
|
|
|
10793
11195
|
const replayed = /* @__PURE__ */ new Set();
|
|
10794
11196
|
let ops = {};
|
|
10795
11197
|
const readStore = () => {
|
|
10796
|
-
const parsed = readQueryDevtoolsStore(scope(), STORAGE_KEY);
|
|
11198
|
+
const parsed = readQueryDevtoolsStore(scope$1(), STORAGE_KEY$1);
|
|
10797
11199
|
const stored = parsed?.ops;
|
|
10798
11200
|
const usable = !!stored && typeof stored === 'object' && !Array.isArray(stored);
|
|
10799
11201
|
const valid = {};
|
|
@@ -10805,11 +11207,11 @@ const readStore = () => {
|
|
|
10805
11207
|
return { enabled: parsed?.enabled === true, ops: valid };
|
|
10806
11208
|
};
|
|
10807
11209
|
const writeStore = () => {
|
|
10808
|
-
if (scope() === 'none') {
|
|
10809
|
-
clearQueryDevtoolsStore(STORAGE_KEY);
|
|
11210
|
+
if (scope$1() === 'none') {
|
|
11211
|
+
clearQueryDevtoolsStore(STORAGE_KEY$1);
|
|
10810
11212
|
return;
|
|
10811
11213
|
}
|
|
10812
|
-
writeQueryDevtoolsStore(scope(), STORAGE_KEY, { enabled: true, ops });
|
|
11214
|
+
writeQueryDevtoolsStore(scope$1(), STORAGE_KEY$1, { enabled: true, ops });
|
|
10813
11215
|
};
|
|
10814
11216
|
const publishRestored = () => restored.set([...carriedOver].map(([id, count]) => ({ id, count, armed: replayed.has(id) })));
|
|
10815
11217
|
const forgetCarriedOver = () => {
|
|
@@ -10842,7 +11244,7 @@ const initQueryDevtoolsOverridePersistence = () => {
|
|
|
10842
11244
|
if (list.length)
|
|
10843
11245
|
carriedOver.set(id, list.length);
|
|
10844
11246
|
}
|
|
10845
|
-
const current = scope();
|
|
11247
|
+
const current = scope$1();
|
|
10846
11248
|
if (current !== 'none')
|
|
10847
11249
|
lastPersistentScope = current;
|
|
10848
11250
|
restoredScope.set(carriedOver.size ? current : null);
|
|
@@ -10852,7 +11254,7 @@ const initQueryDevtoolsOverridePersistence = () => {
|
|
|
10852
11254
|
* Whether armed response overrides are stored and replayed on the next page load - that is, whether
|
|
10853
11255
|
* their scope is anything other than `none`. Off by default.
|
|
10854
11256
|
*/
|
|
10855
|
-
const queryDevtoolsOverridePersistence = /* @__PURE__ */ computed(() => scope() !== 'none', /* @ts-ignore */
|
|
11257
|
+
const queryDevtoolsOverridePersistence = /* @__PURE__ */ computed(() => scope$1() !== 'none', /* @ts-ignore */
|
|
10856
11258
|
...(ngDevMode ? [{ debugName: "queryDevtoolsOverridePersistence" }] : /* istanbul ignore next */ []));
|
|
10857
11259
|
/**
|
|
10858
11260
|
* Where armed response overrides are kept. Switching to a scope that keeps them captures whatever is
|
|
@@ -10861,11 +11263,11 @@ const queryDevtoolsOverridePersistence = /* @__PURE__ */ computed(() => scope()
|
|
|
10861
11263
|
* op exactly where it is for the rest of this page.
|
|
10862
11264
|
*/
|
|
10863
11265
|
const setQueryDevtoolsOverridesScope = (next) => {
|
|
10864
|
-
const previous = scope();
|
|
11266
|
+
const previous = scope$1();
|
|
10865
11267
|
if (next === previous)
|
|
10866
11268
|
return;
|
|
10867
11269
|
// Off both stores first: whichever the previous scope was, its copy must not outlive the change.
|
|
10868
|
-
clearQueryDevtoolsStore(STORAGE_KEY);
|
|
11270
|
+
clearQueryDevtoolsStore(STORAGE_KEY$1);
|
|
10869
11271
|
setQueryDevtoolsSettings({ overrides: next });
|
|
10870
11272
|
if (next === 'none') {
|
|
10871
11273
|
ops = {};
|
|
@@ -10884,106 +11286,309 @@ const setQueryDevtoolsOverridesScope = (next) => {
|
|
|
10884
11286
|
writeStore();
|
|
10885
11287
|
};
|
|
10886
11288
|
/**
|
|
10887
|
-
* Turns persistence on or off - the panel's "Keep across reloads" toggle. On restores the last scope
|
|
10888
|
-
* that kept them (`session` unless Settings picked `local`).
|
|
11289
|
+
* Turns persistence on or off - the panel's "Keep across reloads" toggle. On restores the last scope
|
|
11290
|
+
* that kept them (`session` unless Settings picked `local`).
|
|
11291
|
+
*/
|
|
11292
|
+
const setQueryDevtoolsOverridePersistence = (value) => {
|
|
11293
|
+
setQueryDevtoolsOverridesScope(value ? lastPersistentScope : 'none');
|
|
11294
|
+
};
|
|
11295
|
+
/**
|
|
11296
|
+
* Empties the override store in both browser stores and disarms everything a reload brought back,
|
|
11297
|
+
* without changing where overrides are kept - the panel's "Reset devtools".
|
|
11298
|
+
*/
|
|
11299
|
+
const clearQueryDevtoolsOverrideStore = () => {
|
|
11300
|
+
clearRestoredQueryDevtoolsOverrides();
|
|
11301
|
+
ops = {};
|
|
11302
|
+
clearQueryDevtoolsStore(STORAGE_KEY$1);
|
|
11303
|
+
};
|
|
11304
|
+
/**
|
|
11305
|
+
* The ops this page load inherited from the previous one, and for each id whether a live query has taken
|
|
11306
|
+
* it. Overrides that outlive the page that armed them have to announce themselves, because an app being
|
|
11307
|
+
* lied to looks exactly like an app that is broken.
|
|
11308
|
+
*/
|
|
11309
|
+
const restoredQueryDevtoolsOverrides =
|
|
11310
|
+
/* @__PURE__ */ restored.asReadonly();
|
|
11311
|
+
/**
|
|
11312
|
+
* The scope those ops came back from, or `null` when this page inherited none. `local` is the one worth
|
|
11313
|
+
* naming in the banner: it means they outlived closing the tab, not just a reload.
|
|
11314
|
+
*/
|
|
11315
|
+
const queryDevtoolsRestoredOverridesScope =
|
|
11316
|
+
/* @__PURE__ */ restoredScope.asReadonly();
|
|
11317
|
+
/** Disarms every op this page load inherited and empties the store - the banner's one-click way out. */
|
|
11318
|
+
const clearRestoredQueryDevtoolsOverrides = () => {
|
|
11319
|
+
for (const id of replayed) {
|
|
11320
|
+
armedRecorders.get(id)?.clearAll();
|
|
11321
|
+
armedRecorders.delete(id);
|
|
11322
|
+
}
|
|
11323
|
+
for (const id of carriedOver.keys())
|
|
11324
|
+
writeOps(id, []);
|
|
11325
|
+
forgetCarriedOver();
|
|
11326
|
+
};
|
|
11327
|
+
/**
|
|
11328
|
+
* Forgets a destroyed query's recorder. Ops already persisted stay in the store on purpose - they are
|
|
11329
|
+
* keyed by an id the next page load re-derives, which is what "Keep across reloads" replays them from.
|
|
11330
|
+
* @internal
|
|
11331
|
+
*/
|
|
11332
|
+
const releaseQueryDevtoolsOverridePersistence = (id) => {
|
|
11333
|
+
armedRecorders.delete(id);
|
|
11334
|
+
if (scope$1() !== 'none')
|
|
11335
|
+
return;
|
|
11336
|
+
const { [id]: _dropped, ...rest } = ops;
|
|
11337
|
+
ops = rest;
|
|
11338
|
+
};
|
|
11339
|
+
/**
|
|
11340
|
+
* Wraps a query's overrides recorder so its armed ops are stored and replayed. Replay happens here, at
|
|
11341
|
+
* registration, rather than in the panel - a query can run long before the panel is ever opened.
|
|
11342
|
+
*
|
|
11343
|
+
* Ids are reload-deterministic (descriptor + per-descriptor counter), so the same queries created in the
|
|
11344
|
+
* same order re-arm correctly; an id the store holds that nobody claims is reported by
|
|
11345
|
+
* {@link restoredQueryDevtoolsOverrides} rather than guessed at.
|
|
11346
|
+
* @internal
|
|
11347
|
+
*/
|
|
11348
|
+
const withQueryDevtoolsOverridePersistence = (id, recorder) => {
|
|
11349
|
+
const inherited = ops[id];
|
|
11350
|
+
if (scope$1() !== 'none' && inherited?.length) {
|
|
11351
|
+
for (const op of inherited)
|
|
11352
|
+
recorder.arm(op);
|
|
11353
|
+
armedRecorders.set(id, recorder);
|
|
11354
|
+
replayed.add(id);
|
|
11355
|
+
publishRestored();
|
|
11356
|
+
}
|
|
11357
|
+
const sync = () => {
|
|
11358
|
+
const list = recorder.list().map((entry) => entry.op);
|
|
11359
|
+
if (list.length)
|
|
11360
|
+
armedRecorders.set(id, recorder);
|
|
11361
|
+
else
|
|
11362
|
+
armedRecorders.delete(id);
|
|
11363
|
+
if (scope$1() !== 'none')
|
|
11364
|
+
writeOps(id, list);
|
|
11365
|
+
};
|
|
11366
|
+
return {
|
|
11367
|
+
...recorder,
|
|
11368
|
+
arm: (op) => {
|
|
11369
|
+
recorder.arm(op);
|
|
11370
|
+
sync();
|
|
11371
|
+
},
|
|
11372
|
+
clear: (opId) => {
|
|
11373
|
+
recorder.clear(opId);
|
|
11374
|
+
sync();
|
|
11375
|
+
},
|
|
11376
|
+
clearAll: () => {
|
|
11377
|
+
recorder.clearAll();
|
|
11378
|
+
if (replayed.delete(id))
|
|
11379
|
+
publishRestored();
|
|
11380
|
+
sync();
|
|
11381
|
+
},
|
|
11382
|
+
};
|
|
11383
|
+
};
|
|
11384
|
+
|
|
11385
|
+
/**
|
|
11386
|
+
* The designed library. Which of them is **armed** is a separate store under a separate scope - see
|
|
11387
|
+
* {@link queryDevtoolsArmedMocks}.
|
|
11388
|
+
*/
|
|
11389
|
+
const STORAGE_KEY = 'ethlete:query:devtools:mocks:v1';
|
|
11390
|
+
const ARMED_STORAGE_KEY = 'ethlete:query:devtools:mocks:armed:v1';
|
|
11391
|
+
const mocks = /* @__PURE__ */ signal([], /* @ts-ignore */
|
|
11392
|
+
...(ngDevMode ? [{ debugName: "mocks" }] : /* istanbul ignore next */ []));
|
|
11393
|
+
const armed = /* @__PURE__ */ signal(/* @__PURE__ */ new Set(), /* @ts-ignore */
|
|
11394
|
+
...(ngDevMode ? [{ debugName: "armed" }] : /* istanbul ignore next */ []));
|
|
11395
|
+
const armedRestored = /* @__PURE__ */ signal(false, /* @ts-ignore */
|
|
11396
|
+
...(ngDevMode ? [{ debugName: "armedRestored" }] : /* istanbul ignore next */ []));
|
|
11397
|
+
const scope = () => queryDevtoolsSettings().mocks;
|
|
11398
|
+
const armedScope = () => queryDevtoolsSettings().armedMocks;
|
|
11399
|
+
/** The designed mocks, armed or not. Persisted, because losing an hour of authoring is unacceptable. */
|
|
11400
|
+
const queryDevtoolsMocks = /* @__PURE__ */ mocks.asReadonly();
|
|
11401
|
+
/**
|
|
11402
|
+
* The ids currently being served. Not kept across page loads unless the `armedMocks` setting asks for it -
|
|
11403
|
+
* an app that silently serves designed data tomorrow morning is worse than losing which mocks were armed.
|
|
11404
|
+
*/
|
|
11405
|
+
const queryDevtoolsArmedMocks = /* @__PURE__ */ armed.asReadonly();
|
|
11406
|
+
/**
|
|
11407
|
+
* Whether what is armed right now came back from a previous page load rather than being armed by hand.
|
|
11408
|
+
* The panel's armed bar says so, because mocks nobody remembers arming look exactly like a broken API.
|
|
11409
|
+
*/
|
|
11410
|
+
const queryDevtoolsArmedMocksRestored = /* @__PURE__ */ armedRestored.asReadonly();
|
|
11411
|
+
/**
|
|
11412
|
+
* The identity of a designed mock: one per client, method, route **and** declared query. The query is part
|
|
11413
|
+
* of it so `/posts?page=1` and `/posts?page=2` can hold different bodies.
|
|
11414
|
+
*/
|
|
11415
|
+
const queryDevtoolsMockId = (parts) => `${parts.clientName}|${parts.method}|${parts.pattern}${parts.query ? `?${parts.query}` : ''}`;
|
|
11416
|
+
const write = () => {
|
|
11417
|
+
if (scope() === 'none') {
|
|
11418
|
+
clearQueryDevtoolsStore(STORAGE_KEY);
|
|
11419
|
+
return;
|
|
11420
|
+
}
|
|
11421
|
+
writeQueryDevtoolsStore(scope(), STORAGE_KEY, mocks());
|
|
11422
|
+
};
|
|
11423
|
+
const writeArmed = () => {
|
|
11424
|
+
if (armedScope() === 'none') {
|
|
11425
|
+
clearQueryDevtoolsStore(ARMED_STORAGE_KEY);
|
|
11426
|
+
return;
|
|
11427
|
+
}
|
|
11428
|
+
writeQueryDevtoolsStore(armedScope(), ARMED_STORAGE_KEY, [...armed()]);
|
|
11429
|
+
};
|
|
11430
|
+
/** Every change made by hand goes through here, so none of them can leave the store or the bar behind. */
|
|
11431
|
+
const setArmed = (next) => {
|
|
11432
|
+
armed.set(next);
|
|
11433
|
+
armedRestored.set(false);
|
|
11434
|
+
writeArmed();
|
|
11435
|
+
};
|
|
11436
|
+
const isMock = (value) => {
|
|
11437
|
+
const mock = value;
|
|
11438
|
+
return (!!mock &&
|
|
11439
|
+
typeof mock.id === 'string' &&
|
|
11440
|
+
typeof mock.clientName === 'string' &&
|
|
11441
|
+
typeof mock.method === 'string' &&
|
|
11442
|
+
typeof mock.pattern === 'string' &&
|
|
11443
|
+
typeof mock.status === 'number');
|
|
11444
|
+
};
|
|
11445
|
+
/**
|
|
11446
|
+
* Reads the designed library, so a mock can be armed without authoring it again. Called by
|
|
11447
|
+
* `provideQueryDevtools()` after the settings it takes its scope from; nothing else may call it.
|
|
11448
|
+
* @internal
|
|
11449
|
+
*/
|
|
11450
|
+
const initQueryDevtoolsMocks = () => {
|
|
11451
|
+
const stored = readQueryDevtoolsStore(scope(), STORAGE_KEY);
|
|
11452
|
+
// `query` was added after the first version of the store, so an entry written before it still loads.
|
|
11453
|
+
mocks.set(Array.isArray(stored) ? stored.filter(isMock).map((mock) => ({ ...mock, query: mock.query ?? '' })) : []);
|
|
11454
|
+
const kept = readQueryDevtoolsStore(armedScope(), ARMED_STORAGE_KEY);
|
|
11455
|
+
const library = new Set(mocks().map((mock) => mock.id));
|
|
11456
|
+
// An id the library no longer holds would arm nothing, so it is dropped rather than kept waiting for a
|
|
11457
|
+
// mock that may never be designed again.
|
|
11458
|
+
const inherited = Array.isArray(kept)
|
|
11459
|
+
? kept.filter((id) => typeof id === 'string' && library.has(id))
|
|
11460
|
+
: [];
|
|
11461
|
+
armed.set(new Set(inherited));
|
|
11462
|
+
armedRestored.set(inherited.length > 0);
|
|
11463
|
+
};
|
|
11464
|
+
/**
|
|
11465
|
+
* The path of a request URL relative to the client's `baseUrl` (base path included), without its query
|
|
11466
|
+
* string - what a pattern is matched against. A URL outside `baseUrl`, or no `baseUrl` at all, falls
|
|
11467
|
+
* back to the path without its origin.
|
|
11468
|
+
*/
|
|
11469
|
+
const queryDevtoolsRequestPath = (url, baseUrl) => {
|
|
11470
|
+
const withoutQuery = url.split('?')[0] ?? '';
|
|
11471
|
+
const base = baseUrl?.replace(/\/+$/, '');
|
|
11472
|
+
if (base && withoutQuery.startsWith(base)) {
|
|
11473
|
+
const rest = withoutQuery.slice(base.length);
|
|
11474
|
+
if (!rest)
|
|
11475
|
+
return '/';
|
|
11476
|
+
if (rest.startsWith('/'))
|
|
11477
|
+
return rest;
|
|
11478
|
+
}
|
|
11479
|
+
const schemeEnd = withoutQuery.indexOf('://');
|
|
11480
|
+
if (schemeEnd === -1)
|
|
11481
|
+
return withoutQuery;
|
|
11482
|
+
const pathStart = withoutQuery.indexOf('/', schemeEnd + 3);
|
|
11483
|
+
return pathStart === -1 ? '/' : withoutQuery.slice(pathStart);
|
|
11484
|
+
};
|
|
11485
|
+
const segmentsOf$1 = (path) => path.split('/').filter(Boolean);
|
|
11486
|
+
/** The query string of a request URL, without the `?`. */
|
|
11487
|
+
const queryOf = (url) => url.split('?')[1] ?? '';
|
|
11488
|
+
/**
|
|
11489
|
+
* Whether a request carries every query parameter a mock declares. Extra parameters are ignored - a mock
|
|
11490
|
+
* says what the request must ask for, not everything it may.
|
|
10889
11491
|
*/
|
|
10890
|
-
const
|
|
10891
|
-
|
|
11492
|
+
const matchesQueryDevtoolsMockQuery = (declared, url) => {
|
|
11493
|
+
if (!declared)
|
|
11494
|
+
return true;
|
|
11495
|
+
const asked = new URLSearchParams(queryOf(url));
|
|
11496
|
+
return [...new URLSearchParams(declared)].every(([key, value]) => asked.getAll(key).includes(value));
|
|
10892
11497
|
};
|
|
10893
11498
|
/**
|
|
10894
|
-
*
|
|
10895
|
-
*
|
|
11499
|
+
* Whether a request path is the route a pattern describes. A `:param` segment matches any single
|
|
11500
|
+
* non-empty segment, which is the whole point: a mock is armed for a route, not for the one URL a query
|
|
11501
|
+
* happened to build.
|
|
10896
11502
|
*/
|
|
10897
|
-
const
|
|
10898
|
-
|
|
10899
|
-
|
|
10900
|
-
|
|
11503
|
+
const matchesQueryDevtoolsMockPattern = (pattern, path) => {
|
|
11504
|
+
const patternSegments = segmentsOf$1(pattern);
|
|
11505
|
+
const pathSegments = segmentsOf$1(path);
|
|
11506
|
+
if (patternSegments.length !== pathSegments.length)
|
|
11507
|
+
return false;
|
|
11508
|
+
return patternSegments.every((segment, index) => segment.startsWith(':') || segment === pathSegments[index]);
|
|
10901
11509
|
};
|
|
10902
11510
|
/**
|
|
10903
|
-
*
|
|
10904
|
-
*
|
|
10905
|
-
* lied to looks exactly like an app that is broken.
|
|
10906
|
-
*/
|
|
10907
|
-
const restoredQueryDevtoolsOverrides =
|
|
10908
|
-
/* @__PURE__ */ restored.asReadonly();
|
|
10909
|
-
/**
|
|
10910
|
-
* The scope those ops came back from, or `null` when this page inherited none. `local` is the one worth
|
|
10911
|
-
* naming in the banner: it means they outlived closing the tab, not just a reload.
|
|
11511
|
+
* Adds a designed mock, or replaces the one already under its id. Arming is untouched: re-designing a
|
|
11512
|
+
* body while it is being served changes what the next request gets.
|
|
10912
11513
|
*/
|
|
10913
|
-
const
|
|
10914
|
-
|
|
10915
|
-
|
|
10916
|
-
|
|
10917
|
-
|
|
10918
|
-
|
|
10919
|
-
|
|
10920
|
-
|
|
10921
|
-
|
|
10922
|
-
|
|
10923
|
-
|
|
11514
|
+
const saveQueryDevtoolsMock = (mock) => {
|
|
11515
|
+
mocks.update((current) => {
|
|
11516
|
+
const index = current.findIndex((entry) => entry.id === mock.id);
|
|
11517
|
+
if (index === -1)
|
|
11518
|
+
return [...current, mock];
|
|
11519
|
+
return current.map((entry) => (entry.id === mock.id ? mock : entry));
|
|
11520
|
+
});
|
|
11521
|
+
write();
|
|
11522
|
+
};
|
|
11523
|
+
/** Removes a designed mock, disarming it on the way out. */
|
|
11524
|
+
const deleteQueryDevtoolsMock = (id) => {
|
|
11525
|
+
mocks.update((current) => current.filter((entry) => entry.id !== id));
|
|
11526
|
+
armQueryDevtoolsMock(id, false);
|
|
11527
|
+
write();
|
|
11528
|
+
};
|
|
11529
|
+
/** Starts or stops serving one designed mock. Arming a mock that is not in the library does nothing. */
|
|
11530
|
+
const armQueryDevtoolsMock = (id, isArmed) => {
|
|
11531
|
+
if (isArmed && !mocks().some((mock) => mock.id === id))
|
|
11532
|
+
return;
|
|
11533
|
+
const next = new Set(armed());
|
|
11534
|
+
if (isArmed)
|
|
11535
|
+
next.add(id);
|
|
11536
|
+
else
|
|
11537
|
+
next.delete(id);
|
|
11538
|
+
setArmed(next);
|
|
10924
11539
|
};
|
|
11540
|
+
/** Starts serving every mock in the library - the counterpart of {@link clearQueryDevtoolsArmedMocks}. */
|
|
11541
|
+
const armAllQueryDevtoolsMocks = () => setArmed(new Set(mocks().map((mock) => mock.id)));
|
|
11542
|
+
/** Stops serving every mock, leaving the library alone - the armed bar's one-click way out. */
|
|
11543
|
+
const clearQueryDevtoolsArmedMocks = () => setArmed(new Set());
|
|
10925
11544
|
/**
|
|
10926
|
-
*
|
|
10927
|
-
*
|
|
10928
|
-
*
|
|
11545
|
+
* Where the armed set is kept. Switching to a scope that keeps it stores whatever is armed right now, so
|
|
11546
|
+
* the choice reads as "keep these"; switching to `none` empties the store and leaves this page serving
|
|
11547
|
+
* exactly what it was already serving.
|
|
10929
11548
|
*/
|
|
10930
|
-
const
|
|
10931
|
-
|
|
10932
|
-
if (scope() !== 'none')
|
|
11549
|
+
const setQueryDevtoolsArmedMocksScope = (next) => {
|
|
11550
|
+
if (next === armedScope())
|
|
10933
11551
|
return;
|
|
10934
|
-
|
|
10935
|
-
|
|
11552
|
+
// Off both stores first: whichever the previous scope was, its copy must not outlive the change.
|
|
11553
|
+
clearQueryDevtoolsStore(ARMED_STORAGE_KEY);
|
|
11554
|
+
setQueryDevtoolsSettings({ armedMocks: next });
|
|
11555
|
+
writeArmed();
|
|
10936
11556
|
};
|
|
11557
|
+
/** Empties the designed library and disarms everything - the panel's "Reset devtools". */
|
|
11558
|
+
const clearQueryDevtoolsMockStore = () => {
|
|
11559
|
+
mocks.set([]);
|
|
11560
|
+
armed.set(new Set());
|
|
11561
|
+
armedRestored.set(false);
|
|
11562
|
+
clearQueryDevtoolsStore(STORAGE_KEY);
|
|
11563
|
+
clearQueryDevtoolsStore(ARMED_STORAGE_KEY);
|
|
11564
|
+
};
|
|
11565
|
+
const declaredParamCount = (mock) => (mock.query ? [...new URLSearchParams(mock.query)].length : 0);
|
|
10937
11566
|
/**
|
|
10938
|
-
*
|
|
10939
|
-
*
|
|
11567
|
+
* The mock to serve one upcoming attempt, or `null` to let it reach the network. The armed mock declaring
|
|
11568
|
+
* the most query parameters wins, so a special case armed on top of a general one is what answers.
|
|
10940
11569
|
*
|
|
10941
|
-
*
|
|
10942
|
-
* same order re-arm correctly; an id the store holds that nobody claims is reported by
|
|
10943
|
-
* {@link restoredQueryDevtoolsOverrides} rather than guessed at.
|
|
11570
|
+
* Installed as the mock resolver by `provideQueryDevtools()`; nothing else may call it.
|
|
10944
11571
|
* @internal
|
|
10945
11572
|
*/
|
|
10946
|
-
const
|
|
10947
|
-
const
|
|
10948
|
-
if (
|
|
10949
|
-
|
|
10950
|
-
|
|
10951
|
-
|
|
10952
|
-
|
|
10953
|
-
|
|
10954
|
-
|
|
10955
|
-
|
|
10956
|
-
|
|
10957
|
-
|
|
10958
|
-
|
|
10959
|
-
|
|
10960
|
-
|
|
10961
|
-
|
|
10962
|
-
|
|
10963
|
-
};
|
|
10964
|
-
return {
|
|
10965
|
-
...recorder,
|
|
10966
|
-
arm: (op) => {
|
|
10967
|
-
recorder.arm(op);
|
|
10968
|
-
sync();
|
|
10969
|
-
},
|
|
10970
|
-
clear: (opId) => {
|
|
10971
|
-
recorder.clear(opId);
|
|
10972
|
-
sync();
|
|
10973
|
-
},
|
|
10974
|
-
clearAll: () => {
|
|
10975
|
-
recorder.clearAll();
|
|
10976
|
-
if (replayed.delete(id))
|
|
10977
|
-
publishRestored();
|
|
10978
|
-
sync();
|
|
10979
|
-
},
|
|
10980
|
-
};
|
|
11573
|
+
const resolveQueryDevtoolsMockForAttempt = (target) => {
|
|
11574
|
+
const ids = armed();
|
|
11575
|
+
if (!ids.size)
|
|
11576
|
+
return null;
|
|
11577
|
+
const path = queryDevtoolsRequestPath(target.url, target.baseUrl);
|
|
11578
|
+
const mock = mocks()
|
|
11579
|
+
.filter((candidate) => ids.has(candidate.id) &&
|
|
11580
|
+
candidate.clientName === target.clientName &&
|
|
11581
|
+
candidate.method === target.method &&
|
|
11582
|
+
matchesQueryDevtoolsMockPattern(candidate.pattern, path) &&
|
|
11583
|
+
matchesQueryDevtoolsMockQuery(candidate.query, target.url))
|
|
11584
|
+
// The most specific armed mock answers: one that names `page=2` beats one that takes any query, so
|
|
11585
|
+
// arming a general mock does not shadow the special case someone armed on top of it.
|
|
11586
|
+
.sort((a, b) => declaredParamCount(b) - declaredParamCount(a))[0];
|
|
11587
|
+
if (!mock)
|
|
11588
|
+
return null;
|
|
11589
|
+
return { status: mock.status, body: mock.body, latencyMs: mock.latencyMs };
|
|
10981
11590
|
};
|
|
10982
11591
|
|
|
10983
|
-
// Generated from package.json by tools/scripts/generate-version.js - do not edit.
|
|
10984
|
-
/** The version of `@ethlete/query` this build was cut from. */
|
|
10985
|
-
const QUERY_VERSION = '6.0.0-next.54';
|
|
10986
|
-
|
|
10987
11592
|
/** How deep a schema is expanded before a nested branch is given up on. */
|
|
10988
11593
|
const MAX_DEPTH = 12;
|
|
10989
11594
|
/** How many elements are generated for an array, whatever its `minItems` says. */
|
|
@@ -11202,9 +11807,7 @@ const stringSample = (node, schema, ctx) => {
|
|
|
11202
11807
|
if (ctx.style === 'stress')
|
|
11203
11808
|
return fitLength(generateQueryDevtoolsStringPreset(pickStressStringPreset()), schema);
|
|
11204
11809
|
const title = schema['title'];
|
|
11205
|
-
|
|
11206
|
-
return title;
|
|
11207
|
-
return node.key ?? 'string';
|
|
11810
|
+
return fitLength(typeof title === 'string' && title ? title : (node.key ?? 'string'), schema);
|
|
11208
11811
|
};
|
|
11209
11812
|
const boundsOf = (schema) => {
|
|
11210
11813
|
const exclusiveMin = finiteNumber(schema['exclusiveMinimum']);
|
|
@@ -11224,9 +11827,9 @@ const clampToBounds = (value, bounds) => {
|
|
|
11224
11827
|
const numberSample = (schema, ctx) => {
|
|
11225
11828
|
const bounds = boundsOf(schema);
|
|
11226
11829
|
if (ctx.style === 'placeholder')
|
|
11227
|
-
return bounds.min ?? 0;
|
|
11830
|
+
return clampToBounds(bounds.min ?? 0, bounds);
|
|
11228
11831
|
if (ctx.style === 'realistic') {
|
|
11229
|
-
return generateQueryDevtoolsSampleNumber({ ...bounds, fractional: typeOf(schema) === 'number' });
|
|
11832
|
+
return clampToBounds(generateQueryDevtoolsSampleNumber({ ...bounds, fractional: typeOf(schema) === 'number' }), bounds);
|
|
11230
11833
|
}
|
|
11231
11834
|
const allowsNegative = bounds.min === null || bounds.min < 0;
|
|
11232
11835
|
return clampToBounds(generateQueryDevtoolsNumberPreset(allowsNegative && Math.random() < 0.5 ? 'negative' : 'huge'), bounds);
|
|
@@ -11667,7 +12270,8 @@ const queryDevtoolsResponseHistory = () => queryDevtoolsSettings().responseHisto
|
|
|
11667
12270
|
* @internal
|
|
11668
12271
|
*/
|
|
11669
12272
|
const setQueryDevtoolsResponseHistory = (count) => {
|
|
11670
|
-
providedResponseHistory =
|
|
12273
|
+
providedResponseHistory =
|
|
12274
|
+
count === undefined || Number.isNaN(count) ? DEFAULT_RESPONSE_HISTORY : Math.max(1, Math.floor(count));
|
|
11671
12275
|
};
|
|
11672
12276
|
/** The index of the newest run still in flight, or -1 when none is. */
|
|
11673
12277
|
const lastPendingRunIndex = (runs) => {
|
|
@@ -12301,6 +12905,14 @@ const warnAboutIgnoredOptions = (options) => {
|
|
|
12301
12905
|
return;
|
|
12302
12906
|
console.warn(`provideQueryDevtools() ran before on this page, so ${ignored.join(', ')} of this call ${ignored.length === 1 ? 'is' : 'are'} ignored. The devtools read these options once, from the first call. Declare them there.`);
|
|
12303
12907
|
};
|
|
12908
|
+
/**
|
|
12909
|
+
* Undoes the latch `provideQueryDevtools()` sets, so a later spec in the same worker sees the devtools
|
|
12910
|
+
* disabled again. For specs only.
|
|
12911
|
+
*/
|
|
12912
|
+
const ɵresetQueryDevtoolsForTesting = () => {
|
|
12913
|
+
queryDevtoolsInitialized = false;
|
|
12914
|
+
setQueryDevtoolsRegistrar(null);
|
|
12915
|
+
};
|
|
12304
12916
|
/**
|
|
12305
12917
|
* Enables the `@ethlete/query` devtools. Add this to your application providers (e.g. in
|
|
12306
12918
|
* `bootstrapApplication`) to make query clients, queries, stacks, sequences and auth providers
|
|
@@ -12353,7 +12965,7 @@ const provideQueryDevtools = (options) => {
|
|
|
12353
12965
|
return makeEnvironmentProviders([settleThePillsOnFirstRender()]);
|
|
12354
12966
|
};
|
|
12355
12967
|
|
|
12356
|
-
const getOpName = /\b(?:query|mutation)\s+([\w-]+)(?:\s*\([^)]*\))
|
|
12968
|
+
const getOpName = /\b(?:query|mutation)\s+([\w-]+)(?:\s*\([^)]*\))?(?:\s*@\w+(?:\s*\([^)]*\))?)*\s*\{/;
|
|
12357
12969
|
const STRING_LITERAL = '"""[\\s\\S]*?"""|"(?:[^"\\\\\\n]|\\\\.)*"';
|
|
12358
12970
|
const commentOutsideString = /* @__PURE__ */ new RegExp(`(${STRING_LITERAL})|#[^\\n\\r]*`, 'g');
|
|
12359
12971
|
const whitespaceOutsideString = /* @__PURE__ */ new RegExp(`(${STRING_LITERAL})|\\s+`, 'g');
|
|
@@ -12361,6 +12973,19 @@ const minifyGql = (document) => document
|
|
|
12361
12973
|
.replace(commentOutsideString, (_match, literal) => literal ?? '')
|
|
12362
12974
|
.replace(whitespaceOutsideString, (_match, literal) => literal ?? ' ')
|
|
12363
12975
|
.trim();
|
|
12976
|
+
/** Blanks everything inside braces, so a field named `query` or `mutation` cannot pass for an operation. */
|
|
12977
|
+
const topLevelOnly = (document) => {
|
|
12978
|
+
let depth = 0;
|
|
12979
|
+
let result = '';
|
|
12980
|
+
for (const char of document) {
|
|
12981
|
+
if (char === '}')
|
|
12982
|
+
depth = Math.max(depth - 1, 0);
|
|
12983
|
+
result += depth === 0 ? char : ' ';
|
|
12984
|
+
if (char === '{')
|
|
12985
|
+
depth++;
|
|
12986
|
+
}
|
|
12987
|
+
return result;
|
|
12988
|
+
};
|
|
12364
12989
|
/**
|
|
12365
12990
|
* Builds the GraphQL-over-HTTP payload of a document. `variables` comes back as a JSON string for
|
|
12366
12991
|
* the `GET` transport (the default), where the payload travels as query parameters, and as the map
|
|
@@ -12368,12 +12993,12 @@ const minifyGql = (document) => document
|
|
|
12368
12993
|
*/
|
|
12369
12994
|
const transformGql = (str) => {
|
|
12370
12995
|
const normalizedStr = Array.isArray(str) ? str.join('') : str;
|
|
12371
|
-
const operationName = getOpName.exec(normalizedStr.replace(commentOutsideString, (_match, literal) => (literal ? '""' : '')))?.[1];
|
|
12996
|
+
const operationName = getOpName.exec(topLevelOnly(normalizedStr.replace(commentOutsideString, (_match, literal) => (literal ? '""' : ''))))?.[1];
|
|
12372
12997
|
let minified;
|
|
12373
12998
|
return (variables, transport = 'GET') => {
|
|
12374
12999
|
const data = { query: isDevMode() ? normalizedStr : (minified ??= minifyGql(normalizedStr)) };
|
|
12375
13000
|
if (variables) {
|
|
12376
|
-
data['variables'] = transport === 'GET' ? JSON.stringify(variables) : variables;
|
|
13001
|
+
data['variables'] = transport === 'GET' ? JSON.stringify(sortQueryParamKeys(variables)) : variables;
|
|
12377
13002
|
}
|
|
12378
13003
|
if (operationName) {
|
|
12379
13004
|
data['operationName'] = operationName;
|
|
@@ -12399,7 +13024,8 @@ const gqlTransformerFor = (creatorInternals) => {
|
|
|
12399
13024
|
};
|
|
12400
13025
|
const gql = (strings, ...values) => {
|
|
12401
13026
|
const str = strings.reduce((acc, cur, i) => {
|
|
12402
|
-
|
|
13027
|
+
const value = values[i];
|
|
13028
|
+
return acc + cur + (value === false || value === null || value === undefined ? '' : value);
|
|
12403
13029
|
}, '');
|
|
12404
13030
|
return str;
|
|
12405
13031
|
};
|
|
@@ -12409,9 +13035,11 @@ const createGqlExecuteFn = (executeOptions) => {
|
|
|
12409
13035
|
const executeState = setupQueryExecuteState();
|
|
12410
13036
|
const circularChecker = circularQueryDependencyChecker();
|
|
12411
13037
|
const aborter = createQueryExecutionAborter(executeOptions.state);
|
|
12412
|
-
const reset = () => resetExecuteState({ executeState, executeOptions });
|
|
12413
|
-
const exec = (executeArgs) => {
|
|
13038
|
+
const reset = () => untracked(() => resetExecuteState({ executeState, executeOptions }));
|
|
13039
|
+
const exec = (executeArgs) => untracked(() => {
|
|
12414
13040
|
const { args = executeOptions.state.args(), options } = executeArgs ?? {};
|
|
13041
|
+
if (skipParkedExecution(executeOptions.state, args, executeOptions.creator?.route))
|
|
13042
|
+
return false;
|
|
12415
13043
|
circularChecker.check(args);
|
|
12416
13044
|
let gqlParams = gqlTransformerFor(executeOptions.creatorInternals)(args?.variables, executeOptions.creatorInternals.transport);
|
|
12417
13045
|
if (args?.queryParams && executeOptions.creatorInternals.transport === 'GET') {
|
|
@@ -12451,7 +13079,8 @@ const createGqlExecuteFn = (executeOptions) => {
|
|
|
12451
13079
|
options,
|
|
12452
13080
|
isRefreshable: executeOptions.creatorInternals.method === 'QUERY',
|
|
12453
13081
|
});
|
|
12454
|
-
|
|
13082
|
+
return true;
|
|
13083
|
+
});
|
|
12455
13084
|
exec['reset'] = reset;
|
|
12456
13085
|
exec['abort'] = () => aborter.abort();
|
|
12457
13086
|
exec['aborted$'] = aborter.aborted$;
|
|
@@ -12501,6 +13130,7 @@ const createSecureGqlExecuteFn = (executeOptions) => {
|
|
|
12501
13130
|
authProvider,
|
|
12502
13131
|
deps: executeOptions.deps,
|
|
12503
13132
|
state: executeOptions.state,
|
|
13133
|
+
route: executeOptions.creator?.route,
|
|
12504
13134
|
autoExecutes: shouldAutoExecuteGqlQuery(executeOptions.creatorInternals.method) &&
|
|
12505
13135
|
!executeOptions.queryConfig.onlyManualExecution,
|
|
12506
13136
|
transformAuthAndExec: (executeArgs, executeState) => {
|
|
@@ -12666,7 +13296,7 @@ const v2ExtractExpiresInSeconds = (headers) => {
|
|
|
12666
13296
|
// In seconds
|
|
12667
13297
|
let expiresIn = null;
|
|
12668
13298
|
let maxAge = null;
|
|
12669
|
-
if (cacheControl?.includes('no-cache')) {
|
|
13299
|
+
if (cacheControl?.includes('no-cache') || cacheControl?.includes('no-store')) {
|
|
12670
13300
|
return null;
|
|
12671
13301
|
}
|
|
12672
13302
|
if (cacheControl?.includes('max-age')) {
|
|
@@ -12681,11 +13311,11 @@ const v2ExtractExpiresInSeconds = (headers) => {
|
|
|
12681
13311
|
maxAge = parseInt(m);
|
|
12682
13312
|
}
|
|
12683
13313
|
}
|
|
12684
|
-
if (maxAge && age) {
|
|
13314
|
+
if (maxAge !== null && age) {
|
|
12685
13315
|
const ageSeconds = parseInt(age);
|
|
12686
13316
|
expiresIn = maxAge - ageSeconds;
|
|
12687
13317
|
}
|
|
12688
|
-
else if (maxAge) {
|
|
13318
|
+
else if (maxAge !== null) {
|
|
12689
13319
|
expiresIn = maxAge / 2; // We assume the response is half way to its expiration
|
|
12690
13320
|
}
|
|
12691
13321
|
else if (expires) {
|
|
@@ -12711,6 +13341,9 @@ const serializeBody = (body) => {
|
|
|
12711
13341
|
if (body === null || body === undefined) {
|
|
12712
13342
|
return null;
|
|
12713
13343
|
}
|
|
13344
|
+
if (ArrayBuffer.isView(body)) {
|
|
13345
|
+
return body;
|
|
13346
|
+
}
|
|
12714
13347
|
if (body instanceof ArrayBuffer ||
|
|
12715
13348
|
body instanceof Blob ||
|
|
12716
13349
|
body instanceof FormData ||
|
|
@@ -12754,9 +13387,12 @@ const detectContentTypeHeader = (body) => {
|
|
|
12754
13387
|
return body.type || null;
|
|
12755
13388
|
}
|
|
12756
13389
|
// Array buffers have unknown contents and thus no type can be inferred.
|
|
12757
|
-
if (body instanceof ArrayBuffer) {
|
|
13390
|
+
if (body instanceof ArrayBuffer || ArrayBuffer.isView(body)) {
|
|
12758
13391
|
return null;
|
|
12759
13392
|
}
|
|
13393
|
+
if (body instanceof URLSearchParams) {
|
|
13394
|
+
return 'application/x-www-form-urlencoded;charset=UTF-8';
|
|
13395
|
+
}
|
|
12760
13396
|
// Technically, strings could be a form of JSON data, but it's safe enough
|
|
12761
13397
|
// to assume they're plain strings.
|
|
12762
13398
|
if (typeof body === 'string') {
|
|
@@ -12866,9 +13502,9 @@ const request = (config) => {
|
|
|
12866
13502
|
const body = config.body ?? null;
|
|
12867
13503
|
const url = config.urlWithParams.split('?')[0] || '';
|
|
12868
13504
|
const retryFn = config.retryFn || v2ShouldRetryRequest;
|
|
12869
|
-
let currentRetryCount = 0;
|
|
12870
|
-
let retryTimeout = null;
|
|
12871
13505
|
return new Observable((observer) => {
|
|
13506
|
+
let currentRetryCount = 0;
|
|
13507
|
+
let retryTimeout = null;
|
|
12872
13508
|
const xhr = new XMLHttpRequest();
|
|
12873
13509
|
const reqBody = serializeBody(body);
|
|
12874
13510
|
const setupXhr = () => {
|
|
@@ -13932,6 +14568,10 @@ class LegacyQuery {
|
|
|
13932
14568
|
// NG0205 ("Injector has already been destroyed") from inside the view's cleanup phase.
|
|
13933
14569
|
this.newQuery.subtle.destroyRef.onDestroy(() => this.teardown());
|
|
13934
14570
|
}
|
|
14571
|
+
/** The full URL of the request the underlying query is bound to, `null` before the first execution. */
|
|
14572
|
+
get url() {
|
|
14573
|
+
return this.newQuery.url();
|
|
14574
|
+
}
|
|
13935
14575
|
get rawState() {
|
|
13936
14576
|
return this.toQueryState(this.newQuery.executionState());
|
|
13937
14577
|
}
|
|
@@ -14190,9 +14830,10 @@ const createInertQuery = () => {
|
|
|
14190
14830
|
...base,
|
|
14191
14831
|
execute: noop,
|
|
14192
14832
|
createSnapshot,
|
|
14833
|
+
url: inertSignal(null),
|
|
14193
14834
|
reset: noop,
|
|
14194
14835
|
abort: () => false,
|
|
14195
|
-
asReadonly: () => ({ ...base, createSnapshot }),
|
|
14836
|
+
asReadonly: () => ({ ...base, createSnapshot, url: inertSignal(null) }),
|
|
14196
14837
|
subtle: {
|
|
14197
14838
|
destroy: noop,
|
|
14198
14839
|
setResponse: noop,
|
|
@@ -15871,7 +16512,8 @@ class QueryDirective {
|
|
|
15871
16512
|
this.queryState = queryStateSignal(this.query);
|
|
15872
16513
|
inject(ViewContainerRef).createEmbeddedView(inject(TemplateRef), this.viewContext);
|
|
15873
16514
|
effect(() => {
|
|
15874
|
-
const
|
|
16515
|
+
const rawQuery = this.query();
|
|
16516
|
+
const query = extractQuery(rawQuery);
|
|
15875
16517
|
untracked(() => {
|
|
15876
16518
|
if (!query) {
|
|
15877
16519
|
this.viewContext.$implicit = null;
|
|
@@ -15887,7 +16529,7 @@ class QueryDirective {
|
|
|
15887
16529
|
if (isQueryStatePrepared(query.rawState) || isQueryStateCancelled(query.rawState)) {
|
|
15888
16530
|
query.execute();
|
|
15889
16531
|
}
|
|
15890
|
-
this.viewContext.scope = isQueryCollection(
|
|
16532
|
+
this.viewContext.scope = isQueryCollection(rawQuery) ? rawQuery.type : null;
|
|
15891
16533
|
this.viewContext.query = query;
|
|
15892
16534
|
});
|
|
15893
16535
|
});
|
|
@@ -16957,13 +17599,13 @@ const transformToStringArray = (value) => {
|
|
|
16957
17599
|
};
|
|
16958
17600
|
const transformToNumber = (value) => {
|
|
16959
17601
|
if (typeof value === 'number') {
|
|
16960
|
-
return value;
|
|
17602
|
+
return Number.isFinite(value) ? value : null;
|
|
16961
17603
|
}
|
|
16962
17604
|
if (typeof value === 'string') {
|
|
16963
17605
|
if (!value.trim())
|
|
16964
17606
|
return null;
|
|
16965
17607
|
const number = Number(value);
|
|
16966
|
-
return Number.
|
|
17608
|
+
return Number.isFinite(number) ? number : null;
|
|
16967
17609
|
}
|
|
16968
17610
|
return null;
|
|
16969
17611
|
};
|
|
@@ -16999,7 +17641,7 @@ const transformToBooleanArray = (value) => {
|
|
|
16999
17641
|
const DATE_ONLY_PATTERN = /^(\d{4})-(\d{2})-(\d{2})$/;
|
|
17000
17642
|
const transformToDate = (value) => {
|
|
17001
17643
|
if (value instanceof Date) {
|
|
17002
|
-
return value;
|
|
17644
|
+
return Number.isNaN(value.getTime()) ? null : value;
|
|
17003
17645
|
}
|
|
17004
17646
|
if (typeof value === 'string') {
|
|
17005
17647
|
const date = new Date(value);
|
|
@@ -17026,7 +17668,9 @@ const transformToDateArray = (value) => {
|
|
|
17026
17668
|
};
|
|
17027
17669
|
const transformToSort = (value) => {
|
|
17028
17670
|
if (typeof value === 'string') {
|
|
17029
|
-
const
|
|
17671
|
+
const separator = value.lastIndexOf(':');
|
|
17672
|
+
const active = separator === -1 ? value : value.slice(0, separator);
|
|
17673
|
+
const direction = separator === -1 ? '' : value.slice(separator + 1);
|
|
17030
17674
|
if (!active) {
|
|
17031
17675
|
return null;
|
|
17032
17676
|
}
|
|
@@ -17729,6 +18373,8 @@ const MAX_RESET_PASSES = 10;
|
|
|
17729
18373
|
const changedKeysBetween = (previous, current) => Object.keys(current).filter((key) => !equal(previous?.[key], current[key]));
|
|
17730
18374
|
/** Reads a URL string into the type of the field's default; `null` means the default's type cannot hold it. */
|
|
17731
18375
|
const autoCoerce = (raw, defaultValue) => {
|
|
18376
|
+
if (Array.isArray(raw))
|
|
18377
|
+
return Array.isArray(defaultValue) ? raw : null;
|
|
17732
18378
|
if (typeof raw !== 'string')
|
|
17733
18379
|
return raw;
|
|
17734
18380
|
if (Array.isArray(defaultValue))
|
|
@@ -17756,10 +18402,13 @@ const resolveDebounce = (fieldDefs, changedKeys, live) => {
|
|
|
17756
18402
|
}
|
|
17757
18403
|
return Math.min(...times);
|
|
17758
18404
|
};
|
|
18405
|
+
const isUnserializable = (value) => value === undefined ||
|
|
18406
|
+
(typeof value === 'number' && Number.isNaN(value)) ||
|
|
18407
|
+
(value instanceof Date && Number.isNaN(value.getTime()));
|
|
17759
18408
|
/**
|
|
17760
18409
|
* A value that cannot be told apart from the default once serialized - a cleared text (`''`), an emptied list
|
|
17761
|
-
* (`[]`) or one whose `valueToQueryParam` yields nothing - commits as the
|
|
17762
|
-
* committed state and the filter count does not report it.
|
|
18410
|
+
* (`[]`), `undefined`, `NaN`, an invalid `Date` or one whose `valueToQueryParam` yields nothing - commits as the
|
|
18411
|
+
* default, so the URL reproduces the committed state and the filter count does not report it.
|
|
17763
18412
|
*/
|
|
17764
18413
|
const normalizeLive = (fieldDefs, rawLive, defaults) => {
|
|
17765
18414
|
const live = { ...rawLive };
|
|
@@ -17770,7 +18419,7 @@ const normalizeLive = (fieldDefs, rawLive, defaults) => {
|
|
|
17770
18419
|
const serialized = def.valueToQueryParam?.(value);
|
|
17771
18420
|
const serializedToNothing = !!def.valueToQueryParam && (serialized === null || serialized === undefined);
|
|
17772
18421
|
const clearedNullable = defaults[key] === null && (value === '' || (Array.isArray(value) && value.length === 0));
|
|
17773
|
-
if (serializedToNothing || clearedNullable) {
|
|
18422
|
+
if (serializedToNothing || clearedNullable || isUnserializable(value)) {
|
|
17774
18423
|
live[key] = defaults[key];
|
|
17775
18424
|
}
|
|
17776
18425
|
}
|
|
@@ -17878,9 +18527,12 @@ const createBranch = (fields, initial, injector) => {
|
|
|
17878
18527
|
...(ngDevMode ? [{ debugName: "committed" }] : /* istanbul ignore next */ []));
|
|
17879
18528
|
const tree = form(model, { injector });
|
|
17880
18529
|
let pendingTimer = null;
|
|
18530
|
+
const defaultsRevision = signal(0, /* @ts-ignore */
|
|
18531
|
+
...(ngDevMode ? [{ debugName: "defaultsRevision" }] : /* istanbul ignore next */ []));
|
|
17881
18532
|
const defaultFor = (key) => {
|
|
17882
18533
|
const value = resolveDefault(fields[key]);
|
|
17883
18534
|
defaults[key] = value;
|
|
18535
|
+
defaultsRevision.update((revision) => revision + 1);
|
|
17884
18536
|
return value;
|
|
17885
18537
|
};
|
|
17886
18538
|
const normalizedLive = computed(() => normalizeLive(fields, model(), defaults), /* @ts-ignore */
|
|
@@ -17929,7 +18581,10 @@ const createBranch = (fields, initial, injector) => {
|
|
|
17929
18581
|
fields: tree,
|
|
17930
18582
|
value: committed.asReadonly(),
|
|
17931
18583
|
liveValue: model.asReadonly(),
|
|
17932
|
-
activeFilterCount: computed(() =>
|
|
18584
|
+
activeFilterCount: computed(() => {
|
|
18585
|
+
defaultsRevision();
|
|
18586
|
+
return computeFilterCount(fields, committed(), defaults);
|
|
18587
|
+
}),
|
|
17933
18588
|
setValue: (value, options) => write(clone(value), options),
|
|
17934
18589
|
patchValue: (value, options) => write({ ...model(), ...value }, options),
|
|
17935
18590
|
resetFieldToDefault: (key, options) => write({ ...model(), [key]: defaultFor(key) }, options),
|
|
@@ -17975,9 +18630,12 @@ const defineQueryForm = (config) => {
|
|
|
17975
18630
|
const prefix = config.queryParamPrefix;
|
|
17976
18631
|
const defaults = buildDefaults(fieldDefs);
|
|
17977
18632
|
const defaultValue = clone(defaults);
|
|
18633
|
+
const defaultsRevision = signal(0, /* @ts-ignore */
|
|
18634
|
+
...(ngDevMode ? [{ debugName: "defaultsRevision" }] : /* istanbul ignore next */ []));
|
|
17978
18635
|
const defaultFor = (key) => {
|
|
17979
18636
|
const value = resolveDefault(fieldDefs[key]);
|
|
17980
18637
|
defaults[key] = value;
|
|
18638
|
+
defaultsRevision.update((revision) => revision + 1);
|
|
17981
18639
|
defaultValue[key] = clone(value);
|
|
17982
18640
|
return value;
|
|
17983
18641
|
};
|
|
@@ -18002,7 +18660,10 @@ const defineQueryForm = (config) => {
|
|
|
18002
18660
|
const urlNavigationMarker = {};
|
|
18003
18661
|
const pathOf = (tree) => router.serializeUrl(tree).split(/[?#]/)[0];
|
|
18004
18662
|
const fields = form(model);
|
|
18005
|
-
const activeFilterCount = computed(() =>
|
|
18663
|
+
const activeFilterCount = computed(() => {
|
|
18664
|
+
defaultsRevision();
|
|
18665
|
+
return computeFilterCount(fieldDefs, committed(), defaults);
|
|
18666
|
+
}, /* @ts-ignore */
|
|
18006
18667
|
...(ngDevMode ? [{ debugName: "activeFilterCount" }] : /* istanbul ignore next */ []));
|
|
18007
18668
|
const paramKey = (key) => {
|
|
18008
18669
|
if (!prefix)
|
|
@@ -18261,6 +18922,13 @@ const defineQueryForm = (config) => {
|
|
|
18261
18922
|
}
|
|
18262
18923
|
observeOptions = options;
|
|
18263
18924
|
observing.set(true);
|
|
18925
|
+
if (isDevMode()) {
|
|
18926
|
+
for (const key of options?.persistence?.fields ?? []) {
|
|
18927
|
+
if (!(key in fieldDefs)) {
|
|
18928
|
+
console.warn(`defineQueryForm: persistence.fields references unknown field "${key}". Is it a typo?`);
|
|
18929
|
+
}
|
|
18930
|
+
}
|
|
18931
|
+
}
|
|
18264
18932
|
changesSeenAtObserve = untracked(queryParamChanges);
|
|
18265
18933
|
if (options?.persistence) {
|
|
18266
18934
|
restoreFromStorage(options.persistence, route.snapshot.queryParams);
|
|
@@ -18387,6 +19055,11 @@ const normalizeConfig = (config) => {
|
|
|
18387
19055
|
isResetBy: isResetBy === undefined ? undefined : Array.isArray(isResetBy) ? isResetBy : [isResetBy],
|
|
18388
19056
|
};
|
|
18389
19057
|
};
|
|
19058
|
+
const dateToQueryParam = (value) => {
|
|
19059
|
+
if (value === null)
|
|
19060
|
+
return ET_NULL_VALUE;
|
|
19061
|
+
return value instanceof Date && !Number.isNaN(value.getTime()) ? value.toISOString() : value;
|
|
19062
|
+
};
|
|
18390
19063
|
function queryField(config) {
|
|
18391
19064
|
return {
|
|
18392
19065
|
defaultValue: null,
|
|
@@ -18463,6 +19136,7 @@ function dateQueryField(config) {
|
|
|
18463
19136
|
return {
|
|
18464
19137
|
defaultValue: null,
|
|
18465
19138
|
queryParamToValue: transformToDate,
|
|
19139
|
+
valueToQueryParam: dateToQueryParam,
|
|
18466
19140
|
...normalizeConfig(rest),
|
|
18467
19141
|
};
|
|
18468
19142
|
}
|
|
@@ -18470,6 +19144,11 @@ function dateQueryField(config) {
|
|
|
18470
19144
|
const dateArrayQueryField = (config) => ({
|
|
18471
19145
|
defaultValue: null,
|
|
18472
19146
|
queryParamToValue: transformToDateArray,
|
|
19147
|
+
valueToQueryParam: (value) => {
|
|
19148
|
+
if (value === null)
|
|
19149
|
+
return ET_NULL_VALUE;
|
|
19150
|
+
return value.length ? value.map(dateToQueryParam) : ET_EMPTY_ARRAY_VALUE;
|
|
19151
|
+
},
|
|
18473
19152
|
...normalizeConfig(config),
|
|
18474
19153
|
});
|
|
18475
19154
|
|
|
@@ -18478,6 +19157,7 @@ const WebSocketRuntimeErrorCode = {
|
|
|
18478
19157
|
// Web Socket Client
|
|
18479
19158
|
ROOM_NOT_JOINED: 1000,
|
|
18480
19159
|
MESSAGE_MALFORMED: 1001,
|
|
19160
|
+
AUTH_AND_AUTH_PROVIDER: 1002,
|
|
18481
19161
|
};
|
|
18482
19162
|
const roomNotJoined = (room) => {
|
|
18483
19163
|
return new RuntimeError(WebSocketRuntimeErrorCode.ROOM_NOT_JOINED, `Tried leaving the room "${room}" but it has not been joined.`);
|
|
@@ -18485,6 +19165,9 @@ const roomNotJoined = (room) => {
|
|
|
18485
19165
|
const messageMalformed = () => {
|
|
18486
19166
|
return new RuntimeError(WebSocketRuntimeErrorCode.MESSAGE_MALFORMED, 'A message has been received but it is malformed. Expected a JSON string shaped like { room: string; event: string; data: unknown }.');
|
|
18487
19167
|
};
|
|
19168
|
+
const authAndAuthProvider = (name) => {
|
|
19169
|
+
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.`);
|
|
19170
|
+
};
|
|
18488
19171
|
|
|
18489
19172
|
const MAX_DEVTOOLS_MESSAGES = 100;
|
|
18490
19173
|
const RECONNECT_BASE_DELAY = 1000;
|
|
@@ -18494,7 +19177,10 @@ const createWebSocketClient = (options) => {
|
|
|
18494
19177
|
return defineRootProvider(() => {
|
|
18495
19178
|
if (isPlatformServer(inject(PLATFORM_ID)))
|
|
18496
19179
|
return createServerWebSocketClient();
|
|
18497
|
-
|
|
19180
|
+
if (options.auth && options.authProvider && isDevMode())
|
|
19181
|
+
throw authAndAuthProvider(options.name);
|
|
19182
|
+
const authProvider = options.authProvider?.inject();
|
|
19183
|
+
const auth = authProvider ? () => ({ token: authProvider.accessToken() }) : options.auth;
|
|
18498
19184
|
const socket = options.io(options.url, {
|
|
18499
19185
|
withCredentials: options.withCredentials ?? true,
|
|
18500
19186
|
autoConnect: false,
|
|
@@ -18502,6 +19188,7 @@ const createWebSocketClient = (options) => {
|
|
|
18502
19188
|
auth: auth ? (cb) => cb(typeof auth === 'function' ? auth() : auth) : undefined,
|
|
18503
19189
|
});
|
|
18504
19190
|
const rooms = new Map();
|
|
19191
|
+
const holds = new Set();
|
|
18505
19192
|
const joinsDeliveredThisConnection = new Set();
|
|
18506
19193
|
const joinsDeliveredToClosedConnection = new Set();
|
|
18507
19194
|
const isConnected = signal(false, /* @ts-ignore */
|
|
@@ -18532,66 +19219,66 @@ const createWebSocketClient = (options) => {
|
|
|
18532
19219
|
direction: 'out',
|
|
18533
19220
|
});
|
|
18534
19221
|
};
|
|
18535
|
-
const
|
|
18536
|
-
const
|
|
18537
|
-
|
|
18538
|
-
|
|
18539
|
-
|
|
18540
|
-
|
|
18541
|
-
|
|
18542
|
-
|
|
18543
|
-
|
|
18544
|
-
const
|
|
18545
|
-
|
|
18546
|
-
|
|
18547
|
-
|
|
18548
|
-
|
|
18549
|
-
}
|
|
18550
|
-
emit({ event: 'join-room', data: name, room: name });
|
|
18551
|
-
joinsDeliveredToClosedConnection.delete(name);
|
|
18552
|
-
const messages = new Subject();
|
|
18553
|
-
const newRoom = {
|
|
18554
|
-
latestMessage: signal(null),
|
|
18555
|
-
messages,
|
|
18556
|
-
messages$: messages.asObservable(),
|
|
18557
|
-
joinCount: 1,
|
|
18558
|
-
};
|
|
18559
|
-
rooms.set(name, newRoom);
|
|
18560
|
-
syncDevtoolsRooms();
|
|
18561
|
-
return newRoom;
|
|
19222
|
+
const join = (name) => {
|
|
19223
|
+
const existingRoom = rooms.get(name);
|
|
19224
|
+
if (existingRoom) {
|
|
19225
|
+
existingRoom.joinCount++;
|
|
19226
|
+
return existingRoom;
|
|
19227
|
+
}
|
|
19228
|
+
emit({ event: 'join-room', data: name, room: name });
|
|
19229
|
+
joinsDeliveredToClosedConnection.delete(name);
|
|
19230
|
+
const messages = new Subject();
|
|
19231
|
+
const newRoom = {
|
|
19232
|
+
latestMessage: signal(null),
|
|
19233
|
+
messages,
|
|
19234
|
+
messages$: messages.asObservable(),
|
|
19235
|
+
joinCount: 1,
|
|
18562
19236
|
};
|
|
18563
|
-
|
|
18564
|
-
|
|
18565
|
-
|
|
18566
|
-
|
|
18567
|
-
|
|
18568
|
-
|
|
18569
|
-
|
|
18570
|
-
|
|
18571
|
-
|
|
18572
|
-
|
|
18573
|
-
|
|
18574
|
-
|
|
18575
|
-
|
|
18576
|
-
|
|
18577
|
-
|
|
18578
|
-
|
|
18579
|
-
|
|
18580
|
-
|
|
18581
|
-
|
|
18582
|
-
}
|
|
18583
|
-
});
|
|
18584
|
-
});
|
|
19237
|
+
rooms.set(name, newRoom);
|
|
19238
|
+
syncDevtoolsRooms();
|
|
19239
|
+
return newRoom;
|
|
19240
|
+
};
|
|
19241
|
+
const holdRoom = (hold, name) => {
|
|
19242
|
+
if (name === hold.name)
|
|
19243
|
+
return;
|
|
19244
|
+
if (hold.name !== null && !hold.parked)
|
|
19245
|
+
leaveRoom(hold.name);
|
|
19246
|
+
hold.name = name;
|
|
19247
|
+
if (name === null)
|
|
19248
|
+
hold.parked = false;
|
|
19249
|
+
if (hold.parked)
|
|
19250
|
+
return;
|
|
19251
|
+
hold.room.set(name === null ? null : join(name));
|
|
19252
|
+
};
|
|
19253
|
+
const createHold = () => {
|
|
19254
|
+
const hold = { name: null, parked: false, room: signal(null) };
|
|
19255
|
+
holds.add(hold);
|
|
18585
19256
|
inject(DestroyRef).onDestroy(() => {
|
|
18586
|
-
|
|
18587
|
-
|
|
18588
|
-
joinedRoomName = null;
|
|
18589
|
-
roomData.set(null);
|
|
18590
|
-
}
|
|
19257
|
+
holdRoom(hold, null);
|
|
19258
|
+
holds.delete(hold);
|
|
18591
19259
|
});
|
|
18592
|
-
return
|
|
19260
|
+
return hold;
|
|
19261
|
+
};
|
|
19262
|
+
const joinRoom = (room) => {
|
|
19263
|
+
const hold = createHold();
|
|
19264
|
+
if (typeof room === 'string') {
|
|
19265
|
+
holdRoom(hold, room);
|
|
19266
|
+
}
|
|
19267
|
+
else {
|
|
19268
|
+
effect(() => {
|
|
19269
|
+
const current = room() || null;
|
|
19270
|
+
untracked(() => holdRoom(hold, current));
|
|
19271
|
+
});
|
|
19272
|
+
}
|
|
19273
|
+
return hold.room.asReadonly();
|
|
18593
19274
|
};
|
|
18594
19275
|
const leaveRoom = (room) => {
|
|
19276
|
+
const parkedHold = [...holds].find((hold) => hold.parked && hold.name === room);
|
|
19277
|
+
if (parkedHold) {
|
|
19278
|
+
parkedHold.name = null;
|
|
19279
|
+
parkedHold.parked = false;
|
|
19280
|
+
return;
|
|
19281
|
+
}
|
|
18595
19282
|
const joinedRoom = rooms.get(room);
|
|
18596
19283
|
if (!joinedRoom) {
|
|
18597
19284
|
if (isDevMode())
|
|
@@ -18704,7 +19391,90 @@ const createWebSocketClient = (options) => {
|
|
|
18704
19391
|
});
|
|
18705
19392
|
setupWebSocketConnectionListener();
|
|
18706
19393
|
setupWebSocketListener();
|
|
18707
|
-
|
|
19394
|
+
const dropBufferedEmits = () => {
|
|
19395
|
+
if (socket.sendBuffer)
|
|
19396
|
+
socket.sendBuffer.length = 0;
|
|
19397
|
+
};
|
|
19398
|
+
const endSession = () => {
|
|
19399
|
+
if (reconnectTimer !== null) {
|
|
19400
|
+
clearTimeout(reconnectTimer);
|
|
19401
|
+
reconnectTimer = null;
|
|
19402
|
+
}
|
|
19403
|
+
dropBufferedEmits();
|
|
19404
|
+
socket.disconnect();
|
|
19405
|
+
for (const hold of holds) {
|
|
19406
|
+
if (hold.name === null || hold.parked)
|
|
19407
|
+
continue;
|
|
19408
|
+
hold.parked = true;
|
|
19409
|
+
hold.room.set(null);
|
|
19410
|
+
}
|
|
19411
|
+
for (const room of rooms.values())
|
|
19412
|
+
room.messages.complete();
|
|
19413
|
+
rooms.clear();
|
|
19414
|
+
joinsDeliveredThisConnection.clear();
|
|
19415
|
+
joinsDeliveredToClosedConnection.clear();
|
|
19416
|
+
syncDevtoolsRooms();
|
|
19417
|
+
};
|
|
19418
|
+
const rejoinParkedRooms = () => {
|
|
19419
|
+
for (const hold of holds) {
|
|
19420
|
+
if (!hold.parked || hold.name === null)
|
|
19421
|
+
continue;
|
|
19422
|
+
hold.parked = false;
|
|
19423
|
+
hold.room.set(join(hold.name));
|
|
19424
|
+
}
|
|
19425
|
+
};
|
|
19426
|
+
const startConnection = () => {
|
|
19427
|
+
reconnectAttempt = 0;
|
|
19428
|
+
socket.connect();
|
|
19429
|
+
};
|
|
19430
|
+
if (authProvider) {
|
|
19431
|
+
let hasConnected = false;
|
|
19432
|
+
let wasAuthenticated = false;
|
|
19433
|
+
let handledLogin = null;
|
|
19434
|
+
let handledSessionId = null;
|
|
19435
|
+
effect(() => {
|
|
19436
|
+
const status = authProvider.sessionStatus();
|
|
19437
|
+
const execution = authProvider.executionState();
|
|
19438
|
+
const sessionId = authProvider.sessionId();
|
|
19439
|
+
untracked(() => {
|
|
19440
|
+
if (destroyed || status === 'unknown' || status === 'restoring')
|
|
19441
|
+
return;
|
|
19442
|
+
if (status === 'anonymous') {
|
|
19443
|
+
if (wasAuthenticated) {
|
|
19444
|
+
endSession();
|
|
19445
|
+
startConnection();
|
|
19446
|
+
}
|
|
19447
|
+
else if (!hasConnected) {
|
|
19448
|
+
startConnection();
|
|
19449
|
+
}
|
|
19450
|
+
wasAuthenticated = false;
|
|
19451
|
+
hasConnected = true;
|
|
19452
|
+
return;
|
|
19453
|
+
}
|
|
19454
|
+
const login = (execution?.type === 'login' || execution?.type === 'tokenSeed') && execution.state === 'success'
|
|
19455
|
+
? execution
|
|
19456
|
+
: null;
|
|
19457
|
+
const startedSession = !wasAuthenticated || sessionId !== handledSessionId || (login !== null && login !== handledLogin);
|
|
19458
|
+
if (login)
|
|
19459
|
+
handledLogin = login;
|
|
19460
|
+
handledSessionId = sessionId;
|
|
19461
|
+
wasAuthenticated = true;
|
|
19462
|
+
if (!hasConnected) {
|
|
19463
|
+
startConnection();
|
|
19464
|
+
}
|
|
19465
|
+
else if (startedSession) {
|
|
19466
|
+
dropBufferedEmits();
|
|
19467
|
+
socket.disconnect();
|
|
19468
|
+
rejoinParkedRooms();
|
|
19469
|
+
startConnection();
|
|
19470
|
+
}
|
|
19471
|
+
hasConnected = true;
|
|
19472
|
+
});
|
|
19473
|
+
});
|
|
19474
|
+
}
|
|
19475
|
+
else {
|
|
19476
|
+
socket.connect();
|
|
19477
|
+
}
|
|
18708
19478
|
const client = {
|
|
18709
19479
|
joinRoom,
|
|
18710
19480
|
isConnected: isConnected.asReadonly(),
|
|
@@ -18751,5 +19521,5 @@ const createServerWebSocketClient = () => {
|
|
|
18751
19521
|
* Generated bundle index. Do not edit.
|
|
18752
19522
|
*/
|
|
18753
19523
|
|
|
18754
|
-
export { AuthBearerRefreshStrategy, BasicAuthProvider, BearerAuthFeatureType, BooleanArrayQueryField, CustomHeaderAuthProvider, DEFAULT_KEEP_UNUSED_FOR, DEFAULT_QUERY_PERSISTENCE_MAX_AGE, DEFAULT_QUERY_PERSISTENCE_MAX_ENTRIES, DEFAULT_QUERY_PERSISTENCE_VERSION, DEFAULT_QUERY_PERSISTENCE_WRITE_DELAY, DateArrayQueryField, DateQueryField, EMPTY_QUERY_DEVTOOLS_FAULT, EntityStore, IGNORED_FILTER_COUNT_FIELDS, INFINITY_QUERY_TOKEN, IS_QUERY_REQUEST, InfinityQuery, InfinityQueryDirective, InfinityQueryTriggerDirective, LegacyQuery, LegacyQueryCreator, MAX_QUERY_BATCH_TOMBSTONES, MAX_QUERY_BATCH_TOMBSTONE_BUCKETS, MAX_QUERY_DEVTOOLS_TOMBSTONES, MAX_UNUSED_ENTRIES, NumberArrayQueryField, ParseHttpErrorCodeToMessageDePipe, ParseHttpErrorCodeToMessageEnPipe, ParseHttpErrorCodeToTitleDePipe, ParseHttpErrorCodeToTitleEnPipe, QUERY_CLIENT_DEVTOOLS_TOKEN, QUERY_DEVTOOLS_FAULT_STATUSES, QUERY_DEVTOOLS_TOKEN_TTL_LIMIT, QUERY_PERSISTENCE_STORE_VERSION, QUERY_SYNC_PROTOCOL_VERSION, QUERY_VERSION, QueryClientFeatureType, QueryDevtoolsComponent, QueryDirective, QueryFeatureType, QueryField, QueryForm, QueryRuntimeErrorCode, QueryShortNamePipe, QueryStateType, QueryStore, SERVER_ERROR_KIND, SERVER_VIOLATION_ERROR_KIND, SPEED_BUFFER_TIME_IN_MS, SearchQueryField, SortQueryField, StringArrayQueryField, V2BearerAuthProvider, V2Query, V2QueryClient, V2QueryCreator, addQueryContainerHandling, addQueryDevtoolsAuthAccount, applyQueryDevtoolsOverrides, applyQueryDevtoolsTokenTtl, applyQueryFeatures, armAllQueryDevtoolsMocks, armQueryDevtoolsMock, armQueryDevtoolsOverrideTransfer, authExtractTokensResponseMissingAccessToken, authExtractTokensResponseMissingRefreshToken, authExtractTokensResponseNotObject, authProviderFeatureUsedMultipleTimes, booleanArrayQueryField, buildQueryCacheKey, buildQueryString, buildRoute, buildTimestampFromSeconds, canOverrideQueryDevtoolsTokenTtl, castQueryCreatorTypes, circularQueryDependency, circularQueryDependencyChecker, clearQueryDevtoolsArmedMocks, clearQueryDevtoolsAuthCredentials, clearQueryDevtoolsAuthSessions, clearQueryDevtoolsFaults, clearQueryDevtoolsMockStore, clearQueryDevtoolsOverrideStore, clearQueryDevtoolsStore, clearQueryDevtoolsTokenTtl, clearQueryDevtoolsTombstones, clearRestoredQueryDevtoolsOverrides, collectLeafPaths, collectQueryDevtoolsSchemaComponents, collectQueryFormLinks, computeQueryAuthHeader, computeQueryBody, computeQueryHeaders, computeQueryMethod, computeQueryQueryParams, consumeSuppressQueryStackDevtools, contentfulGqlLikePaginationAdapter, countUnresolvedQueryDevtoolsOverrides, createAuthGuard, createBaseQuery, createBaseQueryCreator, createBearerAuthProvider, createDefaultRetryFn, createDeleteQuery, createExecuteFn, createGetQuery, createGqlExecuteFn, createGqlMutationViaGet, createGqlMutationViaPost, createGqlQuery, createGqlQueryCreator, createGqlQueryViaGet, createGqlQueryViaPost, createHeadQuery, createHttpRequest, createIndexedDbQueryPersistenceAdapter, createInfinityQueryConfig, createLegacyQueryCreator, createNoopQueryPersistenceAdapter, createOptionsQuery, createPagedQueryStack, createPatchQuery, createPersistentAuthFeature, createPostQuery, createPutQuery, createQuery, createQueryBatch, createQueryClient, createQueryCollection, createQueryCollectionSignal, createQueryCollectionSubject, createQueryContext, createQueryCreator, createQueryDevtoolsFormLinks, createQueryDevtoolsFormLinksRecorder, createQueryDevtoolsOverrides, createQueryDevtoolsOverridesRecorder, createQueryDevtoolsStats, createQueryDevtoolsStatsRecorder, createQueryErrorResponse, createQueryFeature, createQueryInvalidationFilter, createQueryKeyLockManager, createQueryObject, createQueryPersistenceEngine, createQueryRepository, createQuerySnapshotFn, createQueryStack, createQuerySubmission, createQuerySyncEngine, createQuerySyncTransport, createSecureDeleteQuery, createSecureExecuteFactory, createSecureExecuteFn, createSecureGetQuery, createSecureGqlExecuteFn, createSecureGqlMutationViaGet, createSecureGqlMutationViaPost, createSecureGqlQuery, createSecureGqlQueryCreator, createSecureGqlQueryViaGet, createSecureGqlQueryViaPost, createSecureHeadQuery, createSecureOptionsQuery, createSecurePatchQuery, createSecurePostQuery, createSecurePutQuery, createSecureQuery, createSecureQueryCreator, createTrackingFeature, createWebSocketClient, currentQueryDevtoolsBatch, dateArrayQueryField, dateQueryField, decryptBearer, deepFreeze, def, defineQueryForm, deleteQueryDevtoolsMock, describeQueryDevtoolsFeatures, detectContentTypeHeader, detectPaginationShape, dynLikePaginationAdapter, effectComputed, emptyQueryArgs, ethletePaginationAdapter, executeUntilSettled, executeUntilSettled$, extractExpiresInSeconds, extractFormViolations, extractHtmlErrorMessage, extractQuery, fakePaginationAdapter, filterFailure, filterNull, filterQueryStates, filterSuccess, forEachHeader, forgetQueryDevtoolsAuthSession, forgetQueryDevtoolsAuthSessionsFor, formatQueryDevtoolsDuration, generateQueryDevtoolsNumberPreset, generateQueryDevtoolsSampleNumber, generateQueryDevtoolsStringPreset, getBearerAuthProviderName, getDefaultHeaders, getQueryClientName, getQueryFeatureUsage, getResponseUrl, ggLikePaginationAdapter, gql, gqlDataPropertyMissingInResponse, gqlErrorsInResponse, gqlTransformerFor, hasDefaultQueryRetry, hasHeader, hasQueryDevtoolsOverridesAtPath, htmlErrorPayload, htmlQueryErrorParser, ignoreAutoRefresh, initQueryDevtoolsAuthSessions, initQueryDevtoolsFaults, initQueryDevtoolsMocks, initQueryDevtoolsOverridePersistence, initQueryDevtoolsSettings, injectInfinityQueryResponseDelay, injectQueryContext, insertFrom, invalidBaseRouteError, invalidBodyError, invalidRouteError, invalidStateInsideSecureExecuteFactory, isAutoRefresh, isBasicAuthProvider, isBearerAuthProvider, isClassValidatorError, isCreateGqlQueryOptions, isCustomHeaderAuthProvider, isDateShapedLeaf, isEmptyString, isGqlQueryConfig, isHtmlErrorPayload, isIdempotentQueryMethod, isLegacyQuery, isNaN$1 as isNaN, isOverrideOp, isPageOutOfRangeError, isQuery, isQueryCollection, isQueryDevtoolsEnabled, isQueryDevtoolsFaultArmed, isQueryDevtoolsRepositoryLive, isQueryDevtoolsRequestInterceptionEnabled, isQueryErrorResponse, isQueryStateCancelled, isQueryStateFailure, isQueryStateLoading, isQueryStatePrepared, isQueryStateSuccess, isRequestError, isSymfonyError, isSymfonyFormViolationListError, isSymfonyListError, isSymfonyPagerfantaOutOfRangeError, isUnderInvalidatedUrl, legacyPrepareFallbackInjector, legacyPrepareWithoutInjectionContext, loadQueryDevtoolsSchema, loginQueryDevtoolsAuthAccount, logoutQueryDevtoolsAuthSession, mapToPaginated, mapViolationsToFormErrors, matchesQueryDevtoolsMockPattern, matchesQueryDevtoolsMockQuery, maybeExecute, measureQueryDevtoolsPayload, mergeHeaders, nestedEffect, noteQueryFormRead, noteQueryFormReads, numberArrayQueryField, pagedQueryStackNextPageCalledWithoutPreviousPage, pagedQueryStackPageBiggerThanTotalPages, pagedQueryStackPreviousPageCalledButAlreadyAtFirstPage, paginatedEntityValueUpdater, parseAllXhrResponseHeaders, parseHttpErrorCodeToMessageDe, parseHttpErrorCodeToMessageEn, parseHttpErrorCodeToTitleDe, parseHttpErrorCodeToTitleEn, parseQueryDevtoolsOverrideTransfer, parseQueryRoute, patchQueryDevtoolsTokenPayload, pathParamsMissingInRouteFunctionError, provideInfinityQueryResponseDelay, provideLegacyPrepareFallback, provideQueryClientForDevtools, provideQueryDevtools, queryArrayComputed, queryBatchAlreadyRunning, queryBatchWithArgsUsed, queryClientFeatureUsedMultipleTimes, queryComputed, queryComputedTillTruthy, queryCreatedInReactiveContext, queryDevtoolsAbout, queryDevtoolsAllowsLocalAuthSessions, queryDevtoolsApiEnvIds, queryDevtoolsApiEnvIsProduction, queryDevtoolsApiEnvScope, queryDevtoolsApiEnvValues, queryDevtoolsApiEnvs, queryDevtoolsArmedMocks, queryDevtoolsArmedMocksRestored, queryDevtoolsAuthAccountsFor, queryDevtoolsAuthActive, queryDevtoolsAuthFieldsFor, queryDevtoolsAuthOtherScopeCount, queryDevtoolsAuthProviders, queryDevtoolsAuthSessions, queryDevtoolsAuthSessionsFor, queryDevtoolsAuthTabLocal, queryDevtoolsEntries, queryDevtoolsFaults, queryDevtoolsFaultsRestored, queryDevtoolsFnDetail, queryDevtoolsMockId, queryDevtoolsMocks, queryDevtoolsOverridePersistence, queryDevtoolsRequestPath, queryDevtoolsResponseHistory, queryDevtoolsRestoredOverridesScope, queryDevtoolsSchemaNames, queryDevtoolsSchemaRoutes, queryDevtoolsSchemaState, queryDevtoolsSettings, queryDevtoolsStorage, queryDevtoolsTokenTtls, queryErrorMessage, queryErrorMessages, queryExecute, queryExecutedAfterDestroyMessage, queryFeatureUsedMultipleTimes, queryField, querySequence, querySequenceAlreadyRunning, queryStackWithArgsUsed, queryStackWithResponseUpdateUsed, queryStateAlreadyHasKeyError, queryStateCannotTransformError, queryStateDoesNotContainKeyError, queryStateErrorSignal, queryStateLoadingSignal, queryStateResponseSignal, queryStateSignal, readQueryDevtoolsAuthSeed, readQueryDevtoolsAuthSeedFor, readQueryDevtoolsStore, recordExecutionArgs, registerEthleteVersion, registerLiveQueryDevtoolsRepository, registerQueryDevtoolsAuthProvider, registerQueryDevtoolsEntry, registerQueryErrorParser, releaseQueryDevtoolsOverridePersistence, removeFrom, removeQueryDevtoolsAuthAccount, renameQueryDevtoolsAuthSession, request, resetExecuteState, resetPageOnError, resolveInvalidationUrl, resolveQueryDevtoolsFault, resolveQueryDevtoolsFaultForAttempt, resolveQueryDevtoolsMock, resolveQueryDevtoolsMockForAttempt, resolveQueryHeaders, restoredQueryDevtoolsOverrides, runDefaultQueryRetry, runInQueryDevtoolsBatch, runQueryErrorParsers, saveQueryDevtoolsMock, searchQueryField, seedQueryDevtoolsSchemaBody, seedQueryDevtoolsSchemaRoute, serializeBody, serializeQueryDevtoolsOverrideTransfer, setDefaultQueryRetryFn, setQueryDevtoolsApiEnv, setQueryDevtoolsApiEnvs, setQueryDevtoolsAppInfo, setQueryDevtoolsArmedMocksScope, setQueryDevtoolsAuthCredentials, setQueryDevtoolsAuthProviderRegistrar, setQueryDevtoolsAuthSeedReader, setQueryDevtoolsAuthTabLocal, setQueryDevtoolsFault, setQueryDevtoolsFaultResolver, setQueryDevtoolsFaultsScope, setQueryDevtoolsFormLinksFactory, setQueryDevtoolsMockResolver, setQueryDevtoolsOverridePersistence, setQueryDevtoolsOverridesFactory, setQueryDevtoolsOverridesScope, setQueryDevtoolsRegistrar, setQueryDevtoolsResponseHistory, setQueryDevtoolsSchemaLoader, setQueryDevtoolsSettings, setQueryDevtoolsStatsFactory, setQueryDevtoolsTokenPayloadPatcher, setQueryDevtoolsTokenTtl, setQueryDevtoolsUiMounted, setupQueryDependencies, setupQueryExecuteState, setupQueryState, shouldAutoExecuteGqlQuery, shouldAutoExecuteQuery, shouldCacheQuery, shouldRetryRequest, silenceMissingWithArgsFeatureErrorUsedButWithArgsPresent, skipPaginationPageParamCalculator, smartDuplicateArrayItem, snapshotQueryDevtoolsHandle, sortQueryField, splitQueryConfig, stringArrayQueryField, stringifyQueryRouteParts, sumQueryDevtoolsStats, suppressNextQueryStackDevtools, switchQueryCollectionState, switchQueryDevtoolsAuthSession, switchQueryState, symfonyQueryErrorParser, tableSortQueryField, takeUntilResponse, toQuerySignal, toQuerySubject, tokensNotAvailableInsideAuthAndExec, tombstoneOf, trackQueryDevtoolsAuthProvider, transformArrayResponse, transformExecStateToQueryState, transformGql, transformMethod, transformPaginatedResponse, transformToBoolean, transformToBooleanArray, transformToDate, transformToDateArray, transformToNumber, transformToNumberArray, transformToSort, transformToSortQueryParam, transformToString, transformToStringArray, transformToTableSort, transformToTableSortQueryParam, uncacheableRequestHasAllowCacheParam, uncacheableRequestHasCacheKeyParam, unwrapGqlResponseWithErrors, unwrapQuerySyncMessage, v2BuildQueryCacheKey, v2ExtractExpiresInSeconds, v2ShouldCacheQuery, v2ShouldRetryRequest, validateWithQuery, validateWithV2Query, withArgs, withArgsQueryFeatureMissingButRouteIsFunction, withAuthenticationQuery, withAutoRefresh, withAutoRefreshUsedInManualQuery, withAutoRefreshUsedOnUnsupportedHttpMethod, withBearerAuthMultiTabSync, withDefaultRetry, withErrorHandling, withEthleteApiErrors, withHtmlErrorParsing, withInactivityLogout, withLogging, withLongPolling, withLongPollingUsedOnUnsupportedHttpMethod, withLongPollingUsedWithPolling, withMultiTabSync, withPageResetOnError, withPersistentAuth, withPolling, withPollingUsedOnUnsupportedHttpMethod, withQueryDevtoolsOverridePersistence, withQueryPersistence, withRefreshQuery, withResponseUpdate, withSuccessHandling, withSymfonyErrors, withTokenExpirationWarning, withTokenRevocation, withTracking, wrapAsObservableSignal, wrapQuerySyncMessage, writeQueryDevtoolsStore };
|
|
19524
|
+
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, createSkippedSnapshot, 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, ɵresetQueryDevtoolsForTesting, restoredQueryDevtoolsOverrides as ɵrestoredQueryDevtoolsOverrides, saveQueryDevtoolsMock as ɵsaveQueryDevtoolsMock, seedQueryDevtoolsSchemaBody as ɵseedQueryDevtoolsSchemaBody, seedQueryDevtoolsSchemaRoute as ɵseedQueryDevtoolsSchemaRoute, serializeQueryDevtoolsOverrideTransfer as ɵserializeQueryDevtoolsOverrideTransfer, setQueryDevtoolsApiEnv as ɵsetQueryDevtoolsApiEnv, setQueryDevtoolsAppInfo as ɵsetQueryDevtoolsAppInfo, setQueryDevtoolsArmedMocksScope as ɵsetQueryDevtoolsArmedMocksScope, setQueryDevtoolsAuthCredentials as ɵsetQueryDevtoolsAuthCredentials, setQueryDevtoolsAuthTabLocal as ɵsetQueryDevtoolsAuthTabLocal, setQueryDevtoolsFault as ɵsetQueryDevtoolsFault, setQueryDevtoolsFaultsScope as ɵsetQueryDevtoolsFaultsScope, setQueryDevtoolsOverridePersistence as ɵsetQueryDevtoolsOverridePersistence, setQueryDevtoolsOverridesScope as ɵsetQueryDevtoolsOverridesScope, setQueryDevtoolsSettings as ɵsetQueryDevtoolsSettings, smartDuplicateArrayItem as ɵsmartDuplicateArrayItem, snapshotQueryDevtoolsHandle as ɵsnapshotQueryDevtoolsHandle, stringifyQueryRouteParts as ɵstringifyQueryRouteParts, sumQueryDevtoolsStats as ɵsumQueryDevtoolsStats, switchQueryDevtoolsAuthSession as ɵswitchQueryDevtoolsAuthSession, tombstoneOf as ɵtombstoneOf, writeQueryDevtoolsStore as ɵwriteQueryDevtoolsStore };
|
|
18755
19525
|
//# sourceMappingURL=ethlete-query.mjs.map
|